Zweimal vermerkt, einmal gespeichert, Serie verloren
„Markensuche für ‚A Knight of the Seven Kingdoms' abgebrochen — An error occurred while saving the entity changes." Das war meiner, und er hat mehr gekostet als eine Fehlermeldung: der Lauf brach beim Speichern ab, und damit war alles weg, was er für diese Serie gefunden hatte. `VermerkenAsync` sah in der Datenbank nach, ob die Staffel schon vermerkt ist — und eine Abfrage sieht nicht, was derselbe Lauf eben erst hinzugefügt und noch nicht gespeichert hat. Seit der Bildweg dazukam, wird für dieselbe Staffel zweimal vermerkt: einmal nach dem Tonabgleich, einmal vor dem Bild. Die zweite Abfrage fand nichts, legte eine zweite Zeile an, und der eindeutige Schlüssel über (Serie, Staffel, Art) warf sie beim Speichern wieder hinaus. Jetzt wird erst im Änderungsspeicher nachgesehen, dann in der Datenbank. **Und die Meldung taugte nichts.** Das Tagebuch hängt nur `Message` an, und EF Core schreibt dort „See the inner exception for details." — eine Zeile, die ausdrücklich sagt, dass sie die Auskunft nicht enthält. Alle vier Abbruchmeldungen nennen jetzt den innersten Grund über `Fehlertext.FuerMenschen`. **Dazu der zweite Fund aus demselben Protokoll.** „(T)Raumschiff Surprise": kein Abspann, 1318 Proben, Schwelle 51,1, letzte Probe 17 — das Ende ist also dunkel, und trotzdem kein Fund. 1318 Proben auf zwanzig Minuten heissen eine alle 0,9 Sekunden, und die Nachsicht gegenüber hellen Stellen mitten im Abspann war in *Proben* angegeben: zwei Stück, dort also 1,8 Sekunden. Bei einer Datei mit einem Schlüsselbild alle zehn Sekunden waren dieselben zwei Proben zwanzig Sekunden. Dasselbe Zugeständnis, zehnfach verschieden — genau der Fehler, den ich eine Zeile höher bei der Schlusstafel schon behoben hatte. `Nachsicht` zählt jetzt Sekunden, zehn davon. **Und die Suche sagt, woran es lag.** „kein Abspann — das Ende ist hell" gegen „die dunkle Strecke ist mit 20 s zu kurz" sind zwei verschiedene Ursachen mit zwei verschiedenen Antworten; bisher stand dort beides Mal dasselbe. Vier neue Prüfungen, darunter dieselbe Szene bei 0,9 und bei 10 Sekunden Abtastung. Mit der alten Probenzählung ist die dichte rot. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
767491ef03
commit
259996c5ad
@@ -117,7 +117,8 @@ public sealed class Markenerkennung(
|
||||
}
|
||||
catch (Exception fehler)
|
||||
{
|
||||
log.LogWarning(fehler, "Markenerkennung für {Serie} abgebrochen.", serieId);
|
||||
log.LogWarning(fehler, "Markensuche für {Serie} abgebrochen: {Grund}",
|
||||
serieId, Fehlertext.FuerMenschen(fehler));
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -233,7 +234,12 @@ public sealed class Markenerkennung(
|
||||
}
|
||||
catch (Exception fehler)
|
||||
{
|
||||
log.LogWarning(fehler, "Markensuche für „{Serie}“ abgebrochen.", titel);
|
||||
// **Mit dem innersten Text.** Das Tagebuch hängt nur
|
||||
// `Message` an, und bei EF Core steht dort „See the inner
|
||||
// exception for details." — eine Zeile, die ausdrücklich
|
||||
// sagt, dass sie die Auskunft nicht enthält.
|
||||
log.LogWarning(fehler, "Markensuche für „{Serie}“ abgebrochen: {Grund}",
|
||||
titel, Fehlertext.FuerMenschen(fehler));
|
||||
}
|
||||
finally { _laufend.TryRemove(id, out _); }
|
||||
}
|
||||
@@ -260,8 +266,8 @@ public sealed class Markenerkennung(
|
||||
try { await NachScanAsync(mediathek, erneut, CancellationToken.None); }
|
||||
catch (Exception fehler)
|
||||
{
|
||||
log.LogWarning(fehler,
|
||||
"Markenlauf für Mediathek {Kennung} abgebrochen.", mediathek);
|
||||
log.LogWarning(fehler, "Markenlauf für Mediathek {Kennung} abgebrochen: {Grund}",
|
||||
mediathek, Fehlertext.FuerMenschen(fehler));
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -303,7 +309,8 @@ public sealed class Markenerkennung(
|
||||
try { if (await FilmAsync(film.Id, erneut, ct)) gefunden++; }
|
||||
catch (Exception fehler)
|
||||
{
|
||||
log.LogWarning(fehler, "Abspannsuche für „{Film}“ abgebrochen.", titel);
|
||||
log.LogWarning(fehler, "Abspannsuche für „{Film}“ abgebrochen: {Grund}",
|
||||
titel, Fehlertext.FuerMenschen(fehler));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -427,12 +434,12 @@ public sealed class Markenerkennung(
|
||||
// allein ist keine Auskunft: ob die Datei zu wenige Schlüsselbilder
|
||||
// hat, ob der Abspann zu hell war oder ob am Schluss eine Tafel
|
||||
// steht, sieht man nur an diesen drei Zahlen — und sonst gar nicht.
|
||||
if (Abspannkante.Suchen(proben, regeln) is not { } stelle)
|
||||
if (Abspannkante.Suchen(proben, regeln, out var grund) is not { } stelle)
|
||||
{
|
||||
log.LogInformation(
|
||||
"„{Titel}“: kein Abspann — {Proben} Probe(n), Schwelle {Schwelle:0.#}, "
|
||||
+ "letzte Probe {Letzte:0.#}.",
|
||||
name, proben.Count, Abspannkante.Schwelle(proben, regeln),
|
||||
"„{Titel}“: kein Abspann — {Grund}. {Proben} Probe(n), "
|
||||
+ "Schwelle {Schwelle:0.#}, letzte Probe {Letzte:0.#}.",
|
||||
name, grund, proben.Count, Abspannkante.Schwelle(proben, regeln),
|
||||
proben.Count > 0 ? proben[^1].Helligkeit : double.NaN);
|
||||
return false;
|
||||
}
|
||||
@@ -741,8 +748,10 @@ public sealed class Markenerkennung(
|
||||
private async Task VermerkenAsync(
|
||||
FlaroContext db, Guid filmId, Markenart art, CancellationToken ct)
|
||||
{
|
||||
var alt = await db.Markensuchen
|
||||
.FirstOrDefaultAsync(x => x.MovieId == filmId && x.Art == art, ct);
|
||||
var alt = db.Markensuchen.Local
|
||||
.FirstOrDefault(x => x.MovieId == filmId && x.Art == art)
|
||||
?? await db.Markensuchen
|
||||
.FirstOrDefaultAsync(x => x.MovieId == filmId && x.Art == art, ct);
|
||||
|
||||
if (alt is not null) { alt.GelaufenAm = clock.GetUtcNow(); return; }
|
||||
|
||||
@@ -759,8 +768,20 @@ public sealed class Markenerkennung(
|
||||
private async Task VermerkenAsync(
|
||||
FlaroContext db, Guid serieId, int staffel, Markenart art, int folgen, CancellationToken ct)
|
||||
{
|
||||
var alt = await db.Markensuchen
|
||||
.FirstOrDefaultAsync(x => x.SeriesId == serieId && x.Staffel == staffel && x.Art == art, ct);
|
||||
// **Erst im Änderungsspeicher nachsehen, dann in der Datenbank.**
|
||||
//
|
||||
// Eine Abfrage sieht nicht, was derselbe Lauf eben erst hinzugefügt
|
||||
// und noch nicht gespeichert hat. Seit der Bildweg dazukam, wird
|
||||
// für dieselbe Staffel zweimal vermerkt — einmal nach dem
|
||||
// Tonabgleich, einmal vor dem Bild —, und die zweite Abfrage fand
|
||||
// nichts, legte eine zweite Zeile an und brach beim Speichern am
|
||||
// eindeutigen Schlüssel ab. Die Serie verlor dabei alles, was in
|
||||
// diesem Lauf gefunden worden war.
|
||||
var alt = db.Markensuchen.Local
|
||||
.FirstOrDefault(x => x.SeriesId == serieId && x.Staffel == staffel && x.Art == art)
|
||||
?? await db.Markensuchen
|
||||
.FirstOrDefaultAsync(
|
||||
x => x.SeriesId == serieId && x.Staffel == staffel && x.Art == art, ct);
|
||||
|
||||
if (alt is not null)
|
||||
{
|
||||
|
||||
@@ -46,14 +46,26 @@ public readonly record struct Abspannregeln
|
||||
/// </remarks>
|
||||
public double Anteil { get; init; }
|
||||
|
||||
/// <summary>Wie viele helle Proben am Stück eine Strecke nicht brechen.</summary>
|
||||
/// <summary>Wie viele Sekunden hell eine dunkle Strecke nicht brechen.</summary>
|
||||
///
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Eine Schrifttafel blitzt hell auf, ein Logo steht zwei Sekunden im
|
||||
/// Bild. Ohne Nachsicht zerfiele ein Abspann in fünf kurze Strecken,
|
||||
/// von denen keine lang genug wäre.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// <b>In Sekunden, nicht in Proben</b> — dieselbe Lehre wie bei
|
||||
/// <see cref="Schlusstafel"/>, hier eine Zeile höher übersehen. Als
|
||||
/// Probenzahl hing die Nachsicht daran, wie dicht die Datei ihre
|
||||
/// Schlüsselbilder setzt: gemessen an „(T)Raumschiff Surprise" kommen
|
||||
/// 1318 Proben auf zwanzig Minuten, also eine alle 0,9 Sekunden —
|
||||
/// zwei Proben waren dort 1,8 Sekunden Nachsicht. Bei einer Datei mit
|
||||
/// einem Schlüsselbild alle zehn Sekunden waren es zwanzig. Dasselbe
|
||||
/// Zugeständnis, zehnfach verschieden.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public int Lücke { get; init; }
|
||||
public double Nachsicht { get; init; }
|
||||
|
||||
/// <summary>Wie viele Sekunden hell hinter dem Abspann erlaubt sind.</summary>
|
||||
///
|
||||
@@ -73,7 +85,7 @@ public readonly record struct Abspannregeln
|
||||
{
|
||||
Dunkelschwelle = 35,
|
||||
Anteil = 0.55,
|
||||
Lücke = 2,
|
||||
Nachsicht = 10,
|
||||
Schlusstafel = 20,
|
||||
Mindestlänge = 45,
|
||||
Höchstlänge = 12 * 60,
|
||||
@@ -137,9 +149,25 @@ public static class Abspannkante
|
||||
return Math.Max(regeln.Dunkelschwelle, grundton * regeln.Anteil);
|
||||
}
|
||||
|
||||
public static double? Suchen(IReadOnlyList<Bildprobe> proben, Abspannregeln regeln)
|
||||
public static double? Suchen(IReadOnlyList<Bildprobe> proben, Abspannregeln regeln) =>
|
||||
Suchen(proben, regeln, out _);
|
||||
|
||||
/// <param name="grund">
|
||||
/// Woran es lag — für das Protokoll. „Kein Abspann gefunden" ist keine
|
||||
/// Auskunft: ob das Ende zu hell war, die Strecke zu kurz oder zu lang,
|
||||
/// sind drei verschiedene Ursachen mit drei verschiedenen Antworten,
|
||||
/// und ohne diese Zeile beginnt jede Nachfrage wieder beim Raten.
|
||||
/// </param>
|
||||
public static double? Suchen(
|
||||
IReadOnlyList<Bildprobe> proben, Abspannregeln regeln, out string grund)
|
||||
{
|
||||
if (proben.Count < 2) return null;
|
||||
grund = "gefunden";
|
||||
if (proben.Count < 2)
|
||||
{
|
||||
grund = "zu wenige Proben";
|
||||
return null;
|
||||
}
|
||||
|
||||
var schwelle = Schwelle(proben, regeln);
|
||||
|
||||
// **Von hinten.** Der Abspann endet am Dateiende; alles, was davor
|
||||
@@ -150,33 +178,44 @@ public static class Abspannkante
|
||||
// Sender, Studio, Förderer —, oft hell und ein paar Sekunden lang.
|
||||
// Darauf zu bestehen, dass die letzte Probe dunkel ist, hiess: ein
|
||||
// einziges weisses Schlussbild macht den ganzen Abspann unsichtbar.
|
||||
// Dieselbe Nachsicht wie innerhalb der Strecke, also `Lücke`.
|
||||
var letzte = proben.Count - 1;
|
||||
var ende = proben[letzte].Sekunde;
|
||||
while (letzte > 0 && proben[letzte].Helligkeit >= schwelle)
|
||||
{
|
||||
if (ende - proben[letzte].Sekunde >= regeln.Schlusstafel) return null;
|
||||
if (ende - proben[letzte].Sekunde >= regeln.Schlusstafel)
|
||||
{
|
||||
grund = "das Ende ist zu lange hell";
|
||||
return null;
|
||||
}
|
||||
letzte--;
|
||||
}
|
||||
if (proben[letzte].Helligkeit >= schwelle) return null;
|
||||
if (proben[letzte].Helligkeit >= schwelle)
|
||||
{
|
||||
grund = "das Ende ist hell";
|
||||
return null;
|
||||
}
|
||||
|
||||
// **Rückwärts, und helle Stellen in Sekunden verzeihen.** Gezählt
|
||||
// wurden hier früher Proben, und was das bedeutet, hängt allein an
|
||||
// der Schlüsselbilddichte der Datei — siehe `Nachsicht`.
|
||||
var anfang = letzte;
|
||||
var hellAmStück = 0;
|
||||
|
||||
for (var i = letzte - 1; i >= 0; i--)
|
||||
{
|
||||
if (proben[i].Helligkeit < schwelle)
|
||||
{
|
||||
anfang = i;
|
||||
hellAmStück = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (++hellAmStück > regeln.Lücke) break;
|
||||
if (proben[i].Helligkeit < schwelle) { anfang = i; continue; }
|
||||
if (proben[anfang].Sekunde - proben[i].Sekunde > regeln.Nachsicht) break;
|
||||
}
|
||||
|
||||
var länge = proben[letzte].Sekunde - proben[anfang].Sekunde;
|
||||
if (länge < regeln.Mindestlänge || länge > regeln.Höchstlänge) return null;
|
||||
if (länge < regeln.Mindestlänge)
|
||||
{
|
||||
grund = $"die dunkle Strecke ist mit {länge:0} s zu kurz";
|
||||
return null;
|
||||
}
|
||||
if (länge > regeln.Höchstlänge)
|
||||
{
|
||||
grund = $"die dunkle Strecke ist mit {länge:0} s zu lang";
|
||||
return null;
|
||||
}
|
||||
|
||||
// **Die erste dunkle Probe ist nicht der Anfang.** Zwischen ihr und
|
||||
// der letzten hellen davor liegt die Kante irgendwo; wo genau, sagt
|
||||
|
||||
Reference in New Issue
Block a user