Ein Abspann darf aus Stücken bestehen

Zwei Fälle fielen durch, beide am Bestand gesehen. Bei einem Marvel-Film
liegt hinter dem Abspann eine Post-Credit-Szene: die Suche begann am
Dateiende, fand nur den kurzen Rest dahinter und verwarf ihn als zu kurz.
Und ein Abspann, den eine Szene in der Mitte teilt, zerfiel in zwei
Reste, von denen keiner lang genug war. Dieselbe Ursache: der Abspann
musste eine zusammenhängende dunkle Strecke bis zum Dateiende sein.

Jetzt bildet die Suche Läufe, fasst sie über Lücken bis vier Minuten
zusammen — so lang ist die längste bekannte Post-Credit-Szene plus
Reserve — und lässt die **Summe** über die Mindestlänge entscheiden statt
jedes einzelne Stück. Ein Lauf unter fünfzehn Sekunden ist eine Blende
und zählt nicht mit.

Dafür mussten die eindeutigen Indizes über (Titel, Art) weichen; sie
standen mit dem Vermerk „eine Art einmal je Titel" da, und genau das
stimmt nicht mehr. `SchreibenAsync` ersetzt jetzt die Menge, statt eine
Zeile umzuschreiben — das ist der einzige Weg, der in beide Richtungen
stimmt, wenn aus zwei Stücken wieder eines wird.

**Drei Stellen trugen das nicht.** Im Web hiess „Abspann überspringen"
bei einem Film immer Schluss — beim ersten von zwei Stücken bekäme
niemand die Szene je zu sehen; geschlossen wird jetzt nur, wenn nichts
mehr folgt. Auf iOS verwarf der Spieler die Abspannmarke eines Films
ganz; diese Regel gehört nicht dorthin, sondern auf den Server, und ist
ersatzlos weg. Und die Gesehen-Schwelle nahm irgendeine Abspannzeile —
ohne `OrderBy` entschied die Datenbank; gemeint ist immer die früheste.

Dazu der Schalter „Zusätzliche Szenen mit überspringen" (Vorgabe aus) bei
den Geräteeinstellungen, und ein Messwerkzeug: ein Knopf auf der
Filmseite lädt die Kurve je Schlüsselbild als Tabelle — Dunkelanteil des
ganzen Bildes und der beiden Randstreifen. Ob bei Outtakes im Abspann die
Ränder dunkel bleiben, ist eine Vermutung; bevor daraus eine Schwelle
wird, will ich die Zahlen sehen.

Fünf neue Prüfungen, darunter die wichtigste: ein durchgehender Abspann
bleibt ein Stück.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-10-15 16:46:27 +02:00
co-authored by Claude Opus 5
parent 791c00dacc
commit 32f592b83e
18 changed files with 7216 additions and 5343 deletions
+8
View File
@@ -701,6 +701,14 @@ export async function markenHolen(titelId: string): Promise<Marke[]> {
export const mediathekMarken = (id: string, umfang: Markenumfang) =>
anfrage<void>(`/libraries/${id}/marken?umfang=${umfang}`, { method: 'POST' });
/** Die Adresse der Messkurve eines Films — zum Herunterladen.
*
* **Keine Erkennung, sondern Zahlen zum Ansehen.** Welche Schwelle bei
* einem Abspann über laufendem Bild trägt, lässt sich nur an der Kurve
* entscheiden, nicht am Ergebnis der Erkennung. */
export const markenkurveAdresse = (filmId: string) =>
`/api/v1/movies/${filmId}/markenkurve`;
/** Nimmt die Marken eines Films wieder weg — wenn die Erkennung danebenlag. */
export const filmmarkenVerwerfen = (filmId: string) =>
anfrage<void>(`/movies/${filmId}/marken`, { method: 'DELETE' });
+10 -3
View File
@@ -886,9 +886,16 @@ export function spieler(anfrage: Spieleranfrage): void {
weiter ? 'btn' : 'btn btn-primary');
u.onclick = e => {
e.stopPropagation();
// Bei einem Film und bei der letzten Folge heisst „überspringen"
// Schluss: ans Ende zu spulen liesse ein stehendes Bild zurück.
if (abspann && !weiter) { void beenden(); return; }
// **Kommt hinter diesem Stück noch eines, ist der Film nicht aus.**
//
// Ein Abspann kann geteilt sein: davor der eine Teil, dann eine
// Post-Credit-Szene, dann der Rest. Hier stand „bei einem Film
// heisst überspringen Schluss" — und damit bekam niemand die Szene
// je zu sehen. Geschlossen wird nur, wenn nach dieser Marke nichts
// mehr kommt.
const danach = marken.some(m => m.startSeconds >= drin.endSeconds - 1);
if (abspann && !weiter && !danach) { void beenden(); return; }
void springen(drin.endSeconds);
};
markenreihe.appendChild(u);
+11 -1
View File
@@ -3,7 +3,7 @@ import { ApiFehler,
vorschlaege, zuordnen, bildAdresse, istAdmin, gesehen, favorit, weglegen, serienstimmen,
type Film, type Serie, type Folge, type Datei, type Stand,
type Mitwirkender, type Spur, aehnliche, metadatenAuffrischen, scanstand, markenSuchen,
filmmarkenSuchen, filmmarkenVerwerfen,
filmmarkenSuchen, filmmarkenVerwerfen, markenkurveAdresse,
} from '../api';
import {
esc, dauer, groesse, knopf, feld, fehlerzeile, leer, anteil, meldung, hinweisZeigen,
@@ -521,6 +521,16 @@ export function detailseite(art: Art, id: string, zurueck: () => void): HTMLElem
window.setTimeout(() => void laden(), 4000);
}));
// **Die Zahlen hinter dem Urteil.** „Kein Abspann" nennt den Grund,
// aber nicht die Kurve — und ohne die lässt sich keine Schwelle
// begründen. Der Knopf lädt sie als Tabelle herunter.
reihe.appendChild(rundKnopf('protokoll', false, 'Messwerte des Abspanns holen',
async () => {
window.location.href = markenkurveAdresse(eintrag.id);
hinweisZeigen('Die Messung läuft — der Server sieht sich dafür die '
+ 'letzten zwanzig Minuten dreimal an.');
}));
// **Nur, wenn es etwas zu verwerfen gibt.** Ein Knopf, der nichts
// wegnehmen kann, ist eine Behauptung über einen Zustand.
if ('markers' in eintrag && (eintrag.markers?.length ?? 0) > 0) {