diff options
Diffstat (limited to 'README.md')
| -rw-r--r-- | README.md | 102 |
1 files changed, 102 insertions, 0 deletions
diff --git a/README.md b/README.md new file mode 100644 index 0000000..6157744 --- /dev/null +++ b/README.md @@ -0,0 +1,102 @@ +# podkit + +Turns a playlist or video URL into a private podcast feed you can subscribe to +from any podcast app. One Go binary; the fetch step shells out to `yt-dlp`. + +``` +podkit all "<playlist url>" \ + --dir ./audio \ + --title "Character of Physical Law" \ + --artist "Richard Feynman" \ + --base-url "https://your-host/audio/" + +podkit serve --dir ./audio --addr :8080 --new-token +``` + +Point your podcast app at the feed URL that `serve` prints. That's it. + +## Install + +``` +go build -o podkit . +pip install --user yt-dlp # needed only for `podkit fetch` +``` + +`ffmpeg` must also be on PATH — yt-dlp uses it to extract audio. `podkit` +checks for both **before** starting a long download rather than failing during +post-processing, after the bytes are already on disk. + +## Commands + +| Command | What it does | +| --- | --- | +| `podkit fetch <url>` | Download and extract audio | +| `podkit tag` | Write ID3 tags derived from filenames | +| `podkit feed` | Generate `feed.xml` | +| `podkit serve` | Host the directory and feed over HTTP | +| `podkit all <url>` | fetch → tag → feed | + +Each stage runs independently on purpose: the download is the slow part and +shouldn't be repeated while you're adjusting titles or the base URL. + +## Notes on the things that usually go wrong + +These are the parts a hand-rolled version of this pipeline tends to get wrong, +and what `podkit` does instead. + +**Filenames in URLs.** Lecture titles contain spaces and ampersands. Pasted +into an enclosure URL unescaped they produce both an invalid URL and invalid +XML, and the feed won't load at all. `podkit` escapes the final path segment +only — escaping the whole base would mangle the scheme and host. + +**Episode order.** `01, 02, … 10` sorts correctly as text only while the +zero-padding is applied consistently; one unpadded file and lecture 10 lands +after lecture 1. Order comes from the parsed track number, not the filename. +Untracked files sort last rather than jumping to the front. + +**Play direction.** Podcasts default to newest-first, which starts a lecture +course at the final lecture. The feed sets `itunes:type=serial` and gives every +item a distinct ascending `pubDate`, so clients present it in order either way. + +**`guid`.** Marked `isPermaLink="false"`. Without that, a reader is entitled to +treat the value as a URL and fetch it; these identifiers are filenames. + +**Required channel elements.** `description` and `link` are mandatory in +practice — several clients silently reject a feed lacking them. + +**Non-audio files.** Cover art and the `.part` files an interrupted download +leaves behind are skipped by extension, so they can't become episodes. + +**Resumability.** `fetch` keeps a download archive (`.podkit-archive`), so +re-running skips what's already there. Tagging is idempotent, and re-tagging +preserves artwork yt-dlp embedded rather than replacing the tag wholesale. + +**ID3 only on MP3.** ID3v2 is an MP3 convention; writing it into `.opus` or +`.m4a` produces something no player reads, so other formats are left alone. + +## "Private" + +`--token` means unlisted-and-unguessable, not authenticated. Podcast clients +can't do interactive login, so a shared secret in the URL is the only mechanism +that actually works with them — anyone holding the URL has access. Rejections +return 404 rather than 401, so a prober learns nothing. + +For anything beyond a home network, put it behind a reverse proxy with TLS. +`serve` speaks plain HTTP and is not intended to face the internet directly. + +## Sourcing + +`podkit` takes a URL you supply and does nothing to find content. Whether you +may download a given item is between you and whoever published it — check the +source's terms. + +## Tests + +``` +go test ./... +``` + +Covers the feed generator (URL escaping, ordering, XML well-formedness, +non-audio filtering), the tagger (round-trips, `n/total` track frames, +idempotency) and the server (token gating, path traversal, that the escaped +URLs the generator emits actually resolve against the server that serves them). |
