Build Castmagic
KINDAreplaces $19.92/mosaves $239.04/yrback to the verdict
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.
Before step 1
Everything below is assumed from the first step. Tick each one when you actually have it, not when you plan to.
- 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 - 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 - 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 - 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 - 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.
| Variable | Needed | Example | Where the value comes from |
|---|---|---|---|
TARGET_LUFS | required | -16 | Loudness target for spoken word. State it rather than assuming a default. |
TRUE_PEAK_DB | required | -1.5 | True peak ceiling, leaving headroom so lossy encoding does not clip. |
WHISPER_MODEL_PATH | required | ./models/ggml-base.en.bin | The transcription model. Larger is more accurate and considerably slower. |
CLIP_MIN_SECONDS | required | 20 | Shortest clip candidate. Below this a clip rarely carries a self-contained idea. |
CLIP_MAX_SECONDS | required | 90 | Longest clip candidate. |
MODEL_API_KEYsecret | optional | sk-... | For generation. Leave unset to run the audio and transcript phases only. |
OUTPUT_DIR | required | ./out | Where processed audio, transcripts, notes and clips are written. Never the source directory. |
The build, in order
Import and clean, without touching the source
An episode is loudness-normalised and ready, and the original is untouched.
Analysis pass parsed from the filter's structured output rather than its human readable text.
Files
src/measure.pyterminalffmpeg -i episode.wav -af loudnorm=print_format=json -f null -
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.pyOutputs alongside, checksum the source before and after as a test.
Files
tests/test_source_untouched.py
done when · tick each as it passeswatch 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.
Transcript with timestamps and speakers
Every word knows when it was said and, where there are two voices, who said it.
Mono 16 kHz 16 bit from the source, not from the normalised output.
terminalffmpeg -i episode.wav -ar 16000 -ac 1 -c:a pcm_s16le for-transcription.wav
Explicitly. Verify by playing a single word before building anything on it.
Files
src/transcribe.pyAutomatic assignment is a starting point. Corrections must persist and propagate to segments.
Files
src/speakers.py
done when · tick each as it passeswatch 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.
Show notes that cite the recording
Every claim in the generated notes points at a moment you can play.
The model sees numbered segments and must cite them. Request structured output so parsing is not a regular expression.
Files
src/notes.pyA pure function from output plus segment set to a verdict. Unit test it with a crafted bad citation.
Files
src/validate.pytests/test_validate.pyClicking one seeks the audio. This is also how you check the notes before publishing.
Files
src/review.py
done when · tick each as it passeswatch 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.
Chapters and clip candidates
The episode is chaptered, and the moments worth clipping are surfaced rather than hunted for.
Long silences from the audio and topic shifts from the transcript, reconciled rather than one overriding the other.
Files
src/chapters.pyterminalffmpeg -i episode.wav -af silencedetect=n=-35dB:d=1.5 -f null -
Self-contained meaning, length within bounds, single speaker where you want that. Score, then propose.
Files
src/clips.pyTo word boundaries, padded into surrounding silence. Listen to three before trusting the rule.
Files
src/clips.py
done when · tick each as it passeswatch 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.
The export bundle
One action produces everything you need to publish the episode.
Audio, subtitle file, plain transcript, Markdown notes, chapters, clips.
Files
src/export.pyConfirm what it accepts first. Record the choice in the manifest.
Files
docs/host.mdSettings, source hash and every output. This is what makes the bundle reproducible.
Files
src/manifest.py
done when · tick each as it passeswatch 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.
Repurposing, batching and the honest limitsproduct builder
A back catalogue can be processed unattended, and you know what the output is worth.
One bad file must not stop a back catalogue run.
Files
src/batch.pySocial copy gets the same treatment as show notes. A misquote is worse on social.
Files
src/social.pyTen samples, reviewed honestly, with the proportion recorded. This is the number that decides whether the pipeline is worth running.
Files
docs/quality.md
done when · tick each as it passeswatch out- Generated social copy reads fluently and is wrong often enough that publishing it unread will eventually misquote a guest. Sample and record the edit rate rather than assuming, because that number tells you whether this saves time or moves the work.
That is the whole plan for Castmagic. What it deliberately does not cover is below · check the gaps before you call it a replacement.
- The accumulated prompt library of the commercial product.
- Publishing integrations to hosts and social platforms.
- Team review and approval workflow.
- Hosted processing that runs while your machine sleeps.
- large prompt library, integrations, team workflow, and hosted processing
- remote studio reliability
- licensed music libraries
- hosting distribution
- advanced mastering and support
- A guest-facing page generated from the transcript and clips
- Cross-episode search over your whole back catalogue
- Automatic detection of a repeated segment such as an intro, so it is excluded from clips
Need the files? The project pack on the verdict page hands your agent the whole brief · more podcasting.