Changelog¶
1.3.0 (2026-07-10)¶
Features¶
- Detect non-faststart whole-file mp4 (
moovaftermdat) at ingest and log a one-line callout; add opt-inPODSPINE_REMUX_NON_FASTSTARTto remux such books to faststart on demand — a cache-managed stream-copy (no re-encode) served from thesavercache and regenerated/evicted like a cached chapter, never a pinned duplicate — so podcast clients seek quickly. MP3/OGG/FLAC, already-faststart mp4, and chaptered books are unaffected. (#61) - Add per-book
.podspine.tomloverrides: a sidecar beside a single-file book (Author - Title.podspine.toml) or inside a folder book overrides settings for just that book —storage_mode,force_embedded_chapters,remux_non_faststart,default_cover_url, plus troubleshooting knobsdisabled,title,author, andforce_reingest— with precedence sidecar → CLI/env → global config → default. Server-wide keys placed in a per-book file are ignored with a warning. (#62) - Serve whole-file episodes — folder-of-MP3 tracks and chapterless single files — in place, streaming them directly from the read-only library instead of copying them under the data dir; this removes the silent duplication those books used to cost (an existing library reclaims the copies on its next re-scan). Chaptered books are unchanged (
full/saver). (#59) - Add an opt-in
saverstorage mode (PODSPINE_STORAGE_MODE=saver) that keeps chapters in a bounded on-demand cache (PODSPINE_CACHE_SIZE/PODSPINE_CACHE_TTL) instead of keeping every chapter split on disk — cutting the data-dir footprint for chaptered books (whole-file books such as folder-of-MP3 tracks stream in place regardless of storage mode) for a small first-play delay per chapter. Ingest still splits each chapter once to record its real byte length, sosaversaves disk, not ingest time. (#53)
1.2.0 (2026-07-07)¶
Features¶
- Add a
--version/-Vflag sopodspine --versionreports the version (used by the install scripts and package-manager smoke tests). (#36)
1.1.0 (2026-07-05)¶
Features¶
- Add an "Add to a podcast app" subscribe page: the book-page QR now opens
/subscribe/{feed_id}with one-tap "Open in…" deep links for Apple Podcasts, Overcast, Pocket Casts, Castro, AntennaPod, and Podcast Addict (per-app QRs behind an expander) instead of raw feed XML the iOS Camera couldn't open. (#22)
1.0.1 (2026-07-05)¶
Fixes¶
- Set the audio
Content-Typeon/audioresponses so Apple Podcasts and other iOS clients can play episodes (axum-range sets none, which made playback fail with "this episode can't be played on this device"). (#20)
1.0.0 (2026-07-05)¶
First tagged release: a zero-config, self-hosted server that turns a folder of audiobooks into per-chapter podcast RSS feeds any podcast app can play.
Added¶
- Per-chapter podcast feeds with correct ordering: sequential
pubDates (oldest = chapter 1),itunes:episode,itunes:duration, andenclosure lengthread from the real output file. A built-in self-check refuses to serve a malformed feed. - Copy-first chapter splitting via
ffmpegstream copy (no re-encode). - Multi-book library scanning: each top-level audio file or per-book subfolder becomes an independent feed, with collision-free slugs.
- Web UI: a browsable book grid with cover art, plus a per-book page with a copy-feed-URL control, a scannable QR code, and per-app "how to add this" help.
- Cover art: embedded covers extracted to
itunes:image, with an optional feed-level fallback (--default-cover-url). - MP3-folder audiobooks: a folder of per-chapter MP3s ingested as episodes, ordered by track number (falling back to filename order), no re-encode.
- Tier-2 input formats: Ogg Vorbis, Opus, and FLAC, stream-copied into a matching container.
- Chapter sidecars: a
.cue(75 fpsINDEX 01) or.ffmetasidecar is preferred over embedded chapters;--force-embedded-chaptersoverrides. - HTTP Range streaming for audio (seek/scrub), with correct MIME types.
- Zero-config Docker image (multi-arch amd64/arm64, non-root, ffmpeg bundled) and static musl binaries.
- Configuration via CLI flags, environment variables, or a TOML file, with an ffmpeg/ffprobe startup preflight.
Security¶
- Opaque book/episode ids resolved server-side; slugs validated against an allow-list charset and rejected with 404 (path-traversal guard), with the resolved path canonicalized under the data dir as defense-in-depth.
- Bounded
ffmpegconcurrency (semaphore) with a per-child timeout and kill; request concurrency, timeout, and body-size limits on the HTTP layer. - Error responses never leak filesystem paths or
ffmpegstderr. - DRM-free input only. DRM-protected files (
.aax/.aaxc/.aa/.odm) are skipped with a logged notice; Podspine ships no DRM circumvention.