# 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 `` (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 ``:** URL-en i `enclosure`-taggen (som peker på `.mp3`-filen) oppdateres i databasen til å reflektere det *nye* filnavnet. * **RSS ``:** 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.