Build Swell AI
YESreplaces $17/mosaves $204/yrback to the verdict
A local podcast production tool whose hard problems are editing without ever rewriting the source recording, hitting a published loudness target measured on the file you actually ship, and generating notes that can be traced back to the words that were said.
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 interface and the processing.
Get it python.org, or your package manager. open ↗
Verify
python3 --version prints 3.12 or higher - installfree
Why Decode, filtering, loudness measurement and encoding all come from here, and the measurement and the encode agreeing matters more than either alone.
Get it Your package manager or a static build. open ↗
Verify
ffmpeg -filters lists the loudness normalisation filter and -encoders lists your target codec - have readyfree, plus the disk and the time
Why An hour of audio per episode through a hosted service is a recurring bill and a copy of every conversation leaving the machine.
Get it Download a speech recognition model sized for your hardware and confirm it runs on a short clip.
Verify
A one minute clip transcribes with timings - API keyper minute of audio or per thousand tokens; check the current rate
Why Only if you want higher quality than the local model gives; every phase must work without it.
Get it Create a key with the narrowest scope your provider offers.
Verify
A short clip transcribes through the API and the local path still works when the key is removed - have readyfree
Why Loudness, chunk stitching and gating all behave differently on a quiet remote guest than on a clean studio track, and a clean fixture proves nothing.
Get it One studio recording, one noisy remote call, and one already mastered episode.
Verify
All three import and render within the stated tolerance - decidefree
Why The figures are asserted by tests and the published guidance changes, so the version you built against needs recording.
Get it Read the current audio requirements of the directories you publish to and write down the figures and the date. open ↗
Verify
The README names the figures, the source and the date
Data model
Create these before the first phase that stores anything. Changing a table later is the expensive kind of change.
- episodes(id, title, source_path, source_sha256, duration_seconds, sample_rate, channels, imported_at) · source_path is inside the read-only folder - edits(episode_id, ordinal, kind, at_seconds, to_seconds, params_json) · kind is cut, fade, gain, gate or marker, and applying them produces a new file - renders(id, episode_id, edit_sha256, target_lufs, measured_lufs, true_peak_db, format, bitrate, channels, path, rendered_at) - transcripts(id, episode_id, engine, engine_version, language, created_at) - segments(transcript_id, ordinal, start_seconds, end_seconds, speaker, text, confidence) · timings survive a manual correction - chapters(episode_id, start_seconds, title, origin) · origin is manual or derived, and derived ones carry the segments they came from - drafts(id, episode_id, kind, engine, body, citations_json, approved_at, approved_by) · citations_json maps each passage to segment ids
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 |
|---|---|---|---|
MEDIA_DIR | required | ./media | Read-only sources. Nothing in the application writes here. |
RENDERS_DIR | required | ./renders | Everything the tool produces. The only folder retention can reach. |
DATABASE_PATH | required | ./data/podcast.db | Episodes, edits, transcripts, renders and drafts. |
TARGET_LUFS | required | -16 | Taken from the directory's published requirements, with the date recorded. |
TRUE_PEAK_CEILING_DB | required | -1 | Measured on the encoded file, not on the intermediate. |
RENDER_RETENTION_DAYS | required | 60 | Applies to renders only. Sources are never deleted by any path. |
MAX_RENDERS_GB | required | 50 | A render that would exceed this is refused before it starts. |
TRANSCRIPTION_API_KEYsecret | optional | sk-...redacted | Optional. Without it the local model is used and the interface says so. |
The build, in order
Originals that cannot be damaged
Importing a recording makes it read-only and every later operation is additive.
The filesystem is a better guarantee than a code review.
Files
app/media/import.pytests/test_source_untouched.pyAn edit list can be undone; an overwritten file cannot.
Files
app/edits/model.pyIt catches the one path that got it wrong, whenever that appears.
Files
app/render/guard.py
done when · tick each as it passeswatch out- Normalising or trimming in place because it is one ffmpeg command overwrites the only copy of a two hour interview, and the failure is silent until somebody asks for the unedited version. Import into a folder nothing writes to, keep edits as data, and make the render read the source and write elsewhere.
A transcript with timings you can click
Every word has a time, so notes and chapters can point back at the audio.
Fixed boundaries with no overlap duplicate or lose a sentence every time.
Files
app/transcribe/chunk.pytests/test_stitch.pyThe timings are what make every later feature possible.
Files
app/transcribe/edit.pyTwo transcripts of one episode are only comparable if you know what made them.
Files
app/transcribe/model.py
done when · tick each as it passeswatch out- Chunking an hour of audio at fixed boundaries with no overlap cuts sentences in half, and stitching by concatenation then duplicates the words either side of every boundary, which the person only notices when the show notes quote a phrase twice. Overlap the chunks, match the repeated region when stitching, and test with a recording long enough to need several.
Loudness measured on the file you ship
The exported episode meets a published target, verified after encoding rather than before.
The encoded file is the only file anyone will hear.
Files
app/render/loudness.pytests/test_loudness.pyA claim without a measurement is a claim.
Files
app/render/model.pyThe published figures change, and the README is where the next person looks.
Files
docs/audio-targets.md
done when · tick each as it passeswatch out- Measuring loudness on the normalised audio and then encoding to a lossy format ships a file whose true peak is above the ceiling, because the encoder reconstructs a waveform that overshoots the samples it was given; a single loudness pass also gets the target wrong because it cannot see the whole file. Use a two pass measurement, encode, then measure the encoded file and store what you measured.
Notes that point at what was actually said
Every generated passage can be traced to timestamps in the transcript and nothing ships unread.
It is the only cheap defence against a confident invention.
Files
app/drafts/generate.pytests/test_citations.pyThe approval is the point at which a person takes responsibility.
Files
app/export/gate.pyA tool that needs a key to be useful is a tool with a monthly bill.
Files
app/drafts/extract.py
done when · tick each as it passeswatch out- A model given only a rough transcript will confidently supply a guest's employer, a round number and a neat closing quotation, none of which were said, and the host publishes it because it reads well. Require citations per passage, show them beside the draft, refuse to export an unapproved draft, and treat anything with no citation as something to delete rather than to trust.
Exporting an episode and living with the diskproduct builder
A finished episode leaves as a complete set of files, and renders do not quietly fill the drive.
It is what makes an episode reproducible six months later.
Files
app/export/manifest.pyA cleanup script that can reach the masters will eventually reach them.
Files
app/retention.pytests/test_retention_scope.pyFailing before the render beats failing during it.
Files
app/render/limits.py
done when · tick each as it passeswatch out- Keeping every render of every episode fills a disk in a season, and the cleanup script written in a hurry then walks the media folder as well and removes the masters. Give renders a retention and a ceiling, make the delete path physically incapable of reaching the media folder, and put that in a test.
That is the whole plan for Swell AI. What it deliberately does not cover is below · check the gaps before you call it a replacement.
- Remote recording.
- Hosting, feeds and directory analytics.
- Licensed music and voice cloning.
- Automatic publishing of anything.
- templates, integrations, hosted processing, and content history
- remote studio reliability
- licensed music libraries
- hosting distribution
- advanced mastering and support
- A second render profile for a different directory's published figures, once one has shipped a season
- Speaker naming that persists across episodes, built on the existing segment labels
- A diff between two transcripts of the same episode, which the recorded engine versions already make meaningful
Need the files? The project pack on the verdict page hands your agent the whole brief · more podcasting.