„Schon durchsucht" und „hat eine Marke" sind zwei verschiedene Dinge

Genau dazwischen fehlte die Unterscheidung. Ein Film, bei dem die Suche
einmal nichts fand, trägt einen Vermerk und keine Marke — und war damit
für jeden weiteren Lauf erledigt, obwohl er der interessanteste Fall ist.
Es gab nur alles oder nichts, und der Knopf bei der Mediathek schickte
immer „alles": 453 Filme mal zwanzig Minuten Schlüsselbilder, auch für
die vierhundert, bei denen längst etwas stand.

Aus `bool erneut` wird `Markenumfang` mit drei Werten. `Neue` ist, was
der Scan tut, und ändert sich nicht. `Luecken` nimmt sich vor, wo keine
Marke steht — gleich ob dort schon einmal gesucht wurde. `Alles` ist der
alte Holzhammer.

Bei der Mediathek jetzt zwei Knöpfe, weil es zwei Fragen sind: „Fehlende
Marken suchen" ist die alltägliche, „Alles neu durchsuchen" die seltene
und teure, und deren Rückfrage nennt die Zahl der Titel. Auf der
Detailseite bleibt es bei einem Knopf mit „alles" — wer ihn bei genau
diesem Titel drückt, ist mit genau diesem Titel unzufrieden.

Bei einer Serie wird eine Staffel ganz gelesen, sobald einer ihrer
Folgen etwas fehlt. Billiger ginge es nur, indem man die Lücke gar nicht
erst angeht, und genau dafür drückt jemand den Knopf.

Vier Prüfungen am Endpunkt, die messen, ob der Lauf den Film anfasst:
`Neue` überspringt den Durchsuchten, `Luecken` nimmt ihn sich vor,
`Alles` sowieso, und ein Film mit Marke bleibt bei `Luecken` in Ruhe.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-10-15 14:20:47 +02:00
co-authored by Claude Opus 5
parent f2752e6c05
commit f79ecf2757
6 changed files with 271 additions and 54 deletions
+20 -7
View File
@@ -659,9 +659,23 @@ export const naechsteFolgen = (take = 20) =>
*
* Antwortet mit 202: der Lauf dauert Minuten — er liest von jeder Folge
* einer Staffel ein Stück Ton —, und darauf wartet niemand. */
export const markenSuchen = (serieId: string, erneut = false) =>
/** Wie weit ein Markenlauf greift.
*
* **„Schon durchsucht" und „hat eine Marke" sind zwei verschiedene
* Dinge.** Ein Titel, bei dem die Suche einmal nichts fand, trägt einen
* Vermerk und keine Marke — und war damit für jeden weiteren Lauf
* erledigt, obwohl er der interessanteste Fall ist. */
export type Markenumfang =
/** Nur, wo noch nie gesucht wurde. */
| 'Neue'
/** Auch, wo gesucht und nichts gefunden wurde — nicht, wo schon eine Marke steht. */
| 'Luecken'
/** Alles, auch was längst eine Marke hat. */
| 'Alles';
export const markenSuchen = (serieId: string, umfang: Markenumfang = 'Alles') =>
anfrage<void>(
`/series/${serieId}/marken${erneut ? '?erneut=true' : ''}`, { method: 'POST' });
`/series/${serieId}/marken?umfang=${umfang}`, { method: 'POST' });
/** Die Marken eines Titels — Folge oder Film.
*
@@ -684,17 +698,16 @@ export async function markenHolen(titelId: string): Promise<Marke[]> {
*
* **Antwortet sofort, arbeitet weiter.** Der Lauf liest je Folge ein Stück
* Ton; über fünfundneunzig Serien sind das Stunden. */
export const mediathekMarken = (id: string, erneut = false) =>
anfrage<void>(
`/libraries/${id}/marken${erneut ? '?erneut=true' : ''}`, { method: 'POST' });
export const mediathekMarken = (id: string, umfang: Markenumfang) =>
anfrage<void>(`/libraries/${id}/marken?umfang=${umfang}`, { method: 'POST' });
/** Nimmt die Marken eines Films wieder weg — wenn die Erkennung danebenlag. */
export const filmmarkenVerwerfen = (filmId: string) =>
anfrage<void>(`/movies/${filmId}/marken`, { method: 'DELETE' });
/** Stösst die Abspannsuche für einen Film an. */
export const filmmarkenSuchen = (filmId: string, erneut = false) =>
anfrage<void>(`/movies/${filmId}/marken${erneut ? '?erneut=true' : ''}`, { method: 'POST' });
export const filmmarkenSuchen = (filmId: string, umfang: Markenumfang = 'Alles') =>
anfrage<void>(`/movies/${filmId}/marken?umfang=${umfang}`, { method: 'POST' });
export async function folgeLesen(id: string): Promise<Folge | null> {
try {
+2 -2
View File
@@ -513,7 +513,7 @@ export function detailseite(art: Art, id: string, zurueck: () => void): HTMLElem
if (melden && istAdmin() && art === 'filme') {
reihe.appendChild(rundKnopf('uhr', false, 'Abspann neu suchen',
async () => {
await filmmarkenSuchen(eintrag.id, true);
await filmmarkenSuchen(eintrag.id, 'Alles');
hinweisZeigen('Der Abspann wird gesucht. Der Server sieht sich dafür '
+ 'die letzten zwanzig Minuten an — das dauert einen Augenblick.');
// Kurz warten und nachsehen: anders als bei einer Serie ist das
@@ -536,7 +536,7 @@ export function detailseite(art: Art, id: string, zurueck: () => void): HTMLElem
if (melden && istAdmin() && art === 'serien') {
reihe.appendChild(rundKnopf('uhr', false, 'Vorspann und Abspann neu suchen',
async () => {
await markenSuchen(eintrag.id, true);
await markenSuchen(eintrag.id, 'Alles');
hinweisZeigen('Vorspann und Abspann werden gesucht. '
+ 'Der Server liest dafür jede Folge einmal durch — das dauert ein paar Minuten.');
}));
+40 -16
View File
@@ -1,6 +1,6 @@
import {
bibliotheken, bibliothekAnlegen, bibliothekUmbenennen, bibliothekLoeschen, scanStarten,
mediathekMarken,
mediathekMarken, type Markenumfang,
scanstand, metadatenAuffrischen, vermisste, vermisstesEntfernen, type VermissteDatei,
pruefungSetzen, mediathekZuruecksetzen, type Bibliothek,
} from '../../api';
@@ -227,22 +227,45 @@ export function bibliotheksbereich(
// Angebot.** Den gibt es auf der Serienseite; hier gilt er für alles,
// was in dieser Mediathek liegt — Serien über den Tonabgleich, Filme
// über das Bild.
const marken = knopf('Vorspann und Abspann suchen', 'btn');
marken.title = 'Sucht überspringbare Abschnitte in allen Titeln dieser Mediathek.';
marken.onclick = () => nachfragen({
titel: `Vorspann und Abspann in „${aktuell.name}" suchen?`,
text: 'Der Server liest dafür von jeder Folge einmal die Tonspur und sieht '
+ 'sich bei Filmen die letzten zwanzig Minuten an. Über einen grossen '
+ 'Bestand dauert das Stunden — es läuft im Hintergrund weiter, und im '
+ 'Protokoll von heute steht, wie weit es ist. Schon durchsuchte Titel '
+ 'werden noch einmal geprüft.',
bestaetigen: 'Suchen',
tun: async () => {
laufendSetzen(true);
await mediathekMarken(aktuell.id, true);
/** Startet einen Markenlauf und hängt die Standzeile daran. */
function markenlauf(umfang: Markenumfang): void {
laufendSetzen(true);
void mediathekMarken(aktuell.id, umfang).then(() => {
hinweisZeigen('Die Suche läuft. Der Stand steht hier und im Protokoll von heute.');
void verfolgen(false);
},
});
}
// **Zwei Knöpfe, weil es zwei Fragen sind.**
//
// „Was fehlt noch?" ist die alltägliche: nach jeder Verbesserung an
// der Erkennung will man die Titel noch einmal, bei denen nichts
// herauskam — und eben nicht die vierhundert, bei denen längst etwas
// steht. „Alles neu" ist die seltene und teure; sie gehört nicht
// hinter denselben Knopf.
const marken = knopf('Fehlende Marken suchen', 'btn');
marken.title = 'Nimmt sich die Titel vor, bei denen keine Marke steht.';
marken.onclick = () => nachfragen({
titel: `Fehlende Marken in „${aktuell.name}" suchen?`,
text: 'Nur, wo noch kein Vorspann oder Abspann steht. Bei einer Serie '
+ 'wird eine Staffel ganz gelesen, sobald einer ihrer Folgen etwas '
+ 'fehlt — der Tonabgleich vergleicht die Folgen miteinander und '
+ 'geht nicht einzeln. Es läuft im Hintergrund weiter.',
bestaetigen: 'Suchen',
tun: async () => markenlauf('Luecken'),
});
const markenAlle = knopf('Alles neu durchsuchen', 'btn');
markenAlle.title = 'Bestimmt auch vorhandene Marken noch einmal.';
markenAlle.onclick = () => nachfragen({
titel: `Alle Marken in „${aktuell.name}" neu bestimmen?`,
text: `Jeder Titel wird noch einmal durchsucht — auch die, bei denen `
+ `längst eine Marke steht. Der Server liest dafür von jeder Folge die `
+ `Tonspur und sieht sich bei jedem Film die letzten zwanzig Minuten an. `
+ `Bei ${aktuell.itemCount ?? 'vielen'} Titeln dauert das Stunden. `
+ `Für „was fehlt noch?" ist der Knopf daneben der richtige.`,
bestaetigen: 'Alles neu',
tun: async () => markenlauf('Alles'),
});
const umbenennen = knopf('Umbenennen', 'btn');
@@ -379,7 +402,8 @@ export function bibliotheksbereich(
// und danach nachzusehen. Der Knopf heisst „Seitenleiste bearbeiten".
const knoepfe = document.createElement('div');
knoepfe.className = 'knopfreihe';
knoepfe.append(los, auffrischen, marken, pruefung, umbenennen, zuruecksetzen, entfernen);
knoepfe.append(los, auffrischen, marken, markenAlle, pruefung, umbenennen,
zuruecksetzen, entfernen);
// **Nur wenn hier wirklich etwas liegt.** Ein Knopf, der nichts zu tun
// hat, ist schlechter als keiner — und die Zahl daneben sagt, worum es