A console utility to download video or audio from a URL. It inspects the URL first, then lets you pick the format, the quality and the output name — and it downloads several at once without ever blocking the interface.
Doube is a thin, opinionated front end for yt-dlp: yt-dlp does the extracting, Doube does the choosing.
- Python 3.9 or newer
- ffmpeg on
PATH, needed to merge video with audio and to transcode audio to mp3
pip install -e .This installs the doube command. It can also be run without installing, from
the repository root:
python -m doubeRun doube with no arguments to open the download queue, built with
Textual. Paste a URL and press Enter: it is
analysed in the background, and a menu offers only the formats and qualities
that URL actually has, each with its estimated size.
┌─ Doube — video and audio downloads ────────────────────────────────────────┐
│ Paste a URL and press Enter │
│ 3 at a time | saving to . │
│ │
│ ╭──────────────────────────────────────────────────────────────────────╮ │
│ │ Big Buck Bunny 60fps 4K - Official Blender Foundation Short Film │ │
│ │ Blender | 10:35 │ │
│ │ │ │
│ │ Video 2160p webm 60fps vp9 1.3 GiB │ │
│ │ Video 1080p mp4 60fps avc1 275.0 MiB │ │
│ │ Video 720p mp4 60fps avc1 172.9 MiB │ │
│ │ Audio mp3 192 kbps transcoded with ffmpeg │ │
│ │ Audio 129 kbps m4a mp4a 9.8 MiB │ │
│ │ │ │
│ │ Big_Buck_Bunny_60fps_4K_-_Official_Blender_Foundation_Short_Film │ │
│ │ Enter downloads | Tab edits the filename | Esc cancels │ │
│ ╰──────────────────────────────────────────────────────────────────────╯ │
└────────────────────────────────────────────────────────────────────────────┘
Once queued, each download gets its own row with a progress bar, its speed and its estimated time left. Finished downloads stay in the list, so it doubles as a record of what came down:
┌─ Doube — video and audio downloads ────────────────────────────────────────┐
│ Paste a URL and press Enter │
│ 3 at a time | saving to . │
│ │
│ AURA phonk mix Audio mp3 192 │
│ ████████████████████████████████ 100% Completed AURA_phonk_mix.mp3 │
│ │
│ Big Buck Bunny 60fps 4K Video 1080p │
│ ███████████████░░░░░░░░░░░░░░░░░ 47% 1.2 MiB/s ETA 1:36 │
│ │
│ Sintel trailer Video 720p │
│ ████████░░░░░░░░░░░░░░░░░░░░░░░░ 24% 980 KiB/s ETA 2:02 │
└────────────────────────────────────────────────────────────────────────────┘
^q Quit c Clear finished
Nothing in there waits on anything else. Analysing a URL runs on its own thread
and downloads run in a pool, so you can keep pasting URLs while files are coming
down. Three run at a time by default; -j changes that:
doube -j 5 -o ~/Videos| Key | Action |
|---|---|
Enter |
in the URL field, analyse it; in the menu, start the download |
Tab |
move to the filename field, which starts on the media title |
Esc |
close the menu without queueing anything |
c |
clear the finished rows, keeping the running ones |
Ctrl+Q |
quit |
A progress bar only fills completely when the file is really finished. A video
download fetches the video and the audio separately and then merges them, so the
bar is measured against the total of both and the row says Processing while
ffmpeg works.
Pass a URL and a kind and nothing is prompted, which makes Doube scriptable:
doube https://youtu.be/VIDEO --audio # best audio as mp3
doube https://youtu.be/VIDEO --audio -q 320 # mp3 at 320 kbps
doube https://youtu.be/VIDEO --audio --keep-audio-format # original m4a/webm
doube https://youtu.be/VIDEO --video # best quality mp4
doube https://youtu.be/VIDEO --video -q 720 # capped at 720p
doube https://youtu.be/VIDEO --video -o ~/Videos -n clip
doube https://youtu.be/VIDEO --list-formats # inspect and exit| Option | Meaning |
|---|---|
-v, --video |
download video, merged into mp4 |
-a, --audio |
download audio, transcoded to mp3 |
-q, --quality |
best (default), a video height (1080, 720p) or an audio bitrate (192) |
-o, --output-dir |
directory to download into, created if missing |
-n, --name |
output filename without extension |
-j, --jobs |
how many downloads run at once in the interface (default: 3) |
--keep-audio-format |
keep the original audio stream instead of transcoding |
--cookies FILE |
Netscape cookie jar, for age or login restricted media |
-F, --list-formats |
list every available stream and exit |
Passing a URL without --video or --audio opens the interface with that URL
already being analysed.
Some media requires an authenticated session. Export a Netscape cookie jar and
pass it with --cookies:
doube https://youtu.be/VIDEO --video --cookies cookies.txtA cookie jar holds live session tokens, so treat it as a password. cookies.txt
is in .gitignore for that reason; never commit one.
doube/
cli.py argument parsing and the entry point
tui.py the terminal interface
manager.py the pool of concurrent downloads
jobs.py per-download state and progress tracking
choices.py the format and quality menu
media.py URL inspection and quality grouping
downloader.py requests mapped onto yt-dlp options
tests/ unit tests, no network access
docs/diagrams/ draw.io design diagrams
Only media.py and downloader.py talk to yt-dlp. The manager knows nothing
about the screen: it hands out immutable job snapshots, and the interface applies
them on its own thread, which is what keeps the two apart.
python -m unittest discover -s tests -t .The interface tests drive the real application headlessly through Textual's pilot, with the probe and the download replaced by stand-ins, so the whole suite runs without touching the network.
MIT, see LICENSE.