Monorepo for Aesthetic.Computer aesthetic.computer
core marketing podcast SCORE.md
4.7 kB

marketing/podcast — essays → jeffrey-voiced readings #

A production lane that turns written essays (papers/essay-*/*.tex, opinion/*.md) into spoken-word podcast episodes in @jeffrey's ElevenLabs voice, framed like a liturgical reading.

The shape of an episode #

[intro jingle]  →  "A reading of the essay: <Title>, by @jeffrey.
                    Approximately <N> minutes."  →  [the essay, read
                    paragraph by paragraph with breath between]  →
                    "Here ends the reading."  →  [outro jingle]

The framing is deliberately churchy — a reading of the essay, an announced length, a fixed closing ("here ends the reading") — so the catalog feels like a lectionary of AC writing rather than a talk show.

Voice #

ElevenLabs jeffrey-pvc via the production /api/say proxy — the same voice as the pop lane and the 24h recap (provider="jeffrey", voice="neutral:0"). Stability held ≥ 0.5 to keep identity intact. Synthesis costs real money but is content-hash cached (out/cache/), so re-runs are free.

Pipeline (all in bin/) #

  1. essay-to-script.mjs — strips LaTeX/Markdown to clean spoken prose. Drops footnotes, section headings, colophon, URLs; keeps the argument. Emits { title, author, date, paragraphs[], wordCount }.
  2. jingle.mjs — synthesizes intro.wav / outro.wav: a short pentatonic bell motif (ascending in, resolving out). Deterministic, $0, no samples.
  3. cover.mjs — one canonical PALS-mark cover identity for the series and every episode (system/public/purple-pals.svg). Episode titles live in metadata rather than being expressed as substitute/generated logo variants. The cover is embedded as ID3 album art and kept as out/<slug>-cover.png.
  4. produce.mjs — the orchestrator. Narrates intro + each paragraph + outro via /api/say, measures the real body duration with ffprobe to fill in the announced length, assembles jingle + VO + paragraph breaths with ffmpeg (loudnorm → mp3), embeds the cover, and writes a metadata sidecar out/<slug>.json. Output: out/<slug>.mp3.
  5. feed.mjs — aggregates the sidecars into out/index.json (catalog) + out/feed.xml (RSS 2.0 + iTunes), and renders the series cover out/cover.png. Filtered by the shared publish allowlist lib/hosted.mjs.
  6. ship.mjs — the one-command publish: guardrail (allowlist) → Buzzsprout → CDN (mp3 + cover under the hosted name, which backs the papers podcast link) → feed regen → verify. --papers also runs papers deploy+index; --private stages on Buzzsprout. Stops before git (prints the finish block).
  7. publish.mjs (legacy) — stages publish/ + syncs the self-hosted CDN feed. Superseded by ship.mjs + Buzzsprout; kept for the retired RSS path.

Allowlist: lib/hosted.mjs maps each cleared slug → siteName and is the single source of truth for what may publish. A slug absent from it never goes public — ship.mjs refuses it and feed.mjs drops it.

Feed / hosting #

Canonical podcast URL: https://pod.prompt.ac — Buzzsprout custom domain (CNAME pod.prompt.ac → app.buzzsprout.com, DNS-only, in the prompt.ac Cloudflare zone). https://podcast.aesthetic.computer 301-redirects there (proxied CNAME + redirect rule in the aesthetic.computer zone). The subscribable RSS is Buzzsprout's: https://feeds.buzzsprout.com/2628235.rss.

Legacy self-hosted feed (pre-Buzzsprout): DO Spaces (assets-aesthetic-computer) at https://assets.aesthetic.computer/podcast/, feed at /podcast/feed.xml, pushed with publish.mjs --push. Retired as a subscription target — it was never submitted to directories and shouldn't be; Buzzsprout is the distribution path.

Usage #

cd marketing/podcast
node bin/produce.mjs ../../papers/essay-<slug>/<base>.tex --open   # → out/<slug>.mp3
node bin/ship.mjs <slug>          # publish: Buzzsprout + CDN + feed + verify
node bin/ship.mjs <slug> --papers # also deploy the papers PDF + index
# then commit the episode's files + `fish lith/deploy.fish` (ship prints the block)

Legacy self-hosted feed: node bin/feed.mjs && node bin/publish.mjs --push.

Flags: --open (slab-afplay the result), --force (bypass say cache), --stability 0.55 --similarity 0.8 --speed 0.98 (voice tuning), --base <url> on feed.mjs (override the asset host).

Reused from /pop #

  • /api/say invocation pattern + content-hash caching — lifted from pop/bin/say.mjs.
  • Jingle synthesis follows the pop DSP posture (phase-increment sines, exp-decay bells) but is self-contained here.