server/docs/features/podcastfabrikken.md
vegard 4b56560bf9 Lokalt utviklingsmiljø, Whisper-benchmark, AI Gateway og repostruktur
- Sett opp docker-compose.dev.yml med PostgreSQL, Redis, Caddy og Whisper
- Benchmarket faster-whisper (small/medium/large-v3) med norsk tale
- Besluttet medium + initial_prompt som standard, SRT som master-format
- Ny feature-spec: AI Gateway (LiteLLM) med BYOK og Promptfoo-testing
- Definert dataklassifisering (kritisk/gjenskapbar/avledet/flyktig)
- Konkretisert backup-strategi med pg_dump, rsync og restore-prosedyre
- Splittet repos: sidelinja/server (kode) + sidelinja/sidelinja (innhold)
- Oppdatert lokal.md: utviklingsmiljø for kode, ikke prod-replika
- Dokumentert transkripsjonspipeline: Whisper SRT → Git → PG (avledet)
- Live AI-assistent: small-modell, flyktig logg med 30d TTL

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 14:51:15 +01:00

6.7 KiB

Feature Spec: Podcastfabrikken (Lyd & Publiserings-Pipeline)

Filsti: docs/features/podcastfabrikken.md

1. Konsept

Den automatiserte "samlebåndet" som tar over når en ferdigklippet episode er klar, samt verktøyet for å oppdatere eksisterende episoder (f.eks. en rullerende intro-episode). Målet er at maskinen gjør 90 % av grovarbeidet (transkripsjon, metadata, kapittelinndeling), men at redaksjonen alltid kan overstyre resultatet manuelt før publisering.

2. Arkitektur & Dataflyt

Dette er en asynkron arbeidsflyt som kombinerer filsystem, AI, databaser og CI/CD.

  1. Trigger (Opplasting/Oppdatering): Brukeren laster opp en .mp3-fil via SvelteKit-grensesnittet. Dette rutes enten som en ny episode (INSERT), eller en oppdatering av en eksisterende (UPDATE).
  2. Kø-system (PostgreSQL jobbkø): Siden lydprosessering tar tid (CPU-intensivt), legges oppgaven i den felles jobbkøen (se docs/features/jobbkø.md). Opplastingen oppretter to jobber i sekvens: først whisper_transcribe, deretter openrouter_analyze (som trigges automatisk ved fullført transkripsjon).
  3. Transkripsjon (faster-whisper): Rust-worker kaller faster-whisper-server (OpenAI-kompatibelt API, POST /v1/audio/transcriptions) med response_format=srt og mottar SRT direkte. Modell: Systran/faster-whisper-medium med initial_prompt (navneliste).
  4. Lagring av transkripsjon (Git): Rust-worker committer SRT-filen til Forgejo. SRT er master-formatet — redigerbart, tidsstemplet, og et etablert standardformat. Git gir diff, historikk og sporbarhet. Redaksjonen kan redigere SRT direkte.
  5. Avledede formater (PostgreSQL): Ved commit (via Forgejo webhook) parser en Rust-worker SRT-filen og genererer:
    • Ren tekst — strippes fra SRT (fjern tidsstempler/sekvensnummer) for lesbart publiseringsdokument
    • Segmenter — tidsstemplede utdrag koblet til Aktører/Temaer i kunnskapsgrafen
    • Full-text søkeindeks — for oppslag på tvers av episoder
  6. AI-Analyse (OpenRouter): Transkripsjonen sendes til OpenRouter (Claude-modell) for uttrekk av forslag til tittel, sammendrag, show notes og kapittler.
  7. Manuell Godkjenning & Fletting (SvelteKit):
    • For nye episoder: Presenteres som et ferskt utkast.
    • For oppdateringer: Viser AI-ens nye forslag side-om-side med eksisterende metadata. Redaksjonen kan da velge hva som skal beholdes eller flettes (merge).
  8. Publisering (PostgreSQL): Ved "Godkjenn" lagres metadataene permanent i databasen.
  9. RSS-Generering: SvelteKit-appen genererer en oppdatert /feed.xml.

2.1 Episodeside (publisert visning)

Hver publisert episode får en side med:

  • Lydavspiller + sammendrag + kapitler + stikkord
  • Personreferanser og artikler (koblet via kunnskapsgrafen)
  • Fane: SRT (nedlastbar undertekstfil — master-kopi fra Git)
  • Fane: Ren tekst (lesbart transkripsjonsdokument — avledet fra SRT, lagret i PG)

3. Spesialhåndtering: Oppdatering av eksisterende episoder (Cache-busting)

Podcast-apper (Apple, Spotify) og CDN-er cacher innhold aggressivt. For at en endring i f.eks. "Introepisoden" skal slå gjennom hos lytterne, MÅ følgende tekniske regler følges:

  • Filnavn-versjonering (Viktigst!): Den nye lydfilen skal aldri overskrive det gamle filnavnet på disken. Systemet må legge til en hash, UUID eller et tidsstempel (f.eks. intro_v2_1710289000.mp3). Dette tvinger appene til å laste ned filen på nytt.
  • RSS <guid> (Global Unique Identifier): Denne taggen MÅ forbli 100% statisk/uendret fra originalepisoden. Den forteller appene at "Dette er fortsatt samme episode, ikke lag en duplikat".
  • RSS <enclosure>: URL-en i enclosure-taggen (som peker på .mp3-filen) oppdateres i databasen til å reflektere det nye filnavnet.
  • RSS <pubDate>: SvelteKit-grensesnittet skal gi redaksjonen en toggle-knapp ved oppdatering:
    • Alternativ A: "Behold opprinnelig dato" (Episoden oppdateres i det stille for nye lyttere).
    • Alternativ B: "Sett dato til NÅ" (Episoden spretter til toppen av feeden som en ny utgivelse).

4. Whisper-konfigurasjon

  • Tjeneste: fedirz/faster-whisper-server (Docker, OpenAI-kompatibelt API)
  • Endepunkt: POST /v1/audio/transcriptions med response_format=srt
  • Beslutning: SRT direkte fra Whisper, ikke verbose JSON. Verbose JSON inneholder diagnostikk (tokens, logprob, temperatur) som ikke har verdi for oss. SRT gir tidsstempler + tekst i et etablert format som er redigerbart, diffbart i Git, og trivielt å parse til ren tekst og segmenter.
  • Modeller (benchmarket med E277.mp3, 32:45 norsk tale, CPU i7-13900K):
Konfigurasjon Tid (CPU) Seg Tegn Kommentar
small ~6 min 777 25851 Rask, men hyppige feil i egennavn
medium ~18 min 442 26938 God balanse, noen navnefeil
medium + prompt ~17 min 455 26957 Riktige egennavn, anbefalt standard
large-v3 ~24 min 520 14559 Hallusinerer uten VAD — IKKE bruk uten VAD
large-v3 + VAD ~31 min 964 28291 God kvalitet, men noen navnefeil
large-v3 + VAD + prompt ~31 min 964 28295 Best kvalitet, riktige egennavn
  • Anbefaling: medium + initial_prompt som standard. large-v3 + VAD + prompt for best mulig kvalitet der det er verdt ventetiden.
  • Viktig: large-v3 KREVER vad_filter=true — uten hallusinerer modellen repeterende tekst.
  • Språk: Sett language=no eksplisitt for norsk — unngå auto-detect som kan velge dansk/svensk.

4.1 initial_prompt (navneliste)

initial_prompt primes Whisper med ordforråd som forbedrer gjenkjenning av egennavn. Effekten er tydelig:

  • Uten prompt: "Vegard Nøgnes", "SideLinja", "Sidlinja"
  • Med prompt: "Vegard Nøtnæs", "Sidelinja" (riktig)

Prompten bygges automatisk av Rust-worker fra en statisk navneliste + aktører i kunnskapsgrafen:

Sidelinja podcast med Vegard Nøtnæs, Trond Sørensen, Arne Eidshagen,
Peter Hagen, Nicolai Buzatu, Bjørn Einar Drag, Øystein Sjølie

5. Instruks for Claude Code

  • Lydfiler: Håndter filopplasting i SvelteKit strømmende (streaming) for filer >100MB for å unngå minne-lekkasjer.
  • Feilhåndtering: Hvis OpenRouter timer ut eller Whisper feiler, må oppgaven flagges med status error i databasen slik at brukeren kan trigge jobben på nytt manuelt via UI.
  • Opprydding (Disk): Når en fil oppdateres vellykket, skal den gamle/foreldede .mp3-filen enten slettes fra Hetzner-serveren automatisk, eller flyttes til en /archive/-mappe basert på en miljøvariabel.
  • Transkripsjoner: Master-kopi alltid i Git. Aldri rediger avledede formater direkte i PG — de regenereres fra Git-kilden.