Legg til fase 30: komplett podcast-hosting uten ekstern avhengighet
8 oppgaver: iTunes/Podcasting 2.0 RSS-tags, nedlastingsstatistikk (IAB-kompatibel), embed-spiller, import fra eksisterende podcast med prøveimport-flyt (importer → test → re-importer nye → 301), og feed-redirect for å flytte bort. Feature-spec: docs/features/podcast_hosting.md Ingen castopod — podcasten er noder med riktige edges og en feed. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
44327df370
commit
b94576cded
3 changed files with 234 additions and 0 deletions
208
docs/features/podcast_hosting.md
Normal file
208
docs/features/podcast_hosting.md
Normal file
|
|
@ -0,0 +1,208 @@
|
||||||
|
# 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
|
||||||
|
<itunes:author>Sidelinja</itunes:author>
|
||||||
|
<itunes:category text="News & Politics"/>
|
||||||
|
<itunes:explicit>false</itunes:explicit>
|
||||||
|
<itunes:image href="https://synops.no/media/artwork.jpg"/>
|
||||||
|
<podcast:locked>no</podcast:locked>
|
||||||
|
<podcast:transcript url="https://synops.no/pub/sidelinja/ep42.srt"
|
||||||
|
type="application/srt"/>
|
||||||
|
<podcast:chapters url="https://synops.no/pub/sidelinja/ep42-chapters.json"
|
||||||
|
type="application/json+chapters"/>
|
||||||
|
```
|
||||||
|
|
||||||
|
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 <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:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<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:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
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) |
|
||||||
|
|
||||||
|
## 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
|
||||||
|
|
@ -47,6 +47,7 @@ declare -A PHASE_DEPS=(
|
||||||
[27]="26"
|
[27]="26"
|
||||||
[28]="27"
|
[28]="27"
|
||||||
[29]="28"
|
[29]="28"
|
||||||
|
[30]="29"
|
||||||
)
|
)
|
||||||
|
|
||||||
# --- Finn blokkerte faser ---
|
# --- Finn blokkerte faser ---
|
||||||
|
|
|
||||||
25
tasks.md
25
tasks.md
|
|
@ -50,6 +50,7 @@ Fase 25 → Fase 26 (epost)
|
||||||
Fase 26 → Fase 27 (tankekart)
|
Fase 26 → Fase 27 (tankekart)
|
||||||
Fase 27 → Fase 28 (manglende CLI + AI-ruting)
|
Fase 27 → Fase 28 (manglende CLI + AI-ruting)
|
||||||
Fase 28 → Fase 29 (universell input)
|
Fase 28 → Fase 29 (universell input)
|
||||||
|
Fase 29 → Fase 30 (podcast-hosting)
|
||||||
```
|
```
|
||||||
|
|
||||||
Hvis en oppgave er `[?]` eller `[!]`, hoppes den over — og alle
|
Hvis en oppgave er `[?]` eller `[!]`, hoppes den over — og alle
|
||||||
|
|
@ -411,3 +412,27 @@ noden er det som lever videre.
|
||||||
### Kalender-import
|
### Kalender-import
|
||||||
- [ ] 29.11 ICS-import: `synops-calendar` CLI som parser ICS-fil og oppretter noder med `scheduled`-edges. Input: `--file <ics> --collection-id <uuid>`. Duplikatdeteksjon via UID. Oppdatering ved re-import.
|
- [ ] 29.11 ICS-import: `synops-calendar` CLI som parser ICS-fil og oppretter noder med `scheduled`-edges. Input: `--file <ics> --collection-id <uuid>`. Duplikatdeteksjon via UID. Oppdatering ved re-import.
|
||||||
- [ ] 29.12 CalDAV-abonnement: abonner på ekstern CalDAV-kalender (Google, Outlook). Poller periodisk, synkroniserer endringer. Som RSS-feed men for kalenderhendelser.
|
- [ ] 29.12 CalDAV-abonnement: abonner på ekstern CalDAV-kalender (Google, Outlook). Poller periodisk, synkroniserer endringer. Som RSS-feed men for kalenderhendelser.
|
||||||
|
|
||||||
|
## Fase 30: Podcast-hosting — komplett, uten ekstern avhengighet
|
||||||
|
|
||||||
|
Ref: `docs/features/podcast_hosting.md`. Bygg komplett podcast-hosting i Synops.
|
||||||
|
Ingen castopod, ingen ekstern tjeneste. Import fra eksisterende podcast med
|
||||||
|
prøveimport-flyt.
|
||||||
|
|
||||||
|
### RSS og metadata
|
||||||
|
- [ ] 30.1 iTunes/Podcasting 2.0 RSS-tags: utvid synops-rss med `<itunes:*>` og `<podcast:*>` namespace. Tags fra samlingens podcast-trait metadata (author, category, explicit, language). Podcast:transcript og podcast:chapters fra eksisterende edges.
|
||||||
|
- [ ] 30.2 Podcast-trait metadata: utvid podcast-trait med iTunes-felt (itunes_category, itunes_author, explicit, language, redirect_feed). Admin-UI for å redigere.
|
||||||
|
|
||||||
|
### Statistikk
|
||||||
|
- [ ] 30.3 `synops-stats` CLI: parse Caddy access-logger for /media/*-requests. Aggreger nedlastinger per episode per dag. IAB-regler: filtrer bots (user-agent), unik IP per 24t. Output: JSON med episode_id, date, downloads, unique_listeners. `--write` lagrer i PG.
|
||||||
|
- [ ] 30.4 Statistikk-dashboard: vis nedlastinger per episode, trend over tid, topp-episoder, klienter (Apple/Spotify/andre), geografi. Integrert i admin-panelet.
|
||||||
|
|
||||||
|
### Embed-spiller
|
||||||
|
- [ ] 30.5 Podcast-spiller komponent: Svelte-komponent med artwork, tittel, play/pause, progress, waveform, kapittelmerkering. Responsiv. Serveres som iframe-embed: `synops.no/pub/<slug>/<episode>/player`.
|
||||||
|
|
||||||
|
### Import
|
||||||
|
- [ ] 30.6 `synops-import-podcast` CLI: importer eksisterende podcast fra RSS-feed. Parse metadata, last ned lydfiler/artwork/transkripsjoner til CAS, opprett noder med edges. Duplikatdeteksjon via `<guid>`. `--dry-run` for forhåndsvisning. Idempotent: kjør flere ganger, bare nye episoder importeres.
|
||||||
|
- [ ] 30.7 Prøveimport-flyt i frontend: "Importer podcast"-wizard i admin. Steg 1: lim inn RSS-URL, vis forhåndsvisning av episoder. Steg 2: importer (kan ta tid for mange episoder). Steg 3: sjekk resultat, sammenlign feeds. Steg 4: re-importer nye episoder når klar. Steg 5: aktiver 301-redirect på gammel host.
|
||||||
|
|
||||||
|
### Eksport og redirect
|
||||||
|
- [ ] 30.8 Feed-redirect: `redirect_feed`-felt i podcast-trait. Når satt: Caddy returnerer 301 for feed-URL. Brukeren kan alltid flytte bort. Admin-UI med én-klikks aktivering og advarsel.
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue