„No such file or directory" über Dateien, die da waren
Im Protokoll stand für jede einzelne Folge, fpcalc könne die Datei nicht öffnen — während der Spieler dieselbe Datei im selben Augenblick umpackte. Null Marken, und der Grund stand nicht da. `-ts` ist bei fpcalc kein Zeitstempel, sondern ein Schalter: „gib Zeitstempel aus". Die Sekundenzahl dahinter las fpcalc als zweiten Dateinamen, fand ihn nicht und brach ab, bevor es die echte Datei überhaupt ansah. Nachgemessen am Binärprogramm aus dem Abbild: einen Startpunkt gibt es bei fpcalc gar nicht. `-length` begrenzt nur die Dauer vom Anfang an — für den Abspann braucht es aber das Ende. Also einmal die ganze Spur je Folge, und Vorspann wie Abspann sind danach zwei Ausschnitte aus demselben Abdruck. Das ist nicht nur richtig, sondern billiger: eine Folge von 45 Minuten kostet 1,6 Sekunden und ergibt 21 786 Werte — gegenüber zwei Ausschnitten, die zusammen mehr gekostet hätten und nie funktionierten. Die Rate von 0,1239 Sekunden je Wert steht jetzt als Prüfung daneben. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
2c535e6f47
commit
fd79fb4022
@@ -166,28 +166,39 @@ public sealed class Markenerkennung(
|
||||
var geordnet = staffel.OrderBy(f => f.EpisodeNumber).ToList();
|
||||
if (geordnet.Count < 2) continue;
|
||||
|
||||
foreach (var art in new[] { Markenart.Intro, Markenart.Abspann })
|
||||
{
|
||||
// Schon gefunden? Dann nicht noch einmal rechnen — es sei
|
||||
// denn, jemand verlangt es ausdrücklich.
|
||||
if (!erneut && geordnet.All(f =>
|
||||
vorhanden.Any(v => v.EpisodeId == f.Id && v.Art == art)))
|
||||
continue;
|
||||
// **Einmal lesen, zweimal schneiden.** fpcalc kann nicht
|
||||
// springen — es liest immer von vorn. Also die ganze Spur je
|
||||
// Folge, und Vorspann wie Abspann sind danach zwei Ausschnitte
|
||||
// aus demselben Abdruck. Das ist nicht nur richtig, sondern
|
||||
// billiger als zweimal lesen.
|
||||
var fehlt = new[] { Markenart.Intro, Markenart.Abspann }
|
||||
.Where(art => erneut || !geordnet.All(f =>
|
||||
vorhanden.Any(v => v.EpisodeId == f.Id && v.Art == art)))
|
||||
.ToList();
|
||||
if (fehlt.Count == 0) continue;
|
||||
|
||||
var ganze = new Dictionary<Guid, IReadOnlyList<uint>>();
|
||||
foreach (var folge in geordnet)
|
||||
{
|
||||
var abdruck = await leser.LesenAsync(folge.Path, ct);
|
||||
if (abdruck.Count > 0) ganze[folge.Id] = abdruck;
|
||||
}
|
||||
if (ganze.Count < 2) continue;
|
||||
|
||||
foreach (var art in fehlt)
|
||||
{
|
||||
var abdruecke = new List<(Guid Schlüssel, IReadOnlyList<uint> Abdruck)>();
|
||||
var fensteranfang = new Dictionary<Guid, double>();
|
||||
|
||||
foreach (var folge in geordnet)
|
||||
{
|
||||
var laufzeit = folge.Duration?.TotalSeconds ?? 0;
|
||||
var (ab, dauer) = Fenster(art, laufzeit);
|
||||
if (dauer <= 0) continue;
|
||||
if (!ganze.TryGetValue(folge.Id, out var ganz)) continue;
|
||||
|
||||
var abdruck = await leser.LesenAsync(folge.Path, ab, dauer, ct);
|
||||
if (abdruck.Count == 0) continue;
|
||||
var (vonIndex, anzahl) = Ausschnitt(art, ganz.Count);
|
||||
if (anzahl < 2) continue;
|
||||
|
||||
abdruecke.Add((folge.Id, abdruck));
|
||||
fensteranfang[folge.Id] = ab;
|
||||
abdruecke.Add((folge.Id, ganz.Skip(vonIndex).Take(anzahl).ToList()));
|
||||
fensteranfang[folge.Id] = vonIndex * Tonabdruckleser.SekundenJeAbdruck;
|
||||
}
|
||||
|
||||
if (abdruecke.Count < 2) continue;
|
||||
@@ -215,15 +226,21 @@ public sealed class Markenerkennung(
|
||||
log.LogInformation("Markenerkennung für {Serie}: {Anzahl} Marken.", serieId, gefunden);
|
||||
}
|
||||
|
||||
/// <summary>Welcher Ausschnitt der Folge abgetastet wird.</summary>
|
||||
private static (double Ab, double Dauer) Fenster(Markenart art, double laufzeit)
|
||||
/// <summary>Welcher Ausschnitt des Abdrucks abgetastet wird.</summary>
|
||||
///
|
||||
/// <remarks>
|
||||
/// In Werten, nicht in Sekunden: der Abdruck ist eine Reihe, und
|
||||
/// geschnitten wird an einer Stelle darin. Die Laufzeit steht nicht mehr
|
||||
/// in der Rechnung — die Länge des Abdrucks ist sie.
|
||||
/// </remarks>
|
||||
private static (int Von, int Anzahl) Ausschnitt(Markenart art, int werte)
|
||||
{
|
||||
if (art == Markenart.Intro) return (0, laufzeit > 0 ? Math.Min(Introfenster, laufzeit) : Introfenster);
|
||||
var fenster = (int)Math.Round(
|
||||
(art == Markenart.Intro ? Introfenster : Abspannfenster)
|
||||
/ Tonabdruckleser.SekundenJeAbdruck);
|
||||
|
||||
// Ohne bekannte Laufzeit lässt sich das Ende nicht finden.
|
||||
if (laufzeit <= 0) return (0, 0);
|
||||
var ab = Math.Max(0, laufzeit - Abspannfenster);
|
||||
return (ab, laufzeit - ab);
|
||||
if (fenster >= werte) return (0, werte);
|
||||
return art == Markenart.Intro ? (0, fenster) : (werte - fenster, fenster);
|
||||
}
|
||||
|
||||
private async Task<bool> SchreibenAsync(
|
||||
|
||||
@@ -38,6 +38,10 @@ public sealed class Tonabdruckleser(ILogger<Tonabdruckleser> log)
|
||||
/// zwei Dritteln Überlappung: 4096/3 / 11025 Sekunden. Die Zahl steht
|
||||
/// hier, weil nur mit ihr aus einem Abdruckbereich eine Zeitangabe wird.
|
||||
/// </remarks>
|
||||
/// <remarks>
|
||||
/// Nachgerechnet an einer Datei von 2700 Sekunden: 21 786 Werte, also
|
||||
/// 0,12393 Sekunden je Wert. Die Formel trifft es auf vier Stellen.
|
||||
/// </remarks>
|
||||
public const double SekundenJeAbdruck = 4096.0 / 3.0 / 11025.0;
|
||||
|
||||
/// <summary>Wo <c>fpcalc</c> liegt — überschreibbar wie ffmpeg selbst.</summary>
|
||||
@@ -75,12 +79,28 @@ public sealed class Tonabdruckleser(ILogger<Tonabdruckleser> log)
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Der Abdruck eines Abschnitts, oder eine leere Reihe.</summary>
|
||||
/// <summary>Der Abdruck der ganzen Tonspur, oder eine leere Reihe.</summary>
|
||||
///
|
||||
/// <param name="ab">Ab welcher Sekunde gelesen wird.</param>
|
||||
/// <param name="dauer">Wie viele Sekunden gelesen werden.</param>
|
||||
public async Task<IReadOnlyList<uint>> LesenAsync(
|
||||
string pfad, double ab, double dauer, CancellationToken ct = default)
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// <b>Die ganze Datei, nicht ein Ausschnitt — weil fpcalc nicht springen
|
||||
/// kann.</b> Hier stand einmal <c>-ts</c> mit einer Sekundenzahl, in der
|
||||
/// Annahme, das sei ein Startpunkt. Es ist ein Schalter: „gib
|
||||
/// Zeitstempel aus". Die Zahl dahinter las fpcalc als zweiten
|
||||
/// Dateinamen, fand ihn nicht und brach mit Code 2 ab — bei jeder
|
||||
/// einzelnen Folge, und im Protokoll stand sechsmal „No such file or
|
||||
/// directory" über Dateien, die sehr wohl da waren.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Einen Startpunkt gibt es bei fpcalc überhaupt nicht; <c>-length</c>
|
||||
/// begrenzt nur die Dauer vom Anfang an. Für den Abspann braucht es aber
|
||||
/// das Ende. Also einmal die ganze Spur, und geschnitten wird danach —
|
||||
/// gemessen an einer Folge von 45 Minuten: 1,6 Sekunden und 21 786
|
||||
/// Werte. Das ist weniger als die zwei Ausschnitte vorher gekostet
|
||||
/// hätten, und es stimmt.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public async Task<IReadOnlyList<uint>> LesenAsync(string pfad, CancellationToken ct = default)
|
||||
{
|
||||
if (!await VorhandenAsync(ct))
|
||||
{
|
||||
@@ -93,9 +113,10 @@ public sealed class Tonabdruckleser(ILogger<Tonabdruckleser> log)
|
||||
ArgumentList =
|
||||
{
|
||||
"-raw",
|
||||
// Beide als ganze Sekunden: fpcalc nimmt nichts anderes an.
|
||||
"-ts", Math.Max(0, (int)ab).ToString(CultureInfo.InvariantCulture),
|
||||
"-length", Math.Max(1, (int)Math.Ceiling(dauer)).ToString(CultureInfo.InvariantCulture),
|
||||
// **Null heisst: keine Grenze.** Die Vorgabe sind zwei
|
||||
// Minuten — damit fände man den Vorspann einer Folge
|
||||
// vielleicht noch, den Abspann nie.
|
||||
"-length", "0",
|
||||
pfad,
|
||||
},
|
||||
RedirectStandardOutput = true,
|
||||
|
||||
Reference in New Issue
Block a user