Initial commit
This commit is contained in:
290
skills/youtube-video-to-audio/scripts/download_audio.py
Normal file
290
skills/youtube-video-to-audio/scripts/download_audio.py
Normal file
@@ -0,0 +1,290 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
YouTube Audio Downloader
|
||||
|
||||
This module provides functionality to download audio from YouTube videos using yt-dlp.
|
||||
It supports cross-platform operation (Windows, macOS, Linux) and includes automatic
|
||||
yt-dlp binary management with built-in protection against overwriting existing files.
|
||||
|
||||
Key Features:
|
||||
- Download audio from YouTube videos as MP3 files
|
||||
- Automatic yt-dlp binary download and updates
|
||||
- Cross-platform support (Windows, macOS, Linux)
|
||||
- Customizable audio quality and bitrate
|
||||
- Default output location with overwrite capability
|
||||
- Custom output locations with overwrite protection
|
||||
- Automatic directory creation for custom paths
|
||||
|
||||
Default Behavior:
|
||||
- Output: ~/tmp/download_audio_result.mp3 (allows overwrite)
|
||||
- Quality: bestaudio[abr<=64]/worstaudio
|
||||
- Format: MP3
|
||||
- Bitrate: 64K
|
||||
|
||||
Custom Output:
|
||||
- Cannot overwrite existing files (raises FileExistsError)
|
||||
- Creates parent directories automatically
|
||||
|
||||
Example Usage:
|
||||
# Download with defaults (allows overwrite)
|
||||
$ python download_audio.py --video-url "https://www.youtube.com/watch?v=d6rZtgHcbWA"
|
||||
|
||||
# Custom quality and bitrate
|
||||
$ python download_audio.py --video-url "https://youtu.be/xyz" --quality "bestaudio" --bitrate "128K"
|
||||
|
||||
# Custom output location (cannot overwrite existing)
|
||||
$ python download_audio.py --video-url "https://youtu.be/xyz" --output "/path/to/audio.mp3"
|
||||
|
||||
Requirements:
|
||||
- ffmpeg installed at ~/ffmpeg
|
||||
- Internet connection for downloading yt-dlp and videos
|
||||
- yt-dlp will be automatically downloaded if not present
|
||||
|
||||
Platform Support:
|
||||
- Windows: Downloads yt-dlp.exe
|
||||
- macOS: Downloads yt-dlp_macos
|
||||
- Linux: Downloads yt-dlp_linux
|
||||
|
||||
Author: sanhe
|
||||
Plugin: youtube@sanhe-claude-code-plugins
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import json
|
||||
import subprocess
|
||||
import urllib.request
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
|
||||
# Detect OS
|
||||
IS_WINDOWS = False
|
||||
IS_MACOS = False
|
||||
IS_LINUX = False
|
||||
|
||||
_platform = sys.platform
|
||||
if _platform in ["win32", "cygwin"]:
|
||||
IS_WINDOWS = True
|
||||
elif _platform == "darwin":
|
||||
IS_MACOS = True
|
||||
elif _platform == "linux":
|
||||
IS_LINUX = True
|
||||
else:
|
||||
raise RuntimeError(f"Unsupported platform: {_platform}")
|
||||
|
||||
dir_home = Path.home()
|
||||
dir_tmp = dir_home / "tmp"
|
||||
try:
|
||||
dir_tmp.mkdir(exist_ok=True)
|
||||
except Exception: # pragma: no cover
|
||||
pass
|
||||
path_audio = dir_tmp / "download_audio_result.mp3"
|
||||
|
||||
if IS_WINDOWS:
|
||||
filename = "yt-dlp.exe"
|
||||
path_yt_dlp = dir_home / "yt-dlp.exe"
|
||||
elif IS_MACOS:
|
||||
filename = "yt-dlp_macos"
|
||||
path_yt_dlp = dir_home / "yt-dlp_macos"
|
||||
elif IS_LINUX:
|
||||
filename = "yt-dlp_linux"
|
||||
path_yt_dlp = dir_home / "yt-dlp_linux"
|
||||
else:
|
||||
raise RuntimeError("Unsupported operating system")
|
||||
|
||||
path_ffmpeg = dir_home / "ffmpeg"
|
||||
|
||||
|
||||
def get_latest_yt_dlp_release() -> str:
|
||||
"""Get the latest yt-dlp release version from GitHub API."""
|
||||
api_url = "https://api.github.com/repos/yt-dlp/yt-dlp/releases/latest"
|
||||
|
||||
with urllib.request.urlopen(api_url) as response:
|
||||
data = json.loads(response.read().decode())
|
||||
tag_name = data["tag_name"]
|
||||
return tag_name
|
||||
|
||||
|
||||
def download_yt_dlp():
|
||||
"""Download the appropriate yt-dlp binary based on the OS."""
|
||||
# Get latest release version
|
||||
latest_version = get_latest_yt_dlp_release()
|
||||
print(f"Latest yt-dlp version: {latest_version}")
|
||||
|
||||
# Determine download URL and local path based on OS
|
||||
download_url = f"https://github.com/yt-dlp/yt-dlp/releases/download/{latest_version}/{filename}"
|
||||
|
||||
print(f"Downloading {filename} from {download_url}...")
|
||||
print(f"Saving to {path_yt_dlp}...")
|
||||
|
||||
# Download the file
|
||||
urllib.request.urlretrieve(download_url, path_yt_dlp)
|
||||
|
||||
# Set executable permissions for macOS and Linux
|
||||
if IS_MACOS or IS_LINUX:
|
||||
os.chmod(path_yt_dlp, 0o755)
|
||||
|
||||
print(f"Successfully downloaded yt-dlp to {path_yt_dlp}")
|
||||
|
||||
|
||||
def download_audio(
|
||||
video_url: str,
|
||||
quality: str = "bestaudio[abr<=64]/worstaudio",
|
||||
audio_format: str = "mp3",
|
||||
bitrate: str = "64K",
|
||||
output_path: str = None,
|
||||
):
|
||||
"""
|
||||
Download audio from a YouTube video using yt-dlp.
|
||||
|
||||
Args:
|
||||
video_url: YouTube video URL to download
|
||||
quality: yt-dlp quality selector (default: "bestaudio[abr<=64]/worstaudio")
|
||||
audio_format: Output audio format (default: "mp3")
|
||||
bitrate: Audio bitrate (default: "64K")
|
||||
output_path: Custom output path (default: ~/tmp/download_audio_result.mp3)
|
||||
|
||||
Returns:
|
||||
Path to the downloaded audio file
|
||||
"""
|
||||
output = output_path if output_path else str(path_audio)
|
||||
|
||||
args = [
|
||||
f"{path_yt_dlp}",
|
||||
"-f",
|
||||
quality,
|
||||
"--extract-audio",
|
||||
"--audio-format",
|
||||
audio_format,
|
||||
"--audio-quality",
|
||||
bitrate,
|
||||
"-o",
|
||||
output,
|
||||
"--restrict-filenames",
|
||||
"--ffmpeg-location",
|
||||
f"{str(path_ffmpeg)}",
|
||||
video_url,
|
||||
]
|
||||
result = subprocess.run(args, check=True, capture_output=True)
|
||||
return output
|
||||
|
||||
|
||||
def main():
|
||||
"""
|
||||
Main CLI entry point for downloading YouTube audio.
|
||||
|
||||
This script downloads audio from YouTube videos using yt-dlp and ffmpeg.
|
||||
It automatically downloads yt-dlp if not present in the home directory.
|
||||
|
||||
Example usage:
|
||||
# Use default output location (allows overwrite)
|
||||
python download_audio.py --video-url "https://www.youtube.com/watch?v=d6rZtgHcbWA"
|
||||
|
||||
# Specify custom quality and bitrate
|
||||
python download_audio.py --video-url "https://youtu.be/xyz" --quality "bestaudio" --bitrate "128K"
|
||||
|
||||
# Specify custom output location (cannot overwrite existing file)
|
||||
python download_audio.py --video-url "https://youtu.be/xyz" --output "/path/to/output.mp3"
|
||||
|
||||
Requirements:
|
||||
- ffmpeg must be installed at ~/ffmpeg
|
||||
- yt-dlp will be automatically downloaded to home directory if not present
|
||||
|
||||
Note:
|
||||
- Default location (~/tmp/download_audio_result.mp3) allows overwrite
|
||||
- Custom output locations cannot overwrite existing files
|
||||
"""
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Download audio from YouTube videos as MP3 files using yt-dlp",
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
epilog="""
|
||||
Examples:
|
||||
%(prog)s --video-url "https://www.youtube.com/watch?v=d6rZtgHcbWA"
|
||||
%(prog)s --video-url "https://youtu.be/xyz" --quality "bestaudio" --bitrate "128K"
|
||||
%(prog)s --video-url "https://youtu.be/xyz" --output "/custom/path/audio.mp3"
|
||||
|
||||
Quality options:
|
||||
bestaudio - Best quality audio available
|
||||
bestaudio[abr<=64] - Best audio with max 64kbps bitrate
|
||||
worstaudio - Lowest quality audio (smallest file)
|
||||
""",
|
||||
)
|
||||
|
||||
# Required arguments
|
||||
parser.add_argument(
|
||||
"--video-url",
|
||||
type=str,
|
||||
required=True,
|
||||
help="YouTube video URL to download (required)",
|
||||
)
|
||||
|
||||
# Optional arguments for yt-dlp customization
|
||||
parser.add_argument(
|
||||
"--quality",
|
||||
type=str,
|
||||
default="bestaudio[abr<=64]/worstaudio",
|
||||
help='Quality selector for yt-dlp (default: "bestaudio[abr<=64]/worstaudio")',
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--audio-format",
|
||||
type=str,
|
||||
default="mp3",
|
||||
help="Output audio format: mp3, aac, wav, etc. (default: mp3)",
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--bitrate",
|
||||
type=str,
|
||||
default="64K",
|
||||
help="Audio bitrate: 64K, 128K, 192K, 320K, etc. (default: 64K)",
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--output",
|
||||
type=str,
|
||||
default=None,
|
||||
help=f"Custom output file path (default: {path_audio}). Note: Default location allows overwrite, custom locations cannot overwrite existing files.",
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Ensure yt-dlp is downloaded
|
||||
if not path_yt_dlp.exists():
|
||||
print("yt-dlp not found, downloading...")
|
||||
download_yt_dlp()
|
||||
|
||||
# Determine output path and check for existing files
|
||||
if args.output:
|
||||
path_output = Path(args.output).expanduser()
|
||||
# For custom locations, check if file already exists
|
||||
if (path_output != path_audio) and path_output.exists():
|
||||
raise FileExistsError(
|
||||
f"Output file already exists at {path_output}. "
|
||||
f"Please choose a different location or remove the existing file. "
|
||||
f"(Default location {path_audio} allows overwrite)"
|
||||
)
|
||||
# Ensure output directory exists
|
||||
path_output.parent.mkdir(parents=True, exist_ok=True)
|
||||
output_path_str = str(path_output)
|
||||
else:
|
||||
# Use default location (allows overwrite)
|
||||
path_audio.unlink(missing_ok=True)
|
||||
output_path_str = None
|
||||
|
||||
# Download the audio
|
||||
output_file = download_audio(
|
||||
video_url=args.video_url,
|
||||
quality=args.quality,
|
||||
audio_format=args.audio_format,
|
||||
bitrate=args.bitrate,
|
||||
output_path=output_path_str,
|
||||
)
|
||||
|
||||
print(f"✓ Audio successfully downloaded from {args.video_url}")
|
||||
print(f"✓ Saved to: {output_file}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user