Initial commit

This commit is contained in:
Zhongwei Li
2025-11-30 08:38:41 +08:00
commit 7d5b628e7d
13 changed files with 1610 additions and 0 deletions

View 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()