Initial commit: YouTube Sync Docker container
- Alpine-based Docker image with yt-dlp - Configurable channel sync script - Unraid template for Community Applications - Automatic scheduling via cron - Emby/Plex/Jellyfin compatible output structure
This commit is contained in:
18
.gitignore
vendored
Normal file
18
.gitignore
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
# Docker
|
||||
*.log
|
||||
|
||||
# Testing
|
||||
test/
|
||||
downloads/
|
||||
config/
|
||||
|
||||
# Editor
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
37
Dockerfile
Normal file
37
Dockerfile
Normal file
@@ -0,0 +1,37 @@
|
||||
FROM alpine:3.19
|
||||
|
||||
# Install dependencies
|
||||
RUN apk add --no-cache \
|
||||
python3 \
|
||||
py3-pip \
|
||||
ffmpeg \
|
||||
bash \
|
||||
curl \
|
||||
tzdata \
|
||||
dcron \
|
||||
&& pip3 install --no-cache-dir yt-dlp --break-system-packages
|
||||
|
||||
# Create directories
|
||||
RUN mkdir -p /downloads /config /app
|
||||
|
||||
# Copy scripts
|
||||
COPY sync.sh /app/sync.sh
|
||||
COPY entrypoint.sh /app/entrypoint.sh
|
||||
RUN chmod +x /app/sync.sh /app/entrypoint.sh
|
||||
|
||||
# Set working directory
|
||||
WORKDIR /app
|
||||
|
||||
# Environment defaults
|
||||
ENV DOWNLOAD_DIR=/downloads \
|
||||
CONFIG_DIR=/config \
|
||||
SYNC_SCHEDULE="0 2 * * *" \
|
||||
MAX_QUALITY=1080 \
|
||||
SLEEP_INTERVAL=2 \
|
||||
TZ=America/Phoenix
|
||||
|
||||
# Expose volume mount points
|
||||
VOLUME ["/downloads", "/config"]
|
||||
|
||||
# Run entrypoint
|
||||
ENTRYPOINT ["/app/entrypoint.sh"]
|
||||
21
LICENSE
Normal file
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2026 Arizona Computer Guru LLC
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
173
README.md
Normal file
173
README.md
Normal file
@@ -0,0 +1,173 @@
|
||||
# YouTube Channel Sync Docker
|
||||
|
||||
Automatically download and organize YouTube channel videos for Emby, Plex, or Jellyfin media servers.
|
||||
|
||||
## Features
|
||||
|
||||
- Downloads videos in best quality up to 1080p (configurable)
|
||||
- Organizes videos by season folders (year-based)
|
||||
- Proper episode naming for media servers: `S2026E001 - Video Title - [VideoID].mp4`
|
||||
- Embeds thumbnails and metadata
|
||||
- Creates `tvshow.nfo` files for Emby/Plex
|
||||
- Tracks downloaded videos to avoid re-downloading
|
||||
- Supports YouTube cookies for authentication (bypasses bot detection)
|
||||
- Scheduled automatic syncs via cron
|
||||
- Lightweight Alpine Linux base (~200MB)
|
||||
|
||||
## Quick Start (Unraid)
|
||||
|
||||
1. Install from Community Applications: Search for "YouTube Sync"
|
||||
2. Configure paths:
|
||||
- Download Directory: `/mnt/user/media/YouTube` (or your media location)
|
||||
- Config Directory: `/mnt/user/appdata/youtube-sync`
|
||||
3. Edit `/mnt/user/appdata/youtube-sync/channels.txt` and add your channels
|
||||
4. (Optional) Add YouTube cookies to `/mnt/user/appdata/youtube-sync/cookies.txt`
|
||||
5. Start the container
|
||||
|
||||
## Configuration
|
||||
|
||||
### channels.txt Format
|
||||
|
||||
```
|
||||
# Format: CHANNEL_ID|Channel Name
|
||||
UCfDNi1aEljAQ17mUrfUjkvg|Alton Brown
|
||||
UCoq2qlWgvvKJzW_hBkLIE8w|Flavour Trip
|
||||
```
|
||||
|
||||
**Finding Channel IDs:**
|
||||
1. Go to the channel's main page on YouTube
|
||||
2. View page source (Ctrl+U or Cmd+U)
|
||||
3. Search for "channelId" or check the URL structure
|
||||
|
||||
### YouTube Cookies (Optional but Recommended)
|
||||
|
||||
YouTube may block downloads without authentication. To fix this:
|
||||
|
||||
**Method 1: Browser Extension**
|
||||
1. Install "Get cookies.txt LOCALLY" extension for Firefox/Chrome
|
||||
2. Go to YouTube.com while logged in
|
||||
3. Click the extension icon and export cookies
|
||||
4. Save as `/mnt/user/appdata/youtube-sync/cookies.txt`
|
||||
|
||||
**Method 2: yt-dlp command**
|
||||
```bash
|
||||
yt-dlp --cookies-from-browser firefox --cookies cookies.txt --skip-download "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `SYNC_SCHEDULE` | `0 2 * * *` | Cron schedule (2 AM daily). Use `manual` to disable. |
|
||||
| `MAX_QUALITY` | `1080` | Maximum video quality (480, 720, 1080, 1440, 2160) |
|
||||
| `SLEEP_INTERVAL` | `2` | Seconds between downloads (helps avoid rate limiting) |
|
||||
| `TZ` | `America/Phoenix` | Timezone for scheduling |
|
||||
| `PUID` | `99` | User ID for file ownership |
|
||||
| `PGID` | `100` | Group ID for file ownership |
|
||||
|
||||
## Docker Compose
|
||||
|
||||
```yaml
|
||||
version: '3'
|
||||
services:
|
||||
youtube-sync:
|
||||
image: azcomputerguru/youtube-sync:latest
|
||||
container_name: youtube-sync
|
||||
environment:
|
||||
- SYNC_SCHEDULE=0 2 * * *
|
||||
- MAX_QUALITY=1080
|
||||
- SLEEP_INTERVAL=2
|
||||
- TZ=America/Phoenix
|
||||
- PUID=99
|
||||
- PGID=100
|
||||
volumes:
|
||||
- /mnt/user/media/YouTube:/downloads
|
||||
- /mnt/user/appdata/youtube-sync:/config
|
||||
restart: unless-stopped
|
||||
```
|
||||
|
||||
## Docker CLI
|
||||
|
||||
```bash
|
||||
docker run -d \
|
||||
--name youtube-sync \
|
||||
-e SYNC_SCHEDULE="0 2 * * *" \
|
||||
-e MAX_QUALITY=1080 \
|
||||
-e TZ=America/Phoenix \
|
||||
-v /mnt/user/media/YouTube:/downloads \
|
||||
-v /mnt/user/appdata/youtube-sync:/config \
|
||||
azcomputerguru/youtube-sync:latest
|
||||
```
|
||||
|
||||
## Manual Sync
|
||||
|
||||
To run a sync immediately:
|
||||
|
||||
```bash
|
||||
docker exec youtube-sync /app/sync.sh
|
||||
```
|
||||
|
||||
## Output Structure
|
||||
|
||||
```
|
||||
/downloads/
|
||||
├── Alton Brown/
|
||||
│ ├── tvshow.nfo
|
||||
│ ├── .downloaded.txt (tracking file)
|
||||
│ ├── Season 2024/
|
||||
│ │ ├── S2024E001 - Title - [VideoID].mp4
|
||||
│ │ ├── S2024E001 - Title - [VideoID].info.json
|
||||
│ │ └── ...
|
||||
│ └── Season 2025/
|
||||
│ └── ...
|
||||
└── Flavour Trip/
|
||||
├── tvshow.nfo
|
||||
└── Season 2026/
|
||||
└── ...
|
||||
```
|
||||
|
||||
## Emby/Plex Setup
|
||||
|
||||
1. Add the download directory as a TV Shows library
|
||||
2. Use "Local Media Assets (TV)" scanner
|
||||
3. Episodes will appear organized by channel name and year
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### "Sign in to confirm you're not a bot" Error
|
||||
|
||||
YouTube is blocking downloads. Solution: Add cookies.txt (see Configuration section)
|
||||
|
||||
### Videos Not Downloading
|
||||
|
||||
1. Check `channels.txt` format (must be `CHANNEL_ID|Name`, no spaces around `|`)
|
||||
2. Verify channel IDs are correct
|
||||
3. Check container logs: `docker logs youtube-sync`
|
||||
4. Ensure `/downloads` directory is writable
|
||||
|
||||
### Scheduling Not Working
|
||||
|
||||
1. Verify cron schedule format is correct
|
||||
2. Check timezone is set properly
|
||||
3. Set `SYNC_SCHEDULE=manual` and run manually to test
|
||||
4. Check logs: `docker logs youtube-sync`
|
||||
|
||||
## Building From Source
|
||||
|
||||
```bash
|
||||
git clone https://github.com/azcomputerguru/youtube-sync-docker
|
||||
cd youtube-sync-docker
|
||||
docker build -t youtube-sync .
|
||||
```
|
||||
|
||||
## Support
|
||||
|
||||
Issues and pull requests: https://github.com/azcomputerguru/youtube-sync-docker
|
||||
|
||||
## License
|
||||
|
||||
MIT License - See LICENSE file for details
|
||||
|
||||
## Credits
|
||||
|
||||
Built by Arizona Computer Guru LLC using [yt-dlp](https://github.com/yt-dlp/yt-dlp)
|
||||
56
entrypoint.sh
Normal file
56
entrypoint.sh
Normal file
@@ -0,0 +1,56 @@
|
||||
#!/bin/bash
|
||||
# Entrypoint script for YouTube Sync Docker container
|
||||
|
||||
set -e
|
||||
|
||||
echo "=========================================="
|
||||
echo "YouTube Channel Sync Docker"
|
||||
echo "=========================================="
|
||||
echo "Download Directory: $DOWNLOAD_DIR"
|
||||
echo "Config Directory: $CONFIG_DIR"
|
||||
echo "Sync Schedule: $SYNC_SCHEDULE"
|
||||
echo "Max Quality: ${MAX_QUALITY}p"
|
||||
echo "Sleep Interval: ${SLEEP_INTERVAL}s"
|
||||
echo "Timezone: $TZ"
|
||||
echo "=========================================="
|
||||
|
||||
# Create example channels file if it doesn't exist
|
||||
if [ ! -f "$CONFIG_DIR/channels.txt" ]; then
|
||||
echo "[INFO] Creating example channels.txt..."
|
||||
cat > "$CONFIG_DIR/channels.txt" << 'EOF'
|
||||
# YouTube Channel Configuration
|
||||
# Format: CHANNEL_ID|Channel Name
|
||||
# One channel per line. Lines starting with # are ignored.
|
||||
#
|
||||
# Examples:
|
||||
# UCfDNi1aEljAQ17mUrfUjkvg|Alton Brown
|
||||
# UCoq2qlWgvvKJzW_hBkLIE8w|Flavour Trip
|
||||
#
|
||||
# To find a channel ID:
|
||||
# 1. Go to the channel's main page
|
||||
# 2. View page source (Ctrl+U or Cmd+U)
|
||||
# 3. Search for "channelId" or look in the URL
|
||||
EOF
|
||||
fi
|
||||
|
||||
# Set up cron job if SYNC_SCHEDULE is provided
|
||||
if [ "$SYNC_SCHEDULE" != "manual" ]; then
|
||||
echo "[INFO] Setting up cron schedule: $SYNC_SCHEDULE"
|
||||
echo "$SYNC_SCHEDULE /app/sync.sh >> /var/log/youtube-sync.log 2>&1" > /etc/crontabs/root
|
||||
|
||||
# Start crond in the background
|
||||
crond -f -l 2 &
|
||||
CRON_PID=$!
|
||||
echo "[INFO] Cron daemon started (PID: $CRON_PID)"
|
||||
|
||||
# Run initial sync
|
||||
echo "[INFO] Running initial sync..."
|
||||
/app/sync.sh || true
|
||||
|
||||
# Keep container running and monitor cron
|
||||
echo "[INFO] Container ready. Watching for scheduled syncs..."
|
||||
wait $CRON_PID
|
||||
else
|
||||
echo "[INFO] Manual mode enabled. Running sync once..."
|
||||
/app/sync.sh
|
||||
fi
|
||||
102
sync.sh
Normal file
102
sync.sh
Normal file
@@ -0,0 +1,102 @@
|
||||
#!/bin/bash
|
||||
# YouTube Channel Sync Script
|
||||
# Reads channel configuration from /config/channels.txt
|
||||
|
||||
set -e
|
||||
|
||||
DOWNLOAD_DIR="${DOWNLOAD_DIR:-/downloads}"
|
||||
CONFIG_DIR="${CONFIG_DIR:-/config}"
|
||||
COOKIES_FILE="${CONFIG_DIR}/cookies.txt"
|
||||
CHANNELS_FILE="${CONFIG_DIR}/channels.txt"
|
||||
MAX_QUALITY="${MAX_QUALITY:-1080}"
|
||||
SLEEP_INTERVAL="${SLEEP_INTERVAL:-2}"
|
||||
|
||||
# Check if channels file exists
|
||||
if [ ! -f "$CHANNELS_FILE" ]; then
|
||||
echo "[ERROR] Channels file not found: $CHANNELS_FILE"
|
||||
echo "[INFO] Creating example channels.txt file..."
|
||||
cat > "$CHANNELS_FILE" << 'EOF'
|
||||
# YouTube Channel Configuration
|
||||
# Format: CHANNEL_ID|Channel Name
|
||||
# One channel per line. Lines starting with # are ignored.
|
||||
#
|
||||
# Example:
|
||||
# UCfDNi1aEljAQ17mUrfUjkvg|Alton Brown
|
||||
# UCoq2qlWgvvKJzW_hBkLIE8w|Flavour Trip
|
||||
EOF
|
||||
echo "[INFO] Please edit $CHANNELS_FILE and add your channels"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if cookies file exists
|
||||
COOKIES_PARAM=""
|
||||
if [ -f "$COOKIES_FILE" ]; then
|
||||
echo "[INFO] Using cookies from: $COOKIES_FILE"
|
||||
COOKIES_PARAM="--cookies $COOKIES_FILE"
|
||||
else
|
||||
echo "[WARNING] No cookies file found at: $COOKIES_FILE"
|
||||
echo "[WARNING] Some videos may fail to download without authentication"
|
||||
fi
|
||||
|
||||
# Parse and sync channels
|
||||
echo "=========================================="
|
||||
echo "YouTube Channel Sync - $(date)"
|
||||
echo "=========================================="
|
||||
|
||||
while IFS='|' read -r CHANNEL_ID CHANNEL_NAME || [ -n "$CHANNEL_ID" ]; do
|
||||
# Skip empty lines and comments
|
||||
[[ -z "$CHANNEL_ID" || "$CHANNEL_ID" =~ ^[[:space:]]*# ]] && continue
|
||||
|
||||
# Trim whitespace
|
||||
CHANNEL_ID=$(echo "$CHANNEL_ID" | xargs)
|
||||
CHANNEL_NAME=$(echo "$CHANNEL_NAME" | xargs)
|
||||
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo "Syncing: $CHANNEL_NAME"
|
||||
echo "Channel ID: $CHANNEL_ID"
|
||||
echo "=========================================="
|
||||
|
||||
yt-dlp \
|
||||
$COOKIES_PARAM \
|
||||
--format "bestvideo[ext=mp4][height<=${MAX_QUALITY}]+bestaudio[ext=m4a]/best[ext=mp4]/best" \
|
||||
--merge-output-format mp4 \
|
||||
--output "$DOWNLOAD_DIR/$CHANNEL_NAME/Season %(upload_date>%Y)s/S%(upload_date>%Y)sE%(playlist_index)03d - %(title).100B - [%(id)s].%(ext)s" \
|
||||
--write-info-json \
|
||||
--embed-thumbnail \
|
||||
--embed-metadata \
|
||||
--write-subs \
|
||||
--sub-langs "en.*" \
|
||||
--embed-subs \
|
||||
--download-archive "$DOWNLOAD_DIR/$CHANNEL_NAME/.downloaded.txt" \
|
||||
--no-overwrites \
|
||||
--ignore-errors \
|
||||
--sleep-interval "$SLEEP_INTERVAL" \
|
||||
--max-sleep-interval 5 \
|
||||
"https://www.youtube.com/channel/$CHANNEL_ID/videos"
|
||||
|
||||
# Create tvshow.nfo for Emby/Plex
|
||||
echo ""
|
||||
echo "Creating tvshow.nfo for $CHANNEL_NAME..."
|
||||
cat > "$DOWNLOAD_DIR/$CHANNEL_NAME/tvshow.nfo" << EOFNFO
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<tvshow>
|
||||
<title>$CHANNEL_NAME</title>
|
||||
<showtitle>$CHANNEL_NAME</showtitle>
|
||||
<plot>YouTube channel: $CHANNEL_NAME</plot>
|
||||
<genre>YouTube</genre>
|
||||
<studio>YouTube</studio>
|
||||
<premiered></premiered>
|
||||
<uniqueid type="youtube" default="true">$CHANNEL_ID</uniqueid>
|
||||
</tvshow>
|
||||
EOFNFO
|
||||
|
||||
echo "Done with $CHANNEL_NAME"
|
||||
echo ""
|
||||
|
||||
done < "$CHANNELS_FILE"
|
||||
|
||||
echo "=========================================="
|
||||
echo "All channels synced successfully!"
|
||||
echo "Completed: $(date)"
|
||||
echo "=========================================="
|
||||
49
youtube-sync.xml
Normal file
49
youtube-sync.xml
Normal file
@@ -0,0 +1,49 @@
|
||||
<?xml version="1.0"?>
|
||||
<Container version="2">
|
||||
<Name>youtube-sync</Name>
|
||||
<Repository>azcomputerguru/youtube-sync</Repository>
|
||||
<Registry>https://hub.docker.com/r/azcomputerguru/youtube-sync</Registry>
|
||||
<Network>bridge</Network>
|
||||
<MyIP/>
|
||||
<Shell>bash</Shell>
|
||||
<Privileged>false</Privileged>
|
||||
<Support>https://github.com/azcomputerguru/youtube-sync-docker</Support>
|
||||
<Project>https://github.com/azcomputerguru/youtube-sync-docker</Project>
|
||||
<Overview>
|
||||
Automatically download and organize YouTube channel videos for Emby/Plex/Jellyfin.
|
||||
|
||||
Features:
|
||||
- Downloads videos in best quality up to 1080p (configurable)
|
||||
- Organizes by season folders (by year)
|
||||
- Creates proper episode naming for media servers
|
||||
- Embeds thumbnails and metadata
|
||||
- Creates tvshow.nfo files for Emby/Plex
|
||||
- Tracks downloaded videos to avoid duplicates
|
||||
- Supports YouTube cookies for authentication
|
||||
- Scheduled automatic syncs via cron
|
||||
|
||||
Configuration:
|
||||
1. Edit /mnt/user/appdata/youtube-sync/channels.txt
|
||||
2. Add your channels in format: CHANNEL_ID|Channel Name
|
||||
3. (Optional) Add YouTube cookies to /mnt/user/appdata/youtube-sync/cookies.txt
|
||||
</Overview>
|
||||
<Category>MediaApp:Video MediaServer:Video</Category>
|
||||
<WebUI/>
|
||||
<TemplateURL>https://raw.githubusercontent.com/azcomputerguru/youtube-sync-docker/main/youtube-sync.xml</TemplateURL>
|
||||
<Icon>https://raw.githubusercontent.com/azcomputerguru/youtube-sync-docker/main/icon.png</Icon>
|
||||
<ExtraParams/>
|
||||
<PostArgs/>
|
||||
<CPUset/>
|
||||
<DateInstalled/>
|
||||
<DonateText/>
|
||||
<DonateLink/>
|
||||
<Requires/>
|
||||
<Config Name="Download Directory" Target="/downloads" Default="/mnt/user/media/YouTube" Mode="rw" Description="Where downloaded videos will be stored" Type="Path" Display="always" Required="true" Mask="false">/mnt/user/media/YouTube</Config>
|
||||
<Config Name="Config Directory" Target="/config" Default="/mnt/user/appdata/youtube-sync" Mode="rw" Description="Configuration files (channels.txt, cookies.txt)" Type="Path" Display="always" Required="true" Mask="false">/mnt/user/appdata/youtube-sync</Config>
|
||||
<Config Name="Sync Schedule" Target="SYNC_SCHEDULE" Default="0 2 * * *" Mode="" Description="Cron schedule for automatic syncs (default: 2 AM daily). Use 'manual' to disable scheduling." Type="Variable" Display="always" Required="false" Mask="false">0 2 * * *</Config>
|
||||
<Config Name="Max Quality" Target="MAX_QUALITY" Default="1080" Mode="" Description="Maximum video quality (480, 720, 1080, 1440, 2160)" Type="Variable" Display="always" Required="false" Mask="false">1080</Config>
|
||||
<Config Name="Sleep Interval" Target="SLEEP_INTERVAL" Default="2" Mode="" Description="Seconds to wait between downloads (helps avoid rate limiting)" Type="Variable" Display="advanced" Required="false" Mask="false">2</Config>
|
||||
<Config Name="Timezone" Target="TZ" Default="America/Phoenix" Mode="" Description="Timezone for scheduling (TZ database name)" Type="Variable" Display="advanced" Required="false" Mask="false">America/Phoenix</Config>
|
||||
<Config Name="PUID" Target="PUID" Default="99" Mode="" Description="User ID for file ownership" Type="Variable" Display="advanced" Required="false" Mask="false">99</Config>
|
||||
<Config Name="PGID" Target="PGID" Default="100" Mode="" Description="Group ID for file ownership" Type="Variable" Display="advanced" Required="false" Mask="false">100</Config>
|
||||
</Container>
|
||||
Reference in New Issue
Block a user