Commit graph

27 commits

Author SHA1 Message Date
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
f30bf2fcdc Podcasts: Favoriten, Folgenliste mit Filtern und Verlauf, Hörstatistik, OPML-Export
Some checks failed
Build and push Docker image / build (push) Successful in 15s
Test / test (push) Failing after 27s
- EpisodeProgress.favorite (Migration 0006) und POST episodes/<id>/favorite/
  (setzen oder umschalten; ändert updated_at nicht, damit Verlauf und
  Statistik nicht verfälscht werden)
- GET episodes/: alle Folgen aller Abos, Filter all/unplayed/in_progress/
  played/favorites/history, feed_id, q, offset/limit/total; zwei Abfragen
  pro Seite, ohne Shownotes
- Folgendetails liefern zusätzlich favorite und last_played_at
- GET stats/: Hörzeit gesamt und pro Podcast, optional nur die letzten N Tage
- GET feeds/export.opml: Abos als OPML 2.0 (lässt sich wieder importieren)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 08:12:27 +02:00
fd0a103fb2 Podcasts: state/ ohne Shownotes in der Inbox
Some checks failed
Build and push Docker image / build (push) Successful in 16s
Test / test (push) Failing after 28s
Seit die Shownotes content:encoded nehmen (bis 50 KB pro Folge), würde
die eine Anfrage der Apps mit 200 Inbox-Folgen sehr groß. state/ lässt
sie weg; die Apps holen sie pro Folge über episodes/<id>/. inbox/ für
das Web liefert sie weiter.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 08:01:34 +02:00
f4b8bcc891 Podcasts: Kapitel, Shownotes und Transkripte für die Apps
- Feeds liefern jetzt Podlove-Kapitel (psc:chapters), Podcasting-2.0-Kapitel
  (podcast:chapters, JSON-URL) und Transkripte (podcast:transcript, auch
  mehrere); als Beschreibung wird content:encoded bevorzugt.
- Bekannte Folgen werden beim Aktualisieren nachgezogen, aber nur die Zeilen,
  die sich wirklich geändert haben.
- Neuer Endpunkt GET /podcasts/episodes/<id>/ mit bereinigten Shownotes,
  Zeitmarken, Kapiteln, Transkripten und Fortschritt. JSON-Kapitel werden
  einmal serverseitig geholt (nur öffentliche Adressen, max. 1 MB) und an
  der Folge gespeichert.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 07:56:44 +02:00
a1e17081c5 Podcasts: neues Abo bringt nur die neueste Folge in die Inbox
All checks were successful
Build and push Docker image / build (push) Successful in 16s
Test / test (push) Successful in 2m3s
Beim ersten Abruf eines Feeds (Abonnieren, OPML-Import, gpodder) kam der
ganze Rückkatalog als „neu“ in die Inbox (bei Lage der Nation 200
Folgen). Jetzt zählt nur die neueste; die älteren sind ausgeblendet,
nicht gehört, und stehen weiter in der Folgenliste des Podcasts. Auch
automatisches Einreihen nimmt beim ersten Abruf nur diese eine.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 07:48:45 +02:00
176771a407 Podcasts: Position in der Warteschlange einstellbar; Feeds nur noch auf dem Server holen (SW v51)
All checks were successful
Build and push Docker image / build (push) Successful in 19s
Test / test (push) Successful in 2m0s
Warteschlange
- PodcastSettings: wohin neue Folgen kommen, getrennt für Hinzufügen von
  Hand (auch beim Abspielen einer Folge außerhalb der Warteschlange) und
  für automatisch eingereihte neue Folgen: ans Ende, an den Anfang oder
  nach die laufende Folge. Voreinstellung wie bisher (Hand: Ende,
  automatisch: Anfang). GET/POST /podcasts/settings/.
- queue/add nimmt auch mehrere Folgen (episode_ids), eine Position
  (where) und die laufende Folge (current_episode_id); ohne Angabe gilt
  die zuletzt angehörte, nicht fertige Folge als laufende.
- Positionen werden lückenlos neu durchnummeriert.

Feeds
- POST /podcasts/feeds/refresh-all/ aktualisiert alle Feeds des Nutzers
  auf dem Server: Downloads parallel, jede Feed-Adresse nur einmal (auch
  über Nutzer hinweg), mit Timeout; kürzlich aktualisierte werden
  übersprungen. Der stündliche Cron-Job nutzt dasselbe.
- GET /podcasts/state/ liefert Abos, Warteschlange, Inbox und
  Einstellungen in einer Antwort (für die Apps).
- Web: „↻ All“ ist eine Anfrage statt einer pro Feed; die stündliche
  Schleife im Browser lädt nur noch die Listen neu, statt jeden Feed
  selbst anzustoßen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 07:26:16 +02:00
marwin
e9c5b8058b Centralize remaining magic numbers in settings.py
All checks were successful
Build and push Docker image / build (push) Successful in 13s
Test / test (push) Successful in 15s
- HIGHLIGHTS_MAX_BYTES, BOOKMARKS_MAX_BYTES: books size limits now in settings
- PODCAST_INBOX_PAGE_SIZE: shared between podcasts/views.py and app.js via DIORA_CONFIG
- VOLUME_DEFAULT: radio stream player default volume
- ITUNES_TIMEOUT: unified iTunes API timeout (was 5s vs 6s inconsistency)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 21:10:14 +02:00
Marwin Schulz
f049c6ae66 Inbox/queue: clickable feed titles and episode titles
All checks were successful
Build and push Docker image / build (push) Successful in 12s
Test / test (push) Successful in 14s
- Feed title in inbox and queue is now a link that opens the feed's episode list
- Episode title in inbox is now clickable and opens the show notes sidebar
- Backend: include description in inbox API response so sidebar has content

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 09:12:36 +01:00
Marwin Schulz
afcbe087bb Replace 'recently refreshed' sort with 'most recent episode' in feed list
All checks were successful
Build and push Docker image / build (push) Successful in 12s
Test / test (push) Successful in 15s
Annotates feed queryset with Max(episodes__pub_date) so feeds are sorted
by when their latest episode was published, not when the feed was last fetched.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 09:07:01 +01:00
Marwin Schulz
92801c9bbf Add podcast enhancements: AntennaPod parity features + inbox management
All checks were successful
Build and push Docker image / build (push) Successful in 13s
Test / test (push) Successful in 16s
- Auto-play next episode from queue when current episode ends
- Sleep timer (N minutes or end-of-episode) with countdown in button
- In-feed episode filter (client-side search)
- Auto-queue new episodes per feed (⚡Q toggle, inserts at top of queue)
- More playback speeds: 1¾× and 2½× added
- Progress bars + structured meta line in all episode list views (feed, inbox, queue)
- Queue drag-and-drop reorder
- Feed list search filter and sort options (A–Z, Z–A, recently added/refreshed)
- DB migration: PodcastFeed.auto_queue, EpisodeProgress.dismissed
- Inbox: dismiss episodes without marking played, checkboxes for multi-select,
  bulk actions (add to queue, mark played, download, dismiss), load-more pagination
- Refresh button in single feed view header
- Hourly background refresh of all subscribed feeds
- Full Media Session API for radio and podcast: Windows taskbar thumbnail buttons
  (play/pause/stop/next/seek) now work correctly for both modes
- Playing an episode auto-adds it to the queue if not already there

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 08:55:11 +01:00
Marwin Schulz
2bd83f6315 Add podcast feature with feed management, Docker cron, and ebook reader assets
All checks were successful
Build and push Docker image / build (push) Successful in 12s
Test / test (push) Successful in 13s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-19 13:39:59 +01:00