Vier Sekunden, die ffprobe zu früh aufhörte zu lesen
Die Untertitel kamen vier Sekunden zu spät. Nicht an ihnen lag es: der
Server meldete dem Spieler einen Anfang, der vor dem lag, an dem der
Strom wirklich begann, und genau diese Differenz landet eins zu eins im
Versatz.
Die Schlüsselbildsuche las mit `-read_intervals` bis eine halbe Sekunde
hinter den Wunsch. ffprobe bricht am Ende eines Fensters auf Paketebene
ab — das letzte Schlüsselbild darin kam regelmässig gar nicht mehr
heraus, und die Suche gab das davor zurück. Bei Bildgruppen von vier
Sekunden sind das vier Sekunden.
Nachgemessen gegen die Stelle, an der ffmpeg tatsächlich anfängt, an
einer Datei mit Schlüsselbildern alle vier Sekunden:
Wunsch Suche (0,5 s) ffmpeg Abweichung
52,0 48 52,0 −4,0
73,3 68 72,0 −4,0
100,2 96 100,0 −4,0
Mit zehn Sekunden Nachlauf lag dieselbe Messung bei allen sieben Werten
auf 0,000. Was über den Wunsch hinausgelesen wird, verwirft die
Filterung ohnehin.
Die Prüfung vergleicht nicht gegen eine erwartete Zahl, sondern gegen
ffmpeg selbst — mit dem alten Wert fällt sie um.
Dazu der Filmteil der Abspannerkennung: `Abspannkante` als reine
Rechnung mit elf Prüfungen, `Bildabtaster` über ffmpeg, Marken auch an
Filmen, Knöpfe zum Suchen und Verwerfen auf der Filmseite. An einer
erzeugten Datei trifft die Kante auf vier Sekunden genau.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
8112c15d4d
commit
7673c73757
@@ -87,6 +87,8 @@ export type Film = {
|
||||
libraryId: string | null;
|
||||
/** Das Bild, das jetzt im Aufmacher gilt — vom Server gewählt. */
|
||||
heroImage?: string | null;
|
||||
/** Der Abspann, sofern er im Bild gefunden wurde. */
|
||||
markers?: Marke[] | null;
|
||||
};
|
||||
export type Serie = {
|
||||
id: string; title: string; year: number | null; overview: string | null;
|
||||
@@ -656,6 +658,31 @@ export const markenSuchen = (serieId: string, erneut = false) =>
|
||||
anfrage<void>(
|
||||
`/series/${serieId}/marken${erneut ? '?erneut=true' : ''}`, { method: 'POST' });
|
||||
|
||||
/** Die Marken eines Titels — Folge oder Film.
|
||||
*
|
||||
* **Eine Kennung, zwei mögliche Wege.** Der Spieler bekommt nur eine
|
||||
* Kennung und weiss nicht, was dahintersteckt; erst die Antwort sagt es.
|
||||
* Eine 404 auf beiden Wegen heisst: keine Marken, und mehr hängt nicht
|
||||
* daran. */
|
||||
export async function markenHolen(titelId: string): Promise<Marke[]> {
|
||||
const folge = await folgeLesen(titelId);
|
||||
if (folge) return folge.markers ?? [];
|
||||
try {
|
||||
const f = await anfrage<{ markers?: Marke[] | null }>(`/movies/${titelId}`);
|
||||
return f.markers ?? [];
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/** 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 async function folgeLesen(id: string): Promise<Folge | null> {
|
||||
try {
|
||||
return (await anfrage<Folge | undefined>(`/episodes/${id}`)) ?? null;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type HlsTyp from 'hls.js';
|
||||
import {
|
||||
stromStarten, stromBeenden, untertitelHolen, fortschritt, nachbarfolge, folgeLesen,
|
||||
stromStarten, stromBeenden, untertitelHolen, fortschritt, nachbarfolge, markenHolen,
|
||||
type Stromauskunft, type Spurauskunft, type Folge, type Marke,
|
||||
} from './api';
|
||||
import { dauer, knopf, meldung, hinweisZeigen } from './util';
|
||||
@@ -643,14 +643,14 @@ export function spieler(anfrage: Spieleranfrage): void {
|
||||
// je Wiedergabe. Erst beim Antippen zu fragen hiesse, einen Knopf zu
|
||||
// zeigen, von dem noch niemand weiss, ob er etwas tut.
|
||||
void (async () => {
|
||||
const [davor, danach, diese] = await Promise.all([
|
||||
const [davor, danach, gefunden] = await Promise.all([
|
||||
nachbarfolge(anfrage.titelId, 'previous'),
|
||||
nachbarfolge(anfrage.titelId, 'next'),
|
||||
folgeLesen(anfrage.titelId),
|
||||
markenHolen(anfrage.titelId),
|
||||
]);
|
||||
vorige = davor;
|
||||
naechste = danach;
|
||||
marken = diese?.markers ?? [];
|
||||
marken = gefunden;
|
||||
if (geschlossen) return;
|
||||
vorigeKnopf.classList.toggle('hidden', vorige?.files[0] == null);
|
||||
naechsteKnopf.classList.toggle('hidden', naechste?.files[0] == null);
|
||||
|
||||
@@ -3,6 +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,
|
||||
} from '../api';
|
||||
import {
|
||||
esc, dauer, groesse, knopf, feld, fehlerzeile, leer, anteil, meldung, hinweisZeigen,
|
||||
@@ -492,6 +493,34 @@ export function detailseite(art: Art, id: string, zurueck: () => void): HTMLElem
|
||||
// will aber genau das: noch einmal, und zwar jetzt. Ein Knopf, der je
|
||||
// nach gespeichertem Zustand mal etwas tut und mal nicht, wäre der
|
||||
// schlechtere Tausch.
|
||||
// **Bei einem Film sucht der Server im Bild.** Ein Abspann verrät sich
|
||||
// bei einer Serie dadurch, dass er in jeder Folge derselbe ist; ein
|
||||
// Film hat nichts, womit er sich vergleichen liesse. Dort zählt, dass
|
||||
// er dunkel ist und bis zum Ende durchhält — und das trifft längst
|
||||
// nicht auf jeden Film zu.
|
||||
if (melden && istAdmin() && art === 'filme') {
|
||||
reihe.appendChild(rundKnopf('uhr', false, 'Abspann neu suchen',
|
||||
async () => {
|
||||
await filmmarkenSuchen(eintrag.id, true);
|
||||
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
|
||||
// hier in Sekunden durch.
|
||||
window.setTimeout(() => void laden(), 4000);
|
||||
}));
|
||||
|
||||
// **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) {
|
||||
reihe.appendChild(rundKnopf('schliessen', false, 'Abspannmarke verwerfen',
|
||||
async () => {
|
||||
await filmmarkenVerwerfen(eintrag.id);
|
||||
hinweisZeigen('Marke entfernt. Der nächste Scan legt sie nicht wieder an.');
|
||||
await laden();
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
if (melden && istAdmin() && art === 'serien') {
|
||||
reihe.appendChild(rundKnopf('uhr', false, 'Vorspann und Abspann neu suchen',
|
||||
async () => {
|
||||
|
||||
Reference in New Issue
Block a user