commit 0ffb54e12e5a11aa5a3a4210d6660c63d8147138 Author: azcomputerguru Date: Fri May 8 18:52:04 2026 -0400 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 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ddca873 --- /dev/null +++ b/.gitignore @@ -0,0 +1,18 @@ +# Docker +*.log + +# Testing +test/ +downloads/ +config/ + +# Editor +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# OS +.DS_Store +Thumbs.db diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..558e703 --- /dev/null +++ b/Dockerfile @@ -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"] diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f4f885d --- /dev/null +++ b/LICENSE @@ -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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..b2eaa3c --- /dev/null +++ b/README.md @@ -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) diff --git a/entrypoint.sh b/entrypoint.sh new file mode 100644 index 0000000..3b43221 --- /dev/null +++ b/entrypoint.sh @@ -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 diff --git a/sync.sh b/sync.sh new file mode 100644 index 0000000..abd9901 --- /dev/null +++ b/sync.sh @@ -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 + + + $CHANNEL_NAME + $CHANNEL_NAME + YouTube channel: $CHANNEL_NAME + YouTube + YouTube + + $CHANNEL_ID + +EOFNFO + + echo "Done with $CHANNEL_NAME" + echo "" + +done < "$CHANNELS_FILE" + +echo "==========================================" +echo "All channels synced successfully!" +echo "Completed: $(date)" +echo "==========================================" diff --git a/youtube-sync.xml b/youtube-sync.xml new file mode 100644 index 0000000..4514be2 --- /dev/null +++ b/youtube-sync.xml @@ -0,0 +1,49 @@ + + + youtube-sync + azcomputerguru/youtube-sync + https://hub.docker.com/r/azcomputerguru/youtube-sync + bridge + + bash + false + https://github.com/azcomputerguru/youtube-sync-docker + https://github.com/azcomputerguru/youtube-sync-docker + + 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 + + MediaApp:Video MediaServer:Video + + https://raw.githubusercontent.com/azcomputerguru/youtube-sync-docker/main/youtube-sync.xml + https://raw.githubusercontent.com/azcomputerguru/youtube-sync-docker/main/icon.png + + + + + + + + /mnt/user/media/YouTube + /mnt/user/appdata/youtube-sync + 0 2 * * * + 1080 + 2 + America/Phoenix + 99 + 100 +