your-ytd
A YouTube downloader built on yt-dlp, with two frontends over one core:
- CLI (
ytdl) — interactive prompts or fully scripted flags - Web UI (
webui/, Reflex) — paste a link, tick playlist items, watch live progress
Features
- Single videos and playlists (also works with channel
/videosURLs) - Guided mode: bare
ytdlprompts for URL, destination, selection and quality - Quality menu with real resolutions and approximate file sizes
- Best available, per-resolution (up to 4K), or audio-only
- Playlist selection: download all, or pick items by number/range (
1,3,5-8) - Destination directory with
~/Downloadsfallback, created if missing - Live progress (terminal one-liner / web progress bars) and final saved path
- One failed video never aborts the rest of a playlist
Install
pipx install your-ytd # recommended — isolated CLI install
# or: pip install your-ytd # into whatever environment you prefer
Requires Python 3.9+ and pipx (or pip).
ffmpeg is required to merge video+audio streams. Modern YouTube serves most videos as separate streams, so without it you are effectively limited to audio-only downloads:
sudo apt install ffmpeg
# or, without root, a static build:
# https://johnvansickle.com/ffmpeg/ -> extract into ~/.local/bin
Development setup
git clone https://github.com/manaf-dev/your-ytd
cd your-ytd
python3 -m venv .venv
source .venv/bin/activate
pip install -e .
Usage
ytdl [URL] [-d DEST] [-q QUALITY] [-s SELECTION]
| Argument | Description |
|---|---|
URL |
YouTube video or playlist URL — prompted for if omitted |
-d, --dest |
Destination directory (default: ~/Downloads) — prompted for if omitted |
-q, --quality |
best, audio, or a max height like 1080 / 720p — skips the quality menu |
-s, --select |
Playlists only: all or items like 1,3,5-8 — skips the selection prompt |
Guided mode
Run ytdl with no arguments and it walks you through everything, one prompt
at a time — Enter accepts the shown default:
$ ytdl
URL: https://www.youtube.com/playlist?list=...
Destination [/home/you/Downloads]:
Fetching info...
Playlist: My Playlist (12 videos)
1) First video [3:21]
...
Enter 'a' to download all, or pick items by number/range (e.g. 1,3,5-8). 'q' cancels.
> a
Available qualities:
1) Best available for each video
2) Up to 2160p
...
Choose quality [1]:
Anything passed as a flag is skipped, so you can guide just one step
(ytdl URL still asks for destination/quality). When stdin is not a
terminal (scripts, cron), prompts never appear — missing pieces fall back
to defaults and a URL becomes a required argument.
How -q resolves:
- On a single video it picks the exact menu row:
240matches240p (mp4, merged),bestmatchesBest available video+audio. - On a playlist it picks a cap row:
1080matchesUp to 1080p, and each video gets the best stream at or below that height. - Invalid values exit with an error that lists the valid options; the same
goes for malformed
-sexpressions.
Examples
# fully guided prompts
ytdl
# interactive, default destination
ytdl "https://www.youtube.com/watch?v=VIDEO_ID"
# non-interactive: 1080p into a chosen folder
ytdl "https://www.youtube.com/watch?v=VIDEO_ID" -d ~/Videos/youtube -q 1080
# whole playlist, audio only
ytdl "https://www.youtube.com/playlist?list=PLAYLIST_ID" -s all -q audio
# selected episodes at up to 720p
ytdl "https://www.youtube.com/playlist?list=PLAYLIST_ID" -s 1,3,5-8 -q 720
python -m ytdl URL # same thing without the installed entry point
Interactive flow
Single video:
Me at the zoo
Available qualities:
1) Best available video+audio
2) 240p (mp4, merged) ~302.0 KiB
3) Audio only (m4a) ~302.0 KiB
Choose quality [1]:
Press Enter to accept the default, or type a number.
Playlists first list every entry with durations, then prompt:
Enter 'a' to download all, or pick items by number/range (e.g. 1,3,5-8). 'q' cancels.
> 1,3,5-8 # subset
> a # everything
> q # cancel
The quality choice is made once and applied to every selected video; resolution caps adapt automatically to whatever each video offers.
How it works
- Probe — the URL is fetched once with playlist entries flattened, so even 500-item playlists list instantly; single videos resolve fully with their format lists.
- Select — playlists prompt for
all/ a subset (ytdl/cli.pyreuses the same parser for-s). - Quality — one menu row is chosen and turned into a yt-dlp format selector: exact stream IDs for a single video, height caps for playlists.
- Download — each video downloads independently (progress line, then the saved path); one failure never aborts the rest.
Web UI (v2)
Same engine as the CLI, in the browser: thumbnail preview for videos, scrollable playlist checklist with an "All" switch, quality dropdown with sizes, live progress bars per download, and a downloads panel with queued/merging/done/failed states. The destination picker is a server-side folder browser (navigate, jump to Home/Downloads, create subfolders on demand) and the last chosen folder is remembered across restarts.
Setup (once, inside the venv):
pip install -e ".[web]" # installs Reflex alongside yt-dlp
Run:
cd webui
reflex run
First run compiles the frontend (downloads bun + npm packages — needs a
minute or two). Then open http://localhost:3000. The backend API listens on
http://localhost:8080 (set via backend_port in webui/rxconfig.py).
Downloads run on the machine where the server runs, into the destination
you type in step 2.
Project layout
pyproject.toml # packaging; [web] extra pulls in Reflex
ytdl/ # shared core + CLI
cli.py # argument parsing and main flow (one helper per step)
downloader.py # yt-dlp wrapper: probe, quality menus, download
ui.py # terminal prompts: URL, destination, selection, quality
__main__.py # python -m ytdl support
webui/ # Reflex app (v2 frontend)
rxconfig.py # theme + plugins
webui/state.py # UI state + thread-pool worker feeding progress
webui/webui.py # page layout and components
Troubleshooting
- Downloads suddenly fail / "Unable to extract ..." — YouTube changes
constantly and yt-dlp is updated to keep up. Update the package:
pipx upgrade your-ytd # or: pip install -U your-ytd
- Only audio offered / merge errors — ffmpeg is missing or not on PATH.
Check with
ffmpeg -version. - Age/region restricted videos fail to probe — not supported in v1;
yt-dlp supports cookies if you ever need it (
--cookiesequivalent).
Notes / known limits
- Filenames are
<title>.<ext>; already-completed files are not re-downloaded. - Merged files use mp4 when mp4 streams exist; audio-only keeps its native container (m4a/webm).
- Very large playlists list quickly because entries are fetched flat, then resolved individually at download time.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file your_ytd-1.0.0.tar.gz.
File metadata
- Download URL: your_ytd-1.0.0.tar.gz
- Upload date:
- Size: 13.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
bf630ae796291c0ca56198eb31d520b770a00f9be7d15f66d2b0e2a1279f2a05
|
|
| MD5 |
6e02c14d8d0fd434a03e8290a97d3c96
|
|
| BLAKE2b-256 |
7094d5479bc4ca6a608ee6bff580a20c537f07867d43eb8cc1a4dfab56a17ffd
|
Provenance
The following attestation bundles were made for your_ytd-1.0.0.tar.gz:
Publisher:
release.yml on manaf-dev/your-ytd
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
your_ytd-1.0.0.tar.gz -
Subject digest:
bf630ae796291c0ca56198eb31d520b770a00f9be7d15f66d2b0e2a1279f2a05 - Sigstore transparency entry: 2581951705
- Sigstore integration time:
-
Permalink:
manaf-dev/your-ytd@9bdaa532480036c7e73755515670370fac4e58b9 -
Branch / Tag:
refs/tags/v1.0.0 - Owner: https://github.com/manaf-dev
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@9bdaa532480036c7e73755515670370fac4e58b9 -
Trigger Event:
push
-
Statement type:
File details
Details for the file your_ytd-1.0.0-py3-none-any.whl.
File metadata
- Download URL: your_ytd-1.0.0-py3-none-any.whl
- Upload date:
- Size: 14.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f6187cbdf5f3431941bf71b14eba66b283b50c877c703e03a3a3d30000330086
|
|
| MD5 |
5ecfe6b3d1d33e19b3df015aa5fb773d
|
|
| BLAKE2b-256 |
856a0c88b078edfb1af65fb928fb98d1d37c3d2d3fa9335d38930ee90489ac4d
|
Provenance
The following attestation bundles were made for your_ytd-1.0.0-py3-none-any.whl:
Publisher:
release.yml on manaf-dev/your-ytd
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
your_ytd-1.0.0-py3-none-any.whl -
Subject digest:
f6187cbdf5f3431941bf71b14eba66b283b50c877c703e03a3a3d30000330086 - Sigstore transparency entry: 2581951728
- Sigstore integration time:
-
Permalink:
manaf-dev/your-ytd@9bdaa532480036c7e73755515670370fac4e58b9 -
Branch / Tag:
refs/tags/v1.0.0 - Owner: https://github.com/manaf-dev
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@9bdaa532480036c7e73755515670370fac4e58b9 -
Trigger Event:
push
-
Statement type: