synops/docs/features/podcast_hosting.md
vegard c5239d2923 Feed-redirect: 301 for podcast som flyttes til ny host (oppgave 30.8)
Når redirect_feed er satt i podcast-trait, returnerer maskinrommet
HTTP 301 Moved Permanently med Location-header i stedet for å serve
feeden. iTunes new-feed-url-taggen bevares også i RSS-en for klienter
som ikke følger 301.

Admin-UI: erstatter det enkle tekstfeltet med tre tilstander:
- Inaktiv: knapp "Flytt podcast til annen plattform..."
- Bekreftelse: advarsel + URL-felt + rød "Aktiver redirect"-knapp
- Aktiv: gul statusindikator med deaktiver-knapp

Backend: sjekker redirect_feed tidlig i generate_feed() og returnerer
301 før noe annet arbeid gjøres (DB-oppslag for episodes osv).
2026-03-19 00:31:39 +00:00

7.2 KiB

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

Implementert i synops-rss og maskinrommet/src/rss.rs. Begge genererer nå:

Channel-level: itunes:author, itunes:category, itunes:explicit, itunes:image (fra og_image-edge), itunes:type, podcast:locked.

Item-level: itunes:title, itunes:duration, itunes:explicit, itunes:image (episode-bilde), podcast:transcript (SRT fra transcription_segments), podcast:chapters (JSON fra chapter-edges).

Metadata leses fra samlingens podcast-trait:

{
  "traits": {
    "podcast": {
      "itunes_category": "News & Politics",
      "itunes_author": "Sidelinja",
      "explicit": false,
      "language": "no",
      "redirect_feed": null
    }
  }
}

Merk: Transcript- og chapters-URL-ene (/{short_id}/transcript.srt, /{short_id}/chapters.json) krever at offentlige endepunkt legges til i maskinrommet for å servere disse. De genereres i feeden, men serveres ikke ennå.

2. Nedlastingsstatistikk

Caddy logger allerede alle requests. synops-stats parser loggene og aggregerer:

synops-stats --collection-id <uuid> --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:

<iframe src="https://synops.no/pub/sidelinja/ep42/player"
        width="100%" height="180" frameborder="0"></iframe>

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:

synops-import-podcast --feed-url https://gammel-host.no/feed.xml \
                      --collection-id <uuid> \
                      [--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 <uuid> --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 <uuid> --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 <guid> fra RSS for å identifisere episoder. Kjørt to ganger = ingen duplikater. Bare nye episoder importeres.

Hva importeres

RSS-felt Synops
<title> 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:

// I podcast-trait:
{
  "redirect_feed": "https://ny-host.no/feed.xml"
}

Når satt: maskinrommet returnerer HTTP 301 Moved Permanently for /pub/{slug}/feed.xml med Location-header til ny URL. Apple/Spotify oppdaterer automatisk. I tillegg inkluderes <itunes:new-feed-url> i RSS-en for klienter som ikke følger 301.

Admin-UI har én-klikks aktivering med advarsel. Redirecten kan deaktiveres når som helst fra podcast-trait-innstillingene.

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