Build Castmagic

KINDAreplaces $19.92/mosaves $239.04/yrback to the verdict

0%0 of 20 items done

Saved on this device only. Tick prerequisites first, then work the phases in order · do not start one until the checks above it pass.

Import an episode, clean the audio, transcribe with word timestamps and speakers, generate show notes with real timestamps, find clip candidates from the transcript, and export the audio, the transcript, the notes and the clips as production files, with every generated claim traceable to a moment in the recording.

estimated effort multi-daythe files for this build are in the project pack

RuntimePython 3.12 driving ffmpeg and a transcriberAudioffmpeg for cleanup and clip extractionTranscriptionwhisper.cpp with word-level timestamps and speaker labelsGenerationA model applied to the transcript, never to the audioStorageSource audio untouched, outputs and a manifest alongside

Before step 1

Everything below is assumed from the first step. Tick each one when you actually have it, not when you plan to.

  1. installfree

    Why Runs the pipeline.

    Get it python.org or your package manager, then a virtual environment.

    Verify python3 --version prints 3.12 or higher

  2. installfree

    Why Cleanup, measurement and clip extraction all run through it.

    Get it brew install ffmpeg or your package manager. Confirm the filters exist rather than assuming. open ↗

    Verify ffmpeg -filters lists loudnorm and afftdn

  3. installfree, but transcription takes real time on your machine

    Why Word timestamps are the spine of chapters, clips and citations. Segment-level output cannot support any of them.

    Get it Build whisper.cpp and download a model. Confirm word-level output before building on it. open ↗

    Verify Output contains per-word start and end times

  4. API keypay per token; an hour-long transcript summarised is typically a few cents

    Why Phase 3 onward. Cleanup and transcription work with no model at all.

    Get it A provider key with a spend limit, or Ollama for a local model with lower quality output.

    Verify A generated note returns with valid citations

  5. have readyfree

    Why Speaker labelling, chapter detection and clip scoring all need real conversation. A monologue hides most of the difficulty.

    Get it Use your own recordings. At least one over forty minutes with two voices.

    Verify Three episodes are available with one two-speaker recording over forty minutes

Data model

Create these before the first phase that stores anything. Changing a table later is the expensive kind of change.

- episodes(id, source_path, title, duration_ms, imported_at, content_hash)
- words(episode_id, ordinal, text, start_ms, end_ms, speaker) · the spine everything else references
- segments(id, episode_id, start_ms, end_ms, speaker, text) · sentence-level, derived from words
- chapters(id, episode_id, start_ms, title, source) · source is silence, transcript or manual
- notes(id, episode_id, kind, body, cited_segment_ids_json, generated_at) · kind is summary, show_notes or description
- clips(id, episode_id, start_ms, end_ms, reason, score, exported_path, approved)
- Every generated artefact records the segment identifiers it drew from, so a claim in the show notes can be played back.

Environment variables

These go in a .env file the app reads at startup. The pack's .env.example is this table as a file · copy it, never commit the filled-in version.

VariableNeededExampleWhere the value comes from
TARGET_LUFSrequired-16Loudness target for spoken word. State it rather than assuming a default.
TRUE_PEAK_DBrequired-1.5True peak ceiling, leaving headroom so lossy encoding does not clip.
WHISPER_MODEL_PATHrequired./models/ggml-base.en.binThe transcription model. Larger is more accurate and considerably slower.
CLIP_MIN_SECONDSrequired20Shortest clip candidate. Below this a clip rarely carries a self-contained idea.
CLIP_MAX_SECONDSrequired90Longest clip candidate.
MODEL_API_KEYsecretoptionalsk-...For generation. Leave unset to run the audio and transcript phases only.
OUTPUT_DIRrequired./outWhere processed audio, transcripts, notes and clips are written. Never the source directory.

The build, in order

  1. Import and clean, without touching the source

    An episode is loudness-normalised and ready, and the original is untouched.

    1. Analysis pass parsed from the filter's structured output rather than its human readable text.

      Files src/measure.py

      terminal
      ffmpeg -i episode.wav -af loudnorm=print_format=json -f null -
    2. Feed the measured values back in. Around -16 LUFS with a -1.5 dBTP ceiling is a common spoken word target; state yours.

      Files src/normalise.py

    3. Outputs alongside, checksum the source before and after as a test.

      Files tests/test_source_untouched.py

    done when · tick each as it passes
    watch out
    • Single pass loudness normalisation compresses dynamics to reach the target and makes speech sound pumped. Measure first, then apply the measured values with linear correction, which applies a fixed gain instead. On a spoken word episode the difference is audible immediately.
  2. Transcript with timestamps and speakers

    Every word knows when it was said and, where there are two voices, who said it.

    1. Mono 16 kHz 16 bit from the source, not from the normalised output.

      terminal
      ffmpeg -i episode.wav -ar 16000 -ac 1 -c:a pcm_s16le for-transcription.wav
    2. Explicitly. Verify by playing a single word before building anything on it.

      Files src/transcribe.py

    3. Automatic assignment is a starting point. Corrections must persist and propagate to segments.

      Files src/speakers.py

    done when · tick each as it passes
    watch out
    • Segment-level timestamps are the default and are useless for clips, because a segment is a sentence and a clip boundary needs a word. Request word-level output explicitly and check it by playing a single word's range from the middle of a sentence.
  3. Show notes that cite the recording

    Every claim in the generated notes points at a moment you can play.

    1. The model sees numbered segments and must cite them. Request structured output so parsing is not a regular expression.

      Files src/notes.py

    2. A pure function from output plus segment set to a verdict. Unit test it with a crafted bad citation.

      Files src/validate.pytests/test_validate.py

    3. Clicking one seeks the audio. This is also how you check the notes before publishing.

      Files src/review.py

    done when · tick each as it passes
    watch out
    • A model summarising an hour of conversation will confidently state something that was never said, and it will be the most quotable line in the notes. Require citations by segment identifier and validate them, because the failure mode here is publishing a quote your guest did not say.
  4. Chapters and clip candidates

    The episode is chaptered, and the moments worth clipping are surfaced rather than hunted for.

    1. Long silences from the audio and topic shifts from the transcript, reconciled rather than one overriding the other.

      Files src/chapters.py

      terminal
      ffmpeg -i episode.wav -af silencedetect=n=-35dB:d=1.5 -f null -
    2. Self-contained meaning, length within bounds, single speaker where you want that. Score, then propose.

      Files src/clips.py

    3. To word boundaries, padded into surrounding silence. Listen to three before trusting the rule.

      Files src/clips.py

    done when · tick each as it passes
    watch out
    • A clip cut mid-word is unusable and a clip cut exactly at a word boundary often clips the breath before it, which sounds abrupt. Snap to word boundaries and pad into the surrounding silence, and always let a person hear a candidate before it is exported.
  5. The export bundle

    One action produces everything you need to publish the episode.

    1. Audio, subtitle file, plain transcript, Markdown notes, chapters, clips.

      Files src/export.py

    2. Confirm what it accepts first. Record the choice in the manifest.

      Files docs/host.md

    3. Settings, source hash and every output. This is what makes the bundle reproducible.

      Files src/manifest.py

    done when · tick each as it passes
    watch out
    • Chapter marker formats differ between podcast hosts and a file the host rejects is discovered at upload time, usually late. Check the format your host accepts before building the exporter, and record which one you targeted in the manifest.
what this build does not replace
after v1, if you want it

Need the files? The project pack on the verdict page hands your agent the whole brief · more podcasting.