Inspiration
Every podcast episode and recorded talk hides a second production: the release kit. Transcript, chapters, show notes, titles, cover art, quote cards, captioned audiogram clips. Independent creators burn ~3 hours per episode across five disconnected tools — then store the results in a folder called final_final_v2 on a laptop that will eventually die.
We built the missing machine: drop in the audio, review what comes back, and seal it all to a permanent, verifiable archive. You made the episode. B-Side makes everything else.
What it does
One audio drop returns the entire release kit, live over SSE:
- Transcript with word-level timings (AssemblyAI via Genblaze)
- Direction — 3 titles, summary, show notes, timestamped chapters, and 3 pull quotes anchored verbatim to the spoken words, plus an art brief
- Episode art — FLUX under a deterministic quality gate that rejects blank/flat frames and retries across the provider plan before a human ever sees them
- Quote cards — model art + deterministic typography (image models can't spell; our typography engine can)
- Audiograms — the creator's real voice under karaoke captions that light up word-by-word (ASS
\ktiming straight from the transcript), composed by ffmpeg as a first-class Genblaze provider step - Human review gate — approve, or reject with feedback; rejects regenerate with
parent_run_idlineage you can inspect - Sealed release — every asset's bytes are fetched back from B2 and re-hashed; any mismatch fails the seal; the kit ships as a ZIP via presigned URL
Every asset has an evidence drawer with a "fetch bytes & re-hash now" button that does a live Backblaze B2 round trip in front of you (~1s, ✓ BYTES MATCH).
How we built it
FastAPI + a durable SQLite job queue (exponential backoff, per-provider circuit breakers, idempotent stages, orphan recovery) drives a seven-stage pipeline where Genblaze is the spine, not a wrapper: Pipeline.ingest for the creator's audio and transcript, AssemblyAIProvider for STT, chat() with a real cross-vendor fallback chain (Gemini → NVIDIA NIM Llama 3.3 70B), FLUX → Gemini image fallback, two first-party custom SyncProviders (quote cards, audiograms) built on the documented extension contract, a subclassed StepCache so reruns never pay twice, and ObjectStorageSink with hierarchical keys partitioned per show.
Backblaze B2 is the system of record — four planes in one private bucket (app documents rewritten after every stage, media, SDK-native run manifests, expiring scratch). The local database is disposable: /judge has a restore button that rebuilds the entire application state from the bucket alone. React + Vite on the front, a record-label liner-notes design system, hand-rolled CSS. Deployed on Railway; 26 tests (unit / live-B2 integration / e2e) + lint on GitHub Actions.
Challenges we ran into
- FLUX black frames. Long prompts (>~250 chars) and empty payloads silently produce near-black images. We bisected it live, added prompt distillation — and turned the bug into a feature: a deterministic image evaluator that rejects blank/flat frames and retries across the fallback plan.
- Media types lie. FLUX returns JPEG bytes labeled PNG, which downstream APIs reject. We do a 16-byte ranged read from B2 and sniff magic bytes — the object's actual type, not its claimed one.
- Stale caches poison sinks. A cached step can point at a
file://asset that no longer exists. OurValidatingStepCacheverifies asset existence before honoring a hit. - Proving durability. We killed the live service mid-run on production — the queue recovered the orphaned job, resumed at the exact stage, and sealed the episode. Then we wiped the database and rebuilt everything from B2. Both are reproducible from
/judge.
Accomplishments that we're proud of
The audiogram moment: a judge hears a real human voice while the exact words light up in sync — generated, composed, hash-verified, and archived by one pipeline. And the evidence drawer: every claim in this writeup has a button next to it.
What we learned
Deterministic quality gates catch failure, not taste — the human gate is the real bar. Model slugs rot fast, so every model is swappable via env. And "trust me" is not a storage story: fetched-byte verification changed how we think about generative pipelines.
What's next
Multi-episode batch runs, publish hooks (RSS, YouTube, socials) straight from the sealed kit, and voice-cloned multilingual audiograms — every derivative still sealed to B2 with lineage back to the original master.
Try it in 60 seconds (no sign-in)
- Open https://bside-production.up.railway.app/judge
- Click "Run the pipeline now" — watch every stage go green, live (~2 min)
- Open any asset's ⛨ verify drawer → "Fetch bytes & re-hash now" → ✓ BYTES MATCH
- Click "Restore state from B2" — the whole app rebuilds from the bucket alone
- Download the sealed kit ZIP ## What it does
Log in or sign up for Devpost to join the conversation.