Build Alitu

KINDAreplaces $38/mosaves $456/yrback to the verdict

0%0 of 17 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.

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.

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

Audioffmpeg with loudness normalisation to a stated targetAssemblyA declarative episode structure of segmentsHostingObject storage with a stable address scheme decided up frontFeedGenerated with immutable identifiers and validated before publishRuntimePython 3.12 with a job queue

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 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

  2. 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

  3. pennies 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

  4. 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

  5. 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.

VariableNeededExampleWhere the value comes from
SHOW_BASE_URLrequiredhttps://media.example.com/showThe media address prefix. Changing it later breaks every existing subscriber.
FEED_URLrequiredhttps://example.com/feed.xmlWhere the feed lives. Directories store this and it should never move.
LOUDNESS_TARGET_LUFSrequired-16Stated, and consistent across every episode.
TRUE_PEAK_CEILING_DBrequired-1.0So encoding does not introduce clipping after normalisation.
MAX_GAIN_DBrequired20Above this a recording is reported rather than normalised into noise.
STORAGE_CREDENTIALSsecretrequireda scoped keyScoped to the media bucket only.

The build, in order

  1. Audio that sounds the same every week

    Episodes are consistent in level between themselves and against the platform.

    1. Consistency between episodes is what listeners notice.

      Files src/loudness.py

    2. So a problem episode is explicable rather than mysterious.

      Files src/measure.py

    3. Loud and hissy is worse than quiet. Report it instead.

      Files src/loudness.py

    done when · tick each as it passes
    watch 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.
  2. Assembling an episode reproducibly

    The same segments produce the same file every time.

    1. So a render is reproducible rather than a one-off result.

      Files src/assemble.py

    2. Checksummed in the suite. Processing twice is not recoverable.

      Files tests/test_sources_untouched.py

    3. A hard join between segments is a click.

      Files src/assemble.py

    done when · tick each as it passes
    watch 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.
  3. Addresses that will not move

    Where an episode file lives is decided once and never changes.

    1. Not the title. A typo correction should not move the file.

      Files src/storage.py

    2. Podcast clients resume partial downloads constantly.

      Files src/storage.py

    3. Changing it later is a migration your audience experiences.

      Files docs/media-urls.md

    done when · tick each as it passes
    watch 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.
  4. A feed that is right the first time

    The feed validates and its identifiers are permanent.

    1. In the schema. This is the commitment that cannot be walked back.

      Files src/schema.sqltests/test_guid_immutable.py

    2. The feed is consumed by clients you cannot fix.

      Files src/validate_feed.py

    3. A validator passing is not the same as a client rendering it correctly.

      Files tests/clients.md

    done when · tick each as it passes
    watch 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.
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.