217 lines
7.1 KiB
Python
217 lines
7.1 KiB
Python
#!/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()
|