Vibecode Podsqueeze
track this build4 phases, 12 steps, beginner friendly0%The core loop is small enough for a capable coding agent to produce a useful local version in one sitting. For Podsqueeze, transform an episode into show notes, chapters, quotes, and short text assets. The hard boundary is hosted transcription, media clipping, templates, and publishing convenience, plus audio infrastructure, distribution, and production polish.
You are building a lean indie version of Podsqueeze. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== README.md ===== # Podsqueeze · indie build A local episode assistant whose hard problems are keeping every timestamp on the original file's timeline, transcribing a long recording without mangling a word at each chunk boundary, and exporting at the loudness and in the formats the platforms actually publish. Estimated effort: **weekend**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | Python with ffmpeg and a job table in SQLite, one process | transcribing an hour of audio takes minutes, so nothing runs inside a request | | Originals | The master file is read-only and never rewritten | it is the only artefact here you cannot regenerate | | Timeline | One timeline, the original's, for every timestamp in the system | a trimmed working copy is a different clock and chapters land in the wrong place | | Transcription | One interface, a local model by default and an API as an option, returning word-level times | word times are what make chapters, clips and captions possible at all | | Text | One model call per artefact with the transcript as the only source, each regenerable alone | regenerating a description should not discard the title you spent ten minutes on | | Loudness | Measured with a meter implementing the published recommendation, targets read from the platform's own page | the target is a number somebody else publishes and occasionally changes | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **Python 3.12 or newer** · free - Why: Runs the jobs, the transcription interface and the interface. - Get it: python.org, or your package manager. - Verify: python3 --version prints 3.12 or higher - [ ] **ffmpeg with loudness measurement and silence detection** · free - Why: Decoding, silence detection, clip rendering, loudness measurement and encoding all go through it, so a cut-down build will fail halfway through the pipeline. - Get it: Your package manager, or a static build for your platform. - Verify: A loudness measurement and a silence detection both run on a sample file - [ ] **A transcription model returning word-level timings** · free locally, pennies per hour hosted - Why: Segment-level timings are not enough: chapters, clips and captions all need word boundaries. - Get it: A local speech model with word timestamps, or an API that returns them. - Verify: A one minute sample returns per-word start and end times - [ ] **Two real episodes, one of them long** · free - Why: Chunk boundaries, drift and loudness only misbehave on a long recording with real silences, and a three minute sample proves nothing. - Get it: Your own recordings, one over ninety minutes, one with two speakers. - Verify: Both import, and the long one crosses several chunk boundaries - [ ] **A second player that reads chapters and captions** · free - Why: The only way to know chapters and transcripts exported correctly is to open them somewhere you did not write. - Get it: Any mature player that displays chapter markers and loads a caption file. - Verify: A known good file shows its chapters and captions in it - [ ] **The loudness target, true peak ceiling and chapter rules you will follow** · free - Why: These are published numbers that change, and the build asserts them, so where and when you read them has to be written down. - Get it: Read the platform's current audio and chapter guidance, note the figures and the date, and record both in the repository. - Verify: The figures appear in configuration and the source and date are in the README ## Quick start Follow Phase 1 in `BUILD_PLAN.md`; it creates the project. Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - Remote recording with guests. - Hosting, the feed and directory analytics. - Licensed music and voice cloning. - Video clips with burned-in captions. - hosted transcription, media clipping, templates, and publishing convenience - remote studio reliability - licensed music libraries - hosting distribution - advanced mastering and support If one of those is essential to you, that is the reason to keep paying for Podsqueeze, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · Podsqueeze The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a local episode assistant that transcribes a recording and drafts its show notes, chapters and clips to replace Podsqueeze. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Stack (fixed, do not substitute) - Runtime: Python with ffmpeg and a job table in SQLite, one process · transcribing an hour of audio takes minutes, so nothing runs inside a request - Originals: The master file is read-only and never rewritten · it is the only artefact here you cannot regenerate - Timeline: One timeline, the original's, for every timestamp in the system · a trimmed working copy is a different clock and chapters land in the wrong place - Transcription: One interface, a local model by default and an API as an option, returning word-level times · word times are what make chapters, clips and captions possible at all - Text: One model call per artefact with the transcript as the only source, each regenerable alone · regenerating a description should not discard the title you spent ten minutes on - Loudness: Measured with a meter implementing the published recommendation, targets read from the platform's own page · the target is a number somebody else publishes and occasionally changes ### Data model (create this before Phase 1) - episodes(id, title, source_path, sha256, duration_s, sample_rate, channels, container, imported_at) · the source path is read and never written - jobs(id, episode_id, kind, state, attempts, started_at, finished_at, error) · transcribe, analyse, render - words(episode_id, ordinal, text, start_s, end_s, speaker, confidence) · times on the original's timeline, always, with no exceptions - segments(episode_id, ordinal, start_s, end_s, speaker, text) · derived from words and regenerable at any time - chapters(episode_id, ordinal, start_s, title, source) · source is auto or edited, and an edit is never overwritten by a regeneration - artefacts(episode_id, kind, model, prompt_version, generated_body, edited_body, generated_at) · title, description, keywords and social posts, each separately regenerable - clips(id, episode_id, start_s, end_s, reason, rendered_path, integrated_lufs, true_peak_dbtp) - loudness(episode_id, stage, integrated_lufs, true_peak_dbtp, measured_at) · stage is source, rendered or encoded, because encoding moves the peak ### Phase 1 · Import that never touches the master Build: Probe the file, record duration, sample rate, channels and container, and hash it. Decode a working copy for processing at whatever rate the transcriber wants, in a separate directory. Queue work in a jobs table. Add a test that hashes the source after every command the tool exposes and compares it with the hash taken at import. Trap: Trimming silence from the working copy before transcription looks like an obvious saving, and it shifts every word time relative to the file you will actually publish, so chapters drift progressively later through the episode and nobody notices until a listener mentions that the markers are off by a minute near the end. Keep one timeline, the original's, and if you must trim for processing then keep the mapping back. Done when: The source file's hash is identical after every command the tool exposes, asserted in the suite rather than assumed. A working copy is created for processing and is never written back over the source, verified by path checks. Probe results are recorded and a file the tool cannot decode fails at import with the reason rather than at transcription. Every stored time is expressed on the original's timeline, asserted against a deliberately trimmed working copy. Do not build yet: No transcription, chapters, clips or export. ### Phase 2 · A transcript with word times you can trust Build: Split the audio on detected silence rather than at fixed intervals, with an overlap between chunks. Transcribe each chunk and stitch by matching words in the overlap rather than by trusting the boundary. Store word level times and confidence, mark low confidence segments, and offer optional speaker labels. Trap: Splitting an hour of audio into fixed ten minute chunks cuts a word in half at each boundary and produces a mangled token and a timing gap in six places, and because the transcript reads perfectly everywhere else those six places go straight into the show notes. Split on detected silence, overlap the chunks, and de-duplicate the overlap by matching words. Done when: A transcript of a two hour file has no duplicated or truncated word at any chunk boundary, asserted by comparing the overlap regions. Word times are monotonic across the whole file and every segment's times fall within its neighbours'. Low confidence segments are marked and visible rather than silently included. Re-running transcription on the same file with the same model reproduces the same word times within a stated tolerance. Do not build yet: No chapters, notes, clips or export yet. ### Phase 3 · Chapters and notes drawn only from the transcript Build: Propose chapters at topic shifts, snap each start to a real word start and then to the nearest detected silence, and drop any the audio cannot support. Apply the platform's own published guidance for chapter count, minimum length and title length, read from its current page rather than from memory, and record which version you followed. Draft the description with timestamps, keeping every human edit separate from the generated text. Trap: A model asked for chapters returns plausible timestamps that correspond to nothing in the audio, usually suspiciously round numbers, and a listener pressing a chapter lands mid-sentence in the middle of a different topic. Every chapter start must be an actual word start from the transcript, snapped to a silence, and a proposal the audio cannot support is dropped rather than rounded into place. Done when: Every chapter start matches a word start in the transcript and falls inside a detected silence, asserted across a generated set. Chapters satisfy the constraints you recorded from the platform's current guidance, and the version you followed is noted in the repository. Editing a chapter title survives a regeneration of the description, tested. An episode below the length at which chapters are recommended produces none and says why. Do not build yet: No clips, encoding or export yet. ### Phase 4 · Clips that begin and end on a sentence Build: Propose candidate quotes from the transcript with a reason for each. Expand the boundaries outwards to the surrounding silences, add a short fade at each end, and render from the original file rather than from the processed working copy. Measure the rendered clip's loudness and record it. Trap: Cutting at the exact start and end times of a quote clips the first consonant and swallows the tail of the last word, so every clip is amateurish in precisely the same way and it is never obvious why. Expand the boundaries to the silences on either side, add a short fade, and check the rendered file rather than the plan. Done when: Rendered clips start and end inside a detected silence and carry a fade of the stated length, verified by measuring the rendered file. Clips are rendered from the original rather than from a processed copy, verified by path. Each clip's loudness is measured after rendering and stored. A candidate whose boundaries cannot be expanded without swallowing another speaker is dropped with its reason recorded. Do not build yet: No encoding, loudness targets or export yet. ### Phase 5 · Export that a host will accept Build: Encode to the bitrate and sample rate the platform publishes for mono and for stereo. Normalise to the published integrated loudness target with its true peak ceiling, measured with a meter implementing the recommendation the platform cites, and measure again on the encoded file. Write chapters both into the file's own metadata and as a separate chapters document for the feed. Export the transcript as WebVTT and SRT. Write a manifest naming every output, the transcript version and the model that produced each artefact. Trap: Normalising to a peak of zero rather than to an integrated loudness target leaves the episode noticeably quieter than everything else in a listener's queue, and the encoder can then push the true peak above the ceiling and clip, which is exactly why the published guidance says to precondition before encoding. Measure integrated loudness and true peak with a conforming meter, target the current published figures, and measure the encoded file rather than the intermediate. Done when: The encoded episode measures within the stated tolerance of the loudness target you recorded and its true peak sits at or below the published ceiling, measured on the encoded file. Chapters are readable both from the file's own metadata and from the exported chapters document, verified by opening the file in a second player. The transcript exports as WebVTT and as SRT and both load in a player. The manifest names every output with the transcript version and the model that produced it. Do not build yet: Nothing after this. ### Out of scope (and why) - Real-time remote recording with a guest, which is a network and audio synchronisation project with nothing in common with this one. - Hosting, the feed and directory analytics, which are the parts that need uptime and a relationship with every directory. - Licensed music and voice cloning, which are licensing problems and consent problems rather than engineering ones. - Video clip rendering with burned-in captions, which doubles the pipeline and is worth adding only once the audio path is boring. ### README must contain - That the master file is never written to, and that the suite hashes it after every command - That every timestamp lives on the original's timeline, and what happens if you trim the working copy - That chunked transcription splits on silence with an overlap, and why fixed chunks mangle words - Which published loudness target and true peak ceiling you used, where you read them, and on which date - That chapters are written both into the file and as a separate document, and how to check them in another player ===== AGENTS.md ===== # Agent instructions · Podsqueeze indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: Python with ffmpeg and a job table in SQLite, one process, The master file is read-only and never rewritten, One timeline, the original's, for every timestamp in the system, One interface, a local model by default and an API as an option, returning word-level times, One model call per artefact with the transcript as the only source, each regenerable alone, Measured with a meter implementing the published recommendation, targets read from the platform's own page. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs". ## Known traps - Trimming silence from the working copy before transcription looks like an obvious saving, and it shifts every word time relative to the file you will actually publish, so chapters drift progressively later through the episode and nobody notices until a listener mentions that the markers are off by a minute near the end. Keep one timeline, the original's, and if you must trim for processing then keep the mapping back. - Splitting an hour of audio into fixed ten minute chunks cuts a word in half at each boundary and produces a mangled token and a timing gap in six places, and because the transcript reads perfectly everywhere else those six places go straight into the show notes. Split on detected silence, overlap the chunks, and de-duplicate the overlap by matching words. - A model asked for chapters returns plausible timestamps that correspond to nothing in the audio, usually suspiciously round numbers, and a listener pressing a chapter lands mid-sentence in the middle of a different topic. Every chapter start must be an actual word start from the transcript, snapped to a silence, and a proposal the audio cannot support is dropped rather than rounded into place. - Cutting at the exact start and end times of a quote clips the first consonant and swallows the tail of the last word, so every clip is amateurish in precisely the same way and it is never obvious why. Expand the boundaries to the silences on either side, add a short fade, and check the rendered file rather than the plan. ===== BUILD_PLAN.md ===== # Build plan · Podsqueeze A local episode assistant whose hard problems are keeping every timestamp on the original file's timeline, transcribing a long recording without mangling a word at each chunk boundary, and exporting at the loudness and in the formats the platforms actually publish. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · Import that never touches the master The original is read, hashed and left alone, and every timestamp refers to it. ### Steps 1. Hash at import and after every command The one promise about the master, tested rather than stated. Files: `src/import.py`, `tests/test_master_untouched.py` 2. Decode a working copy into its own directory Separate directory, separate lifetime, separate retention. Files: `src/media/decode.py` 3. Keep the timeline mapping if you trim Every downstream timestamp depends on it. Files: `src/media/timeline.py` ### Done when - [ ] The source hash is unchanged after every command - [ ] Nothing writes to the source path - [ ] An undecodable file fails at import with a reason - [ ] Times are on the original's timeline ### Watch out - Trimming silence from the working copy before transcription looks like an obvious saving, and it shifts every word time relative to the file you will actually publish, so chapters drift progressively later through the episode and nobody notices until a listener mentions that the markers are off by a minute near the end. Keep one timeline, the original's, and if you must trim for processing then keep the mapping back. ## Phase 2 · A transcript with word times you can trust An hour of audio transcribes without a mangled word at every chunk boundary. ### Steps 1. Split on silence with an overlap Fixed boundaries land in the middle of words, every time. Files: `src/transcribe/chunk.py`, `tests/test_boundaries.py` 2. Stitch by matching words in the overlap The overlap exists so the join can be checked rather than trusted. Files: `src/transcribe/stitch.py` 3. Keep confidence per segment and show it The low confidence minute is the one that ends up quoted. Files: `src/ui/transcript.html` ### Done when - [ ] No mangled word at any chunk boundary - [ ] Word times are monotonic across the file - [ ] Low confidence segments are visible - [ ] A rerun reproduces times within tolerance ### Watch out - Splitting an hour of audio into fixed ten minute chunks cuts a word in half at each boundary and produces a mangled token and a timing gap in six places, and because the transcript reads perfectly everywhere else those six places go straight into the show notes. Split on detected silence, overlap the chunks, and de-duplicate the overlap by matching words. ## Phase 3 · Chapters and notes drawn only from the transcript Every chapter starts at a moment that actually exists in the audio. ### Steps 1. Snap every chapter to a word start, then a silence A chapter that lands mid-word is worse than no chapter. Files: `src/chapters/propose.py`, `tests/test_chapter_times.py` 2. Read the platform's guidance and record the version Counts, lengths and title limits are published and they change. Files: `docs/chapter-guidance.md` 3. Keep edits separate from generations Regenerating the description must not eat an edited title. Files: `src/artefacts/store.py` ### Done when - [ ] Chapter starts exist in the transcript - [ ] Chapters meet the recorded guidance - [ ] Edited titles survive a regeneration - [ ] A short episode produces none, with a reason ### Watch out - A model asked for chapters returns plausible timestamps that correspond to nothing in the audio, usually suspiciously round numbers, and a listener pressing a chapter lands mid-sentence in the middle of a different topic. Every chapter start must be an actual word start from the transcript, snapped to a silence, and a proposal the audio cannot support is dropped rather than rounded into place. ## Phase 4 · Clips that begin and end on a sentence A rendered clip sounds like something a person cut. ### Steps 1. Expand to the silences on both sides Then fade, so the cut is inaudible rather than merely short. Files: `src/clips/boundaries.py`, `tests/test_clip_edges.py` 2. Render from the original The working copy exists for transcription, not for output. Files: `src/clips/render.py` 3. Drop candidates that cannot be cut cleanly With the reason, so the list is worth reading. Files: `src/clips/propose.py` ### Done when - [ ] Clips begin and end inside a silence - [ ] Fades are present and the stated length - [ ] Clips are rendered from the original - [ ] Uncuttable candidates are dropped with a reason ### Watch out - Cutting at the exact start and end times of a quote clips the first consonant and swallows the tail of the last word, so every clip is amateurish in precisely the same way and it is never obvious why. Expand the boundaries to the silences on either side, add a short fade, and check the rendered file rather than the plan. ## Not in this build - Remote recording with guests. - Hosting, the feed and directory analytics. - Licensed music and voice cloning. - Video clips with burned-in captions. ## After v1, if you want it - A watched folder that imports a new recording as it lands, which the jobs table already supports - Speaker naming carried across episodes, once speaker labels have proved stable on two-speaker recordings - A per-episode diff of the generated text against your edits, which is the fastest way to improve the prompts ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. Database, working copies and rendered output. Never the masters. DATA_DIR=./data # Required. Read only. The suite hashes everything here after every command. MEDIA_SOURCE_DIR=~/Recordings # Required. Local model or API. Both return word-level times through the same interface. TRANSCRIBE_BACKEND=local # Optional · secret. Only for the API backend. Audio is uploaded there, so choose deliberately. TRANSCRIBE_API_KEY=sk-...redacted # Required. Copied from the platform's current published figure, with the date recorded in the README. TARGET_LUFS=-16 # Required. Also published. Measured on the encoded file, not the intermediate. TRUE_PEAK_CEILING_DBTP=-1 # Required. The overlap that lets the stitcher match words instead of trusting a boundary. CHUNK_OVERLAP_SECONDS=5 # Required. Working copies are large and regenerable, so they go first. WORKDIR_RETENTION_DAYS=14
You are building a lean indie version of Podsqueeze. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== README.md ===== # Podsqueeze · indie build A local episode assistant whose hard problems are keeping every timestamp on the original file's timeline, transcribing a long recording without mangling a word at each chunk boundary, and exporting at the loudness and in the formats the platforms actually publish. Estimated effort: **weekend**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | Python with ffmpeg and a job table in SQLite, one process | transcribing an hour of audio takes minutes, so nothing runs inside a request | | Originals | The master file is read-only and never rewritten | it is the only artefact here you cannot regenerate | | Timeline | One timeline, the original's, for every timestamp in the system | a trimmed working copy is a different clock and chapters land in the wrong place | | Transcription | One interface, a local model by default and an API as an option, returning word-level times | word times are what make chapters, clips and captions possible at all | | Text | One model call per artefact with the transcript as the only source, each regenerable alone | regenerating a description should not discard the title you spent ten minutes on | | Loudness | Measured with a meter implementing the published recommendation, targets read from the platform's own page | the target is a number somebody else publishes and occasionally changes | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **Python 3.12 or newer** · free - Why: Runs the jobs, the transcription interface and the interface. - Get it: python.org, or your package manager. - Verify: python3 --version prints 3.12 or higher - [ ] **ffmpeg with loudness measurement and silence detection** · free - Why: Decoding, silence detection, clip rendering, loudness measurement and encoding all go through it, so a cut-down build will fail halfway through the pipeline. - Get it: Your package manager, or a static build for your platform. - Verify: A loudness measurement and a silence detection both run on a sample file - [ ] **A transcription model returning word-level timings** · free locally, pennies per hour hosted - Why: Segment-level timings are not enough: chapters, clips and captions all need word boundaries. - Get it: A local speech model with word timestamps, or an API that returns them. - Verify: A one minute sample returns per-word start and end times - [ ] **Two real episodes, one of them long** · free - Why: Chunk boundaries, drift and loudness only misbehave on a long recording with real silences, and a three minute sample proves nothing. - Get it: Your own recordings, one over ninety minutes, one with two speakers. - Verify: Both import, and the long one crosses several chunk boundaries - [ ] **A second player that reads chapters and captions** · free - Why: The only way to know chapters and transcripts exported correctly is to open them somewhere you did not write. - Get it: Any mature player that displays chapter markers and loads a caption file. - Verify: A known good file shows its chapters and captions in it - [ ] **The loudness target, true peak ceiling and chapter rules you will follow** · free - Why: These are published numbers that change, and the build asserts them, so where and when you read them has to be written down. - Get it: Read the platform's current audio and chapter guidance, note the figures and the date, and record both in the repository. - Verify: The figures appear in configuration and the source and date are in the README ## Quick start Follow Phase 1 in `BUILD_PLAN.md`; it creates the project. Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - Remote recording with guests. - Hosting, the feed and directory analytics. - Licensed music and voice cloning. - Video clips with burned-in captions. - hosted transcription, media clipping, templates, and publishing convenience - remote studio reliability - licensed music libraries - hosting distribution - advanced mastering and support If one of those is essential to you, that is the reason to keep paying for Podsqueeze, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · Podsqueeze The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a local episode assistant that transcribes a recording and drafts its show notes, chapters and clips to replace Podsqueeze. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Stack (fixed, do not substitute) - Runtime: Python with ffmpeg and a job table in SQLite, one process · transcribing an hour of audio takes minutes, so nothing runs inside a request - Originals: The master file is read-only and never rewritten · it is the only artefact here you cannot regenerate - Timeline: One timeline, the original's, for every timestamp in the system · a trimmed working copy is a different clock and chapters land in the wrong place - Transcription: One interface, a local model by default and an API as an option, returning word-level times · word times are what make chapters, clips and captions possible at all - Text: One model call per artefact with the transcript as the only source, each regenerable alone · regenerating a description should not discard the title you spent ten minutes on - Loudness: Measured with a meter implementing the published recommendation, targets read from the platform's own page · the target is a number somebody else publishes and occasionally changes ### Data model (create this before Phase 1) - episodes(id, title, source_path, sha256, duration_s, sample_rate, channels, container, imported_at) · the source path is read and never written - jobs(id, episode_id, kind, state, attempts, started_at, finished_at, error) · transcribe, analyse, render - words(episode_id, ordinal, text, start_s, end_s, speaker, confidence) · times on the original's timeline, always, with no exceptions - segments(episode_id, ordinal, start_s, end_s, speaker, text) · derived from words and regenerable at any time - chapters(episode_id, ordinal, start_s, title, source) · source is auto or edited, and an edit is never overwritten by a regeneration - artefacts(episode_id, kind, model, prompt_version, generated_body, edited_body, generated_at) · title, description, keywords and social posts, each separately regenerable - clips(id, episode_id, start_s, end_s, reason, rendered_path, integrated_lufs, true_peak_dbtp) - loudness(episode_id, stage, integrated_lufs, true_peak_dbtp, measured_at) · stage is source, rendered or encoded, because encoding moves the peak ### Phase 1 · Import that never touches the master Build: Probe the file, record duration, sample rate, channels and container, and hash it. Decode a working copy for processing at whatever rate the transcriber wants, in a separate directory. Queue work in a jobs table. Add a test that hashes the source after every command the tool exposes and compares it with the hash taken at import. Trap: Trimming silence from the working copy before transcription looks like an obvious saving, and it shifts every word time relative to the file you will actually publish, so chapters drift progressively later through the episode and nobody notices until a listener mentions that the markers are off by a minute near the end. Keep one timeline, the original's, and if you must trim for processing then keep the mapping back. Done when: The source file's hash is identical after every command the tool exposes, asserted in the suite rather than assumed. A working copy is created for processing and is never written back over the source, verified by path checks. Probe results are recorded and a file the tool cannot decode fails at import with the reason rather than at transcription. Every stored time is expressed on the original's timeline, asserted against a deliberately trimmed working copy. Do not build yet: No transcription, chapters, clips or export. ### Phase 2 · A transcript with word times you can trust Build: Split the audio on detected silence rather than at fixed intervals, with an overlap between chunks. Transcribe each chunk and stitch by matching words in the overlap rather than by trusting the boundary. Store word level times and confidence, mark low confidence segments, and offer optional speaker labels. Trap: Splitting an hour of audio into fixed ten minute chunks cuts a word in half at each boundary and produces a mangled token and a timing gap in six places, and because the transcript reads perfectly everywhere else those six places go straight into the show notes. Split on detected silence, overlap the chunks, and de-duplicate the overlap by matching words. Done when: A transcript of a two hour file has no duplicated or truncated word at any chunk boundary, asserted by comparing the overlap regions. Word times are monotonic across the whole file and every segment's times fall within its neighbours'. Low confidence segments are marked and visible rather than silently included. Re-running transcription on the same file with the same model reproduces the same word times within a stated tolerance. Do not build yet: No chapters, notes, clips or export yet. ### Phase 3 · Chapters and notes drawn only from the transcript Build: Propose chapters at topic shifts, snap each start to a real word start and then to the nearest detected silence, and drop any the audio cannot support. Apply the platform's own published guidance for chapter count, minimum length and title length, read from its current page rather than from memory, and record which version you followed. Draft the description with timestamps, keeping every human edit separate from the generated text. Trap: A model asked for chapters returns plausible timestamps that correspond to nothing in the audio, usually suspiciously round numbers, and a listener pressing a chapter lands mid-sentence in the middle of a different topic. Every chapter start must be an actual word start from the transcript, snapped to a silence, and a proposal the audio cannot support is dropped rather than rounded into place. Done when: Every chapter start matches a word start in the transcript and falls inside a detected silence, asserted across a generated set. Chapters satisfy the constraints you recorded from the platform's current guidance, and the version you followed is noted in the repository. Editing a chapter title survives a regeneration of the description, tested. An episode below the length at which chapters are recommended produces none and says why. Do not build yet: No clips, encoding or export yet. ### Phase 4 · Clips that begin and end on a sentence Build: Propose candidate quotes from the transcript with a reason for each. Expand the boundaries outwards to the surrounding silences, add a short fade at each end, and render from the original file rather than from the processed working copy. Measure the rendered clip's loudness and record it. Trap: Cutting at the exact start and end times of a quote clips the first consonant and swallows the tail of the last word, so every clip is amateurish in precisely the same way and it is never obvious why. Expand the boundaries to the silences on either side, add a short fade, and check the rendered file rather than the plan. Done when: Rendered clips start and end inside a detected silence and carry a fade of the stated length, verified by measuring the rendered file. Clips are rendered from the original rather than from a processed copy, verified by path. Each clip's loudness is measured after rendering and stored. A candidate whose boundaries cannot be expanded without swallowing another speaker is dropped with its reason recorded. Do not build yet: No encoding, loudness targets or export yet. ### Phase 5 · Export that a host will accept Build: Encode to the bitrate and sample rate the platform publishes for mono and for stereo. Normalise to the published integrated loudness target with its true peak ceiling, measured with a meter implementing the recommendation the platform cites, and measure again on the encoded file. Write chapters both into the file's own metadata and as a separate chapters document for the feed. Export the transcript as WebVTT and SRT. Write a manifest naming every output, the transcript version and the model that produced each artefact. Trap: Normalising to a peak of zero rather than to an integrated loudness target leaves the episode noticeably quieter than everything else in a listener's queue, and the encoder can then push the true peak above the ceiling and clip, which is exactly why the published guidance says to precondition before encoding. Measure integrated loudness and true peak with a conforming meter, target the current published figures, and measure the encoded file rather than the intermediate. Done when: The encoded episode measures within the stated tolerance of the loudness target you recorded and its true peak sits at or below the published ceiling, measured on the encoded file. Chapters are readable both from the file's own metadata and from the exported chapters document, verified by opening the file in a second player. The transcript exports as WebVTT and as SRT and both load in a player. The manifest names every output with the transcript version and the model that produced it. Do not build yet: Nothing after this. ### Out of scope (and why) - Real-time remote recording with a guest, which is a network and audio synchronisation project with nothing in common with this one. - Hosting, the feed and directory analytics, which are the parts that need uptime and a relationship with every directory. - Licensed music and voice cloning, which are licensing problems and consent problems rather than engineering ones. - Video clip rendering with burned-in captions, which doubles the pipeline and is worth adding only once the audio path is boring. ### README must contain - That the master file is never written to, and that the suite hashes it after every command - That every timestamp lives on the original's timeline, and what happens if you trim the working copy - That chunked transcription splits on silence with an overlap, and why fixed chunks mangle words - Which published loudness target and true peak ceiling you used, where you read them, and on which date - That chapters are written both into the file and as a separate document, and how to check them in another player ===== AGENTS.md ===== # Agent instructions · Podsqueeze indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: Python with ffmpeg and a job table in SQLite, one process, The master file is read-only and never rewritten, One timeline, the original's, for every timestamp in the system, One interface, a local model by default and an API as an option, returning word-level times, One model call per artefact with the transcript as the only source, each regenerable alone, Measured with a meter implementing the published recommendation, targets read from the platform's own page. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs". ## Known traps - Trimming silence from the working copy before transcription looks like an obvious saving, and it shifts every word time relative to the file you will actually publish, so chapters drift progressively later through the episode and nobody notices until a listener mentions that the markers are off by a minute near the end. Keep one timeline, the original's, and if you must trim for processing then keep the mapping back. - Splitting an hour of audio into fixed ten minute chunks cuts a word in half at each boundary and produces a mangled token and a timing gap in six places, and because the transcript reads perfectly everywhere else those six places go straight into the show notes. Split on detected silence, overlap the chunks, and de-duplicate the overlap by matching words. - A model asked for chapters returns plausible timestamps that correspond to nothing in the audio, usually suspiciously round numbers, and a listener pressing a chapter lands mid-sentence in the middle of a different topic. Every chapter start must be an actual word start from the transcript, snapped to a silence, and a proposal the audio cannot support is dropped rather than rounded into place. - Cutting at the exact start and end times of a quote clips the first consonant and swallows the tail of the last word, so every clip is amateurish in precisely the same way and it is never obvious why. Expand the boundaries to the silences on either side, add a short fade, and check the rendered file rather than the plan. ===== BUILD_PLAN.md ===== # Build plan · Podsqueeze A local episode assistant whose hard problems are keeping every timestamp on the original file's timeline, transcribing a long recording without mangling a word at each chunk boundary, and exporting at the loudness and in the formats the platforms actually publish. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · Import that never touches the master The original is read, hashed and left alone, and every timestamp refers to it. ### Steps 1. Hash at import and after every command The one promise about the master, tested rather than stated. Files: `src/import.py`, `tests/test_master_untouched.py` 2. Decode a working copy into its own directory Separate directory, separate lifetime, separate retention. Files: `src/media/decode.py` 3. Keep the timeline mapping if you trim Every downstream timestamp depends on it. Files: `src/media/timeline.py` ### Done when - [ ] The source hash is unchanged after every command - [ ] Nothing writes to the source path - [ ] An undecodable file fails at import with a reason - [ ] Times are on the original's timeline ### Watch out - Trimming silence from the working copy before transcription looks like an obvious saving, and it shifts every word time relative to the file you will actually publish, so chapters drift progressively later through the episode and nobody notices until a listener mentions that the markers are off by a minute near the end. Keep one timeline, the original's, and if you must trim for processing then keep the mapping back. ## Phase 2 · A transcript with word times you can trust An hour of audio transcribes without a mangled word at every chunk boundary. ### Steps 1. Split on silence with an overlap Fixed boundaries land in the middle of words, every time. Files: `src/transcribe/chunk.py`, `tests/test_boundaries.py` 2. Stitch by matching words in the overlap The overlap exists so the join can be checked rather than trusted. Files: `src/transcribe/stitch.py` 3. Keep confidence per segment and show it The low confidence minute is the one that ends up quoted. Files: `src/ui/transcript.html` ### Done when - [ ] No mangled word at any chunk boundary - [ ] Word times are monotonic across the file - [ ] Low confidence segments are visible - [ ] A rerun reproduces times within tolerance ### Watch out - Splitting an hour of audio into fixed ten minute chunks cuts a word in half at each boundary and produces a mangled token and a timing gap in six places, and because the transcript reads perfectly everywhere else those six places go straight into the show notes. Split on detected silence, overlap the chunks, and de-duplicate the overlap by matching words. ## Phase 3 · Chapters and notes drawn only from the transcript Every chapter starts at a moment that actually exists in the audio. ### Steps 1. Snap every chapter to a word start, then a silence A chapter that lands mid-word is worse than no chapter. Files: `src/chapters/propose.py`, `tests/test_chapter_times.py` 2. Read the platform's guidance and record the version Counts, lengths and title limits are published and they change. Files: `docs/chapter-guidance.md` 3. Keep edits separate from generations Regenerating the description must not eat an edited title. Files: `src/artefacts/store.py` ### Done when - [ ] Chapter starts exist in the transcript - [ ] Chapters meet the recorded guidance - [ ] Edited titles survive a regeneration - [ ] A short episode produces none, with a reason ### Watch out - A model asked for chapters returns plausible timestamps that correspond to nothing in the audio, usually suspiciously round numbers, and a listener pressing a chapter lands mid-sentence in the middle of a different topic. Every chapter start must be an actual word start from the transcript, snapped to a silence, and a proposal the audio cannot support is dropped rather than rounded into place. ## Phase 4 · Clips that begin and end on a sentence A rendered clip sounds like something a person cut. ### Steps 1. Expand to the silences on both sides Then fade, so the cut is inaudible rather than merely short. Files: `src/clips/boundaries.py`, `tests/test_clip_edges.py` 2. Render from the original The working copy exists for transcription, not for output. Files: `src/clips/render.py` 3. Drop candidates that cannot be cut cleanly With the reason, so the list is worth reading. Files: `src/clips/propose.py` ### Done when - [ ] Clips begin and end inside a silence - [ ] Fades are present and the stated length - [ ] Clips are rendered from the original - [ ] Uncuttable candidates are dropped with a reason ### Watch out - Cutting at the exact start and end times of a quote clips the first consonant and swallows the tail of the last word, so every clip is amateurish in precisely the same way and it is never obvious why. Expand the boundaries to the silences on either side, add a short fade, and check the rendered file rather than the plan. ## Not in this build - Remote recording with guests. - Hosting, the feed and directory analytics. - Licensed music and voice cloning. - Video clips with burned-in captions. ## After v1, if you want it - A watched folder that imports a new recording as it lands, which the jobs table already supports - Speaker naming carried across episodes, once speaker labels have proved stable on two-speaker recordings - A per-episode diff of the generated text against your edits, which is the fastest way to improve the prompts ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. Database, working copies and rendered output. Never the masters. DATA_DIR=./data # Required. Read only. The suite hashes everything here after every command. MEDIA_SOURCE_DIR=~/Recordings # Required. Local model or API. Both return word-level times through the same interface. TRANSCRIBE_BACKEND=local # Optional · secret. Only for the API backend. Audio is uploaded there, so choose deliberately. TRANSCRIBE_API_KEY=sk-...redacted # Required. Copied from the platform's current published figure, with the date recorded in the README. TARGET_LUFS=-16 # Required. Also published. Measured on the encoded file, not the intermediate. TRUE_PEAK_CEILING_DBTP=-1 # Required. The overlap that lets the stitcher match words instead of trusting a boundary. CHUNK_OVERLAP_SECONDS=5 # Required. Working copies are large and regenerable, so they go first. WORKDIR_RETENTION_DAYS=14
You are building a production product version of Podsqueeze. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== PRODUCT.md ===== # Podsqueeze · product brief ## Problem The core loop is small enough for a capable coding agent to produce a useful local version in one sitting. For Podsqueeze, transform an episode into show notes, chapters, quotes, and short text assets. The hard boundary is hosted transcription, media clipping, templates, and publishing convenience, plus audio infrastructure, distribution, and production polish. ## Product outcome An assistant that leaves your master file byte-identical, produces a transcript with trustworthy word times, proposes chapters that land where it says they do, cuts clips that sound deliberate, and exports at the loudness and in the formats the platforms publish. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - ffmpeg - local audio files - optional transcription API key - sufficient disk space ## Explicit non-goals for v1 - Remote recording with guests. - Hosting, the feed and directory analytics. - Licensed music and voice cloning. - Video clips with burned-in captions. - hosted transcription, media clipping, templates, and publishing convenience - remote studio reliability - licensed music libraries - hosting distribution - advanced mastering and support ## Success criteria - The master is confirmed byte-identical after every command - No mangled word is confirmed at any chunk boundary - Every chapter start is confirmed to exist in the transcript - The encoded file is confirmed within the loudness target and under the ceiling - Chapters and captions are confirmed readable in a second player ===== BRIEF.md ===== # Build brief · Podsqueeze The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a local episode assistant that transcribes a recording and drafts its show notes, chapters and clips to replace Podsqueeze. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Stack (fixed, do not substitute) - Runtime: Python with ffmpeg and a job table in SQLite, one process · transcribing an hour of audio takes minutes, so nothing runs inside a request - Originals: The master file is read-only and never rewritten · it is the only artefact here you cannot regenerate - Timeline: One timeline, the original's, for every timestamp in the system · a trimmed working copy is a different clock and chapters land in the wrong place - Transcription: One interface, a local model by default and an API as an option, returning word-level times · word times are what make chapters, clips and captions possible at all - Text: One model call per artefact with the transcript as the only source, each regenerable alone · regenerating a description should not discard the title you spent ten minutes on - Loudness: Measured with a meter implementing the published recommendation, targets read from the platform's own page · the target is a number somebody else publishes and occasionally changes ### Data model (create this before Phase 1) - episodes(id, title, source_path, sha256, duration_s, sample_rate, channels, container, imported_at) · the source path is read and never written - jobs(id, episode_id, kind, state, attempts, started_at, finished_at, error) · transcribe, analyse, render - words(episode_id, ordinal, text, start_s, end_s, speaker, confidence) · times on the original's timeline, always, with no exceptions - segments(episode_id, ordinal, start_s, end_s, speaker, text) · derived from words and regenerable at any time - chapters(episode_id, ordinal, start_s, title, source) · source is auto or edited, and an edit is never overwritten by a regeneration - artefacts(episode_id, kind, model, prompt_version, generated_body, edited_body, generated_at) · title, description, keywords and social posts, each separately regenerable - clips(id, episode_id, start_s, end_s, reason, rendered_path, integrated_lufs, true_peak_dbtp) - loudness(episode_id, stage, integrated_lufs, true_peak_dbtp, measured_at) · stage is source, rendered or encoded, because encoding moves the peak ### Phase 1 · Import that never touches the master Build: Probe the file, record duration, sample rate, channels and container, and hash it. Decode a working copy for processing at whatever rate the transcriber wants, in a separate directory. Queue work in a jobs table. Add a test that hashes the source after every command the tool exposes and compares it with the hash taken at import. Trap: Trimming silence from the working copy before transcription looks like an obvious saving, and it shifts every word time relative to the file you will actually publish, so chapters drift progressively later through the episode and nobody notices until a listener mentions that the markers are off by a minute near the end. Keep one timeline, the original's, and if you must trim for processing then keep the mapping back. Done when: The source file's hash is identical after every command the tool exposes, asserted in the suite rather than assumed. A working copy is created for processing and is never written back over the source, verified by path checks. Probe results are recorded and a file the tool cannot decode fails at import with the reason rather than at transcription. Every stored time is expressed on the original's timeline, asserted against a deliberately trimmed working copy. Do not build yet: No transcription, chapters, clips or export. ### Phase 2 · A transcript with word times you can trust Build: Split the audio on detected silence rather than at fixed intervals, with an overlap between chunks. Transcribe each chunk and stitch by matching words in the overlap rather than by trusting the boundary. Store word level times and confidence, mark low confidence segments, and offer optional speaker labels. Trap: Splitting an hour of audio into fixed ten minute chunks cuts a word in half at each boundary and produces a mangled token and a timing gap in six places, and because the transcript reads perfectly everywhere else those six places go straight into the show notes. Split on detected silence, overlap the chunks, and de-duplicate the overlap by matching words. Done when: A transcript of a two hour file has no duplicated or truncated word at any chunk boundary, asserted by comparing the overlap regions. Word times are monotonic across the whole file and every segment's times fall within its neighbours'. Low confidence segments are marked and visible rather than silently included. Re-running transcription on the same file with the same model reproduces the same word times within a stated tolerance. Do not build yet: No chapters, notes, clips or export yet. ### Phase 3 · Chapters and notes drawn only from the transcript Build: Propose chapters at topic shifts, snap each start to a real word start and then to the nearest detected silence, and drop any the audio cannot support. Apply the platform's own published guidance for chapter count, minimum length and title length, read from its current page rather than from memory, and record which version you followed. Draft the description with timestamps, keeping every human edit separate from the generated text. Trap: A model asked for chapters returns plausible timestamps that correspond to nothing in the audio, usually suspiciously round numbers, and a listener pressing a chapter lands mid-sentence in the middle of a different topic. Every chapter start must be an actual word start from the transcript, snapped to a silence, and a proposal the audio cannot support is dropped rather than rounded into place. Done when: Every chapter start matches a word start in the transcript and falls inside a detected silence, asserted across a generated set. Chapters satisfy the constraints you recorded from the platform's current guidance, and the version you followed is noted in the repository. Editing a chapter title survives a regeneration of the description, tested. An episode below the length at which chapters are recommended produces none and says why. Do not build yet: No clips, encoding or export yet. ### Phase 4 · Clips that begin and end on a sentence Build: Propose candidate quotes from the transcript with a reason for each. Expand the boundaries outwards to the surrounding silences, add a short fade at each end, and render from the original file rather than from the processed working copy. Measure the rendered clip's loudness and record it. Trap: Cutting at the exact start and end times of a quote clips the first consonant and swallows the tail of the last word, so every clip is amateurish in precisely the same way and it is never obvious why. Expand the boundaries to the silences on either side, add a short fade, and check the rendered file rather than the plan. Done when: Rendered clips start and end inside a detected silence and carry a fade of the stated length, verified by measuring the rendered file. Clips are rendered from the original rather than from a processed copy, verified by path. Each clip's loudness is measured after rendering and stored. A candidate whose boundaries cannot be expanded without swallowing another speaker is dropped with its reason recorded. Do not build yet: No encoding, loudness targets or export yet. ### Phase 5 · Export that a host will accept Build: Encode to the bitrate and sample rate the platform publishes for mono and for stereo. Normalise to the published integrated loudness target with its true peak ceiling, measured with a meter implementing the recommendation the platform cites, and measure again on the encoded file. Write chapters both into the file's own metadata and as a separate chapters document for the feed. Export the transcript as WebVTT and SRT. Write a manifest naming every output, the transcript version and the model that produced each artefact. Trap: Normalising to a peak of zero rather than to an integrated loudness target leaves the episode noticeably quieter than everything else in a listener's queue, and the encoder can then push the true peak above the ceiling and clip, which is exactly why the published guidance says to precondition before encoding. Measure integrated loudness and true peak with a conforming meter, target the current published figures, and measure the encoded file rather than the intermediate. Done when: The encoded episode measures within the stated tolerance of the loudness target you recorded and its true peak sits at or below the published ceiling, measured on the encoded file. Chapters are readable both from the file's own metadata and from the exported chapters document, verified by opening the file in a second player. The transcript exports as WebVTT and as SRT and both load in a player. The manifest names every output with the transcript version and the model that produced it. Do not build yet: Nothing after this. ### Out of scope (and why) - Real-time remote recording with a guest, which is a network and audio synchronisation project with nothing in common with this one. - Hosting, the feed and directory analytics, which are the parts that need uptime and a relationship with every directory. - Licensed music and voice cloning, which are licensing problems and consent problems rather than engineering ones. - Video clip rendering with burned-in captions, which doubles the pipeline and is worth adding only once the audio path is boring. ### README must contain - That the master file is never written to, and that the suite hashes it after every command - That every timestamp lives on the original's timeline, and what happens if you trim the working copy - That chunked transcription splits on silence with an overlap, and why fixed chunks mangle words - Which published loudness target and true peak ceiling you used, where you read them, and on which date - That chapters are written both into the file and as a separate document, and how to check them in another player ===== ARCHITECTURE.md ===== # Architecture · Podsqueeze ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | Python with ffmpeg and a job table in SQLite, one process | transcribing an hour of audio takes minutes, so nothing runs inside a request | | Originals | The master file is read-only and never rewritten | it is the only artefact here you cannot regenerate | | Timeline | One timeline, the original's, for every timestamp in the system | a trimmed working copy is a different clock and chapters land in the wrong place | | Transcription | One interface, a local model by default and an API as an option, returning word-level times | word times are what make chapters, clips and captions possible at all | | Text | One model call per artefact with the transcript as the only source, each regenerable alone | regenerating a description should not discard the title you spent ten minutes on | | Loudness | Measured with a meter implementing the published recommendation, targets read from the platform's own page | the target is a number somebody else publishes and occasionally changes | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Import | Probing, hashing and the working copy. | Any decoder, provided the source is never written and the timeline is preserved. | | Transcription | Silence-based chunking, stitching and word times. | Any model, provided it returns word-level timings on the original's timeline. | | Chapters | Proposals snapped to real moments, with edits kept. | Any proposer, provided every start exists in the transcript. | | Clips | Boundary expansion, fades and rendering from the original. | Any renderer, provided cuts land in silence. | | Export | Encoding, loudness, chapters and captions. | Any encoder, provided loudness is measured on the encoded output. | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `DATA_DIR` · required · Database, working copies and rendered output. Never the masters. - `MEDIA_SOURCE_DIR` · required · Read only. The suite hashes everything here after every command. - `TRANSCRIBE_BACKEND` · required · Local model or API. Both return word-level times through the same interface. - `TRANSCRIBE_API_KEY` · optional, secret · Only for the API backend. Audio is uploaded there, so choose deliberately. - `TARGET_LUFS` · required · Copied from the platform's current published figure, with the date recorded in the README. - `TRUE_PEAK_CEILING_DBTP` · required · Also published. Measured on the encoded file, not the intermediate. - `CHUNK_OVERLAP_SECONDS` · required · The overlap that lets the stitcher match words instead of trusting a boundary. - `WORKDIR_RETENTION_DAYS` · required · Working copies are large and regenerable, so they go first. ## Production baseline - Security: least privilege, input validation at every boundary, secret redaction in logs, rate limits on abuse-prone paths, no invented security primitives. - Data: explicit schema and migrations, transactional writes where integrity matters, backup and restore procedures that have been exercised. - Integrations: adapters around third-party providers, idempotent webhook or job processing, bounded retries, timeouts. - Observability: structured logs with request or operation ids, an error-tracking hook, and health and readiness checks where a server exists. - Quality: unit tests for domain rules, integration tests at module boundaries, one end-to-end test of the critical path. ## Decision records For each dependency in the stack table, keep a short note: why it was chosen, its failure mode, and how it is replaced. Do not add infrastructure until a requirement in `PRODUCT.md` justifies it. ===== AGENTS.md ===== # Agent instructions · Podsqueeze product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: Python with ffmpeg and a job table in SQLite, one process, The master file is read-only and never rewritten, One timeline, the original's, for every timestamp in the system, One interface, a local model by default and an API as an option, returning word-level times, One model call per artefact with the transcript as the only source, each regenerable alone, Measured with a meter implementing the published recommendation, targets read from the platform's own page. - Implement milestone by milestone from `MILESTONES.md`; keep each change reviewable and leave the application runnable at every commit. - Treat authentication, payments, encryption, imports, webhooks and destructive actions as high-risk boundaries when present. - Never invent cryptography or silently weaken a requirement to make a check pass. - Put every external service behind an interface with a deterministic fake for tests. - Add migrations and rollback or recovery notes for every persistent data change. - Log useful operational context without credentials, tokens, passwords or personal data. - Update documentation and run every check before completing a milestone. ## Known traps - Trimming silence from the working copy before transcription looks like an obvious saving, and it shifts every word time relative to the file you will actually publish, so chapters drift progressively later through the episode and nobody notices until a listener mentions that the markers are off by a minute near the end. Keep one timeline, the original's, and if you must trim for processing then keep the mapping back. - Splitting an hour of audio into fixed ten minute chunks cuts a word in half at each boundary and produces a mangled token and a timing gap in six places, and because the transcript reads perfectly everywhere else those six places go straight into the show notes. Split on detected silence, overlap the chunks, and de-duplicate the overlap by matching words. - A model asked for chapters returns plausible timestamps that correspond to nothing in the audio, usually suspiciously round numbers, and a listener pressing a chapter lands mid-sentence in the middle of a different topic. Every chapter start must be an actual word start from the transcript, snapped to a silence, and a proposal the audio cannot support is dropped rather than rounded into place. - Cutting at the exact start and end times of a quote clips the first consonant and swallows the tail of the last word, so every clip is amateurish in precisely the same way and it is never obvious why. Expand the boundaries to the silences on either side, add a short fade, and check the rendered file rather than the plan. - Normalising to a peak of zero rather than to an integrated loudness target leaves the episode noticeably quieter than everything else in a listener's queue, and the encoder can then push the true peak above the ceiling and clip, which is exactly why the published guidance says to precondition before encoding. Measure integrated loudness and true peak with a conforming meter, target the current published figures, and measure the encoded file rather than the intermediate. ===== MILESTONES.md ===== # Delivery milestones · Podsqueeze Estimated effort: **weekend** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · Import that never touches the master The original is read, hashed and left alone, and every timestamp refers to it. ### Steps 1. Hash at import and after every command The one promise about the master, tested rather than stated. Files: `src/import.py`, `tests/test_master_untouched.py` 2. Decode a working copy into its own directory Separate directory, separate lifetime, separate retention. Files: `src/media/decode.py` 3. Keep the timeline mapping if you trim Every downstream timestamp depends on it. Files: `src/media/timeline.py` ### Done when - [ ] The source hash is unchanged after every command - [ ] Nothing writes to the source path - [ ] An undecodable file fails at import with a reason - [ ] Times are on the original's timeline ### Watch out - Trimming silence from the working copy before transcription looks like an obvious saving, and it shifts every word time relative to the file you will actually publish, so chapters drift progressively later through the episode and nobody notices until a listener mentions that the markers are off by a minute near the end. Keep one timeline, the original's, and if you must trim for processing then keep the mapping back. ## M2 · A transcript with word times you can trust An hour of audio transcribes without a mangled word at every chunk boundary. ### Steps 1. Split on silence with an overlap Fixed boundaries land in the middle of words, every time. Files: `src/transcribe/chunk.py`, `tests/test_boundaries.py` 2. Stitch by matching words in the overlap The overlap exists so the join can be checked rather than trusted. Files: `src/transcribe/stitch.py` 3. Keep confidence per segment and show it The low confidence minute is the one that ends up quoted. Files: `src/ui/transcript.html` ### Done when - [ ] No mangled word at any chunk boundary - [ ] Word times are monotonic across the file - [ ] Low confidence segments are visible - [ ] A rerun reproduces times within tolerance ### Watch out - Splitting an hour of audio into fixed ten minute chunks cuts a word in half at each boundary and produces a mangled token and a timing gap in six places, and because the transcript reads perfectly everywhere else those six places go straight into the show notes. Split on detected silence, overlap the chunks, and de-duplicate the overlap by matching words. ## M3 · Chapters and notes drawn only from the transcript Every chapter starts at a moment that actually exists in the audio. ### Steps 1. Snap every chapter to a word start, then a silence A chapter that lands mid-word is worse than no chapter. Files: `src/chapters/propose.py`, `tests/test_chapter_times.py` 2. Read the platform's guidance and record the version Counts, lengths and title limits are published and they change. Files: `docs/chapter-guidance.md` 3. Keep edits separate from generations Regenerating the description must not eat an edited title. Files: `src/artefacts/store.py` ### Done when - [ ] Chapter starts exist in the transcript - [ ] Chapters meet the recorded guidance - [ ] Edited titles survive a regeneration - [ ] A short episode produces none, with a reason ### Watch out - A model asked for chapters returns plausible timestamps that correspond to nothing in the audio, usually suspiciously round numbers, and a listener pressing a chapter lands mid-sentence in the middle of a different topic. Every chapter start must be an actual word start from the transcript, snapped to a silence, and a proposal the audio cannot support is dropped rather than rounded into place. ## M4 · Clips that begin and end on a sentence A rendered clip sounds like something a person cut. ### Steps 1. Expand to the silences on both sides Then fade, so the cut is inaudible rather than merely short. Files: `src/clips/boundaries.py`, `tests/test_clip_edges.py` 2. Render from the original The working copy exists for transcription, not for output. Files: `src/clips/render.py` 3. Drop candidates that cannot be cut cleanly With the reason, so the list is worth reading. Files: `src/clips/propose.py` ### Done when - [ ] Clips begin and end inside a silence - [ ] Fades are present and the stated length - [ ] Clips are rendered from the original - [ ] Uncuttable candidates are dropped with a reason ### Watch out - Cutting at the exact start and end times of a quote clips the first consonant and swallows the tail of the last word, so every clip is amateurish in precisely the same way and it is never obvious why. Expand the boundaries to the silences on either side, add a short fade, and check the rendered file rather than the plan. ## M5 · Export that a host will accept (production only) The files leave in the formats the platforms publish and at the loudness they ask for. ### Steps 1. Target integrated loudness, not peak Peak normalisation is why an episode sounds quiet next to everything else. Files: `src/export/loudness.py`, `tests/test_loudness.py` 2. Measure the encoded file, not the intermediate The encoder is what pushes the true peak over the ceiling. Files: `src/export/encode.py` 3. Write chapters twice and open them elsewhere File metadata and a feed document, checked in a second player. Files: `src/export/chapters.py`, `docs/verification.md` ### Done when - [ ] The encoded file meets the loudness target - [ ] True peak is at or below the ceiling - [ ] Chapters read in a second player - [ ] Transcript exports load as WebVTT and SRT ### Watch out - Normalising to a peak of zero rather than to an integrated loudness target leaves the episode noticeably quieter than everything else in a listener's queue, and the encoder can then push the true peak above the ceiling and clip, which is exactly why the published guidance says to precondition before encoding. Measure integrated loudness and true peak with a conforming meter, target the current published figures, and measure the encoded file rather than the intermediate. ===== OPERATIONS.md ===== # Operations · Podsqueeze ## Backup The masters, which are backed up wherever you already keep recordings, and the database, which holds transcripts, edited titles and chapter edits. The working copies and rendered clips are regenerable and are the first thing to delete when disk runs short. ## Restore Restore the database and point it at the masters. The hash recorded at import is what reconnects a row to a file, so a restored folder with renamed files reconnects by hash rather than by path. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring For a local tool the checks live in the suite: the master unchanged after every command, no mangled word at a chunk boundary, chapter starts existing in the transcript, and the encoded file meeting its loudness target. Job failures belong on the episode page. ## Incident checklist Chapters drifting through an episode means something processed on a trimmed copy and the timeline mapping was lost, which the timeline assertion catches. An episode that sounds quiet next to others means the export measured the intermediate rather than the encoded file. 1. Contain the issue without destroying evidence or user data. 2. Record the timeline and affected scope. 3. Rotate exposed secrets and revoke compromised sessions or credentials. 4. Restore from a verified backup when needed. 5. Document the root cause, the remediation and the regression test. ## Release gate - [ ] The master is confirmed byte-identical after every command - [ ] No mangled word is confirmed at any chunk boundary - [ ] Every chapter start is confirmed to exist in the transcript - [ ] The encoded file is confirmed within the loudness target and under the ceiling - [ ] Chapters and captions are confirmed readable in a second player ## Launch constraint Do not market omitted Podsqueeze capabilities as implemented. The non-goals in `PRODUCT.md` remain user-visible limitations until they are deliberately delivered. ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. Database, working copies and rendered output. Never the masters. DATA_DIR=./data # Required. Read only. The suite hashes everything here after every command. MEDIA_SOURCE_DIR=~/Recordings # Required. Local model or API. Both return word-level times through the same interface. TRANSCRIBE_BACKEND=local # Optional · secret. Only for the API backend. Audio is uploaded there, so choose deliberately. TRANSCRIBE_API_KEY=sk-...redacted # Required. Copied from the platform's current published figure, with the date recorded in the README. TARGET_LUFS=-16 # Required. Also published. Measured on the encoded file, not the intermediate. TRUE_PEAK_CEILING_DBTP=-1 # Required. The overlap that lets the stitcher match words instead of trusting a boundary. CHUNK_OVERLAP_SECONDS=5 # Required. Working copies are large and regenerable, so they go first. WORKDIR_RETENTION_DAYS=14
# Podsqueeze · indie build A local episode assistant whose hard problems are keeping every timestamp on the original file's timeline, transcribing a long recording without mangling a word at each chunk boundary, and exporting at the loudness and in the formats the platforms actually publish. Estimated effort: **weekend**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | Python with ffmpeg and a job table in SQLite, one process | transcribing an hour of audio takes minutes, so nothing runs inside a request | | Originals | The master file is read-only and never rewritten | it is the only artefact here you cannot regenerate | | Timeline | One timeline, the original's, for every timestamp in the system | a trimmed working copy is a different clock and chapters land in the wrong place | | Transcription | One interface, a local model by default and an API as an option, returning word-level times | word times are what make chapters, clips and captions possible at all | | Text | One model call per artefact with the transcript as the only source, each regenerable alone | regenerating a description should not discard the title you spent ten minutes on | | Loudness | Measured with a meter implementing the published recommendation, targets read from the platform's own page | the target is a number somebody else publishes and occasionally changes | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **Python 3.12 or newer** · free - Why: Runs the jobs, the transcription interface and the interface. - Get it: python.org, or your package manager. - Verify: python3 --version prints 3.12 or higher - [ ] **ffmpeg with loudness measurement and silence detection** · free - Why: Decoding, silence detection, clip rendering, loudness measurement and encoding all go through it, so a cut-down build will fail halfway through the pipeline. - Get it: Your package manager, or a static build for your platform. - Verify: A loudness measurement and a silence detection both run on a sample file - [ ] **A transcription model returning word-level timings** · free locally, pennies per hour hosted - Why: Segment-level timings are not enough: chapters, clips and captions all need word boundaries. - Get it: A local speech model with word timestamps, or an API that returns them. - Verify: A one minute sample returns per-word start and end times - [ ] **Two real episodes, one of them long** · free - Why: Chunk boundaries, drift and loudness only misbehave on a long recording with real silences, and a three minute sample proves nothing. - Get it: Your own recordings, one over ninety minutes, one with two speakers. - Verify: Both import, and the long one crosses several chunk boundaries - [ ] **A second player that reads chapters and captions** · free - Why: The only way to know chapters and transcripts exported correctly is to open them somewhere you did not write. - Get it: Any mature player that displays chapter markers and loads a caption file. - Verify: A known good file shows its chapters and captions in it - [ ] **The loudness target, true peak ceiling and chapter rules you will follow** · free - Why: These are published numbers that change, and the build asserts them, so where and when you read them has to be written down. - Get it: Read the platform's current audio and chapter guidance, note the figures and the date, and record both in the repository. - Verify: The figures appear in configuration and the source and date are in the README ## Quick start Follow Phase 1 in `BUILD_PLAN.md`; it creates the project. Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - Remote recording with guests. - Hosting, the feed and directory analytics. - Licensed music and voice cloning. - Video clips with burned-in captions. - hosted transcription, media clipping, templates, and publishing convenience - remote studio reliability - licensed music libraries - hosting distribution - advanced mastering and support If one of those is essential to you, that is the reason to keep paying for Podsqueeze, and the README should say so rather than pretend.
# Build brief · Podsqueeze The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a local episode assistant that transcribes a recording and drafts its show notes, chapters and clips to replace Podsqueeze. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Stack (fixed, do not substitute) - Runtime: Python with ffmpeg and a job table in SQLite, one process · transcribing an hour of audio takes minutes, so nothing runs inside a request - Originals: The master file is read-only and never rewritten · it is the only artefact here you cannot regenerate - Timeline: One timeline, the original's, for every timestamp in the system · a trimmed working copy is a different clock and chapters land in the wrong place - Transcription: One interface, a local model by default and an API as an option, returning word-level times · word times are what make chapters, clips and captions possible at all - Text: One model call per artefact with the transcript as the only source, each regenerable alone · regenerating a description should not discard the title you spent ten minutes on - Loudness: Measured with a meter implementing the published recommendation, targets read from the platform's own page · the target is a number somebody else publishes and occasionally changes ### Data model (create this before Phase 1) - episodes(id, title, source_path, sha256, duration_s, sample_rate, channels, container, imported_at) · the source path is read and never written - jobs(id, episode_id, kind, state, attempts, started_at, finished_at, error) · transcribe, analyse, render - words(episode_id, ordinal, text, start_s, end_s, speaker, confidence) · times on the original's timeline, always, with no exceptions - segments(episode_id, ordinal, start_s, end_s, speaker, text) · derived from words and regenerable at any time - chapters(episode_id, ordinal, start_s, title, source) · source is auto or edited, and an edit is never overwritten by a regeneration - artefacts(episode_id, kind, model, prompt_version, generated_body, edited_body, generated_at) · title, description, keywords and social posts, each separately regenerable - clips(id, episode_id, start_s, end_s, reason, rendered_path, integrated_lufs, true_peak_dbtp) - loudness(episode_id, stage, integrated_lufs, true_peak_dbtp, measured_at) · stage is source, rendered or encoded, because encoding moves the peak ### Phase 1 · Import that never touches the master Build: Probe the file, record duration, sample rate, channels and container, and hash it. Decode a working copy for processing at whatever rate the transcriber wants, in a separate directory. Queue work in a jobs table. Add a test that hashes the source after every command the tool exposes and compares it with the hash taken at import. Trap: Trimming silence from the working copy before transcription looks like an obvious saving, and it shifts every word time relative to the file you will actually publish, so chapters drift progressively later through the episode and nobody notices until a listener mentions that the markers are off by a minute near the end. Keep one timeline, the original's, and if you must trim for processing then keep the mapping back. Done when: The source file's hash is identical after every command the tool exposes, asserted in the suite rather than assumed. A working copy is created for processing and is never written back over the source, verified by path checks. Probe results are recorded and a file the tool cannot decode fails at import with the reason rather than at transcription. Every stored time is expressed on the original's timeline, asserted against a deliberately trimmed working copy. Do not build yet: No transcription, chapters, clips or export. ### Phase 2 · A transcript with word times you can trust Build: Split the audio on detected silence rather than at fixed intervals, with an overlap between chunks. Transcribe each chunk and stitch by matching words in the overlap rather than by trusting the boundary. Store word level times and confidence, mark low confidence segments, and offer optional speaker labels. Trap: Splitting an hour of audio into fixed ten minute chunks cuts a word in half at each boundary and produces a mangled token and a timing gap in six places, and because the transcript reads perfectly everywhere else those six places go straight into the show notes. Split on detected silence, overlap the chunks, and de-duplicate the overlap by matching words. Done when: A transcript of a two hour file has no duplicated or truncated word at any chunk boundary, asserted by comparing the overlap regions. Word times are monotonic across the whole file and every segment's times fall within its neighbours'. Low confidence segments are marked and visible rather than silently included. Re-running transcription on the same file with the same model reproduces the same word times within a stated tolerance. Do not build yet: No chapters, notes, clips or export yet. ### Phase 3 · Chapters and notes drawn only from the transcript Build: Propose chapters at topic shifts, snap each start to a real word start and then to the nearest detected silence, and drop any the audio cannot support. Apply the platform's own published guidance for chapter count, minimum length and title length, read from its current page rather than from memory, and record which version you followed. Draft the description with timestamps, keeping every human edit separate from the generated text. Trap: A model asked for chapters returns plausible timestamps that correspond to nothing in the audio, usually suspiciously round numbers, and a listener pressing a chapter lands mid-sentence in the middle of a different topic. Every chapter start must be an actual word start from the transcript, snapped to a silence, and a proposal the audio cannot support is dropped rather than rounded into place. Done when: Every chapter start matches a word start in the transcript and falls inside a detected silence, asserted across a generated set. Chapters satisfy the constraints you recorded from the platform's current guidance, and the version you followed is noted in the repository. Editing a chapter title survives a regeneration of the description, tested. An episode below the length at which chapters are recommended produces none and says why. Do not build yet: No clips, encoding or export yet. ### Phase 4 · Clips that begin and end on a sentence Build: Propose candidate quotes from the transcript with a reason for each. Expand the boundaries outwards to the surrounding silences, add a short fade at each end, and render from the original file rather than from the processed working copy. Measure the rendered clip's loudness and record it. Trap: Cutting at the exact start and end times of a quote clips the first consonant and swallows the tail of the last word, so every clip is amateurish in precisely the same way and it is never obvious why. Expand the boundaries to the silences on either side, add a short fade, and check the rendered file rather than the plan. Done when: Rendered clips start and end inside a detected silence and carry a fade of the stated length, verified by measuring the rendered file. Clips are rendered from the original rather than from a processed copy, verified by path. Each clip's loudness is measured after rendering and stored. A candidate whose boundaries cannot be expanded without swallowing another speaker is dropped with its reason recorded. Do not build yet: No encoding, loudness targets or export yet. ### Phase 5 · Export that a host will accept Build: Encode to the bitrate and sample rate the platform publishes for mono and for stereo. Normalise to the published integrated loudness target with its true peak ceiling, measured with a meter implementing the recommendation the platform cites, and measure again on the encoded file. Write chapters both into the file's own metadata and as a separate chapters document for the feed. Export the transcript as WebVTT and SRT. Write a manifest naming every output, the transcript version and the model that produced each artefact. Trap: Normalising to a peak of zero rather than to an integrated loudness target leaves the episode noticeably quieter than everything else in a listener's queue, and the encoder can then push the true peak above the ceiling and clip, which is exactly why the published guidance says to precondition before encoding. Measure integrated loudness and true peak with a conforming meter, target the current published figures, and measure the encoded file rather than the intermediate. Done when: The encoded episode measures within the stated tolerance of the loudness target you recorded and its true peak sits at or below the published ceiling, measured on the encoded file. Chapters are readable both from the file's own metadata and from the exported chapters document, verified by opening the file in a second player. The transcript exports as WebVTT and as SRT and both load in a player. The manifest names every output with the transcript version and the model that produced it. Do not build yet: Nothing after this. ### Out of scope (and why) - Real-time remote recording with a guest, which is a network and audio synchronisation project with nothing in common with this one. - Hosting, the feed and directory analytics, which are the parts that need uptime and a relationship with every directory. - Licensed music and voice cloning, which are licensing problems and consent problems rather than engineering ones. - Video clip rendering with burned-in captions, which doubles the pipeline and is worth adding only once the audio path is boring. ### README must contain - That the master file is never written to, and that the suite hashes it after every command - That every timestamp lives on the original's timeline, and what happens if you trim the working copy - That chunked transcription splits on silence with an overlap, and why fixed chunks mangle words - Which published loudness target and true peak ceiling you used, where you read them, and on which date - That chapters are written both into the file and as a separate document, and how to check them in another player
# Agent instructions · Podsqueeze indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: Python with ffmpeg and a job table in SQLite, one process, The master file is read-only and never rewritten, One timeline, the original's, for every timestamp in the system, One interface, a local model by default and an API as an option, returning word-level times, One model call per artefact with the transcript as the only source, each regenerable alone, Measured with a meter implementing the published recommendation, targets read from the platform's own page. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs". ## Known traps - Trimming silence from the working copy before transcription looks like an obvious saving, and it shifts every word time relative to the file you will actually publish, so chapters drift progressively later through the episode and nobody notices until a listener mentions that the markers are off by a minute near the end. Keep one timeline, the original's, and if you must trim for processing then keep the mapping back. - Splitting an hour of audio into fixed ten minute chunks cuts a word in half at each boundary and produces a mangled token and a timing gap in six places, and because the transcript reads perfectly everywhere else those six places go straight into the show notes. Split on detected silence, overlap the chunks, and de-duplicate the overlap by matching words. - A model asked for chapters returns plausible timestamps that correspond to nothing in the audio, usually suspiciously round numbers, and a listener pressing a chapter lands mid-sentence in the middle of a different topic. Every chapter start must be an actual word start from the transcript, snapped to a silence, and a proposal the audio cannot support is dropped rather than rounded into place. - Cutting at the exact start and end times of a quote clips the first consonant and swallows the tail of the last word, so every clip is amateurish in precisely the same way and it is never obvious why. Expand the boundaries to the silences on either side, add a short fade, and check the rendered file rather than the plan.
# Build plan · Podsqueeze A local episode assistant whose hard problems are keeping every timestamp on the original file's timeline, transcribing a long recording without mangling a word at each chunk boundary, and exporting at the loudness and in the formats the platforms actually publish. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · Import that never touches the master The original is read, hashed and left alone, and every timestamp refers to it. ### Steps 1. Hash at import and after every command The one promise about the master, tested rather than stated. Files: `src/import.py`, `tests/test_master_untouched.py` 2. Decode a working copy into its own directory Separate directory, separate lifetime, separate retention. Files: `src/media/decode.py` 3. Keep the timeline mapping if you trim Every downstream timestamp depends on it. Files: `src/media/timeline.py` ### Done when - [ ] The source hash is unchanged after every command - [ ] Nothing writes to the source path - [ ] An undecodable file fails at import with a reason - [ ] Times are on the original's timeline ### Watch out - Trimming silence from the working copy before transcription looks like an obvious saving, and it shifts every word time relative to the file you will actually publish, so chapters drift progressively later through the episode and nobody notices until a listener mentions that the markers are off by a minute near the end. Keep one timeline, the original's, and if you must trim for processing then keep the mapping back. ## Phase 2 · A transcript with word times you can trust An hour of audio transcribes without a mangled word at every chunk boundary. ### Steps 1. Split on silence with an overlap Fixed boundaries land in the middle of words, every time. Files: `src/transcribe/chunk.py`, `tests/test_boundaries.py` 2. Stitch by matching words in the overlap The overlap exists so the join can be checked rather than trusted. Files: `src/transcribe/stitch.py` 3. Keep confidence per segment and show it The low confidence minute is the one that ends up quoted. Files: `src/ui/transcript.html` ### Done when - [ ] No mangled word at any chunk boundary - [ ] Word times are monotonic across the file - [ ] Low confidence segments are visible - [ ] A rerun reproduces times within tolerance ### Watch out - Splitting an hour of audio into fixed ten minute chunks cuts a word in half at each boundary and produces a mangled token and a timing gap in six places, and because the transcript reads perfectly everywhere else those six places go straight into the show notes. Split on detected silence, overlap the chunks, and de-duplicate the overlap by matching words. ## Phase 3 · Chapters and notes drawn only from the transcript Every chapter starts at a moment that actually exists in the audio. ### Steps 1. Snap every chapter to a word start, then a silence A chapter that lands mid-word is worse than no chapter. Files: `src/chapters/propose.py`, `tests/test_chapter_times.py` 2. Read the platform's guidance and record the version Counts, lengths and title limits are published and they change. Files: `docs/chapter-guidance.md` 3. Keep edits separate from generations Regenerating the description must not eat an edited title. Files: `src/artefacts/store.py` ### Done when - [ ] Chapter starts exist in the transcript - [ ] Chapters meet the recorded guidance - [ ] Edited titles survive a regeneration - [ ] A short episode produces none, with a reason ### Watch out - A model asked for chapters returns plausible timestamps that correspond to nothing in the audio, usually suspiciously round numbers, and a listener pressing a chapter lands mid-sentence in the middle of a different topic. Every chapter start must be an actual word start from the transcript, snapped to a silence, and a proposal the audio cannot support is dropped rather than rounded into place. ## Phase 4 · Clips that begin and end on a sentence A rendered clip sounds like something a person cut. ### Steps 1. Expand to the silences on both sides Then fade, so the cut is inaudible rather than merely short. Files: `src/clips/boundaries.py`, `tests/test_clip_edges.py` 2. Render from the original The working copy exists for transcription, not for output. Files: `src/clips/render.py` 3. Drop candidates that cannot be cut cleanly With the reason, so the list is worth reading. Files: `src/clips/propose.py` ### Done when - [ ] Clips begin and end inside a silence - [ ] Fades are present and the stated length - [ ] Clips are rendered from the original - [ ] Uncuttable candidates are dropped with a reason ### Watch out - Cutting at the exact start and end times of a quote clips the first consonant and swallows the tail of the last word, so every clip is amateurish in precisely the same way and it is never obvious why. Expand the boundaries to the silences on either side, add a short fade, and check the rendered file rather than the plan. ## Not in this build - Remote recording with guests. - Hosting, the feed and directory analytics. - Licensed music and voice cloning. - Video clips with burned-in captions. ## After v1, if you want it - A watched folder that imports a new recording as it lands, which the jobs table already supports - Speaker naming carried across episodes, once speaker labels have proved stable on two-speaker recordings - A per-episode diff of the generated text against your edits, which is the fastest way to improve the prompts
# Copy to .env and fill in. Never commit .env; this file documents it. # Required. Database, working copies and rendered output. Never the masters. DATA_DIR=./data # Required. Read only. The suite hashes everything here after every command. MEDIA_SOURCE_DIR=~/Recordings # Required. Local model or API. Both return word-level times through the same interface. TRANSCRIBE_BACKEND=local # Optional · secret. Only for the API backend. Audio is uploaded there, so choose deliberately. TRANSCRIBE_API_KEY=sk-...redacted # Required. Copied from the platform's current published figure, with the date recorded in the README. TARGET_LUFS=-16 # Required. Also published. Measured on the encoded file, not the intermediate. TRUE_PEAK_CEILING_DBTP=-1 # Required. The overlap that lets the stitcher match words instead of trusting a boundary. CHUNK_OVERLAP_SECONDS=5 # Required. Working copies are large and regenerable, so they go first. WORKDIR_RETENTION_DAYS=14
# Podsqueeze · product brief ## Problem The core loop is small enough for a capable coding agent to produce a useful local version in one sitting. For Podsqueeze, transform an episode into show notes, chapters, quotes, and short text assets. The hard boundary is hosted transcription, media clipping, templates, and publishing convenience, plus audio infrastructure, distribution, and production polish. ## Product outcome An assistant that leaves your master file byte-identical, produces a transcript with trustworthy word times, proposes chapters that land where it says they do, cuts clips that sound deliberate, and exports at the loudness and in the formats the platforms publish. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - ffmpeg - local audio files - optional transcription API key - sufficient disk space ## Explicit non-goals for v1 - Remote recording with guests. - Hosting, the feed and directory analytics. - Licensed music and voice cloning. - Video clips with burned-in captions. - hosted transcription, media clipping, templates, and publishing convenience - remote studio reliability - licensed music libraries - hosting distribution - advanced mastering and support ## Success criteria - The master is confirmed byte-identical after every command - No mangled word is confirmed at any chunk boundary - Every chapter start is confirmed to exist in the transcript - The encoded file is confirmed within the loudness target and under the ceiling - Chapters and captions are confirmed readable in a second player
# Build brief · Podsqueeze The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a local episode assistant that transcribes a recording and drafts its show notes, chapters and clips to replace Podsqueeze. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Stack (fixed, do not substitute) - Runtime: Python with ffmpeg and a job table in SQLite, one process · transcribing an hour of audio takes minutes, so nothing runs inside a request - Originals: The master file is read-only and never rewritten · it is the only artefact here you cannot regenerate - Timeline: One timeline, the original's, for every timestamp in the system · a trimmed working copy is a different clock and chapters land in the wrong place - Transcription: One interface, a local model by default and an API as an option, returning word-level times · word times are what make chapters, clips and captions possible at all - Text: One model call per artefact with the transcript as the only source, each regenerable alone · regenerating a description should not discard the title you spent ten minutes on - Loudness: Measured with a meter implementing the published recommendation, targets read from the platform's own page · the target is a number somebody else publishes and occasionally changes ### Data model (create this before Phase 1) - episodes(id, title, source_path, sha256, duration_s, sample_rate, channels, container, imported_at) · the source path is read and never written - jobs(id, episode_id, kind, state, attempts, started_at, finished_at, error) · transcribe, analyse, render - words(episode_id, ordinal, text, start_s, end_s, speaker, confidence) · times on the original's timeline, always, with no exceptions - segments(episode_id, ordinal, start_s, end_s, speaker, text) · derived from words and regenerable at any time - chapters(episode_id, ordinal, start_s, title, source) · source is auto or edited, and an edit is never overwritten by a regeneration - artefacts(episode_id, kind, model, prompt_version, generated_body, edited_body, generated_at) · title, description, keywords and social posts, each separately regenerable - clips(id, episode_id, start_s, end_s, reason, rendered_path, integrated_lufs, true_peak_dbtp) - loudness(episode_id, stage, integrated_lufs, true_peak_dbtp, measured_at) · stage is source, rendered or encoded, because encoding moves the peak ### Phase 1 · Import that never touches the master Build: Probe the file, record duration, sample rate, channels and container, and hash it. Decode a working copy for processing at whatever rate the transcriber wants, in a separate directory. Queue work in a jobs table. Add a test that hashes the source after every command the tool exposes and compares it with the hash taken at import. Trap: Trimming silence from the working copy before transcription looks like an obvious saving, and it shifts every word time relative to the file you will actually publish, so chapters drift progressively later through the episode and nobody notices until a listener mentions that the markers are off by a minute near the end. Keep one timeline, the original's, and if you must trim for processing then keep the mapping back. Done when: The source file's hash is identical after every command the tool exposes, asserted in the suite rather than assumed. A working copy is created for processing and is never written back over the source, verified by path checks. Probe results are recorded and a file the tool cannot decode fails at import with the reason rather than at transcription. Every stored time is expressed on the original's timeline, asserted against a deliberately trimmed working copy. Do not build yet: No transcription, chapters, clips or export. ### Phase 2 · A transcript with word times you can trust Build: Split the audio on detected silence rather than at fixed intervals, with an overlap between chunks. Transcribe each chunk and stitch by matching words in the overlap rather than by trusting the boundary. Store word level times and confidence, mark low confidence segments, and offer optional speaker labels. Trap: Splitting an hour of audio into fixed ten minute chunks cuts a word in half at each boundary and produces a mangled token and a timing gap in six places, and because the transcript reads perfectly everywhere else those six places go straight into the show notes. Split on detected silence, overlap the chunks, and de-duplicate the overlap by matching words. Done when: A transcript of a two hour file has no duplicated or truncated word at any chunk boundary, asserted by comparing the overlap regions. Word times are monotonic across the whole file and every segment's times fall within its neighbours'. Low confidence segments are marked and visible rather than silently included. Re-running transcription on the same file with the same model reproduces the same word times within a stated tolerance. Do not build yet: No chapters, notes, clips or export yet. ### Phase 3 · Chapters and notes drawn only from the transcript Build: Propose chapters at topic shifts, snap each start to a real word start and then to the nearest detected silence, and drop any the audio cannot support. Apply the platform's own published guidance for chapter count, minimum length and title length, read from its current page rather than from memory, and record which version you followed. Draft the description with timestamps, keeping every human edit separate from the generated text. Trap: A model asked for chapters returns plausible timestamps that correspond to nothing in the audio, usually suspiciously round numbers, and a listener pressing a chapter lands mid-sentence in the middle of a different topic. Every chapter start must be an actual word start from the transcript, snapped to a silence, and a proposal the audio cannot support is dropped rather than rounded into place. Done when: Every chapter start matches a word start in the transcript and falls inside a detected silence, asserted across a generated set. Chapters satisfy the constraints you recorded from the platform's current guidance, and the version you followed is noted in the repository. Editing a chapter title survives a regeneration of the description, tested. An episode below the length at which chapters are recommended produces none and says why. Do not build yet: No clips, encoding or export yet. ### Phase 4 · Clips that begin and end on a sentence Build: Propose candidate quotes from the transcript with a reason for each. Expand the boundaries outwards to the surrounding silences, add a short fade at each end, and render from the original file rather than from the processed working copy. Measure the rendered clip's loudness and record it. Trap: Cutting at the exact start and end times of a quote clips the first consonant and swallows the tail of the last word, so every clip is amateurish in precisely the same way and it is never obvious why. Expand the boundaries to the silences on either side, add a short fade, and check the rendered file rather than the plan. Done when: Rendered clips start and end inside a detected silence and carry a fade of the stated length, verified by measuring the rendered file. Clips are rendered from the original rather than from a processed copy, verified by path. Each clip's loudness is measured after rendering and stored. A candidate whose boundaries cannot be expanded without swallowing another speaker is dropped with its reason recorded. Do not build yet: No encoding, loudness targets or export yet. ### Phase 5 · Export that a host will accept Build: Encode to the bitrate and sample rate the platform publishes for mono and for stereo. Normalise to the published integrated loudness target with its true peak ceiling, measured with a meter implementing the recommendation the platform cites, and measure again on the encoded file. Write chapters both into the file's own metadata and as a separate chapters document for the feed. Export the transcript as WebVTT and SRT. Write a manifest naming every output, the transcript version and the model that produced each artefact. Trap: Normalising to a peak of zero rather than to an integrated loudness target leaves the episode noticeably quieter than everything else in a listener's queue, and the encoder can then push the true peak above the ceiling and clip, which is exactly why the published guidance says to precondition before encoding. Measure integrated loudness and true peak with a conforming meter, target the current published figures, and measure the encoded file rather than the intermediate. Done when: The encoded episode measures within the stated tolerance of the loudness target you recorded and its true peak sits at or below the published ceiling, measured on the encoded file. Chapters are readable both from the file's own metadata and from the exported chapters document, verified by opening the file in a second player. The transcript exports as WebVTT and as SRT and both load in a player. The manifest names every output with the transcript version and the model that produced it. Do not build yet: Nothing after this. ### Out of scope (and why) - Real-time remote recording with a guest, which is a network and audio synchronisation project with nothing in common with this one. - Hosting, the feed and directory analytics, which are the parts that need uptime and a relationship with every directory. - Licensed music and voice cloning, which are licensing problems and consent problems rather than engineering ones. - Video clip rendering with burned-in captions, which doubles the pipeline and is worth adding only once the audio path is boring. ### README must contain - That the master file is never written to, and that the suite hashes it after every command - That every timestamp lives on the original's timeline, and what happens if you trim the working copy - That chunked transcription splits on silence with an overlap, and why fixed chunks mangle words - Which published loudness target and true peak ceiling you used, where you read them, and on which date - That chapters are written both into the file and as a separate document, and how to check them in another player
# Architecture · Podsqueeze ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | Python with ffmpeg and a job table in SQLite, one process | transcribing an hour of audio takes minutes, so nothing runs inside a request | | Originals | The master file is read-only and never rewritten | it is the only artefact here you cannot regenerate | | Timeline | One timeline, the original's, for every timestamp in the system | a trimmed working copy is a different clock and chapters land in the wrong place | | Transcription | One interface, a local model by default and an API as an option, returning word-level times | word times are what make chapters, clips and captions possible at all | | Text | One model call per artefact with the transcript as the only source, each regenerable alone | regenerating a description should not discard the title you spent ten minutes on | | Loudness | Measured with a meter implementing the published recommendation, targets read from the platform's own page | the target is a number somebody else publishes and occasionally changes | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Import | Probing, hashing and the working copy. | Any decoder, provided the source is never written and the timeline is preserved. | | Transcription | Silence-based chunking, stitching and word times. | Any model, provided it returns word-level timings on the original's timeline. | | Chapters | Proposals snapped to real moments, with edits kept. | Any proposer, provided every start exists in the transcript. | | Clips | Boundary expansion, fades and rendering from the original. | Any renderer, provided cuts land in silence. | | Export | Encoding, loudness, chapters and captions. | Any encoder, provided loudness is measured on the encoded output. | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `DATA_DIR` · required · Database, working copies and rendered output. Never the masters. - `MEDIA_SOURCE_DIR` · required · Read only. The suite hashes everything here after every command. - `TRANSCRIBE_BACKEND` · required · Local model or API. Both return word-level times through the same interface. - `TRANSCRIBE_API_KEY` · optional, secret · Only for the API backend. Audio is uploaded there, so choose deliberately. - `TARGET_LUFS` · required · Copied from the platform's current published figure, with the date recorded in the README. - `TRUE_PEAK_CEILING_DBTP` · required · Also published. Measured on the encoded file, not the intermediate. - `CHUNK_OVERLAP_SECONDS` · required · The overlap that lets the stitcher match words instead of trusting a boundary. - `WORKDIR_RETENTION_DAYS` · required · Working copies are large and regenerable, so they go first. ## Production baseline - Security: least privilege, input validation at every boundary, secret redaction in logs, rate limits on abuse-prone paths, no invented security primitives. - Data: explicit schema and migrations, transactional writes where integrity matters, backup and restore procedures that have been exercised. - Integrations: adapters around third-party providers, idempotent webhook or job processing, bounded retries, timeouts. - Observability: structured logs with request or operation ids, an error-tracking hook, and health and readiness checks where a server exists. - Quality: unit tests for domain rules, integration tests at module boundaries, one end-to-end test of the critical path. ## Decision records For each dependency in the stack table, keep a short note: why it was chosen, its failure mode, and how it is replaced. Do not add infrastructure until a requirement in `PRODUCT.md` justifies it.
# Agent instructions · Podsqueeze product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: Python with ffmpeg and a job table in SQLite, one process, The master file is read-only and never rewritten, One timeline, the original's, for every timestamp in the system, One interface, a local model by default and an API as an option, returning word-level times, One model call per artefact with the transcript as the only source, each regenerable alone, Measured with a meter implementing the published recommendation, targets read from the platform's own page. - Implement milestone by milestone from `MILESTONES.md`; keep each change reviewable and leave the application runnable at every commit. - Treat authentication, payments, encryption, imports, webhooks and destructive actions as high-risk boundaries when present. - Never invent cryptography or silently weaken a requirement to make a check pass. - Put every external service behind an interface with a deterministic fake for tests. - Add migrations and rollback or recovery notes for every persistent data change. - Log useful operational context without credentials, tokens, passwords or personal data. - Update documentation and run every check before completing a milestone. ## Known traps - Trimming silence from the working copy before transcription looks like an obvious saving, and it shifts every word time relative to the file you will actually publish, so chapters drift progressively later through the episode and nobody notices until a listener mentions that the markers are off by a minute near the end. Keep one timeline, the original's, and if you must trim for processing then keep the mapping back. - Splitting an hour of audio into fixed ten minute chunks cuts a word in half at each boundary and produces a mangled token and a timing gap in six places, and because the transcript reads perfectly everywhere else those six places go straight into the show notes. Split on detected silence, overlap the chunks, and de-duplicate the overlap by matching words. - A model asked for chapters returns plausible timestamps that correspond to nothing in the audio, usually suspiciously round numbers, and a listener pressing a chapter lands mid-sentence in the middle of a different topic. Every chapter start must be an actual word start from the transcript, snapped to a silence, and a proposal the audio cannot support is dropped rather than rounded into place. - Cutting at the exact start and end times of a quote clips the first consonant and swallows the tail of the last word, so every clip is amateurish in precisely the same way and it is never obvious why. Expand the boundaries to the silences on either side, add a short fade, and check the rendered file rather than the plan. - Normalising to a peak of zero rather than to an integrated loudness target leaves the episode noticeably quieter than everything else in a listener's queue, and the encoder can then push the true peak above the ceiling and clip, which is exactly why the published guidance says to precondition before encoding. Measure integrated loudness and true peak with a conforming meter, target the current published figures, and measure the encoded file rather than the intermediate.
# Delivery milestones · Podsqueeze Estimated effort: **weekend** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · Import that never touches the master The original is read, hashed and left alone, and every timestamp refers to it. ### Steps 1. Hash at import and after every command The one promise about the master, tested rather than stated. Files: `src/import.py`, `tests/test_master_untouched.py` 2. Decode a working copy into its own directory Separate directory, separate lifetime, separate retention. Files: `src/media/decode.py` 3. Keep the timeline mapping if you trim Every downstream timestamp depends on it. Files: `src/media/timeline.py` ### Done when - [ ] The source hash is unchanged after every command - [ ] Nothing writes to the source path - [ ] An undecodable file fails at import with a reason - [ ] Times are on the original's timeline ### Watch out - Trimming silence from the working copy before transcription looks like an obvious saving, and it shifts every word time relative to the file you will actually publish, so chapters drift progressively later through the episode and nobody notices until a listener mentions that the markers are off by a minute near the end. Keep one timeline, the original's, and if you must trim for processing then keep the mapping back. ## M2 · A transcript with word times you can trust An hour of audio transcribes without a mangled word at every chunk boundary. ### Steps 1. Split on silence with an overlap Fixed boundaries land in the middle of words, every time. Files: `src/transcribe/chunk.py`, `tests/test_boundaries.py` 2. Stitch by matching words in the overlap The overlap exists so the join can be checked rather than trusted. Files: `src/transcribe/stitch.py` 3. Keep confidence per segment and show it The low confidence minute is the one that ends up quoted. Files: `src/ui/transcript.html` ### Done when - [ ] No mangled word at any chunk boundary - [ ] Word times are monotonic across the file - [ ] Low confidence segments are visible - [ ] A rerun reproduces times within tolerance ### Watch out - Splitting an hour of audio into fixed ten minute chunks cuts a word in half at each boundary and produces a mangled token and a timing gap in six places, and because the transcript reads perfectly everywhere else those six places go straight into the show notes. Split on detected silence, overlap the chunks, and de-duplicate the overlap by matching words. ## M3 · Chapters and notes drawn only from the transcript Every chapter starts at a moment that actually exists in the audio. ### Steps 1. Snap every chapter to a word start, then a silence A chapter that lands mid-word is worse than no chapter. Files: `src/chapters/propose.py`, `tests/test_chapter_times.py` 2. Read the platform's guidance and record the version Counts, lengths and title limits are published and they change. Files: `docs/chapter-guidance.md` 3. Keep edits separate from generations Regenerating the description must not eat an edited title. Files: `src/artefacts/store.py` ### Done when - [ ] Chapter starts exist in the transcript - [ ] Chapters meet the recorded guidance - [ ] Edited titles survive a regeneration - [ ] A short episode produces none, with a reason ### Watch out - A model asked for chapters returns plausible timestamps that correspond to nothing in the audio, usually suspiciously round numbers, and a listener pressing a chapter lands mid-sentence in the middle of a different topic. Every chapter start must be an actual word start from the transcript, snapped to a silence, and a proposal the audio cannot support is dropped rather than rounded into place. ## M4 · Clips that begin and end on a sentence A rendered clip sounds like something a person cut. ### Steps 1. Expand to the silences on both sides Then fade, so the cut is inaudible rather than merely short. Files: `src/clips/boundaries.py`, `tests/test_clip_edges.py` 2. Render from the original The working copy exists for transcription, not for output. Files: `src/clips/render.py` 3. Drop candidates that cannot be cut cleanly With the reason, so the list is worth reading. Files: `src/clips/propose.py` ### Done when - [ ] Clips begin and end inside a silence - [ ] Fades are present and the stated length - [ ] Clips are rendered from the original - [ ] Uncuttable candidates are dropped with a reason ### Watch out - Cutting at the exact start and end times of a quote clips the first consonant and swallows the tail of the last word, so every clip is amateurish in precisely the same way and it is never obvious why. Expand the boundaries to the silences on either side, add a short fade, and check the rendered file rather than the plan. ## M5 · Export that a host will accept (production only) The files leave in the formats the platforms publish and at the loudness they ask for. ### Steps 1. Target integrated loudness, not peak Peak normalisation is why an episode sounds quiet next to everything else. Files: `src/export/loudness.py`, `tests/test_loudness.py` 2. Measure the encoded file, not the intermediate The encoder is what pushes the true peak over the ceiling. Files: `src/export/encode.py` 3. Write chapters twice and open them elsewhere File metadata and a feed document, checked in a second player. Files: `src/export/chapters.py`, `docs/verification.md` ### Done when - [ ] The encoded file meets the loudness target - [ ] True peak is at or below the ceiling - [ ] Chapters read in a second player - [ ] Transcript exports load as WebVTT and SRT ### Watch out - Normalising to a peak of zero rather than to an integrated loudness target leaves the episode noticeably quieter than everything else in a listener's queue, and the encoder can then push the true peak above the ceiling and clip, which is exactly why the published guidance says to precondition before encoding. Measure integrated loudness and true peak with a conforming meter, target the current published figures, and measure the encoded file rather than the intermediate.
# Operations · Podsqueeze ## Backup The masters, which are backed up wherever you already keep recordings, and the database, which holds transcripts, edited titles and chapter edits. The working copies and rendered clips are regenerable and are the first thing to delete when disk runs short. ## Restore Restore the database and point it at the masters. The hash recorded at import is what reconnects a row to a file, so a restored folder with renamed files reconnects by hash rather than by path. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring For a local tool the checks live in the suite: the master unchanged after every command, no mangled word at a chunk boundary, chapter starts existing in the transcript, and the encoded file meeting its loudness target. Job failures belong on the episode page. ## Incident checklist Chapters drifting through an episode means something processed on a trimmed copy and the timeline mapping was lost, which the timeline assertion catches. An episode that sounds quiet next to others means the export measured the intermediate rather than the encoded file. 1. Contain the issue without destroying evidence or user data. 2. Record the timeline and affected scope. 3. Rotate exposed secrets and revoke compromised sessions or credentials. 4. Restore from a verified backup when needed. 5. Document the root cause, the remediation and the regression test. ## Release gate - [ ] The master is confirmed byte-identical after every command - [ ] No mangled word is confirmed at any chunk boundary - [ ] Every chapter start is confirmed to exist in the transcript - [ ] The encoded file is confirmed within the loudness target and under the ceiling - [ ] Chapters and captions are confirmed readable in a second player ## Launch constraint Do not market omitted Podsqueeze capabilities as implemented. The non-goals in `PRODUCT.md` remain user-visible limitations until they are deliberately delivered.
# Copy to .env and fill in. Never commit .env; this file documents it. # Required. Database, working copies and rendered output. Never the masters. DATA_DIR=./data # Required. Read only. The suite hashes everything here after every command. MEDIA_SOURCE_DIR=~/Recordings # Required. Local model or API. Both return word-level times through the same interface. TRANSCRIBE_BACKEND=local # Optional · secret. Only for the API backend. Audio is uploaded there, so choose deliberately. TRANSCRIBE_API_KEY=sk-...redacted # Required. Copied from the platform's current published figure, with the date recorded in the README. TARGET_LUFS=-16 # Required. Also published. Measured on the encoded file, not the intermediate. TRUE_PEAK_CEILING_DBTP=-1 # Required. The overlap that lets the stitcher match words instead of trusting a boundary. CHUNK_OVERLAP_SECONDS=5 # Required. Working copies are large and regenerable, so they go first. WORKDIR_RETENTION_DAYS=14
$ choose a build depth, inspect the files, then open the complete pack in your agent
People still pay for Podsqueeze because creators pay to remove fragile audio plumbing and publishing chores from a release schedule. The recurring cost buys codec support, loudness standards, transcription, storage, feeds, analytics, and deliverability to directories, not just the visible interface.
xhosted transcription, media clipping, templates, and publishing convenience
xremote studio reliability
xlicensed music libraries
xhosting distribution
xadvanced mastering and support
Don't feel like building it? These folks already made it free.
SSpeakrPodcast repurposing with your own model endpoints instead of another monthly invoice.open source↗no votes, no pay-to-list · just what's real
Podsqueeze pricing
| plan | monthly | annual (per mo) | what you get |
|---|---|---|---|
| starter | — | $8.99/workspace | 120 processing minutes/month; 8 video clips; 4 GB/file; unused minutes can accumulate |
| pro | — | $49/workspace | 320 processing minutes/month; 20 video clips; 10 GB/file; unused minutes can accumulate |
| agency lite | — | $89/workspace | 600 processing minutes/month; 40 video clips; 10 GB/file; unused minutes can accumulate |
| enterprise | custom | — | Custom processing minutes, clips, seats and support |
free tierno free tier
billingannual prices displayed with a stated 30% discount; exact month-to-month dollar amounts were not extractable
hidden costsMinutes accumulate only up to 3× the current plan allowance and while the account remains on the same or a higher plan.
verified 2026-08-14 · source ↗
Vibecode Podsqueeze
Yes. A competent AI coding agent (Claude Code, Codex, Cursor) can build a usable personal Podsqueeze replacement in one session with the prompt on this page. It runs on your own machine or server with no subscription.
How much does Podsqueeze cost?
Podsqueeze costs about $8.99/month (Starter, checked 2026-08-14), which is $107.88 per year. That's what you save by replacing it with one prompt.
What do I lose by replacing Podsqueeze?
Honestly: hosted transcription, media clipping, templates, and publishing convenience; remote studio reliability; licensed music libraries; hosting distribution; advanced mastering and support. If any of those are load-bearing for you, keep paying.
Is there an open-source alternative to Podsqueeze?
Yes: Speakr (Podcast repurposing with your own model endpoints instead of another monthly invoice.) The prompt is for when you want it exactly your way.