server/docs/concepts/podcastfabrikken.md
vegard a5985ef3f8 Dokumentasjon, erfaringslogg, migrasjoner og infra-oppdateringer
- Omorganiser docs/: konsepter, features, infra og proposals i egne mapper
- Ny docs/erfaringer/ med lærdommer fra chat-implementering (Svelte 5, SpacetimeDB, adapter-mønster)
- Oppdater ARCHITECTURE.md: Lag 1 status, ny §10 Erfaringslogg, SpacetimeDB i lokal dev
- Oppdater synkronisering.md med implementeringsstatus og designvalg
- Oppdater lokal.md med SpacetimeDB og AI Gateway
- Utvid PG-skjema med channels, messages, media_files, message_revisions
- Legg til seed_dev.sql, migration_safety.md, .env.example
- Nye feature-specs: chat, kanban, whiteboard, live_ai, lydmeldinger m.fl.
- Nye konsept-specs: studioet, møterommet, redaksjonen, den asynkrone gjesten m.fl.
- SpacetimeDB og AI Gateway i docker-compose.dev.yml
- collect-docs.sh inkluderer erfaringer/

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

8 KiB

Konsept: Podcastfabrikken (Lyd & Publiserings-Pipeline)

Filsti: docs/concepts/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/infra/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. Workspace-spesifikk konfigurasjon

Hver workspace har sin egen podcast-konfigurasjon, lagret i workspaces.settings (JSONB):

5.1 Mediefiler

Lydfiler lagres i undermapper per workspace: /srv/sidelinja/media/{workspace_slug}/. Caddy ruter trafikk basert på domene (fra workspaces.domain) til riktig undermappe.

5.2 Transkripsjoner

Det opprettes ett Forgejo-repo per workspace for SRT-filer, slik at historikk og redigering ikke blandes på tvers av podcaster.

5.3 AI-prompts

  • Whisper initial_prompt: Navnelister og kontekst lagres per workspace i settings.whisper_prompt. Rust-worker bygger prompten fra statisk liste + aktører i workspace-ets kunnskapsgraf.
  • LLM system-prompts: OpenRouter-prompts for metadata-uttrekk lagres i settings.llm_prompts slik at AI-en kjenner konteksten og vertene for akkurat den podcasten.

5.4 RSS-feed

SvelteKit genererer /feed.xml dynamisk basert på domenet forespørselen kommer fra (matcher workspaces.domain), eller workspace-slug som fallback.

5.5 Statistikk

Rust-workeren stats_parse knytter nedlastingstall fra Caddy-logger til riktig workspace_id basert på filsti i loggen.

6. 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.
  • Workspace: Alle jobber, mediefiler og metadata opprettes med riktig workspace_id. Hent workspace-config (prompts, domene) fra workspaces.settings.