Jellyfin-Anbindung: Bestand und Sichtungen abgleichen #1
Notifications
Total Time Spent: 18 minutes
florian.wilde
18 minutes
No due date set.
Dependencies
No dependencies set.
Reference: florian.wilde/flimmer#1
Reference in New Issue
Block a user
Worum es geht
Wer seine Mediathek selbst hostet, pflegt Bestand und Gesehen-Status meist schon in Jellyfin. Beides doppelt zu führen macht auf Dauer niemand — die Stände laufen auseinander. Flimmer kennt seit 0.7.0 „hab ich", weiß aber nicht, was tatsächlich auf dem Server liegt und was dort schon gelaufen ist.
Ziel: Jellyfin einmal verbinden, danach hält sich Flimmer von selbst auf Stand.
Warum das sauber geht
Jellyfin liefert zu jedem Item die
ProviderIdsmit — darunter die TMDB-ID. Das ist genau der Schlüssel, auf dem Flimmers Katalog ohnehin aufbaut. Es braucht also keine unscharfe Suche über Titel und Jahr:ProviderIds.TmdbTitle.tmdbId(TypMOVIE)ProviderIds.TmdbTitle.tmdbId(TypSERIES)ParentIndexNumber/IndexNumberEpisode.seasonNumber/episodeNumberUserData.Played,PlayCount,LastPlayedDateWatchGelesen wird über
GET /Items?Recursive=true&IncludeItemTypes=Movie,Episode&Fields=ProviderIds&enableUserData=true&userId=…Zwei Haken, vorab geprüft
UserDatafehlt, wenn man nur einen API-Key benutzt (jellyfin#11408). Man bekommt den Bestand, aber ohne Gesehen-Status — und zwar ohne Fehlermeldung. Deshalb: Anmeldung überAuthenticateByNamemit echtem Zugangstoken statt eines API-Keys, und die Einrichtung muss aktiv prüfen, dassUserDataankommt. Sonst wundert man sich über einen stillen Leerlauf.Der Endpunkt zum Zurückschreiben ist umgezogen — von
/Users/{userId}/PlayedItems/{itemId}auf/UserPlayedItems/{itemId}?userId=, mit bekannten Problemen bei API-Key-Anmeldung (jellyfin#11501). Deshalb liegt das Zurückschreiben in einer eigenen, abschaltbaren Stufe mit Versionsweiche.Grenze, die man kennen muss
Jellyfin kennt
PlayCountundLastPlayedDate, aber keine Liste einzelner Sichtungen. Rewatch-Durchläufe lassen sich daraus nicht rekonstruieren — übernommen wird der letzte Termin. Ein dreimal geschauter Film erscheint in Flimmers Statistik also mit einem Termin, nicht mit dreien. Das ist eine Eigenschaft der Quelle, kein Umsetzungsfehler; es gehört in die Anleitung.Ausbaustufen
Stufe 1 — Bestand übernehmen
owned = true,ownedSource = "Jellyfin"Stufe 2 — Sichtungen übernehmen
Watch-Einträgen mitLastPlayedDate(userId, episodeId, run)verhindertlastSyncAtStufe 3 — Zurückschreiben (abschaltbar, standardmäßig aus)
Datenmodell
Dazu auf
Watchein Feldsource(FLIMMER|JELLYFIN) — sonst lässt sich später nicht mehr sagen, was woher kam, und ein Zurücknehmen des Abgleichs wird unmöglich.MediaServerItemist kein Luxus: es macht Folgeläufe billig und ist die Voraussetzung fürs Zurückschreiben.Sicherheit
Der Zugangstoken liegt in der Datenbank und muss mit
AUTH_SECRETverschlüsselt werden — im Klartext hat er dort nichts verloren. Selbstsignierte Zertifikate und Adressen im lokalen Netz müssen funktionieren.Abnahme
UserData, sagt die Oberfläche das deutlich statt stumm nichts zu tundocs/ANLEITUNG.mdAufwand
Stufe 1 und 2 zusammen etwa im Umfang der TMDB-Anbindung: Client, Zuordnung, Job, Einstellungsseite, Tests. Stufe 3 ist klein, braucht aber die Versionsweiche.
Emby spricht praktisch dieselbe API — deshalb ist
kindim Datenmodell schon vorgesehen. Plex hätte eine andere Schnittstelle, aber dasselbe Prinzip über die Provider-IDs.Angelegt auf ausdrücklichen Wunsch direkt in Gitea. Nicht mit einem Nebula-Task verknüpft — der Status müsste dort von Hand nachgezogen werden.
🤖 Claude Code hat mit der Umsetzung begonnen.
feat/1-jellyfin-anbindungMediaServerConnection,MediaServerItem,Watch.source), Token mitAUTH_SECRETverschlüsseltAuthenticateByName; der Verbindungstest prüft ausdrücklich, obUserDatamitkommt — genau der stille Leerlauf aus Haken 1Zum Umfang: Ich setze Stufe 1 und 2 um. Die Abnahmeliste deckt genau diese ab — Stufe 3 (Zurückschreiben nach Jellyfin) kommt dort nicht vor, ist laut Beschreibung standardmäßig aus und bräuchte eine Versionsweiche, die ich ohne echten Server nicht verlässlich prüfen kann. Das Feld
writeBacklege ich wie beschrieben an, die Logik dahinter bleibt aber leer. Sag Bescheid, wenn Stufe 3 mit hineinsoll — dann als eigener Durchgang, wenn du eine Instanz zum Testen bereitstellen kannst.Zeiterfassung läuft.
✅ Umsetzung abgeschlossen — bereit zum Testen.
Was geändert wurde
MediaServerConnection,MediaServerItem,Watch.source, samt Migrationsrc/lib/jellyfin.ts(Client) undsrc/lib/mediaserver.ts(Zuordnung, Bestand, Sichtungen, Bericht)src/lib/secrets.ts: Token mit AES-256-GCM verschlüsselt, Schlüssel ausAUTH_SECRETdocs/ANLEITUNG.md: eigener Abschnitt samt der Rewatch-GrenzeGeänderte Dateien
prisma/schema.prisma,prisma/migrations/20260902070826_medienserver/src/lib/jellyfin.ts,src/lib/mediaserver.ts,src/lib/secrets.ts,src/lib/sync.tssrc/components/settings/MediaServer.tsx,src/app/einstellungen/page.tsx,src/app/actions/settings.tstests/fixtures/jellyfin.ts,tests/mediaserver.test.ts,docs/ANLEITUNG.mdZum Umfang: Stufe 1 und 2 sind drin, Stufe 3 (Zurückschreiben) nicht — sie steht nicht in der Abnahmeliste und bräuchte eine Versionsweiche, die ohne echten Server nicht verlässlich zu prüfen ist.
writeBackist als Feld angelegt.Geprüft
Elf Tests gegen nachgebildete Antworten; 78 Tests grün, Typecheck sauber. Zusätzlich der ganze Klickweg gegen einen echten HTTP-Server im Produktionsbau:
UserDataEin Zwischenergebnis war lehrreich: der erste Lauf gegen echte Seed-Daten ergab 0 Sichtungen. Kein Fehler, sondern korrektes Entdoppeln — die Episoden waren im Seed längst abgehakt. Erst mit offenen Inhalten zeigte sich der Import.
So testest du es
git switch feat/1-jellyfin-anbindungnpm run db:migrate— es kommt eine neue Migrationnpm run dev, dann Einstellungen → MedienserverAufgewendete Zeit: 00:19
Pull Request: #13
Nach Freigabe:
/issue-freigabe 1🚀 Freigegeben und nach
mainübernommen.2deb7e1Vor dem Merge noch einmal geprüft: Typecheck sauber, 78 Tests grün.
⚠️ Beim Aktualisieren der Instanz kommt eine neue Migration mit (
20260902070826_medienserver). Zusammen mit der Passkey-Migration sind es damit zwei, die beim nächsten Update eingespielt werden.Stufe 3 (Zurückschreiben nach Jellyfin) ist nicht Teil dieser Umsetzung — sie stand nicht in der Abnahmeliste. Das Feld
writeBackliegt vorbereitet im Datenmodell.Issue wird geschlossen.