First commit
This commit is contained in:
178
README.md
Normal file
178
README.md
Normal file
@ -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 <your-gitea-repo-url>
|
||||
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 <your-repo-url>
|
||||
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!**
|
216
yt-channel-archiver.py
Normal file
216
yt-channel-archiver.py
Normal file
@ -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()
|
Reference in New Issue
Block a user