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:
Scarriffle
2026-10-13 06:42:16 +02:00
co-authored by Claude Opus 5
parent 8112c15d4d
commit 7673c73757
22 changed files with 2825 additions and 32 deletions
@@ -216,4 +216,84 @@ public class MarkenTests
"die Folgenzahl löst den nächsten Lauf aus");
}
}
/// <summary>Ein Film trägt seine Abspannmarke genauso.</summary>
[Test]
public async Task Ein_Film_bekommt_seine_Marke()
{
Datei("Matrix (1999)/Matrix (1999).mkv");
using var server = new FlaroServer();
var client = await Eingelesen(server, "Movies");
var filme = await client.GetFromJsonAsync<Page<MovieResponse>>("/api/v1/movies");
var film = filme!.Items[0].Id;
await server.InDerDatenbank(async db =>
{
db.Marken.Add(new Marke
{
MovieId = film, Art = Markenart.Abspann,
VonSekunden = 6900, BisSekunden = 7200, ErkanntAm = DateTimeOffset.UtcNow,
});
await db.SaveChangesAsync();
});
var einzeln = await client.GetFromJsonAsync<MovieResponse>($"/api/v1/movies/{film}");
var liste = await client.GetFromJsonAsync<Page<MovieResponse>>("/api/v1/movies");
Assert.Multiple(() =>
{
Assert.That(einzeln!.Markers!.Single().Kind, Is.EqualTo("credits"));
Assert.That(einzeln.Markers!.Single().StartSeconds, Is.EqualTo(6900));
Assert.That(liste!.Items[0].Markers, Has.Count.EqualTo(1), "auch in der Liste");
});
}
/// <summary>Eine falsche Marke lässt sich wieder wegnehmen.</summary>
///
/// <remarks>
/// Ohne diesen Weg wäre die Erkennung eine Blackbox, die manchmal recht
/// hat: ein Fehlgriff bliebe stehen, und niemand könnte etwas dagegen
/// tun ausser die Datenbank anzufassen.
/// </remarks>
[Test]
public async Task Eine_falsche_Filmmarke_laesst_sich_verwerfen()
{
Datei("Matrix (1999)/Matrix (1999).mkv");
using var server = new FlaroServer();
var client = await Eingelesen(server, "Movies");
var filme = await client.GetFromJsonAsync<Page<MovieResponse>>("/api/v1/movies");
var film = filme!.Items[0].Id;
await server.InDerDatenbank(async db =>
{
db.Marken.Add(new Marke
{
MovieId = film, Art = Markenart.Abspann,
VonSekunden = 60, BisSekunden = 7200, ErkanntAm = DateTimeOffset.UtcNow,
});
db.Markensuchen.Add(new Markensuche
{
MovieId = film, Art = Markenart.Abspann, Folgen = 1,
GelaufenAm = DateTimeOffset.UtcNow,
});
await db.SaveChangesAsync();
});
var weg = await client.DeleteAsync($"/api/v1/movies/{film}/marken");
var danach = await client.GetFromJsonAsync<MovieResponse>($"/api/v1/movies/{film}");
var vermerke = 0;
await server.InDerDatenbank(async db =>
vermerke = await db.Markensuchen.CountAsync(x => x.MovieId == film));
Assert.Multiple(() =>
{
Assert.That(weg.StatusCode, Is.EqualTo(HttpStatusCode.NoContent));
Assert.That(danach!.Markers, Is.Empty);
Assert.That(vermerke, Is.EqualTo(1),
"der Vermerk bleibt, sonst legte der nächste Scan dieselbe Marke wieder an");
});
}
}