Build Alitu
KINDAreplaces $38/mosaves $456/yrback to the verdict
A podcast production pipeline that levels and assembles episodes and then publishes a feed which behaves correctly in every client, where the discipline is that identifiers and addresses are permanent commitments rather than implementation details.
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 Orchestrates rendering, upload and feed generation.
Get it python.org or your package manager, then a virtual environment.
Verify
python3 --version prints 3.12 or higher - installfree
Why Normalisation, assembly and measurement all run through it, and reimplementing any of it is not the project.
Get it Your package manager, confirming the loudness filter is available. open ↗
Verify
It measures and normalises a file to a stated target - accountpennies per month plus bandwidth, which is the real cost
Why Podcast clients resume partial downloads, and storage that does not support ranges produces failures that look like corrupt files.
Get it Any provider serving range requests with correct content types. Decide the address scheme before uploading anything.
Verify
A partial download resumes correctly from the stored file - have readyfree
Why A feed passing validation is not the same as a client rendering it correctly, and the differences between clients are where the surprises live.
Get it Install two different podcast apps and subscribe to your feed by address before publishing publicly.
Verify
Both clients show artwork, episode order and descriptions correctly - decidefree
Why Both are permanent commitments visible to every subscriber, and changing either after publishing is a migration your audience experiences as broken episodes.
Get it Decide how identifiers are generated and how media addresses are formed, from immutable values only. Write it down before the first publish.
Verify
A written document states both schemes and confirms neither derives from mutable data
Data model
Create these before the first phase that stores anything. Changing a table later is the expensive kind of change.
- shows(id, title, author, category, explicit, artwork_path, feed_url, base_media_url) - episodes(id, show_id, number, season, title, description, guid, published_at, duration_seconds, state) - segments(episode_id, ordinal, kind, source_path, start_ms, end_ms, gain_db) · kind is intro, content, ad or outro - renders(episode_id, revision, output_path, bytes, loudness_lufs, rendered_at) - publications(episode_id, feed_revision, published_at, enclosure_url) · what the feed said and when - feed_revisions(id, show_id, xml_path, generated_at, validated, validator_output) - An episode's guid is written once at first publish and has no update path anywhere in the schema.
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 |
|---|---|---|---|
SHOW_BASE_URL | required | https://media.example.com/show | The media address prefix. Changing it later breaks every existing subscriber. |
FEED_URL | required | https://example.com/feed.xml | Where the feed lives. Directories store this and it should never move. |
LOUDNESS_TARGET_LUFS | required | -16 | Stated, and consistent across every episode. |
TRUE_PEAK_CEILING_DB | required | -1.0 | So encoding does not introduce clipping after normalisation. |
MAX_GAIN_DB | required | 20 | Above this a recording is reported rather than normalised into noise. |
STORAGE_CREDENTIALSsecret | required | a scoped key | Scoped to the media bucket only. |
The build, in order
Audio that sounds the same every week
Episodes are consistent in level between themselves and against the platform.
Consistency between episodes is what listeners notice.
Files
src/loudness.pySo a problem episode is explicable rather than mysterious.
Files
src/measure.pyLoud and hissy is worse than quiet. Report it instead.
Files
src/loudness.py
done when · tick each as it passeswatch out- Normalising a quiet recording to the target by applying enormous gain brings the room noise up with it, producing an episode that is loud and hissy rather than quiet and clean. Measure first, and report that a recording needs re-recording rather than fixing it beyond what the material supports.
Assembling an episode reproducibly
The same segments produce the same file every time.
So a render is reproducible rather than a one-off result.
Files
src/assemble.pyChecksummed in the suite. Processing twice is not recoverable.
Files
tests/test_sources_untouched.pyA hard join between segments is a click.
Files
src/assemble.py
done when · tick each as it passeswatch out- Editing the source recording in place to fix a level means the original is gone and the next time you need to re-render with a different intro you are working from a file that has already been processed twice. Keep sources read only, describe the assembly, and render from both.
Addresses that will not move
Where an episode file lives is decided once and never changes.
Not the title. A typo correction should not move the file.
Files
src/storage.pyPodcast clients resume partial downloads constantly.
Files
src/storage.pyChanging it later is a migration your audience experiences.
Files
docs/media-urls.md
done when · tick each as it passeswatch out- Building the media address from the episode title means correcting a typo in the title moves the file, breaking every client that already has the old address and potentially causing re-downloads. Derive the address from something immutable, and document the scheme before the first episode goes out.
A feed that is right the first time
The feed validates and its identifiers are permanent.
In the schema. This is the commitment that cannot be walked back.
Files
src/schema.sqltests/test_guid_immutable.pyThe feed is consumed by clients you cannot fix.
Files
src/validate_feed.pyA validator passing is not the same as a client rendering it correctly.
Files
tests/clients.md
done when · tick each as it passeswatch out- Regenerating identifiers from episode data means any correction changes them, and a changed identifier makes an already-heard episode appear as new to every subscriber, re-downloading it and sometimes duplicating it in directories. Write the identifier once at first publish and give it no update path in the schema.
Scheduling, corrections and knowing it workedproduct builder
Episodes release on time and a mistake can be fixed without breaking anyone.
The feed changes at the moment, not when someone remembers.
Files
src/schedule.pyReplace in place or new address. Both have consequences worth stating.
Files
docs/corrections.mdFrom your own logs, with the limitations stated rather than implied away.
Files
src/analytics.py
done when · tick each as it passeswatch out- Replacing an episode's audio file at the same address means clients that already downloaded keep the old version and new listeners get the new one, with no way to tell which anyone has. Decide whether corrections replace in place or publish a new address, and state the consequence either way rather than discovering it.
That is the whole plan for Alitu. What it deliberately does not cover is below · check the gaps before you call it a replacement.
- Transcription and show notes.
- Directory submission and management.
- Dynamic ad insertion.
- Meaningful analytics.
- production automation, hosting integrations, support, and creator-specific polish
- remote studio reliability
- licensed music libraries
- hosting distribution
- advanced mastering and support
- Chapter markers, which several clients support and few shows use
- A transcript published alongside, which helps discovery and accessibility together
- Trailer and bonus episode types, once the feed handling is proven
Need the files? The project pack on the verdict page hands your agent the whole brief · more podcasting.