From 48ba590adb2fdf4f186af6ddb0b3228069b2c2f0 Mon Sep 17 00:00:00 2001 From: Kevin Thompson Date: Wed, 23 Jul 2025 14:21:50 -0500 Subject: [PATCH] First commit --- README.md | 178 +++++++++++++++++++++++++++++++++ yt-channel-archiver.py | 216 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 394 insertions(+) create mode 100644 README.md create mode 100644 yt-channel-archiver.py diff --git a/README.md b/README.md new file mode 100644 index 0000000..43fbe2f --- /dev/null +++ b/README.md @@ -0,0 +1,178 @@ +# YouTube Channel Archiver + +A cross-platform Python script for downloading and archiving entire YouTube channels using `yt-dlp`. Perfect for content preservation, offline viewing, and creating personal archives of your favorite channels. + +## ✨ Features + +- šŸŒ **Cross-platform**: Works on macOS, Linux, and Windows +- šŸ“¦ **Automatic setup**: Installs `yt-dlp` dependency automatically if needed +- šŸ“ **Smart organization**: Creates channel-specific folders with date-organized files +- šŸ”„ **Resume support**: Tracks downloaded videos to avoid duplicates and resume interrupted downloads +- šŸŽ„ **Quality options**: Choose from multiple video qualities or audio-only downloads +- šŸ“ **Metadata preservation**: Downloads video descriptions, info, and thumbnails +- ⚔ **Error resilient**: Continues downloading even if individual videos fail +- šŸŽÆ **Flexible input**: Supports various YouTube URL formats and channel IDs + +## šŸš€ Quick Start + +```bash +# Clone the repository +git clone +cd youtube-channel-archiver + +# Run the script (it will install yt-dlp automatically if needed) +python youtube_archiver.py "https://www.youtube.com/@channelname" +``` + +## šŸ“‹ Requirements + +- **Python 3.6+** +- **pip** (for automatic yt-dlp installation) +- Internet connection + +The script will automatically install `yt-dlp` if it's not already available on your system. + +## šŸ”§ Installation + +### Option 1: Direct Download +Download `youtube_archiver.py` and run it directly - no additional setup required! + +### Option 2: Clone Repository +```bash +git clone +cd youtube-channel-archiver +chmod +x youtube_archiver.py # On Unix systems +``` + +### Option 3: Manual yt-dlp Installation +If you prefer to install yt-dlp manually: +```bash +pip install yt-dlp +``` + +## šŸ“– Usage + +### Basic Usage +```bash +python youtube_archiver.py "CHANNEL_URL" +``` + +### Supported URL Formats +- `https://www.youtube.com/@channelname` +- `https://www.youtube.com/c/channelname` +- `https://www.youtube.com/user/username` +- `https://www.youtube.com/channel/UCxxxxxxxxxxxxxxxxxxx` +- `UCxxxxxxxxxxxxxxxxxxx` (Channel ID only) + +### Command Line Options + +| Option | Description | Default | +|--------|-------------|---------| +| `--output`, `-o` | Output directory | Current directory | +| `--quality`, `-q` | Video quality (`best`, `worst`, `720p`, `1080p`, `480p`) | `best` | +| `--audio-only`, `-a` | Download audio only | False | +| `--no-thumbnails` | Skip thumbnail downloads | False | +| `--no-metadata` | Skip metadata files | False | +| `--help`, `-h` | Show help message | - | + +### Examples + +```bash +# Download all videos in best quality +python youtube_archiver.py "https://www.youtube.com/@examplechannel" + +# Download to specific directory with 720p quality +python youtube_archiver.py "https://www.youtube.com/@examplechannel" --output ./Downloads --quality 720p + +# Audio-only downloads +python youtube_archiver.py "https://www.youtube.com/@musicchannel" --audio-only + +# Minimal download (no thumbnails or metadata) +python youtube_archiver.py "https://www.youtube.com/@newschannel" --no-thumbnails --no-metadata +``` + +## šŸ“ Output Structure + +The script creates an organized directory structure: + +``` +Channel_Name/ +ā”œā”€ā”€ download_archive.txt # Tracks downloaded videos +ā”œā”€ā”€ 20240115 - Video Title 1.mp4 +ā”œā”€ā”€ 20240115 - Video Title 1.info.json +ā”œā”€ā”€ 20240115 - Video Title 1.description +ā”œā”€ā”€ 20240115 - Video Title 1.webp +ā”œā”€ā”€ 20240116 - Video Title 2.mp4 +└── ... +``` + +### File Types +- **`.mp4/.webm/etc`**: Video files +- **`.info.json`**: Video metadata (duration, views, description, etc.) +- **`.description`**: Video description text +- **`.webp/.jpg`**: Thumbnails +- **`download_archive.txt`**: List of downloaded video IDs (prevents re-downloading) + +## šŸ”„ Resuming Downloads + +The script automatically tracks downloaded videos in `download_archive.txt`. If a download is interrupted: + +1. Simply run the same command again +2. Already downloaded videos will be skipped +3. New or failed videos will be downloaded + +## ⚠ Legal Considerations + +- **Respect copyright**: Only download content you have permission to archive +- **Personal use**: This tool is intended for personal archiving and offline viewing +- **YouTube ToS**: Be aware of YouTube's Terms of Service regarding content downloading +- **Fair use**: Consider fair use principles in your jurisdiction + +## šŸ›  Troubleshooting + +### Common Issues + +**"yt-dlp not found" error** +- The script should install it automatically +- If not, manually install: `pip install yt-dlp` + +**Permission denied errors** +- On Unix systems: `chmod +x youtube_archiver.py` +- Run with appropriate permissions + +**Network/download errors** +- The script continues on errors - check the output for specific failures +- Some videos may be unavailable due to geographic restrictions or privacy settings + +**Python not found** +- Ensure Python 3.6+ is installed and in your PATH +- Try `python3` instead of `python` on some systems + +### Getting Help + +If you encounter issues: +1. Check the console output for specific error messages +2. Ensure you have a stable internet connection +3. Verify the channel URL is correct and publicly accessible +4. Try running with `--quality worst` to test with smaller files + +## šŸ¤ Contributing + +Contributions are welcome! Please feel free to: +- Report bugs +- Suggest features +- Submit pull requests +- Improve documentation + +## šŸ“„ License + +This project is provided as-is for educational and personal archiving purposes. Please ensure your use complies with applicable laws and YouTube's Terms of Service. + +## šŸ™ Acknowledgments + +- Built using [yt-dlp](https://github.com/yt-dlp/yt-dlp) - the excellent YouTube downloading library +- Inspired by the need for content preservation and offline access + +--- + +**⭐ Star this repository if you find it useful!** diff --git a/yt-channel-archiver.py b/yt-channel-archiver.py new file mode 100644 index 0000000..f42cc41 --- /dev/null +++ b/yt-channel-archiver.py @@ -0,0 +1,216 @@ +#!/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()