youtube-dl-nas
Asynchronous WebSocket server for queue-based YouTube downloads with user authentication, optimized for private NAS (Do…
git clone https://github.com/hyeonsangjeon/youtube-dl-nas.githyeonsangjeon/youtube-dl-nasyoutube-dl-nas
youtube-dl-nas is a small NAS-friendly download queue for videos, audio, and subtitles. It wraps yt-dlp with an authenticated web dashboard, real-time progress updates, download history, file actions, and a REST API for automation.
Docker Hub: https://hub.docker.com/r/modenaf360/youtube-dl-nas/
Current release: 26.0731 (2026-07-31)
Need automatic full-channel backups instead?
youtube-dl-nasremains the small URL download queue. For scheduled channel backups, existingarchive.txtand NAS-folder import, missing-only downloads, and disk-first recovery, see Channel Vault NAS.
| Choose | Best fit |
|---|---|
| youtube-dl-nas | Paste individual video/playlist URLs into a compact authenticated queue |
| Channel Vault NAS | Register channels once and keep only missing/new videos backed up automatically |
Highlights
- Queue video, audio, or subtitle downloads from a browser.
- Keep the lightweight queue safe across container restarts with JSON state, partial-download continuation, duplicate guards, and removable waiting jobs.
- Share a URL from an installed Android PWA, an Android HTTP Shortcut, or an iOS Shortcut workflow.
- Track current activity with ordered queued jobs, progress, transfer speed, ETA, title, channel, and thumbnail.
- Review download history and mounted folder files in compact list or thumbnail grid views with search, filters, newest-first sorting, and 20-item numbered pages.
- Surface pre-existing files in
/downfoldereven when they do not have saved download metadata. - Preview saved video and audio in the dashboard, retry failed items, download files, delete history rows, or delete physical files.
- Compare SRT, VTT, ASS, and SSA files with a verified transcript using
nlptutticharacter/word error metrics and keyword preservation checks. - Persist the queue, history, terms acceptance, and the signed-session secret under
./metadata. - Automate downloads through a simple REST API.
- Keep
yt-dlpcurrent at startup and every hour by default without stopping the app when an update check fails. - Install or upgrade
nlptuttiwhen each new container starts so Subtitle QA uses the current package release. - Include Deno and the matching
yt-dlp-ejscomponents required for current YouTube JavaScript challenges. - Run cleanly on NAS or home-server Docker setups.
Screenshots
Dashboard Workflow
- Paste a URL, choose Video, Audio, or Subtitle mode, then submit it to the queue.
- Watch the Current Activity panel for progress, speed, ETA, and the ordered list of jobs waiting next. Waiting jobs can be removed before they start. Jobs restored after a container restart are labeled in the queue.
- Use Files & History to switch between compact list and thumbnail grid views. The default sort is newest downloaded first.
- Search with the
Searchbutton or Enter, then move through results with 20-item page buttons. - Preview video or audio directly, or select an item to open its source URL, metadata state, file details, and actions.
- For a subtitle file, select Subtitle QA, paste a verified reference transcript, optionally add comma-separated keywords, and run the comparison.
Subtitle QA
Subtitle QA reads the selected subtitle file directly from /downfolder and compares its spoken text with the reference transcript you provide. The result includes character accuracy (CRR), character error rate (CER), word error rate (WER), edit counts, and optional keyword preservation. Lower CER/WER and higher character accuracy are better.
The feature supports downloaded or mounted .srt, .vtt, .ass, and .ssa files. Reference text is processed only inside your NAS container and is not sent to an external service.
Mounted Files And Metadata
Files already present in /downfolder are scanned into Files & History even if they were not downloaded by this app version. Those rows show Mounted folder and No metadata because source URL, channel, and quality details are not available.
Clearing history rows does not delete files. Kept files are reloaded from /downfolder and shown again as mounted files. Use the file delete action only when you want to remove the physical file.
Safe Queue And Restart Recovery
The active request and waiting jobs are written atomically to queue_state.json in the metadata volume. After a container restart, the interrupted active request is restored first, followed by the remaining queue in its original order. yt-dlp --continue reuses compatible partial files from /downfolder/.incomplete; whether a remote source can resume the exact byte range depends on that source.
Equivalent URLs with share-tracking parameters removed are not queued twice with the same download profile. After metadata extraction, the stable extractor and media ID provide a second duplicate check against files already on the NAS. Failed history items remain retryable. Send "force": true through the REST API only when overwriting an existing download is intentional.
Mount /usr/src/app/metadata persistently to retain the safe queue across container replacement. The queue remains intentionally file-backed and does not require a database.
Quick Start
Docker Compose is the recommended installation because it preserves both downloads and application state:
cp .env.example .env docker compose up -d
Edit .env before starting and set at least MY_ID and MY_PW. Downloads are stored in ./downloads; queue state, history, terms acceptance, and the session secret are stored in ./config.
The equivalent docker run command is:
docker run -d \ --name youtube-dl-nas \ --restart unless-stopped \ -e MY_ID=nas-user \ -e MY_PW=change-this-password \ -v /volume2/youtube-dl:/downfolder \ -v /volume2/docker/youtube-dl-nas:/usr/src/app/metadata \ -p 8080:8080 \ modenaf360/youtube-dl-nas
Open http://localhost:8080, sign in with MY_ID / MY_PW, accept the Terms of Use on first launch, and submit a URL.
Time Zone
docker run -d \ --name youtube-dl-nas \ --restart unless-stopped \ -e TZ=Asia/Seoul \ -e MY_ID=nas-user \ -e MY_PW=change-this-password \ -v /volume2/youtube-dl:/downfolder \ -v /volume2/docker/youtube-dl-nas:/usr/src/app/metadata \ -p 8080:8080 \ modenaf360/youtube-dl-nas
Host Network With Custom App Port
docker run -d \ --name youtube-dl-nas \ --restart unless-stopped \ --net=host \ -e APP_PORT=9999 \ -e MY_ID=nas-user \ -e MY_PW=change-this-password \ -v /volume2/youtube-dl:/downfolder \ -v /volume2/docker/youtube-dl-nas:/usr/src/app/metadata \ modenaf360/youtube-dl-nas
Docker Options
| Option | Description |
|---|---|
-v host:/downfolder |
Required persistent download volume. Keep the guest path as /downfolder. |
-v host:/usr/src/app/metadata |
Recommended persistent state volume for the restart-safe queue, history, terms acceptance, and signed sessions. |
-p host:guest |
Port forwarding. The app defaults to 8080. |
-e MY_ID |
Required login ID. Avoid values starting with !, $, or &. |
-e MY_PW |
Required login password. Avoid values starting with !, $, or &. |
-e TZ |
Optional container time zone, for example Asia/Seoul. |
-e APP_PORT |
Optional app port. Defaults to 8080. |
-e PROXY |
Optional proxy value passed to yt-dlp. Defaults to empty. |
-e PUID, -e PGID |
Optional numeric owner for new download and state files. Defaults to 0. |
-e UMASK |
Optional file creation mask. Defaults to 022. |
-e YTDLP_AUTO_UPDATE |
Keep the startup and scheduled yt-dlp updater enabled. Defaults to true. |
-e YTDLP_UPDATE_INTERVAL |
Updater interval in seconds. Defaults to 3600, with a minimum of 300. |
-e NLPTUTTI_AUTO_UPDATE |
Install or upgrade nlptutti once whenever a new container starts. Defaults to true. |
-e NLPTUTTI_UPDATE_TIMEOUT |
Maximum runtime package-update duration in seconds. Defaults to 180. |
-e YTDLP_COOKIES_FILE |
Optional path to a mounted Netscape-format cookies file. |
-e YTDLP_EXTRA_ARGS |
Optional administrator-controlled extra arguments parsed with shell-style quoting. |
-e YDLNAS_API_TOKEN |
Optional Bearer token for integrations. Normal ID/password API authentication remains available. |
-e COOKIE_SECURE |
Set to true when the dashboard is served exclusively over HTTPS. |
Mobile Sharing
- Android over HTTPS: install the dashboard as a PWA, then select youtube-dl NAS from the Android share sheet.
- Android over local HTTP: import the provided HTTP Shortcuts template, run 1. Configure NAS once, and enter the normal dashboard URL, ID, and password.
- iPhone/iPad: install the signed Download to NAS Shortcut, replace its endpoint and credential placeholders, then share URLs directly to the NAS.
See the mobile sharing guide or the source in docs/mobile. No relay server is used; the phone sends URLs directly to the NAS. GitHub Pages only hosts the manual and import files.
REST API
Queue a Download
curl -X POST http://localhost:8080/youtube-dl/rest \
-H 'Content-Type: application/json' \
-d '{
"url": "https://www.youtube.com/watch?v=s9mO5q6GiAc",
"resolution": "best",
"id": "iamgroot",
"pw": "1234"
}'
Successful response:
{
"success": true,
"queued": true,
"duplicate": false,
"msg": "download has started",
"Remaining downloading count": "7"
}
The ID/password fields remain the default integration method. When YDLNAS_API_TOKEN is configured, advanced clients may omit them and send:
curl -X POST http://localhost:8080/youtube-dl/rest \
-H 'Authorization: Bearer your-token' \
-H 'Content-Type: application/json' \
-d '{"url":"https://youtu.be/s9mO5q6GiAc","resolution":"best"}'
The API returns "duplicate": true without adding another job when the same URL and profile are already queued or saved on the NAS. Add "force": true only when an intentional repeat download is required.
Supported resolution examples:
best2160p,1440p,1080p,720p,480p,360p,240p,144paudio-m4a,audio-mp3vtt|en,vtt|ko,srt|en,srt|ko
Authenticated Dashboard APIs
These endpoints are used by the web UI and require a valid login cookie:
| Endpoint | Method | Purpose |
|---|---|---|
/youtube-dl/status |
GET |
Read the active download, live transfer details, ordered queue, and connected clients. |
/youtube-dl/q/<job_id>/remove |
POST |
Remove a waiting job before it becomes active. |
/youtube-dl/history |
GET |
Read normalized download history plus mounted /downfolder files that are not in metadata yet. |
/youtube-dl/history/retry/<uuid> |
POST |
Queue a previous history item again. |
/youtube-dl/history/delete/<uuid> |
POST |
Delete the history row only. |
/youtube-dl/history/delete-file/<uuid> |
POST |
Delete the physical file and related history rows. |
/youtube-dl/history/clear |
POST |
Clear history rows while keeping downloaded files. |
/youtube-dl/subtitle-qa/<uuid> |
POST |
Compare a stored SRT/VTT/ASS/SSA file with a reference transcript using nlptutti. |
/static/preview/<uuid> |
GET |
Stream an existing video or audio file inline for the authenticated preview player. |
Local Development
Install dependencies:
pip install -r requirements.txt pip install -r requirements-dev.txt
Prepare Auth.json with local credentials, then run:
python youtube-dl-server.py
The app reads APP_PORT from Auth.json; the default is typically 8080 when substituted by the container entrypoint.
Useful checks before committing:
python3 -m py_compile youtube-dl-server.py node --check static/logical_js/logic.js pytest -q docker compose --env-file .env.example config git diff --check docker build -t youtube-dl-nas:local .
Container Build And Publishing
Build locally:
docker build -t youtube-dl-nas:local .
Run the local image:
docker run --rm \ -e MY_ID=tester \ -e MY_PW=secret \ -v "$PWD/downfolder:/downfolder" \ -v "$PWD/metadata:/usr/src/app/metadata" \ -p 8080:8080 \ youtube-dl-nas:local
The GitHub Actions workflow builds the Docker image for pull requests without publishing. Pushes to the default branch or version tags publish multi-architecture linux/amd64 and linux/arm64 images to both Docker Hub (modenaf360/youtube-dl-nas) and GHCR (ghcr.io/hyeonsangjeon/youtube-dl-nas), including latest on the default branch plus branch/tag and sha- tags.
Configure these repository secrets before publishing to Docker Hub:
DOCKERHUB_USERNAMEDOCKERHUB_TOKEN
GHCR publishing uses the workflow's built-in GITHUB_TOKEN; no additional registry secret is required.
That keeps every Git change build-verified without publishing unreviewed images.
Architecture
The application is a Python Bottle server running inside a Debian-based Python container. Browser and REST requests enter the same single-worker queue, whose active and pending jobs are atomically mirrored to the metadata volume without a database. Completed files are written to /downfolder. A failure-isolated scheduler checks for current yt-dlp and matching EJS components at startup and hourly by default. Each new container also installs or upgrades nlptutti before the app starts; a package-index failure leaves the download queue running but temporarily disables Subtitle QA. Deno supplies the JavaScript runtime used by current YouTube extraction challenges.
- Web server:
bottle - WebSocket:
bottle-websocket - Download engine:
yt-dlp - Subtitle quality metrics:
nlptutti - Original queue server base:
python queue server
Synology Notes
When using Synology Container Manager or Docker UI, mount a download folder to /downfolder, mount a configuration folder to /usr/src/app/metadata, and set MY_ID, MY_PW, and optional environment variables in the container settings. Use compose.yaml as a Container Manager project when available.
Volume setup:
ID and password setup:
Legal Disclaimer
This tool is based on yt-dlp and is provided solely for personal and legitimate use in accordance with applicable laws. Users are responsible for complying with copyright laws. Downloading or distributing copyrighted material without permission from the rightsholder may violate applicable laws.
This project does not encourage or support unauthorized use. The developer bears no legal responsibility for unauthorized or illegal use by users.
Release Notes
Full release history lives in CHANGELOG.md.
more like this
DroidKlipp
Install and monitor KlipperScreen on Android via DroidKlipp APK, USB ADB forwarding, and WiFi fallback





