Metadata-Version: 2.4
Name: your-ytd
Version: 1.0.0
Summary: Minimal interactive CLI YouTube downloader built on yt-dlp
Author-email: Manaf <manaf18.m@gmail.com>
License: MIT
Project-URL: Homepage, https://github.com/manaf-dev/your-ytd
Project-URL: Repository, https://github.com/manaf-dev/your-ytd
Project-URL: Issues, https://github.com/manaf-dev/your-ytd/issues
Keywords: youtube,downloader,yt-dlp,cli,video,audio
Classifier: Development Status :: 5 - Production/Stable
Classifier: Environment :: Console
Classifier: Intended Audience :: End Users/Desktop
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Topic :: Internet
Classifier: Topic :: Multimedia :: Video
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: yt-dlp>=2024.1.0
Provides-Extra: web
Requires-Dist: reflex>=0.6.0; extra == "web"
Dynamic: license-file

# your-ytd

A YouTube downloader built on [yt-dlp](https://github.com/yt-dlp/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 `/videos` URLs)
- Guided mode: bare `ytdl` prompts 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 `~/Downloads` fallback, 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

```bash
pipx install your-ytd        # recommended — isolated CLI install
# or: pip install your-ytd   # into whatever environment you prefer
```

Requires Python 3.9+ and [pipx](https://pipx.pypa.io/) (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:

```bash
sudo apt install ffmpeg
# or, without root, a static build:
#   https://johnvansickle.com/ffmpeg/ -> extract into ~/.local/bin
```

## Development setup

```bash
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: `240` matches
  `240p (mp4, merged)`, `best` matches `Best available video+audio`.
- On a **playlist** it picks a cap row: `1080` matches `Up 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 `-s` expressions.

### Examples

```bash
# 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

1. **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.
2. **Select** — playlists prompt for `all` / a subset (`ytdl/cli.py` reuses
   the same parser for `-s`).
3. **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.
4. **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):

```bash
pip install -e ".[web]"     # installs Reflex alongside yt-dlp
```

Run:

```bash
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:
  ```bash
  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 (`--cookies` equivalent).

## 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.
