#!/usr/bin/env python3 """ YouTube Channel Archiver A cross-platform script to download all videos from a YouTube channel using yt-dlp. Supports macOS, Linux, and Windows. """ import os import sys import subprocess import argparse import platform from pathlib import Path def check_dependencies(): """Check if yt-dlp is installed and available.""" try: result = subprocess.run(['yt-dlp', '--version'], capture_output=True, text=True, check=True) print(f"✓ yt-dlp is installed: {result.stdout.strip()}") return True except (subprocess.CalledProcessError, FileNotFoundError): return False def install_ytdlp(): """Attempt to install yt-dlp using pip.""" print("yt-dlp not found. Attempting to install...") try: subprocess.run([sys.executable, '-m', 'pip', 'install', 'yt-dlp'], check=True) print("✓ yt-dlp installed successfully!") return True except subprocess.CalledProcessError: print("✗ Failed to install yt-dlp automatically.") print("Please install yt-dlp manually:") print(" pip install yt-dlp") print(" or visit: https://github.com/yt-dlp/yt-dlp") return False def create_download_directory(channel_url, base_path=None): """Create a directory for downloads based on channel name.""" if base_path is None: base_path = Path.cwd() # Extract channel name using yt-dlp try: result = subprocess.run([ 'yt-dlp', '--print', 'uploader', '--playlist-end', '1', # Only get first video to extract channel name channel_url ], capture_output=True, text=True, check=True) channel_name = result.stdout.strip() # Clean channel name for use as directory name channel_name = "".join(c for c in channel_name if c.isalnum() or c in (' ', '-', '_')).rstrip() except subprocess.CalledProcessError: # Fallback to generic name if channel name extraction fails channel_name = "YouTube_Channel_Archive" download_dir = Path(base_path) / channel_name download_dir.mkdir(exist_ok=True) return download_dir def download_channel(channel_url, output_dir, quality='best', audio_only=False, download_thumbnails=True, download_metadata=True): """Download all videos from a YouTube channel.""" # Build yt-dlp command cmd = ['yt-dlp'] # Output template - organized by upload date output_template = str(output_dir / "%(upload_date)s - %(title)s.%(ext)s") cmd.extend(['-o', output_template]) # Quality settings if audio_only: cmd.extend(['-f', 'bestaudio/best']) else: if quality == 'best': cmd.extend(['-f', 'best']) elif quality == 'worst': cmd.extend(['-f', 'worst']) else: # Custom quality (e.g., '720p', '1080p') cmd.extend(['-f', f'best[height<={quality[:-1]}]']) # Additional options cmd.extend([ '--ignore-errors', # Continue on errors '--no-overwrites', # Don't re-download existing files '--continue', # Resume partial downloads '--extract-flat', # Don't download, just list (we'll remove this) ]) # Remove extract-flat, we actually want to download cmd.remove('--extract-flat') # Archive file to track downloaded videos archive_file = output_dir / 'download_archive.txt' cmd.extend(['--download-archive', str(archive_file)]) # Metadata options if download_metadata: cmd.extend([ '--write-info-json', # Save video metadata '--write-description', # Save video description ]) # Thumbnail options if download_thumbnails: cmd.extend([ '--write-thumbnail', '--write-all-thumbnails' # Get all available thumbnail sizes ]) # Add channel URL cmd.append(channel_url) print(f"Starting download to: {output_dir}") print(f"Command: {' '.join(cmd)}") print("-" * 50) try: # Run the download command subprocess.run(cmd, check=True, cwd=output_dir) print("-" * 50) print("✓ Download completed successfully!") except subprocess.CalledProcessError as e: print(f"✗ Download failed with error code: {e.returncode}") print("Some videos may have been downloaded successfully.") return False except KeyboardInterrupt: print("\n⚠ Download interrupted by user.") print("You can resume later - already downloaded videos won't be re-downloaded.") return False return True def main(): parser = argparse.ArgumentParser( description="Download all videos from a YouTube channel", formatter_class=argparse.RawDescriptionHelpFormatter, epilog=""" Examples: python %(prog)s "https://www.youtube.com/@channelname" python %(prog)s "https://www.youtube.com/c/channelname" --quality 720p python %(prog)s "https://www.youtube.com/user/username" --audio-only python %(prog)s "UCxxxxxxxxxxxxxxxxxxx" --output ./Downloads """ ) parser.add_argument('channel_url', help='YouTube channel URL or channel ID') parser.add_argument('--output', '-o', help='Output directory (default: current directory)') parser.add_argument('--quality', '-q', choices=['best', 'worst', '720p', '1080p', '480p'], default='best', help='Video quality (default: best)') parser.add_argument('--audio-only', '-a', action='store_true', help='Download only audio') parser.add_argument('--no-thumbnails', action='store_true', help='Skip downloading thumbnails') parser.add_argument('--no-metadata', action='store_true', help='Skip downloading metadata files') args = parser.parse_args() print(f"YouTube Channel Archiver") print(f"Platform: {platform.system()}") print("-" * 50) # Check dependencies if not check_dependencies(): if not install_ytdlp(): sys.exit(1) # Create output directory try: output_dir = create_download_directory(args.channel_url, args.output) print(f"Download directory: {output_dir}") except Exception as e: print(f"✗ Error creating download directory: {e}") sys.exit(1) # Start download success = download_channel( args.channel_url, output_dir, quality=args.quality, audio_only=args.audio_only, download_thumbnails=not args.no_thumbnails, download_metadata=not args.no_metadata ) if success: print(f"\n✓ All videos downloaded to: {output_dir}") print(f"Archive file created at: {output_dir / 'download_archive.txt'}") else: sys.exit(1) if __name__ == "__main__": main()