Skip to content

Instantly share code, notes, and snippets.

@Codycody31
Created October 29, 2025 18:47
Show Gist options
  • Select an option

  • Save Codycody31/fa360c62b80e86b101bc067d4cd18c76 to your computer and use it in GitHub Desktop.

Select an option

Save Codycody31/fa360c62b80e86b101bc067d4cd18c76 to your computer and use it in GitHub Desktop.
#!/usr/bin/env python3
import argparse
import subprocess
import sys
from pathlib import Path
def download_playlist(url: str, output_dir: Path, args) -> bool:
"""Download YouTube playlist with yt-dlp."""
output_dir.mkdir(parents=True, exist_ok=True)
# yt-dlp command configuration
cmd = [
"yt-dlp",
"--extract-audio",
"--audio-format", "mp3",
"--audio-quality", "0", # best quality
"--write-info-json",
"--write-thumbnail",
"--convert-thumbnails", "webp",
"--output", str(output_dir / "%(title)s.%(ext)s"),
"--embed-metadata",
"--no-playlist" if args.no_playlist else "--yes-playlist",
]
# Optional: limit number of items
if args.max_downloads:
cmd.extend(["--max-downloads", str(args.max_downloads)])
# Optional: date filters
if args.date_after:
cmd.extend(["--dateafter", args.date_after])
if args.date_before:
cmd.extend(["--datebefore", args.date_before])
# Optional: playlist range
if args.playlist_start:
cmd.extend(["--playlist-start", str(args.playlist_start)])
if args.playlist_end:
cmd.extend(["--playlist-end", str(args.playlist_end)])
# Add any extra arguments
if args.yt_dlp_args:
cmd.extend(args.yt_dlp_args.split())
# Add the URL last
cmd.append(url)
print(f"[info] Downloading from: {url}")
print(f"[info] Output directory: {output_dir}")
print(f"[cmd] {' '.join(cmd)}\n")
try:
result = subprocess.run(cmd, check=True)
return result.returncode == 0
except subprocess.CalledProcessError as e:
print(f"[error] yt-dlp failed with exit code {e.returncode}", file=sys.stderr)
return False
except FileNotFoundError:
print("[error] yt-dlp not found. Install it with: pip install yt-dlp", file=sys.stderr)
return False
def embed_metadata(output_dir: Path, args):
"""Run the embed script on downloaded files."""
embed_script = Path(__file__).parent / "embed_webp_to_mp3.py"
if not embed_script.exists():
print(f"[warn] Embed script not found at {embed_script}, skipping metadata embedding.")
return False
print(f"\n[info] Embedding metadata into MP3 files...")
cmd = [sys.executable, str(embed_script), str(output_dir)]
# Pass through relevant arguments
if args.default_album:
cmd.extend(["--default-album", args.default_album])
if args.prefer_channel_as_artist:
cmd.append("--prefer-channel-as-artist")
if args.no_jpeg_fallback:
cmd.append("--no-jpeg-fallback")
if args.no_webp:
cmd.append("--no-webp")
if args.require_cover:
cmd.append("--require-cover")
try:
result = subprocess.run(cmd, check=True)
return result.returncode == 0
except subprocess.CalledProcessError as e:
print(f"[error] Embed script failed with exit code {e.returncode}", file=sys.stderr)
return False
def main():
ap = argparse.ArgumentParser(
description="Download YouTube playlist and embed metadata into MP3 files.",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
%(prog)s "https://www.youtube.com/playlist?list=..." -o ~/Music/MyPlaylist
%(prog)s "https://www.youtube.com/watch?v=..." --no-playlist -o ~/Music/Singles
%(prog)s "PLxxx" -o ~/Music/Favorites --max-downloads 10
"""
)
# Required arguments
ap.add_argument("url", help="YouTube playlist URL or ID")
# Output options
ap.add_argument("-o", "--output", required=True,
help="Output directory for downloaded files")
# yt-dlp options
ap.add_argument("--no-playlist", action="store_true",
help="Download only the video (not the playlist)")
ap.add_argument("--max-downloads", type=int,
help="Maximum number of items to download")
ap.add_argument("--playlist-start", type=int,
help="Playlist video to start at (default: 1)")
ap.add_argument("--playlist-end", type=int,
help="Playlist video to end at (default: last)")
ap.add_argument("--date-after", metavar="DATE",
help="Download only videos uploaded on or after this date (YYYYMMDD)")
ap.add_argument("--date-before", metavar="DATE",
help="Download only videos uploaded on or before this date (YYYYMMDD)")
ap.add_argument("--yt-dlp-args",
help="Additional yt-dlp arguments (as a single quoted string)")
# Embed script options
ap.add_argument("--default-album", default="YouTube Music",
help="Album name to use if JSON has none (default: %(default)s)")
ap.add_argument("--prefer-channel-as-artist", action="store_true",
help="Prefer channel/uploader as Artist/Album Artist")
ap.add_argument("--no-jpeg-fallback", action="store_true",
help="Do not embed JPEG fallback")
ap.add_argument("--no-webp", action="store_true",
help="Do not embed WEBP (embed only JPEG fallback)")
ap.add_argument("--require-cover", action="store_true",
help="Skip files without a matching .webp")
# Workflow options
ap.add_argument("--download-only", action="store_true",
help="Download files but don't embed metadata")
ap.add_argument("--embed-only", action="store_true",
help="Skip download, only embed metadata")
args = ap.parse_args()
output_dir = Path(args.output).expanduser().resolve()
# Download phase
if not args.embed_only:
success = download_playlist(args.url, output_dir, args)
if not success:
print("\n[error] Download failed, stopping.", file=sys.stderr)
sys.exit(1)
# Embed phase
if not args.download_only:
success = embed_metadata(output_dir, args)
if not success:
print("\n[warn] Metadata embedding encountered errors.")
print("\n[done] All operations completed.")
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment