Skip to content

0.14.0: dl and torrent — the media commands - #27

Merged
ralyodio merged 1 commit into
masterfrom
add-dl-command
Aug 29, 2026
Merged

0.14.0: dl and torrent — the media commands#27
ralyodio merged 1 commit into
masterfrom
add-dl-command

Conversation

@ralyodio

@ralyodio ralyodio commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

Two thin fronts, in the shape vid is a thin front for ffmpeg. Pairs with moshcoder/moshcode#448, which makes the binaries they need installable.

dl — yt-dlp

dl https://example.com/watch?v=abc          # the video
dl --height 720 https://example.com/v/abc   # capped
dl audio https://example.com/watch?v=abc    # → .m4a
dl info https://example.com/watch?v=abc     # nothing downloaded

--no-playlist by default. A YouTube link copied while a mix is playing carries list=, and yt-dlp reads that as "download all of it" — the difference between one file and two hundred, on a command whose entire input is a pasted URL.

ffmpeg-awareness in the format selector. Above ~720p the picture and sound arrive as separate streams needing a mux, so without ffmpeg the bv*+ba alternatives are not a lower-quality option but a wasted download: yt-dlp fetches both halves and only then finds it cannot merge. Without ffmpeg dl restricts itself to single-stream formats and warns. dl audio is -x, which is ffmpeg, so there it is a hard requirement.

findBinary grew a probe flag: it ran candidates with -version, which ImageMagick and ffmpeg answer 0 — but yt-dlp's parser reads that single dash as seven combined short options and exits non-zero, so probing it the same way would report an installed binary missing.

torrent — create-torrent + torlnk

torrent create ./album              # writes album.torrent, prints the magnet
torrent seed ./album                # …and hands the magnet to torlnk
torrent magnet album.torrent
torrent info album.torrent

create-torrent writes a file and never prints a hash; torlnk takes a magnet rather than a file. The two do not meet without something in between.

The info hash is computed here, not by adding a dependency: a SHA-1 over the bencoded info dictionary. The hard requirement is that the bytes be the original ones — decode and re-encode and the hash changes the moment a client wrote a key in an order we did not reproduce. So src/torrent.ts scans bencode for the span and hashes it verbatim, in about forty lines, with a test asserting the result equals the info hash a real client computed for a torrent it made itself.

Trackers are the part that looks cosmetic and is not. A browser can only ever be a WebRTC peer, so a torrent with no wss:// tracker is invisible to every web player — on the DHT, found by desktop clients, and showing the browser a torrent with no peers, which reads as dead rather than as a missing tracker. Every entry in the default list was checked rather than copied. Of the list the WebTorrent tooling ships by default:

tracker state
tracker.leechers-paradise.org no DNS at all
tracker.coppersurfer.tk UDP connect times out
tracker.empire-js.us UDP connect times out
tracker.btorrent.xyz self-signed cert; a browser refuses it

What is left is two WSS trackers that complete a WebSocket handshake and four UDP trackers that return a connection id.

--private is opt-in and named, because a private torrent is excluded from the DHT by every client that honours the flag — the opposite of the reason to make one here. Seed duration is deliberately not a flag: torlnk's --seed-time is a daemon setting, not per-torrent, and a flag that silently does nothing is worse than none.

Testing

pnpm test — 490 pass (35 new), pnpm typecheck clean. Both commands smoke-tested end to end: dl on its help/missing-binary/bad-verb paths, and torrent create on a real directory producing a torrent whose info hash matches create-torrent's own reader.

🤖 Generated with Claude Code

https://claude.ai/code/session_01LkEHoAhsoDhqVeJ1MJH9yD

A thin front for yt-dlp in the same shape `vid` is a thin front for ffmpeg:
the handful of things worth not remembering the flags for, out of the way for
everything else. Argv construction and output parsing live in src/download.ts
so they can be reasoned about without a network; bin/dl.ts spawns, inheriting
stdio because yt-dlp's progress line is the whole user interface of a long
transfer and a captured one arrives all at once, after the wait it explained.

Two defaults are decisions rather than plumbing:

`--no-playlist`. A YouTube link copied from the browser while a mix is playing
carries `list=`, and yt-dlp reads that as "download all of it" — the difference
between one file and two hundred, on a command whose entire input is a pasted
URL. The whole list is something you ask for.

ffmpeg-awareness in the format selector. Above about 720p the picture and the
sound arrive separately and have to be muxed, so on a box without ffmpeg the
`bv*+ba` alternatives are not a lower-quality option but a wasted download:
yt-dlp fetches both halves and only then finds it cannot merge them. Without
ffmpeg `dl` restricts itself to single-stream formats and warns that it did.
`dl audio` is `-x`, which *is* ffmpeg, so there it is a hard requirement with
nothing to downgrade to.

findBinary grew a probe flag. It ran every candidate with `-version`, which
ImageMagick and ffmpeg both answer 0 — but yt-dlp's parser reads that single
dash as seven combined short options and exits non-zero, so probing it the same
way would have reported an installed binary missing. youtube-dl is accepted as
a fallback name, second, because it still exists on plenty of boxes and is
years behind on everything but a plain YouTube URL.

The verb is optional (`dl <url>` is the common case), which is only unambiguous
because a URL never collides with `audio`/`info`/`formats`. A first argument
that is neither is a typo worth naming rather than a hostname to hand to yt-dlp.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LkEHoAhsoDhqVeJ1MJH9yD
Comment thread test/download.test.ts
});

it('writes where it was told to', () => {
expect(downloadArgs({ url: URL, kind: 'video', dir: '/tmp/out' })).toEqual(
Comment thread test/download.test.ts

it('writes where it was told to', () => {
expect(downloadArgs({ url: URL, kind: 'video', dir: '/tmp/out' })).toEqual(
expect.arrayContaining(['-P', '/tmp/out']),
@github-actions

Copy link
Copy Markdown

ThreatCrush Security Scan

15 finding(s)

HIGH/CRITICAL: 5 | MEDIUM: 1 | LOW: 9

Severity Rule Location
HIGH sh-remote-script-execution root-ubuntu.sh:134
HIGH sh-remote-script-execution root-ubuntu.sh:2597
HIGH sh-remote-script-execution root-ubuntu.sh:2601
HIGH sh-remote-script-execution root-ubuntu.sh:2654
HIGH sh-remote-script-execution root-ubuntu.sh:3683
MEDIUM redos-nested-quantifier src/domain-free.ts:56
LOW secret-generic-credential src/credentials.ts:36
LOW insecure-temp-file test/blog.test.ts:73
LOW insecure-temp-file test/blog.test.ts:74
LOW insecure-temp-file test/credentials.test.ts:43
LOW insecure-temp-file test/credentials.test.ts:44
LOW secret-generic-api-key test/credentials.test.ts:208
LOW insecure-temp-file test/download.test.ts:99
LOW insecure-temp-file test/download.test.ts:100
LOW secret-generic-credential test/shorten.test.ts:36

Snippets are redacted; ThreatCrush never prints matched credential material.

@ralyodio
ralyodio merged commit 01dab4d into master Aug 29, 2026
5 checks passed
@ralyodio
ralyodio deleted the add-dl-command branch August 29, 2026 16:11
@ralyodio ralyodio changed the title 0.13.0: dl — download a video, or just its audio, through yt-dlp 0.14.0: dl and torrent — the media commands Aug 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants