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:
2026-05-08 18:52:04 -04:00
commit 0ffb54e12e
7 changed files with 456 additions and 0 deletions

18
.gitignore vendored Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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>