summaryrefslogtreecommitdiff
path: root/README.md
diff options
context:
space:
mode:
Diffstat (limited to 'README.md')
-rw-r--r--README.md102
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).