# Feature: Podcast-hosting — komplett, uten ekstern avhengighet ## Konsept Synops hoster podcast selv — ingen castopod, ingen ekstern tjeneste. Podcasten er noder med riktige edges og en RSS-feed med riktige tags. Vi har 80% allerede. ## Hva vi har | Funksjon | Status | Komponent | |----------|--------|-----------| | RSS-feed | ✓ | synops-rss | | Mediefiler med byte-range | ✓ | CAS + Caddy | | HTML-sider per episode | ✓ | synops-render + Tera | | Metadata | ✓ | Noder med edges | | Transkripsjoner | ✓ | synops-transcribe + segmenter | | Kapitler | ✓ | chapter-edges | | Show notes | ✓ | show_notes-edge | | Lydprosessering | ✓ | synops-audio | ## Hva vi mangler ### 1. Podcast-spesifikke RSS-tags Utvid synops-rss med iTunes og Podcasting 2.0 namespace: ```xml Sidelinja false no ``` Metadata fra samlingens podcast-trait: ```jsonc { "traits": { "podcast": { "itunes_category": "News & Politics", "itunes_author": "Sidelinja", "explicit": false, "language": "no", "redirect_feed": null } } } ``` ### 2. Nedlastingsstatistikk Caddy logger allerede alle requests. `synops-stats` parser loggene og aggregerer: ```bash synops-stats --collection-id --period 30d → Nedlastinger per episode per dag → Unike lyttere (IP + user-agent per 24t, IAB-regler) → Geografi (fra IP, valgfritt) → Klienter (Apple Podcasts, Spotify, etc. fra user-agent) ``` Lagres i PG — visbart i admin-dashboard. ### 3. Embed-spiller Liten Svelte-komponent for å embedde episoder på nettsider: ```html ``` Viser: artwork, tittel, play/pause, progress, waveform, kapittelmerkering. Responsivt. Fungerer uten JavaScript (fallback til lydfil-lenke). ### 4. Katalog-distribusjon Apple Podcasts, Spotify, Google Podcasts trenger bare RSS-URL-en. Submit én gang manuelt, de poller feeden. Admin-UI: felt for å lime inn RSS-URL og se status per katalog (submitted/live/pending). ## Import fra annen host ### `synops-import-podcast` CLI-verktøy som importerer en eksisterende podcast fra RSS-feed: ```bash synops-import-podcast --feed-url https://gammel-host.no/feed.xml \ --collection-id \ [--dry-run] \ [--write] ``` For hver episode: 1. Parse metadata fra RSS (tittel, beskrivelse, pubDate, etc.) 2. Last ned MP3 → CAS (deduplisering gratis) 3. Last ned artwork → CAS 4. Last ned transkripsjon/kapitler hvis tilgjengelig 5. Opprett content-node med all metadata 6. Opprett media-node + has_media-edge 7. Opprett belongs_to-edge → samling 8. Sett published_at fra pubDate Podcast-metadata (author, category, artwork) → samlingens podcast-trait. ### Prøveimport-flyten Import er ikke alt-eller-ingenting. Den støtter en gradvis migrasjon: ``` Uke 1: Prøveimport synops-import-podcast --feed-url https://gammel.no/feed.xml \ --collection-id --write → Alle episoder importert → Sjekk at alt ser riktig ut i Synops → Prøvepubliser RSS-feed: synops.no/pub/sidelinja/feed.xml → Sammenlign med original feed → Hør på noen episoder, sjekk metadata → Ikke fornøyd? Slett samlingen, start på nytt Uke 2: Test med lyttere → Del den nye feed-URL-en med noen testlyttere → Sjekk at spillere (Apple, Spotify) håndterer den → Publiser en ny episode direkte i Synops Uke 3: Restimorter + cutover synops-import-podcast --feed-url https://gammel.no/feed.xml \ --collection-id --write → Idempotent: eksisterende episoder skippes (duplikatdeteksjon via guid) → Nye episoder siden sist importeres → Slå på 301 redirect på gammel host → Apple/Spotify oppdaterer automatisk innen noen dager → Ferdig — podcasten lever nå i Synops ``` ### Duplikatdeteksjon Import bruker `` fra RSS for å identifisere episoder. Kjørt to ganger = ingen duplikater. Bare nye episoder importeres. ### Hva importeres | RSS-felt | Synops | |----------|--------| | `` | node.title | | `<description>` | node.content | | `<enclosure url>` | media-node i CAS | | `<pubDate>` | metadata.published_at | | `<itunes:duration>` | metadata.duration | | `<itunes:episode>` | metadata.episode_number | | `<itunes:season>` | metadata.season_number | | `<itunes:image>` | media-node + og_image-edge | | `<podcast:transcript>` | last ned → synops-transcribe parsing | | `<podcast:chapters>` | chapter-edges | | `<guid>` | metadata.guid (for duplikatdeteksjon) | ## Erstatning av lydfiler (re-publisering) Når en episode re-publiseres med ny lydfil (f.eks. etter redigering i lydstudioet): ``` 1. Opprett ny media-node i CAS 2. Opprett derived_from-edge (ny → gammel) 3. Flytt has_media-edge fra gammel fil → ny fil 4. Gammel fil: ingen aktive edges → pruning-kandidat 5. Grace period: 30 dager (konfigurerbart) → RSS-cacher hos Apple/Spotify trenger tid til å oppdatere 6. Etter grace period: gammel fil prunes fra CAS ``` Den gamle filen har `derived_from`-edge innover (ny peker på gammel) men ingen `has_media`-edge utover. Den er historikk, ikke aktiv innhold. Noden lever videre som tombstone — metadata bevares, binærfilen slettes fra disk. Historikken er sporbar via `derived_from`-kjeden. ## Eksport / flytte bort Brukeren eier dataene sine. Flytte bort er enkelt: ```jsonc // I podcast-trait: { "redirect_feed": "https://ny-host.no/feed.xml" } ``` Når satt: Caddy returnerer 301 for feed-URL. Apple/Spotify oppdaterer automatisk. Brukeren kan også eksportere all data: - RSS-feed med alle episoder - Lydfiler fra CAS - Transkripsjoner, kapitler, show notes Alt er noder → alt er eksporterbart. ## Komponenter | Feature | Rolle | |---------|-------| | synops-rss | RSS-generering med iTunes/Podcasting 2.0 tags | | synops-import-podcast | Import fra eksisterende RSS-feed | | synops-stats | Nedlastingsstatistikk fra Caddy-logger | | CAS + Caddy | Mediaserving med byte-range | | synops-render | Episode-sider og embed-spiller | | Admin-UI | Katalogstatus, statistikk, redirect-konfig | ## Bygger på - `docs/concepts/podcastfabrikken.md` — produksjonspipeline - `docs/concepts/studioet.md` — innspilling - `docs/proposals/podcasting_2_0.md` — Podcasting 2.0 tags - `docs/features/lydstudio.md` — postproduksjon