Zwei Uhren, und niemand hatte sie verglichen

Untertitel zeigten auf keiner Spur Text. Der Grund: die Zeitstempel einer
WebVTT-Spur zählen ab Dateianfang, der Strom zählt ab null. Wer bei Minute
30 einstieg, bekam jeden Satz dreissig Minuten in die Zukunft geschoben —
und weil ein Strom nur bis zum Filmende reicht, erschien überhaupt keiner
mehr.

Gemessen im Spieler, vorher: „Fortsetzen ab 8:37" geklickt, deutsche Spur
gewählt, Standanzeige 8:38 — angezeigt wurde der Satz von Sekunde 0.
Versatz 515 Sekunden, also genau der Einstiegspunkt.

Nachher, derselbe Weg: Standanzeige 9:02, angezeigt „Deutsch bei 540 s".

Verschoben wird auf dem Server, nicht im Browser: dort hiesse es, die Spur
selbst zu zerlegen und als VTTCue neu aufzubauen — ein eigener Parser für
ein Format, das der Browser schon kann. Hier ist es eine Zeilenersetzung,
und sie lässt sich prüfen. Was vor dem Einstieg endet, fällt samt Text weg;
was darüber hinwegreicht, fängt bei null an.

Zwei Kleinigkeiten lagen daneben und sind mit erledigt:

- `srclang` bekam „ger".slice(0, 2) und damit „ge" — kein gültiges Kürzel.
  Jetzt bleibt das Feld leer, wenn das Kürzel nicht zweistellig ist.
- Scheiterte das Laden einer Spur, geschah nichts Sichtbares. Jetzt sagt
  es der Spieler.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-10-07 21:28:39 +02:00
co-authored by Claude Opus 5
parent 57fff63516
commit 51d1db4641
5 changed files with 275 additions and 9 deletions
@@ -212,16 +212,24 @@ public static class StreamEndpoints
// So lassen sie sich während der Wiedergabe umschalten, ohne dass
// irgendetwas neu gerechnet wird. In einer fMP4-Segmentierung hätten
// sie ohnehin keinen Platz.
// **`ab` ist nicht geschmückt, sondern nötig.** Die Zeitstempel einer
// Untertitelspur zählen ab Dateianfang, der Strom zählt ab null —
// steigt jemand bei Minute 30 ein, liegt jeder Satz dreissig Minuten
// in dessen Zukunft, und zwar für den ganzen Rest des Films. Gemessen
// im Spieler: bei „Fortsetzen ab 8:37" stand der Satz von Sekunde 0
// auf dem Bild.
app.MapGet("/api/v1/files/{id:guid}/subtitles/{spur:int}.vtt", async (
Guid id, int spur, FlaroContext db, UntertitelLeser leser, CancellationToken ct) =>
Guid id, int spur, FlaroContext db, UntertitelLeser leser,
CancellationToken ct, double ab = 0) =>
{
var datei = await db.MediaFiles.AsNoTracking().FirstOrDefaultAsync(f => f.Id == id, ct);
if (datei is null || !File.Exists(datei.Path)) return Results.NotFound();
var vtt = await leser.AlsWebVttAsync(datei.Path, spur, ct);
return vtt is null
? Results.NotFound()
: Results.Text(vtt, "text/vtt", Encoding.UTF8);
if (vtt is null) return Results.NotFound();
return Results.Text(
Untertitelversatz.Verschieben(vtt, Math.Max(0, ab)), "text/vtt", Encoding.UTF8);
}).RequireAuthorization();
}