Ein doppelter Platzhalter riss jede Serie mit
`log.LogInformation("… {Folgen} … {Folgen}.", a, b, c, d, e)` — sechs
Platzhalter, fünf Argumente. Vorlagen sind positionsbezogen: auch ein
wiederholter Name verbraucht ein weiteres Argument. Der Logger warf eine
Ausnahme, die riss `StaffelMeldenAsync` mit, und die wiederum den ganzen
Serienlauf. Gefunden war dann alles umsonst — „A Knight of the Seven
Kingdoms" und „Alice in Borderland" haben ihre Marken deshalb verloren,
nachdem sie längst berechnet waren.
**CA2017 hatte das gemeldet.** Als Warnung, unter hundert anderen Zeilen,
und ich habe beim Bauen nach „warning CS" gegriffen. Eine neue
`Directory.Build.props` macht genau diese eine Regel zum Fehler — nicht
alle Warnungen, das wäre eine andere Entscheidung. Gegengeprüft: mit der
kaputten Zeile bricht der Bau jetzt ab.
**Und der zweite Fund, aus „Alice im Wunderland".** Die Zeile meldete
„Schwelle 247,6", und das kann bei 0–255 nicht entstehen: die Schwelle
ist Grundton mal 0,55, das wären 450 gewesen. `signalstats` rechnet in
der Skala des Bildformats — bei acht Bit 0–255, bei zehn Bit 0–1023.
Gemessen am selben erzeugten Bild: YAVG 127 gegen YAVG 500. Alle
Schwellen hier sind an acht Bit gemessen; bei einer 10-Bit-Datei bedeutete
dieselbe Zahl ein Viertel.
`format=gray` vor `signalstats` bringt jede Datei auf dieselbe Skala. Es
rechnet dabei auch den Fernsehbereich 16–235 auf 0–255 um — linear, und
die dunklen Werte trifft das am stärksten: der gemessene Abspann liegt
jetzt bei 28 statt bei 40. Der Boden wandert entsprechend von 35 auf 22.
Dazu: die Schlusszeile je Serie trennt Vorspann und Abspann, statt eine
Zahl aus beidem zu machen — „14 Marken" beantwortet nicht, ob die Serie
ihre Vorspänne bekommen hat.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
259996c5ad
commit
f2752e6c05
@@ -0,0 +1,23 @@
|
|||||||
|
<Project>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Eine kaputte Protokollvorlage ist kein Schönheitsfehler.
|
||||||
|
|
||||||
|
`log.LogInformation("… {Folgen} … {Folgen}.", a, b, c, d, e)` wirft zur
|
||||||
|
Laufzeit — jeder Platzhalter verbraucht ein Argument, auch ein
|
||||||
|
wiederholter. Geschieht das in einem Hintergrundlauf, reisst die
|
||||||
|
Ausnahme den ganzen Lauf mit, und alles, was er bis dahin gefunden
|
||||||
|
hatte, ist verloren. Genau so ist es der Markenerkennung ergangen.
|
||||||
|
|
||||||
|
CA2017 hatte das längst gemeldet — als Warnung unter hundert anderen
|
||||||
|
Zeilen, und damit übersehen. Als Fehler ist es nicht zu übersehen.
|
||||||
|
|
||||||
|
Nur diese eine Regel, nicht alle Warnungen: ein pauschales
|
||||||
|
`TreatWarningsAsErrors` wäre eine andere Entscheidung und gehört nicht
|
||||||
|
nebenbei getroffen.
|
||||||
|
-->
|
||||||
|
<PropertyGroup>
|
||||||
|
<WarningsAsErrors>$(WarningsAsErrors);CA2017</WarningsAsErrors>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -510,7 +510,11 @@ public sealed class Markenerkennung(
|
|||||||
log.LogInformation("„{Serie}“: {Staffeln} Staffel(n), {Folgen} Folge(n).",
|
log.LogInformation("„{Serie}“: {Staffeln} Staffel(n), {Folgen} Folge(n).",
|
||||||
serienname, staffeln.Count, folgen.Count);
|
serienname, staffeln.Count, folgen.Count);
|
||||||
|
|
||||||
var gefunden = 0;
|
// Getrennt gezählt: „14 Marken" beantwortet nicht, ob diese Serie
|
||||||
|
// ihre Vorspänne bekommen hat — und das ist die häufigere Frage.
|
||||||
|
var gefundenIntro = 0;
|
||||||
|
var gefundenAbspann = 0;
|
||||||
|
var bildfunde = 0;
|
||||||
var angefasst = false;
|
var angefasst = false;
|
||||||
|
|
||||||
foreach (var staffel in staffeln)
|
foreach (var staffel in staffeln)
|
||||||
@@ -595,9 +599,10 @@ public sealed class Markenerkennung(
|
|||||||
// Ende an; ohne diesen Aufschlag stünde der Knopf bei
|
// Ende an; ohne diesen Aufschlag stünde der Knopf bei
|
||||||
// Minute zwei.
|
// Minute zwei.
|
||||||
var versatz = fensteranfang.GetValueOrDefault(folgenId);
|
var versatz = fensteranfang.GetValueOrDefault(folgenId);
|
||||||
if (await SchreibenAsync(db, folgenId, istFilm: false, art,
|
if (!await SchreibenAsync(db, folgenId, istFilm: false, art,
|
||||||
stelle.Von + versatz, stelle.Bis + versatz, ct))
|
stelle.Von + versatz, stelle.Bis + versatz, ct)) continue;
|
||||||
gefunden++;
|
|
||||||
|
if (art == Markenart.Intro) gefundenIntro++; else gefundenAbspann++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -644,7 +649,8 @@ public sealed class Markenerkennung(
|
|||||||
Name(i), folge.Path, dauer.TotalSeconds, ct))
|
Name(i), folge.Path, dauer.TotalSeconds, ct))
|
||||||
{
|
{
|
||||||
ueberBild.Add(folge.Id);
|
ueberBild.Add(folge.Id);
|
||||||
gefunden++;
|
gefundenAbspann++;
|
||||||
|
bildfunde++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -660,7 +666,10 @@ public sealed class Markenerkennung(
|
|||||||
|
|
||||||
if (angefasst)
|
if (angefasst)
|
||||||
{
|
{
|
||||||
log.LogInformation("„{Serie}“ fertig: {Anzahl} Marke(n).", serienname, gefunden);
|
log.LogInformation(
|
||||||
|
"„{Serie}“ fertig: {Intro} Vorspann, {Abspann} Abspann{Bild}.",
|
||||||
|
serienname, gefundenIntro, gefundenAbspann,
|
||||||
|
bildfunde > 0 ? $", davon {bildfunde} über das Bild" : "");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -668,7 +677,7 @@ public sealed class Markenerkennung(
|
|||||||
serienname);
|
serienname);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Serienstand(gefunden, angefasst);
|
return new Serienstand(gefundenIntro + gefundenAbspann, angefasst);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Was eine Staffel ergeben hat — erst zusammengefasst, dann je Folge.</summary>
|
/// <summary>Was eine Staffel ergeben hat — erst zusammengefasst, dann je Folge.</summary>
|
||||||
@@ -694,9 +703,14 @@ public sealed class Markenerkennung(
|
|||||||
var mitAbspann = marken.Count(m => m.Art == Markenart.Abspann);
|
var mitAbspann = marken.Count(m => m.Art == Markenart.Abspann);
|
||||||
|
|
||||||
log.LogInformation(
|
log.LogInformation(
|
||||||
"„{Serie}“, Staffel {Staffel}: Vorspann in {Intro} von {Folgen} Folge(n), "
|
// **Jeder Platzhalter verbraucht ein Argument, auch ein
|
||||||
+ "Abspann in {Abspann} von {Folgen}.",
|
// wiederholter.** Hier stand `{Folgen}` zweimal und wurde
|
||||||
serienname, staffel, mitIntro, folgen.Count, mitAbspann);
|
// einmal bedient; der Logger warf eine Ausnahme, und die riss
|
||||||
|
// den ganzen Serienlauf mit — gefunden war dann alles umsonst.
|
||||||
|
// CA2017 meldet das jetzt als Fehler, siehe Directory.Build.props.
|
||||||
|
"„{Serie}“, Staffel {Staffel}: {Intro} Vorspann und {Abspann} Abspann "
|
||||||
|
+ "in {Folgen} Folge(n).",
|
||||||
|
serienname, staffel, mitIntro, mitAbspann, folgen.Count);
|
||||||
|
|
||||||
foreach (var (id, name) in folgen)
|
foreach (var (id, name) in folgen)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -25,6 +25,13 @@ public readonly record struct Abspannregeln
|
|||||||
/// realistischer Schriftdichte 32, bei einem dichten Abspann 47. Die
|
/// realistischer Schriftdichte 32, bei einem dichten Abspann 47. Die
|
||||||
/// Fünfunddreissig lag also mitten zwischen den Fällen und schnitt
|
/// Fünfunddreissig lag also mitten zwischen den Fällen und schnitt
|
||||||
/// sie auseinander.
|
/// sie auseinander.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// <b>Und seitdem im vollen Wertebereich.</b> Der Abtaster rechnet
|
||||||
|
/// jede Datei auf 0–255 um, gleich ob sie acht oder zehn Bit hat und
|
||||||
|
/// gleich ob sie den Fernsehbereich 16–235 benutzt. Dieselben
|
||||||
|
/// gemessenen Abspänne liegen darin bei 19 und 36 statt bei 32 und 47;
|
||||||
|
/// der Boden wandert entsprechend von 35 auf 22.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public double Dunkelschwelle { get; init; }
|
public double Dunkelschwelle { get; init; }
|
||||||
|
|
||||||
@@ -83,7 +90,7 @@ public readonly record struct Abspannregeln
|
|||||||
|
|
||||||
public static Abspannregeln Vorgabe => new()
|
public static Abspannregeln Vorgabe => new()
|
||||||
{
|
{
|
||||||
Dunkelschwelle = 35,
|
Dunkelschwelle = 22,
|
||||||
Anteil = 0.55,
|
Anteil = 0.55,
|
||||||
Nachsicht = 10,
|
Nachsicht = 10,
|
||||||
Schlusstafel = 20,
|
Schlusstafel = 20,
|
||||||
|
|||||||
@@ -109,8 +109,20 @@ public sealed partial class Bildabtaster(FfmpegOptions optionen, ILogger<Bildabt
|
|||||||
start.ArgumentList.Add("-an");
|
start.ArgumentList.Add("-an");
|
||||||
start.ArgumentList.Add("-sn");
|
start.ArgumentList.Add("-sn");
|
||||||
start.ArgumentList.Add("-vf");
|
start.ArgumentList.Add("-vf");
|
||||||
|
|
||||||
|
// **`format=gray` vor `signalstats`, und das ist nicht kosmetisch.**
|
||||||
|
//
|
||||||
|
// `signalstats` rechnet in der Skala des Bildformats: bei acht Bit
|
||||||
|
// 0–255, bei zehn Bit 0–1023. Gemessen am selben erzeugten Bild
|
||||||
|
// einmal als 8-Bit und einmal als 10-Bit: YAVG 127 gegen YAVG 500.
|
||||||
|
// Alle Schwellen hier sind an acht Bit gemessen; bei einer
|
||||||
|
// 10-Bit-Datei bedeutete `Dunkelschwelle = 35` plötzlich ein
|
||||||
|
// Viertel davon, und im Protokoll standen Zahlen, die sich mit
|
||||||
|
// denen anderer Dateien nicht vergleichen liessen — „Schwelle
|
||||||
|
// 247,6" war der Hinweis, dem das hier zu verdanken ist.
|
||||||
start.ArgumentList.Add(
|
start.ArgumentList.Add(
|
||||||
$"scale={Breite}:-2,signalstats,metadata=print:key=lavfi.signalstats.YAVG:file=-");
|
$"scale={Breite}:-2,format=gray,signalstats,"
|
||||||
|
+ "metadata=print:key=lavfi.signalstats.YAVG:file=-");
|
||||||
start.ArgumentList.Add("-f");
|
start.ArgumentList.Add("-f");
|
||||||
start.ArgumentList.Add("null");
|
start.ArgumentList.Add("null");
|
||||||
start.ArgumentList.Add("-");
|
start.ArgumentList.Add("-");
|
||||||
|
|||||||
@@ -184,3 +184,82 @@ public class BildabtasterLaufTests
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>Dieselbe Helligkeit, gleich welche Bittiefe die Datei hat.</summary>
|
||||||
|
///
|
||||||
|
/// <remarks>
|
||||||
|
/// <para>
|
||||||
|
/// <b>Der Fehler, für den diese Prüfung steht.</b> <c>signalstats</c>
|
||||||
|
/// rechnet in der Skala des Bildformats: bei acht Bit 0–255, bei zehn Bit
|
||||||
|
/// 0–1023. Alle Schwellen der Abspannsuche sind an acht Bit gemessen —
|
||||||
|
/// bei einer 10-Bit-Datei bedeutete dieselbe Zahl ein Viertel.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// Aufgefallen ist es an einer Protokollzeile: „Schwelle 247,6". Die kann
|
||||||
|
/// bei 0–255 nicht entstehen, denn sie ist Grundton mal 0,55 — das wären
|
||||||
|
/// 450 gewesen.
|
||||||
|
/// </para>
|
||||||
|
/// </remarks>
|
||||||
|
public class BildabtasterBittiefeTests
|
||||||
|
{
|
||||||
|
private string _ordner = "";
|
||||||
|
|
||||||
|
[OneTimeSetUp]
|
||||||
|
public void Erzeugen() =>
|
||||||
|
_ordner = Directory.CreateDirectory(Path.Combine(
|
||||||
|
Path.GetTempPath(), "flaro-bittiefe-" + Guid.CreateVersion7().ToString("N"))).FullName;
|
||||||
|
|
||||||
|
[OneTimeTearDown]
|
||||||
|
public void Aufraeumen()
|
||||||
|
{
|
||||||
|
try { Directory.Delete(_ordner, recursive: true); } catch (IOException) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
private string Erzeugen(string pixelformat)
|
||||||
|
{
|
||||||
|
var datei = Path.Combine(_ordner, $"{pixelformat}.mp4");
|
||||||
|
var start = new ProcessStartInfo("ffmpeg")
|
||||||
|
{
|
||||||
|
ArgumentList =
|
||||||
|
{
|
||||||
|
"-hide_banner", "-loglevel", "error", "-y",
|
||||||
|
"-f", "lavfi", "-i", "testsrc2=size=320x180:rate=24:duration=4",
|
||||||
|
"-c:v", "libx264", "-preset", "ultrafast", "-pix_fmt", pixelformat, datei,
|
||||||
|
},
|
||||||
|
RedirectStandardError = true,
|
||||||
|
UseShellExecute = false,
|
||||||
|
};
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var prozess = Process.Start(start);
|
||||||
|
prozess!.WaitForExit(60_000);
|
||||||
|
if (prozess.ExitCode != 0 || !File.Exists(datei))
|
||||||
|
Assert.Ignore($"ffmpeg kann {pixelformat} hier nicht erzeugen.");
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
Assert.Ignore("Ohne ffmpeg lässt sich das nicht prüfen.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return datei;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task Acht_und_zehn_Bit_ergeben_dieselbe_Zahl()
|
||||||
|
{
|
||||||
|
var abtaster = new Bildabtaster(
|
||||||
|
FfmpegOptions.FromEnvironment(), NullLogger<Bildabtaster>.Instance);
|
||||||
|
|
||||||
|
var acht = await abtaster.HelligkeitAsync(Erzeugen("yuv420p"), bei: 2);
|
||||||
|
var zehn = await abtaster.HelligkeitAsync(Erzeugen("yuv420p10le"), bei: 2);
|
||||||
|
|
||||||
|
Assert.Multiple(() =>
|
||||||
|
{
|
||||||
|
Assert.That(acht, Is.Not.Null);
|
||||||
|
Assert.That(zehn, Is.Not.Null.And.EqualTo(acht!.Value).Within(3),
|
||||||
|
"ohne `format=gray` stünde hier das Vierfache");
|
||||||
|
Assert.That(zehn, Is.LessThan(256), "die Skala muss 0–255 sein");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user