Automated YouTube video downloader with channel monitoring, retention management, and a web interface.
- Monitor YouTube channels and automatically download new videos
- Per-channel and per-video retention policies with cutoff dates
- Web UI for configuration and management
- REST API for programmatic control
- Cookie support for bypassing rate limits
- Automatic yt-dlp updates
- Docker support with multi-stage builds
- Concurrent downloads with configurable limits
# Using docker-compose
docker-compose up -d
# Or manually
docker build -t ytdm .
docker run -d -p 8080:8080 \
-v $(pwd)/data:/app/data \
-v $(pwd)/downloads:/app/downloads \
ytdmAccess the web UI at http://localhost:8080
Requirements: Go 1.21+, Python 3, yt-dlp, ffmpeg (optional)
# Quick start script
./run-local.sh
# Or manually
go build -o ytdm
./ytdmAll configuration can be managed through the web UI or by editing data/config.json:
{
"check_interval_seconds": "5m0s",
"retention_days": 7,
"disable_pruning": false,
"download_dir": "../downloads",
"file_name_pattern": "%(title)s-%(id)s.%(ext)s",
"max_concurrent_downloads": 3,
"yt_dlp": {
"path": "yt-dlp",
"update_interval_seconds": "24h0m0s",
"cookies_browser": "firefox",
"cookies_file": "data/cookies.txt",
"extractor_sleep_interval_seconds": "0s",
"download_throughput_limit": "",
"restrict_filenames": false,
"cache_dir": "data/yt-dlp-cache"
}
}Note: Duration fields in data/config.json use Go duration strings (e.g. "5m0s", "24h0m0s"). The web UI still accepts seconds and converts internally.
- check_interval_seconds: How often to check for new videos (default:
5m0s) - retention_days: Default retention period in days (default: 7)
- disable_pruning: Disable all automatic pruning globally (default: false)
- max_concurrent_downloads: Number of simultaneous downloads (default: 3)
- yt_dlp: Settings for yt-dlp
- path: Path to yt-dlp executable
- update_interval_seconds: How often to auto-update yt-dlp (e.g.
24h0m0s,0sto disable) - cookies_browser: Extract cookies from browser (
firefoxorchrome) - cookies_file: Path to Netscape format cookies file
- extractor_sleep_interval_seconds: Sleep between extractor requests (e.g.
0sto disable) - download_throughput_limit: Limit download speed (e.g.
100K,4.2M) - restrict_filenames: Use yt-dlp's filename restrictions
- cache_dir: Cache directory used by yt-dlp
Each channel can override the global retention with its own retention period and cutoff date:
- Retention Days: Keep videos for N days (0 = use global setting)
- Keep indefinitely (disable pruning): Skip automatic pruning for this channel/video entry
- Cutoff Date: Only download videos published on or after this date
Channel monitoring only downloads videos that are newer than now - retention_days (using channel retention, or global retention when channel retention is unset).
YouTube may require authentication to avoid rate limiting. Two options:
Select browser in the Configuration tab. Requires the browser to be running and logged into YouTube.
- Export cookies using a browser extension (Cookie Editor, etc.)
- Paste Netscape format cookies in the Configuration tab
- Click "Save Pasted Cookies"
Example format:
# Netscape HTTP Cookie File
.youtube.com TRUE / TRUE 1805237469 COOKIE_NAME cookie_value
GET /api/channels- List all channelsPOST /api/channels- Add a channelDELETE /api/channels/{id}- Remove a channel
GET /api/videos- List all videosPOST /api/videos- Add a videoDELETE /api/videos/{id}- Remove a video
GET /api/config- Get configurationPUT /api/config- Update configuration
POST /api/cookies- Save pasted cookiesPOST /api/cookies/clear- Clear all cookies
GET /api/status- Service status
ytdm/
├── data/
│ ├── config.json # Application configuration
│ ├── data.json # Channel/video state
│ └── cookies.txt # YouTube cookies
├── downloads/ # Downloaded videos (organized by channel)
├── static/
│ ├── index.html # Web UI
│ └── app.js # UI JavaScript
├── *.go # Source files
├── *_test.go # Test files
├── Dockerfile
├── docker-compose.yml
└── run-local.sh # Local run script
# Run all tests
go test -v ./...
# Run specific test suite
go test -v -run TestStorage
go test -v -run TestConfig
go test -v -run TestVideoInfoBuilt with Go 1.21 using only the standard library (yt-dlp runs as subprocess).
Project structure:
main.go- Entry point and lifecycle managementconfig.go- Configuration with thread-safe operationsstorage.go- Persistent data managementdownloader.go- yt-dlp wrapperscheduler.go- Background task schedulingapi.go- REST API and web serverupdater.go- yt-dlp auto-updater
yt-dlp automatically updates itself to the latest version:
- Default: Updates every 24 hours
- Configurable via web UI or API
- Set to 0 to disable auto-updates
- Uses yt-dlp's built-in self-update mechanism (
yt-dlp -U)
ffmpeg updates require rebuilding the Docker image:
docker build -t ytdm:latest --no-cache .GET /api/channels- List all channelsPOST /api/channels- Add a new channel{ "name": "Channel Name", "url": "https://youtube.com/@channelname", "retention_days": 60, "cutoff_date": "2024-01-01T00:00:00Z" }cutoff_date(optional): Don't download videos published before this date
DELETE /api/channels/{id}- Remove a channel
GET /api/videos- List all videosPOST /api/videos- Add a new video{ "title": "Video Title", "url": "https://youtube.com/watch?v=VIDEO_ID", "retention_days": 90 }DELETE /api/videos/{id}- Remove a video
GET /api/config- Get current configurationPUT /api/config- Update configuration{ "check_interval_seconds": 600, "retention_days": 60 }
GET /api/status- Get service status
ytdm/
├── main.go # Entry point
├── config.go # Configuration management
├── storage.go # Persistent data storage
├── downloader.go # yt-dlp wrapper
├── scheduler.go # Background task scheduler
├── api.go # REST API handlers
├── static/
│ └── index.html # Web interface
├── Dockerfile # Docker build configuration
├── .dockerignore # Docker ignore file
└── README.md # This file
Downloaded videos are organized by channel:
/downloads/
├── Channel_Name_1/
│ ├── video1.mp4
│ └── video2.mp4
└── Channel_Name_2/
└── video3.mp4
All operations are logged to stdout, including:
- Video downloads
- Removal of old videos
- API requests
- Configuration changes
- Errors and warnings
The service is designed to handle errors gracefully:
- Failed downloads don't stop other downloads
- API errors return proper HTTP status codes
- Service continues running even if individual operations fail
- Automatic retry on transient failures
The application handles system signals (SIGINT/SIGTERM) gracefully:
- In-progress downloads: Always complete before shutdown
- Pending work: Skipped to speed up shutdown
- Timeout: 5-minute maximum wait for downloads to finish
- Clean exit: All resources properly released
When you press Ctrl+C or send a termination signal:
- Service stops accepting new download tasks
- In-progress downloads are allowed to complete
- Once all downloads finish, service exits cleanly
- If downloads take longer than 5 minutes, forces exit
The project includes comprehensive unit tests covering utility functions, storage operations, configuration management, and skip detection logic.
# Run all tests
go test ./...
# Run with verbose output
go test -v ./...
# Run specific tests
go test -v -run TestSanitizeFilename
# Run with coverage report
go test ./... -cover
# Generate detailed coverage report
go test ./... -coverprofile=coverage.out
go tool cover -func=coverage.out
# View coverage in browser
go tool cover -html=coverage.outMIT License