From bfe263d49556651aa7886aead45685524c568620 Mon Sep 17 00:00:00 2001 From: Scarriffle Date: Mon, 12 Oct 2026 20:49:37 +0200 Subject: [PATCH] Vierundzwanzig Bilder, und der Weg dorthin fehlte sowieso MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Zwei Dinge, die zusammengehören. **Den Bilderdialog gab es nur an der Kachel.** Wer auf der Seite eines Films stand und sah, dass das Plakat falsch ist, stand genau dort, wo er nichts dagegen tun konnte — der Weg führte zurück in die Liste und über das Dreipunktmenü. Jetzt steht „Bearbeiten" in der Knopfreihe der Detailseite, bei Filmen wie bei Serien, und nur im Kopf: dieselbe Reihe steht auch über jeder Fassung und jeder Folge. **Und es waren vierundzwanzig, weil dort vierundzwanzig stand.** Bei „365 Tage" hat TMDB neunundsechzig Plakate. Dazu ein zweiter, leiserer Filter: gefragt wurden nur drei Sprachen. Fürs automatische Holen ist das richtig — dort wird eines genommen, und es soll passen. Wer von Hand wählt, will sehen, was es gibt; sortiert wird ohnehin nach Sprache, also steht das Passende weiterhin oben. Co-Authored-By: Claude Opus 5 --- server/src/Flaro.Metadata/TmdbClient.cs | 22 ++++++++-- .../Flaro.Metadata.Tests/TmdbClientTests.cs | 44 +++++++++++++++++++ web/app/src/views/detail.ts | 35 +++++++++++++-- 3 files changed, 94 insertions(+), 7 deletions(-) diff --git a/server/src/Flaro.Metadata/TmdbClient.cs b/server/src/Flaro.Metadata/TmdbClient.cs index a599888..1f63f77 100644 --- a/server/src/Flaro.Metadata/TmdbClient.cs +++ b/server/src/Flaro.Metadata/TmdbClient.cs @@ -420,6 +420,9 @@ public sealed class TmdbClient( /// Schriftzug weggeworfen. Die Auswahl kostet also keine zusätzliche /// Anfrage. /// + /// Wie viele Bilder je Art höchstens zur Wahl stehen. + private const int HöchstensBilderJeArt = 200; + public async Task> BilderAsync( int providerId, bool serie, CancellationToken ct = default) { @@ -428,9 +431,16 @@ public sealed class TmdbClient( var sprache = Sprachkürzel(einstellung.Language); var weg = serie ? "tv" : "movie"; + + // Ohne Sprachfilter. Hier stand + // include_image_language={sprache},en,null — drei von + // vierzig Sprachen, und alles andere kam gar nicht erst an. Für das + // automatische Holen ist das richtig: dort wird eines genommen, und + // es soll das passende sein. Hier wählt ein Mensch, und der will + // sehen, was es gibt. Die Reihenfolge sortiert ohnehin nach + // Sprache, also steht das Passende weiterhin oben. var antwort = await HolenAsync( - $"{weg}/{providerId}/images?include_image_language={Uri.EscapeDataString(sprache)},en,null", - einstellung, ct); + $"{weg}/{providerId}/images", einstellung, ct); if (antwort is null) return []; @@ -448,7 +458,13 @@ public sealed class TmdbClient( foreach (var b in (bilder ?? []).Where(b => !string.IsNullOrWhiteSpace(b.FilePath)) .OrderBy(b => Sprachrang(b.Language, sprache)) .ThenByDescending(b => b.VoteAverage ?? 0) - .Take(24)) + // Nicht vierundzwanzig. Bei „365 Tage" hat + // TMDB neunundsechzig Plakate, und die Liste war + // nach vierundzwanzig zu Ende — ohne dass irgendwo + // stünde, dass da noch mehr ist. Die Grenze bleibt + // als Schutz vor einer Seite mit tausend Bildern, + // liegt aber dort, wo sie niemanden mehr behindert. + .Take(HöchstensBilderJeArt)) { liste.Add(new Bildkandidat( art, Bild(b.FilePath, groesse)!.Url, diff --git a/server/tests/Flaro.Metadata.Tests/TmdbClientTests.cs b/server/tests/Flaro.Metadata.Tests/TmdbClientTests.cs index ad6de17..37498a4 100644 --- a/server/tests/Flaro.Metadata.Tests/TmdbClientTests.cs +++ b/server/tests/Flaro.Metadata.Tests/TmdbClientTests.cs @@ -28,6 +28,50 @@ public class TmdbClientTests Assert.That(attrappe.Aufrufe, Is.Empty, "Ohne Schlüssel darf keine Anfrage hinausgehen"); } + /// Die Bilderliste für die Auswahl von Hand. + /// + /// + /// Zwei Grenzen, die beide störten. Die Anfrage holte nur drei + /// Sprachen, und gezählt wurde bei vierundzwanzig je Art zu Ende — bei + /// „365 Tage" hat TMDB neunundsechzig Plakate, und die fehlenden + /// fünfundvierzig sah niemand. Wer von Hand wählt, soll sehen, was es + /// gibt. + /// + [Test] + public async Task Die_Bilderliste_ist_nicht_bei_vierundzwanzig_zu_Ende() + { + var plakate = string.Join(",", Enumerable.Range(0, 69) + .Select(i => $$"""{"file_path":"/p{{i}}.jpg","iso_639_1":"en","vote_average":1.0}""")); + var attrappe = Attrappe.MitJson($$"""{"posters":[{{plakate}}],"backdrops":[],"logos":[]}"""); + + var bilder = await Client(attrappe).BilderAsync(603, serie: false); + + Assert.Multiple(() => + { + Assert.That(bilder.Count(b => b.Kind == "poster"), Is.EqualTo(69)); + Assert.That(attrappe.Aufrufe[0], Does.Not.Contain("include_image_language"), + "Die Auswahl von Hand soll alle Sprachen sehen"); + }); + } + + /// Die passende Sprache steht trotzdem oben. + [Test] + public async Task Die_eigene_Sprache_steht_vorn() + { + var attrappe = Attrappe.MitJson(""" + {"posters":[ + {"file_path":"/en.jpg","iso_639_1":"en","vote_average":9.0}, + {"file_path":"/de.jpg","iso_639_1":"de","vote_average":1.0}, + {"file_path":"/ja.jpg","iso_639_1":"ja","vote_average":8.0}], + "backdrops":[],"logos":[]} + """); + + var bilder = await Client(attrappe, FesteEinstellungen.MitSchluessel("de-DE")) + .BilderAsync(603, serie: false); + + Assert.That(bilder[0].Url, Does.Contain("/de.jpg")); + } + [Test] public async Task Ein_Film_wird_gelesen() { diff --git a/web/app/src/views/detail.ts b/web/app/src/views/detail.ts index 0907c49..715e99d 100644 --- a/web/app/src/views/detail.ts +++ b/web/app/src/views/detail.ts @@ -19,6 +19,7 @@ import { } from '../sprachwahl'; import { menueAnZiel } from '../menue'; import { meldedialog } from '../melden'; +import { bearbeiten } from '../bearbeiten'; type Art = 'filme' | 'serien'; @@ -475,10 +476,36 @@ export function detailseite(art: Art, id: string, zurueck: () => void): HTMLElem })); } - // **Nicht über `rundKnopf`.** Der schaltet sich beim Klick ab und erst im - // Fehlerfall wieder an — richtig für etwas, das danach die Seite neu lädt. - // Ein Dialog kehrt sofort zurück; der Knopf bliebe für den Rest der Seite - // tot. + // **Bearbeiten — und damit die Bilder — von hier aus erreichbar.** + // + // Den Dialog gab es längst, aber nur am Dreipunktmenü einer Kachel und + // in zwei Einstellungsseiten. Wer auf der Seite eines Films steht und + // sieht, dass das Plakat falsch ist, stand genau dort, wo er nichts + // dagegen tun konnte. + // + // **Nur im Kopf der Seite** (daran erkennbar, dass `melden` gesetzt ist): + // dieselbe Reihe steht über jeder Fassung und jeder Folge, und dort + // meinte der Knopf jedes Mal denselben Titel. + // + // Wie der Melden-Knopf nicht über `rundKnopf`: der schaltet sich beim + // Klick ab und erst im Fehlerfall wieder an — richtig für etwas, das + // danach neu lädt, falsch für einen Dialog, der sofort zurückkehrt. + if (melden && istAdmin()) { + const b = document.createElement('button'); + b.className = 'rund'; + b.innerHTML = icons.stift; + b.title = 'Bearbeiten'; + b.setAttribute('aria-label', 'Bearbeiten'); + b.onclick = () => bearbeiten( + art === 'serien' ? 'series' : 'movies', eintrag.id, + ergebnis => { + // Beim Zusammenlegen überlebt eine andere Kennung — dann führt + // diese Seite ins Leere, und zurück ist die ehrlichere Antwort. + if (ergebnis.verschmolzen) zurueck(); else void laden(); + }); + reihe.appendChild(b); + } + if (melden) { const m = document.createElement('button'); m.className = 'rund';