Commit graph

60 commits

Author SHA1 Message Date
55239f71a0 Transkription: eingebaute Instanz läuft auf dem PC im Tailnet; ist sie nicht erreichbar, wartet der Auftrag und versucht es alle 10 Minuten neu (höchstens zwei Tage); Whisper-Server kann CUDA
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 11:17:23 +01:00
cedc14f714 Merge branch 'wt-tr-pipeline' into wt-tr-int 2026-11-09 10:02:00 +01:00
ae5341d441 Smarte Transkription C: Transkriptions-Pipeline (podcasts/transcribe.py), Endpunkte /transcribe/, /transcription/pending/, /article/claim/, Transkript-Rückfall auf Whisper, transcripts in /feeds/notifications/
Die Folge wird geladen, zu 16 kHz mono dekodiert und an Pausen in Stücke von etwa acht Minuten geschnitten (FLAC für die eingebaute, Opus für fremde Instanzen), parallel an Whisper geschickt (drei Wiederholungen mit wachsender Pause) und per transcripts.merge geglättet. Ein Daemon-Thread arbeitet die Warteschlange ab, ein Auftrag zugleich; hängengebliebene Aufträge starten nach einem Neustart beim ersten Aufruf neu. Abbrechen löscht die Zeile des Auftrags. Gerät der Reservierung: device im Body oder Header X-Diora-Device (docs/transcription.md).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-09 10:01:35 +01:00
9659140f30 Migration der Transkription nach 0028 (nach dem Episodenlink aus dem Teilen-Schritt)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 09:47:20 +01:00
efdc0ffc11 Merge branch 'wt-tr-blog' into wt-tr-int 2026-11-09 09:39:08 +01:00
bd09d81366 Smarte Transkription D: Artikel-Endpunkt und Blog-Anbindung
POST/DELETE /podcasts/episodes/<id>/article/ (podcasts/article.py): legt die Quelle Podcast-Artikel an und erzeugt oder ersetzt das Item der Folge; Sanitizer behält href=#t=<Sekunden>; _item_data und Item-Ansicht liefern episode_id; feeds.js: Folge anhören und Zeitmarken-Links.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-09 09:36:34 +01:00
a937ed8b3e Folgen haben einen Link: PodcastEpisode.link aus dem <link> des Feed-Eintrags (neu und beim Abruf nachgetragen), in episode_detail und allen Folgenlisten
Grundlage fürs Teilen einer Folge; Migration 0027.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-09 09:32:13 +01:00
69442f4a4d Smarte Transkription B: Datenmodell, Einstellungen und Test-Endpunkt
TranscriptionSettings (Token Fernet-verschluesselt wie SiteLogin), TranscriptionJob und EpisodeArticle in podcasts; Source.synthetic in feeds, das Refresh, Aufraeumen, OPML-Export, Kommando und Umstellen auf Nachrichten ueberspringt bzw. verweigert. GET/POST /podcasts/transcription/settings/ und POST /podcasts/transcription/test/ (SSRF-Pruefung ueber chapters._open, jetzt auch fuer POST; eingebaute Instanz aus DIORA_WHISPER_URL ausgenommen).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-09 09:27:01 +01:00
1194472572 Podcasts: Werbung und Schluss in YouTube-Folgen überspringen (SponsorBlock)
Some checks failed
Build and push Docker image / build (push) Successful in 15s
Test / test (push) Successful in 10m56s
Test / e2e (push) Failing after 1m33s
Die Folgen eines YouTube-Kanals werden gehört wie Podcasts; unterbrochen wird man
darin von der Werbung und vom Schluss. Beides markieren andere Hörer bei
SponsorBlock: podcasts/sponsor.py fragt je Folge einmal danach (nur sponsor und
outro, nur Teile, die übersprungen werden, kein „full“) und legt die Antwort an
der Folge ab (PodcastEpisode.sponsor); GET /podcasts/episodes/<id>/sponsor/
antwortet damit. Eine Antwort gilt eine Woche, ein Fehlschlag eine Stunde.

Der Player springt über solche Teile (podcasts-web.js für Browser und Desktop,
in der App später der eigene Player); der Schalter heißt skip_sponsor, steht bei
„Stille überspringen“ im Abspiel- und Tempomenü, ist an und wirkt nur bei
YouTube-Folgen (default on). e2e/sponsor.spec.js spielt es in Chromium durch;
sw v155
2026-10-25 16:18:24 +01:00
a64c801275 Podcast-Radio: die neueste ungehörte Strom-Folge, ohne Spuren (eigene Radio-gehört-Liste, gemischt nach Podcasts, neue Folgen zuerst); Web-Player und Player-Sync; sw v153
Some checks failed
Build and push Docker image / build (push) Failing after 0s
Test / test (push) Failing after 0s
Test / e2e (push) Failing after 0s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 13:17:09 +01:00
410d4806ca Podcasts: Hörbuch-Kapitel kommen nie in die Wiedergabeliste oder Merkliste; Themen nur aus Folgentiteln (schneller, weniger Rauschen)
Some checks failed
Build and push Docker image / build (push) Successful in 15s
Test / test (push) Has been cancelled
Test / e2e (push) Waiting to run
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 19:00:38 +02:00
3fa16b9653 Podcasts stöbern: Volltextsuche (FTS5), ähnliche Folgen, Personen, Themen, Vergessenes, Personen anderswo (Apple/fyyd), Empfehlungen (fyyd); Abo-Favoriten
Some checks failed
Test / e2e (push) Waiting to run
Build and push Docker image / build (push) Successful in 16s
Test / test (push) Has been cancelled
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 18:50:35 +02:00
3ef2bdcb61 Podcasts: Merkliste und Wiedergabeliste (hinter lists_v2), Abo-Stufen inkl. Stamm direkt in die Wiedergabeliste, Abrufintervall pro Abo, Strom-Daten (heard_365, stream/seen, filter=stream)
Some checks failed
Build and push Docker image / build (push) Successful in 13s
Test / test (push) Successful in 9m46s
Test / e2e (push) Failing after 1m23s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 18:05:28 +02:00
f539158a19 Podcasts: „auf einmal“ heißt innerhalb einer Stunde – stündliche Nachrichten bleiben häufig
Some checks failed
Build and push Docker image / build (push) Successful in 16s
Test / test (push) Successful in 9m23s
Test / e2e (push) Failing after 1m21s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 17:30:38 +02:00
2cb5dc4c04 Podcasts: alle Folgen an einem Tag (Archiv, Kurs) zählen als selten, nicht als täglich
Some checks failed
Test / e2e (push) Waiting to run
Build and push Docker image / build (push) Successful in 15s
Test / test (push) Has been cancelled
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 17:29:39 +02:00
ba3c6ff905 Podcasts: Folgen einer Kategorie (?folder=), wie oft ein Podcast erscheint (interval_days) für „Nach Zeitraum, seltene zuerst“
Some checks failed
Test / e2e (push) Waiting to run
Build and push Docker image / build (push) Successful in 14s
Test / test (push) Has been cancelled
- GET /podcasts/episodes/?folder=<name> ('' = ohne Ordner)
- /podcasts/state/: interval_days je Feed (mittlerer Abstand der neuesten 10 Folgen,
  eine Abfrage per Window, eine Stunde gemerkt)
- Podcast-Oberfläche aus diora-android bd98557 (Zeiträume, Ordner-Chips in „Folgen“)
- docs/library.md, sw v134

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 17:23:06 +02:00
a399374216 YouTube-Kanäle: zufällige 404 abfangen; „Automatisch“ in der Android-App
All checks were successful
Build and push Docker image / build (push) Successful in 16s
Test / test (push) Successful in 9m7s
Test / e2e (push) Successful in 2m10s
- YouTube antwortet auf Kanal-Feeds derzeit bei etwa zwei von drei Abrufen
  grundlos mit 404 (Kanäle zeigten „lädt nicht: HTTP 404“): ein YouTube-Feed
  wird bis zu 12-mal im Abstand von 0,25 s gefragt. Tests dazu.
- Erscheinungsbild „Automatisch“: in der Android-App folgt die WebView nicht
  dem System, sondern dem (dunklen) App-Theme – die Seite fragt dort
  DioraNative.systemScheme() und hört __dioraSystemScheme bei einem Wechsel.
sw.js: diora-v129.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 08:16:09 +02:00
8f5be379fe Bibliothek neu: Ordner als Filter-Chips überall, Cover-Raster, Cover verschlüsselt und generiert (noch nicht veröffentlicht)
- Ordner wie Tags (docs/library.md): folders.js zeichnet die Chip-Reihe, das
  Blatt „In Ordner …“ (Häkchen, Neuer Ordner) und „Ordner verwalten“
  (umbenennen = zusammenlegen, löschen, Reihenfolge). E-Books: `folders` im
  verschlüsselten Meta, Reihenfolge in verschlüsselten Bibliotheks-Einstellungen
  (/books/prefs/). Podcasts/Hörbücher: PodcastFeed.folders (Migration 0021,
  übernimmt `folder`), /podcasts/folders/ (+rename, +delete). Blogs: Reihenfolge
  (/feeds/folders/order/).
- Bibliothek (53-library.js): Kopf mit Suche, Sortierung, Raster/Liste und +;
  Chips Angefangen · Ungelesen · Gelesen · Ordner; eine „Weiterlesen“-Karte;
  Mehrfachauswahl (lange drücken). E-Ink und diora Reader beginnen als Liste.
- Cover Ende-zu-Ende verschlüsselt (covers.js, /books/<id>/cover/): aus dem
  E-Book (EPUB-Coverbild, PDF Seite 1), 300 px, mit dem Nutzerschlüssel
  verschlüsselt, einmal hochgeladen; sparsam im Leerlauf, nie beim Lesen, nie
  auf E-Ink.
- Bücher ohne Cover bekommen eins (gencover.js): Stichwörter aus Titel, Autor,
  Regal und Ordnern wählen ein Gestaltungskonzept (Konstruktivismus, Geometrie,
  Psyche, Wellen, Karte, Kosmos, Ornament, Botanik, Berge, Zellen, Raster,
  Schatten, Typo, sonst Bauhaus); ein Hash aus Titel und Autor Palette, Aufbau
  und Details – einzigartig, überall gleich, nichts gespeichert.
- Hörbücher: Ordner-Chips statt Kacheln, „In Ordner …“ mit mehreren Ordnern.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 20:44:23 +02:00
d7930fcdb9 Bibliothek ordnen (Navigation Phase 3): Hörbuch-Reihen und -Ordner, Zeitschriften je Heft
All checks were successful
Build and push Docker image / build (push) Successful in 14s
Test / test (push) Successful in 8m58s
Test / e2e (push) Successful in 1m57s
- Hörbücher: Reihen werden aus den Titeln erkannt („Das Kapital – Erster
  Band“, „…: Teil 3“, „… – Folge 12“, „Der Zauberberg. Erster Band“) und als
  Stapel gezeigt, darin die Bände in Reihenfolge. Ordner je Hörbuch
  (PodcastFeed.folder, Migration 0020), im Buch über ⋮ → Ordner zuweisen.
- Zeitschriften: ein Heft wird als Zeitschrift markiert (⋮ → Als Zeitschrift
  führen, im verschlüsselten Meta); unmarkiert entscheidet weiter der Ordner
  „Zeitschriften“. Die Hefte stehen nach Zeitschrift gruppiert, neueste zuerst.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 15:45:14 +02:00
12b651b772 Hörbücher: Kapitel einzeln als (nicht) gehört, Reihenfolge nach Korrekturen
All checks were successful
Build and push Docker image / build (push) Successful in 14s
Test / test (push) Successful in 8m58s
Test / e2e (push) Successful in 1m54s
- Jedes Kapitel hat ✓/○ zum Umschalten. Ein bis zum Ende gehörtes, das wieder
  als nicht gehört gilt, beginnt von vorn (mark-played setzt die Stelle zurück).
- Feeds, die Titel, Datum oder Nummer schon bekannter Folgen korrigieren, werden
  übernommen: davon hängt die Kapitelreihenfolge ab (Das Kapital, Erster Band,
  hatte Daten aus einer älteren Fassung des Feeds). Ein Aktualisieren von Hand
  und das Markieren als Hörbuch lesen den Feed dafür ganz.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 14:08:44 +02:00
5be1b3fbaf Hörbücher: Podcasts als Hörbuch markieren und in „Bücher“ hören
All checks were successful
Build and push Docker image / build (push) Successful in 15s
Test / test (push) Successful in 8m52s
Test / e2e (push) Successful in 1m59s
Ein Abo lässt sich als Hörbuch (oder Hörspiel) markieren (PodcastFeed.kind).
Es verlässt dann die Podcast-Welt: keine „Neu“-Folgen, keine Warteschlangen-
Automatik, nicht in „Folgen“. In der Bibliothek steht es neben den E-Books
(static/js/audiobooks.js, eine Datei für Web, Desktop und Android):
„Weiterhören“, ein Fortschritt pro Buch, die Kapitel in Reihenfolge,
Lesezeichen mit Notiz, Schlummer bis Kapitelende.

Server: /podcasts/audiobooks/ (+<id>/), /podcasts/bookmarks/…, Migration 0019.
Player (podcasts-web.js): podcastPlayBook spielt die Kapitel nacheinander
außerhalb der Warteschlange; ⏭ markiert nicht als gehört. Vertrag:
docs/audiobooks.md. native-podcast.js aus diora-android 7b1d500.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 13:05:00 +02:00
a3673a6042 YouTube: Kanal eines Videos auch über externalChannelId finden
All checks were successful
Test / e2e (push) Successful in 1m53s
Build and push Docker image / build (push) Successful in 15s
Test / test (push) Successful in 8m35s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 11:15:42 +02:00
3e843aea9f Abo umziehen: Feed-Adresse ändern, alles behalten
Some checks failed
Test / e2e (push) Waiting to run
Build and push Docker image / build (push) Successful in 15s
Test / test (push) Has been cancelled
POST /podcasts/feeds/<id>/move/ gibt einem Abo eine neue Adresse und
behält Einstellungen, Folgen, Stellen, Gespielt- und Favoriten-Markierungen
und die Warteschlange. Bei einer YouTube-Adresse werden Folgen, deren Datei
nach ihrem Video benannt ist (podsync-artige Server), zu diesem Video, damit
der Kanal-Feed sie wiederfindet statt sie doppelt anzulegen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 11:11:44 +02:00
cde90bed8e YouTube-Kanäle als Podcasts abonnieren
All checks were successful
Build and push Docker image / build (push) Successful in 15s
Test / test (push) Successful in 8m34s
Test / e2e (push) Successful in 1m52s
Eine YouTube-Adresse (Kanal, Playlist, Video, Feed) lässt sich wie ein
Podcast abonnieren und in der Suche einfügen. Der Server liest dafür nur
die Kanalseite (einmal) und danach den RSS-Feed des Kanals; Ton oder Video
holt er nie: Die Adresse einer Folge ist die Videoseite (watch?v=…), und
jede App sucht sich den reinen Audio-Stream selbst (docs/youtube.md).
Shorts bleiben draußen, Stille-Analyse und ID3-Kapitel gibt es für solche
Folgen nicht.

Der Player im Browser verweist auf die Apps; die Desktop-App fragt ihren
Hauptprozess (yt-dlp) nach dem Stream. Eine Dauer, die der Feed nicht hat,
meldet der Player mit dem Fortschritt (progress/save: duration_seconds).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 10:33:26 +02:00
de1940b4e5 Stille überspringen: Regeln des Pausen-Kürzers, sanftere Übergänge
All checks were successful
Build and push Docker image / build (push) Successful in 15s
Test / test (push) Successful in 8m20s
Test / e2e (push) Successful in 1m53s
Der Server findet die Pausen jetzt nach den Regeln der App (PauseCutter):
10-ms-Fenster, Rauschboden als gleitendes Perzentil, ein Fünftel jeder Pause
bleibt (60–250 ms), halb davor, halb danach. Ältere Ergebnisse werden neu
berechnet (v: 2).

Der Player läuft durch diese Teile höchstens 4× schnell statt 8×: Chromium
schaltet darüber stumm, und weil currentTime in 10–20-ms-Schritten läuft,
landete der Wechsel zurück bei 8× bis zu 200 ms in der Sprache. Pro Pause
wird das Tempo gewählt, das am meisten spart und trotzdem rechtzeitig endet.
Das Tempo des Podcasts setzt die Rate während einer Pause nicht mehr zurück.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 11:18:16 +02:00
08baf970ca Feed-Reader (Server): Nachrichten und Blogs, Volltext, Meldungen bündeln
Neue App feeds: Quellen sind entweder Nachrichten oder Blog.
- Abruf wie bei Podcasts (ETag, Body-Hash, Backoff), refresh_newsfeeds für den Cron
- Anrisstexte: der Artikel wird von der Seite geholt (trafilatura), HTML bereinigt,
  relative Links absolut
- Nachrichten: gleiche Meldung aus mehreren Quellen wird eine Story (tf-idf, je
  Artikel verglichen), Briefing „Seit letztem Mal“ (Top-Themen, dann nach Ressort),
  verfallen nach news_keep_days (Standard 2)
- Blogs: Ungelesen-Liste je Blog, bleiben bis gelesen
- API unter /feeds/: Quellen suchen/abonnieren/OPML, News, Stories, Blogs,
  Artikel, gelesen, merken

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 19:57:23 +02:00
ae72b1d67a Stille-Suche: Folge erst in Python laden, ffmpeg liest die lokale Datei
All checks were successful
Build and push Docker image / build (push) Successful in 13s
Test / test (push) Successful in 7m4s
Die statische ffmpeg-Build stürzt im Container beim Auflösen von Hostnamen ab (SIGSEGV).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 12:45:19 +02:00
c1bbaccccf Stille überspringen im Browser/Desktop (Server findet die Pausen); Ambient-Rekorder im Audio-Thread; sw v86
Some checks failed
Build and push Docker image / build (push) Successful in 52s
Test / test (push) Has been cancelled
- podcasts/silence.py: ffmpeg (imageio-ffmpeg) findet die Pausen einer Folge einmal
  (silencedetect -35 dB, ab 0,4 s), im Hintergrund, höchstens 2 zugleich; gespeichert in
  PodcastEpisode.silences (Migration 0018); GET episodes/<id>/silences/
- podcasts-web.js: skip_silence als Einstellung (Gerät, pro Podcast vom Server), springt
  beim Abspielen über die Pausen (0,15 s bleiben)
- Drift: Ringpuffer für die Körner als AudioWorklet statt ScriptProcessor im Hauptthread
  (gehäufte Aussetzer am Desktop, wenn die Seite kurz stockte)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 12:38:06 +02:00
14720b3669 Feeds abrufen: unveränderte Feeds nicht mehr parsen, ⟳ antwortet nach höchstens 40 s; sw v83
All checks were successful
Build and push Docker image / build (push) Successful in 17s
Test / test (push) Successful in 6m59s
Der stündliche Abruf der 502 Feeds dauerte 114 s, fast nur Parsen: viele Server
ignorieren ETag/Last-Modified und schicken jedes Mal den ganzen Feed (Lage der Nation
8,4 MB = 5,9 s). ⟳ in der App lief damit in ihr 60-s-Zeitlimit und schlug fehl.

- Fingerabdruck des Feed-Inhalts (ohne lastBuildDate/Kommentare, http_body_hash,
  Migration 0017): derselbe Inhalt zählt als unverändert und wird nicht geparst
- _refresh_feeds: ein Pool für alle Feeds (16 parallel), jeder Feed wird geschrieben,
  sobald er da ist, optionales Zeitlimit
- refresh-all wartet höchstens 40 s (done/left in der Antwort); der Browser-Host
  holt den Stand dann eine Minute später noch einmal, und alle 15 Minuten

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 10:22:01 +02:00
f12451f30e Sleep-Timer: eigene Zeit, Nachtfenster, auch fürs Radio, von anderen Geräten setzen; sw v70
All checks were successful
Build and push Docker image / build (push) Successful in 15s
Test / test (push) Successful in 6m54s
- Player-Sync: Aktion 'sleep' (Minuten, -n Folgen, 0 aus); Reports schicken
  sleep {left_ms, episodes} mit, der State zeigt ihn (Migration 0016)
- Browser-Host: Nachtfenster sleep_from/sleep_to, Automatik auch beim
  Radiostart (auch in diora desktop), Timer des anderen Geräts zählt mit,
  ein Timer, der stundenlang nicht lief, verfällt
- Eingabefenster (customPrompt) liegt über Player und Menüs

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 12:59:16 +02:00
985e635f9a Podcasts: "Folge löschen" (episodes/discard/, discarded in state/), Folgen-Seite, sw v69
All checks were successful
Build and push Docker image / build (push) Successful in 13s
Test / test (push) Successful in 6m52s
- EpisodeProgress.discarded_at (Migration 0015); POST /podcasts/episodes/discard/
  {"episode_ids": [...]}: aus der Warteschlange (_dequeue, auch von queue/remove benutzt)
  und aus "Neu" (dismissed), discarded_at = jetzt; gehört, Stelle, Stern und updated_at
  bleiben; fremde Folgen werden ausgelassen (count)
- state/ liefert "discarded": [{id, at}] der letzten 30 Tage: jedes Gerät löscht seine
  ältere Datei; wieder eingereiht (_enqueue) wird discarded_at gelöscht
- Browser-Host: podcastDiscard (pausiert geladene Folge verlässt den Player, laufende spielt
  zu Ende), podcastPlayEpisodeAt (Kapitel auf der Folgen-Seite)
- static/podcasts aus diora-android ee1a910 (Folgen-Seite, Folge löschen, Datei löschen,
  Wischtexte und Icons, Ziehgriff)
- Tests: DiscardTests

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 20:27:24 +02:00
e097e13a11 Podcast-Oberfläche aus diora-android 339de8f (Podcast-Seite mit ⚙, Wischgesten, Schrift, Farben, Startseite); „Neu“ zählt alle neuen Folgen, sw v66
- state/ liefert inbox_total (alle neuen Folgen) und inbox_limit neben der
  gekürzten Liste; Test dazu.
- Browser: Startseite (nui_start_page) beim Öffnen, „Rückgängig“-Leiste über
  der Radioleiste, helle Ansicht für die neuen Teile.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 19:30:03 +02:00
54fbd58293 Podcast-Routine aus AntennaPod: Abspiel-Einstellungen fürs Konto, Listen und Stille pro Podcast; Oberfläche aus diora-android 304abf0, sw v65
All checks were successful
Build and push Docker image / build (push) Successful in 16s
Test / test (push) Successful in 6m39s
- PodcastSettings (Migration 0014): skip_back / skip_forward (5–60 s),
  smart_played, speed_presets (0,5–3,5×), keep_skipped, delete_dequeue,
  mobile_downloads, mobile_streaming (allow/ask/never), times_by_speed;
  die ersten vier sind anfangs leer (null) und werden mit POST
  settings/ {"initial": {...}} nur dort gesetzt, wo noch nichts steht
- GET/POST /podcasts/settings/ und state.settings mit allen Feldern,
  geprüft, nichts ändert sich bei einem ungültigen Wert
- PodcastFeed: episode_sort, episode_filter, skip_silence in
  feeds/<id>/settings/ und den Feeds des States
- Tempo pro Podcast bis 3,5× (Server und AntennaPod-Import)
- AntennaPod-Import: Sortierung, Filter und Stille pro Podcast, soweit
  diora sie zeigen kann; nicht darstellbare werden gezählt
- Browser-Host: Sprungweiten, "gehört" und keep_skipped aus dem Konto,
  Tempo bis 3,5×, neue Einstellungen werden gespeichert

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 18:46:55 +02:00
3577fca9ba Merge branch 'wt-notify'
All checks were successful
Build and push Docker image / build (push) Successful in 16s
Test / test (push) Successful in 6m28s
# Conflicts:
#	static/js/sw.js
#	static/podcasts/native-podcast.js
2026-10-02 18:03:07 +02:00
69b22cf2e3 Podcast-Listen: Zähler pro Abo, kombinierbare Folgen-Filter, Suche in Shownotes, mehrere Favoriten; Oberfläche aus diora-android, sw v62
- feeds/ und state/: pro Abo episode_count, unplayed_count, new_count (wie
  „Neu“), last_played_at und link – zwei Abfragen, egal wie viele Abos.
- feeds/<id>/episodes/: favorite pro Folge; description, link, rss_url und
  last_refreshed_at des Podcasts für die Infoseite.
- episodes/: filter mit Komma kombinierbar (alle müssen passen), neuer Filter
  queued, ids= (die auf einem Gerät geladenen), sort= (new, old, long, short,
  title, played), notes=1 sucht q auch in den Shownotes (Titeltreffer zuerst).
- POST episodes/favorite/: mehrere Folgen auf einmal (ent)favorisieren.
- Tests dafür; Podcast-Oberfläche aus diora-android übernommen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 16:23:00 +02:00
371c9b9222 Podcast-Einstellung "notify": über neue Folgen benachrichtigen
PodcastFeed.notify (Standard aus) wie auto_download: in feeds/ und
state/, änderbar über feeds/<id>/settings/ (nur true/false). Die Geräte
melden damit neue Folgen dieses Podcasts. Der AntennaPod-Import übernimmt
Feeds.episode_notification (an -> notify an; aus, AntennaPods Standard,
lässt dioras Einstellung stehen; ältere Backups ohne die Spalte: aus).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 15:45:51 +02:00
3eb6a27ec3 Player-Sync: ein Player für alle Geräte (wie Spotify Connect)
Server: PlayerState und PlayerDevice (Migration 0012), /podcasts/player/
(Stand), report/ (das Gerät, das zu spielen anfängt, wird das aktive und
bleibt es auch pausiert, solange es online ist), command/ (Fernbedienung,
transfer auf ein anderes Gerät) und stream/ (Server-Sent Events, zugleich
die Anwesenheit der Geräte). seq steigt in einem UPDATE und nie zurück.

Browser: podcasts-web.js meldet Abspielen, Pause, Springen, Folgen- und
Senderwechsel (alle 10 s beim Spielen), hört den Stream, hört auf, wenn ein
anderes Gerät startet, führt Befehle aus, zeigt das andere Gerät als remote
und übernimmt, was ein verschwundenes Gerät zuletzt hatte. Eine pausierte
Folge folgt einem neueren Stand von anderswo; im Vordergrund wird nach
15 s neu geholt. Podcast-Seite neu kopiert, sw v58.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 15:18:38 +02:00
649cf0f340 AntennaPod-Import: --replace für einen Neuanfang; Podcast-Oberfläche aus diora-android 6c2b071
All checks were successful
Build and push Docker image / build (push) Successful in 15s
Test / test (push) Successful in 5m19s
--replace bestellt Podcasts ab, die das Backup nicht hat, behält nur
AntennaPods Warteschlange und lässt AntennaPod auch über spätere
Änderungen in diora gewinnen. Die Oberfläche bringt den gemeinsamen
Player und die Download-Übersicht (nur in der App sichtbar). sw v54.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 12:44:49 +02:00
07aed26864 AntennaPod-Import: „Neu“ wie in AntennaPod
All checks were successful
Build and push Docker image / build (push) Successful in 16s
Test / test (push) Successful in 5m21s
Folgen archivierter Podcasts (neue Folgen ignorieren), die AntennaPod nie
abgerufen hat, verlassen „Neu“, sofern sie vor dem Backup erschienen
sind; --quiet-diora-only macht das auch für Abos, die nur diora hat.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 12:38:41 +02:00
561776556f AntennaPod-Import: --no-downloads bietet die geladenen Folgen nicht an
All checks were successful
Build and push Docker image / build (push) Successful in 14s
Test / test (push) Successful in 5m16s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 12:14:03 +02:00
8ba82a0e55 Merge branch 'wt-impdl' 2026-10-02 12:02:20 +02:00
fdea47baef AntennaPod-Import: geladene Folgen der App zum Herunterladen anbieten
Folgen, die in AntennaPod auf dem Handy lagen (FeedMedia.downloaded > 0:
Download-Zeitpunkt, bei älteren Versionen 1), merkt sich der Import in
PodcastSettings.import_downloads (episode_id + Größe, neueste zuerst).
Fehlen sie in diora noch, werden sie angelegt (nicht neu). Ein echter Lauf
ersetzt die Liste, der Probelauf schreibt nichts; die Zusammenfassung hat
eine Zeile „Downloads“.

GET /podcasts/import-downloads/ liefert die Folgen wie Einträge von „Neu“
plus size, POST …/clear/ leert die Liste, state/ meldet Anzahl und Bytes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 12:01:42 +02:00
5b2d25e2b2 Video-Podcasts: Folgen mit Video-Enclosure werden aufgenommen (abgespielt wird der Ton)
Audio bleibt bevorzugt, wenn eine Folge beides hat; Endungen werden am
Pfad erkannt (Query-Strings stören nicht mehr). refresh_feeds --full
liest alle Feeds ohne ETag neu ein; dabei gefundene Folgen, die vor dem
letzten Abruf erschienen sind, kommen in die Liste, nicht nach „Neu“.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 11:58:24 +02:00
5a2841e7a6 AntennaPod-Import und automatischer Download pro Podcast
- Neuer Befehl `manage.py import_antennapod <backup.db> --user <name> [--dry-run]
  [--global-auto-download on|off] [--backup-date ...]`: übernimmt aus einer
  AntennaPod-Datenbanksicherung die Abos (Abgleich über normalisierte URL,
  fehlende werden angelegt, lokale übersprungen), die Podcast-Einstellungen
  (Tempo, Intro/Outro, Lautstärke, Filter mit Phrasen in Anführungszeichen,
  Mindestlänge, neue Folgen, „nicht aktualisieren“ -> ignorieren, automatischer
  Download), den Zustand jeder Folge mit Zustand (gehört, neu, Position,
  Favorit, Zeitpunkt), die Warteschlange (AntennaPods Reihenfolge zuerst) und
  nimmt bekannte, dort nicht neue Folgen aus „Neu“. Neuere Änderungen in diora
  bleiben, Favoriten werden vereinigt. Alles in einer Transaktion; ein zweiter
  Lauf ändert nichts mehr.
- PodcastFeed.auto_download (Einstellung pro Podcast, in feeds/, state/ und
  feeds/<id>/settings/) und PodcastSettings.auto_download_default (in settings/
  und state/), mit dem neue Abos (Hinzufügen, OPML, gpodder, Import) starten.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 11:41:04 +02:00
578b0e478b Merge: Kapitel aus der Audiodatei (ID3) und Transkripte
Some checks failed
Build and push Docker image / build (push) Successful in 15s
Test / test (push) Failing after 30s
Migration als 0009 hinter 0008_feed_http_cache eingereiht; Tests an die
neue _fetch_feed-Schnittstelle (FeedFetch) angepasst. 202 Tests grün.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 09:59:33 +02:00
38d7f6c643 Podcasts: Kapitel aus dem ID3-Tag der Audiodatei, Transkripte
- Ohne Kapitel im Feed (kein PSC, keine chapters_url oder dort nichts)
  liest die Folgen-Detailansicht einmalig den ID3v2-Tag am Anfang der
  Audiodatei (Range-Anfrage, höchstens 4 MB, Weiterleitungen mit
  SSRF-Prüfung) und übernimmt dessen CHAP-Frames (v2.3/v2.4,
  Unsynchronisation, erweiterter Header, Synchsafe-Größen, CTOC-Reihenfolge).
  Eigener Parser nach der ID3-Spezifikation; Ergebnis wird wie die
  JSON-Kapitel zwischengespeichert, vorübergehende Fehler nicht.
- Neu: GET /podcasts/episodes/<id>/transcript/ holt das beste Transkript
  (JSON vor VTT vor SRT, Deutsch bzw. ohne Sprachangabe vor Englisch),
  zerlegt es in Abschnitte {start, end, speaker, text}, fasst kurze
  Schnipsel desselben Sprechers bis zum Satzende bzw. ~30 s zusammen und
  speichert das Ergebnis (eigene Tabelle EpisodeTranscript). HTML: 404
  {"error": "unsupported"}.
- Folgen-Detail liefert zusätzlich has_transcript.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 09:54:03 +02:00
9b0848c30c Merge: Podcast-Einstellungen pro Podcast und robusteres Feed-Laden
Migrationen hintereinander statt nebeneinander: 0007_feed_settings,
dann 0008_feed_http_cache. Tests der Podcast-Regeln an die neue
_fetch_feed-Schnittstelle (FeedFetch) angepasst; Feeds-Liste trägt
Einstellungen und Fehlerfelder.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 09:52:31 +02:00
7fdcbb4a3d Podcasts: Einstellungen pro Podcast auf dem Server, Regeln für neue Folgen, Warteschlange sortiert halten
- PodcastFeed (Migration 0007_feed_settings): playback_speed (null = Tempo
  des Geräts), skip_intro, skip_outro, volume (0.2/0.5/1.0/1.5/2.0/2.5),
  new_episodes (inbox/queue/ignore), include_words, exclude_words,
  min_duration. Abos mit auto_queue bekommen new_episodes='queue';
  auto_queue bleibt gleich (new_episodes == 'queue'), auch über
  set-auto-queue/ und beim Speichern im Code.
- POST feeds/<id>/settings/: beliebige Teilmenge, prüft jeden Wert (400 mit
  Feldname, dann wird nichts geändert), nur eigene Abos (sonst 404);
  Antwort sind alle Einstellungen des Abos. feeds/ und state/ liefern sie
  für jedes Abo mit.
- Neue Folgen beim Aktualisieren: was am Filter scheitert (keins der
  Wörter, ein Ausschlusswort im Titel, kürzer als die Mindestlänge) und
  bei 'ignore' alles verlässt sofort „Neu“ (nicht gehört); 'queue' reiht
  an der auto_queue_position ein; 'inbox' wie bisher. Beim ersten Abruf
  zählt weiter nur die neueste Folge.
- PodcastSettings.keep_sorted ('', new, old, short, long, feed): danach
  wird die ganze Warteschlange nach jedem Einreihen, jedem Umsortieren und
  beim Einschalten neu sortiert; settings/ und state/ liefern den Wert.
- Tests für alles davon.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 09:47:49 +02:00
bbe5f6c5c2 Podcasts: Feeds bedingt abrufen, Fehler speichern, Umzüge folgen, Backoff
- Bedingter Abruf: ETag/Last-Modified werden nach einer 200 gespeichert und
  als If-None-Match/If-Modified-Since mitgeschickt. Bei 304 wird nichts
  geparst und keine Folge geschrieben, nur last_refreshed_at aktualisiert und
  der Fehler gelöscht. Teilen sich mehrere Abos eine URL, gehen die Validatoren
  nur mit, wenn alle schon einmal abgerufen wurden und dieselben haben.
- Fehler (Netz, HTTP >= 400, kein Feed) landen in last_error, last_error_at
  und error_count; ein erfolgreicher Abruf setzt sie zurück. Feed-Liste und
  /podcasts/state/ liefern die drei Felder mit.
- Dauerhafte Weiterleitungen (301/308 am Anfang der Kette) ändern rss_url,
  außer der Nutzer hat die neue URL schon abonniert; 302/307 ändern nichts.
- Backoff im stündlichen Job: ab 3 Fehlern in Folge nur alle 2, 4, 8, 16,
  höchstens 24 Stunden. „Alle aktualisieren“ und --feed-id versuchen es
  trotzdem (max_age gilt weiter).
- _fetch_feed liefert jetzt ein FeedFetch-Objekt bzw. wirft FeedFetchError.
- Migration 0007_feed_http_cache.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 09:45:38 +02:00
f9b3a0ce04 Podcasts: Inbox und Gehört wie in AntennaPod
Some checks failed
Build and push Docker image / build (push) Successful in 14s
Test / test (push) Failing after 28s
- Eingereihte und angefangene Folgen verlassen die Inbox (ausgeblendet,
  nicht gehört), statt dort neben der Warteschlange zu stehen.
- Gehört heißt: höchstens 30 s vor dem Ende, wie in den Apps, statt bei
  90 % (bei 2 h waren das 12 Minuten vor Schluss). Gilt auch für den
  gPodder-Sync.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 08:26:28 +02:00