diff --git a/server/Directory.Build.props b/server/Directory.Build.props
new file mode 100644
index 0000000..3a171ee
--- /dev/null
+++ b/server/Directory.Build.props
@@ -0,0 +1,23 @@
+
+
+
+
+ $(WarningsAsErrors);CA2017
+
+
+
diff --git a/server/src/Flaro.Api/Markenerkennung.cs b/server/src/Flaro.Api/Markenerkennung.cs
index b281f48..998ebff 100644
--- a/server/src/Flaro.Api/Markenerkennung.cs
+++ b/server/src/Flaro.Api/Markenerkennung.cs
@@ -510,7 +510,11 @@ public sealed class Markenerkennung(
log.LogInformation("„{Serie}“: {Staffeln} Staffel(n), {Folgen} Folge(n).",
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;
foreach (var staffel in staffeln)
@@ -595,9 +599,10 @@ public sealed class Markenerkennung(
// Ende an; ohne diesen Aufschlag stünde der Knopf bei
// Minute zwei.
var versatz = fensteranfang.GetValueOrDefault(folgenId);
- if (await SchreibenAsync(db, folgenId, istFilm: false, art,
- stelle.Von + versatz, stelle.Bis + versatz, ct))
- gefunden++;
+ if (!await SchreibenAsync(db, folgenId, istFilm: false, art,
+ stelle.Von + versatz, stelle.Bis + versatz, ct)) continue;
+
+ if (art == Markenart.Intro) gefundenIntro++; else gefundenAbspann++;
}
}
@@ -644,7 +649,8 @@ public sealed class Markenerkennung(
Name(i), folge.Path, dauer.TotalSeconds, ct))
{
ueberBild.Add(folge.Id);
- gefunden++;
+ gefundenAbspann++;
+ bildfunde++;
}
}
}
@@ -660,7 +666,10 @@ public sealed class Markenerkennung(
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
{
@@ -668,7 +677,7 @@ public sealed class Markenerkennung(
serienname);
}
- return new Serienstand(gefunden, angefasst);
+ return new Serienstand(gefundenIntro + gefundenAbspann, angefasst);
}
/// Was eine Staffel ergeben hat — erst zusammengefasst, dann je Folge.
@@ -694,9 +703,14 @@ public sealed class Markenerkennung(
var mitAbspann = marken.Count(m => m.Art == Markenart.Abspann);
log.LogInformation(
- "„{Serie}“, Staffel {Staffel}: Vorspann in {Intro} von {Folgen} Folge(n), "
- + "Abspann in {Abspann} von {Folgen}.",
- serienname, staffel, mitIntro, folgen.Count, mitAbspann);
+ // **Jeder Platzhalter verbraucht ein Argument, auch ein
+ // wiederholter.** Hier stand `{Folgen}` zweimal und wurde
+ // 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)
{
diff --git a/server/src/Flaro.Core/Media/Abspannkante.cs b/server/src/Flaro.Core/Media/Abspannkante.cs
index 4e8faf2..36c32c7 100644
--- a/server/src/Flaro.Core/Media/Abspannkante.cs
+++ b/server/src/Flaro.Core/Media/Abspannkante.cs
@@ -25,6 +25,13 @@ public readonly record struct Abspannregeln
/// realistischer Schriftdichte 32, bei einem dichten Abspann 47. Die
/// Fünfunddreissig lag also mitten zwischen den Fällen und schnitt
/// sie auseinander.
+ ///
+ ///
+ /// Und seitdem im vollen Wertebereich. 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.
///
public double Dunkelschwelle { get; init; }
@@ -83,7 +90,7 @@ public readonly record struct Abspannregeln
public static Abspannregeln Vorgabe => new()
{
- Dunkelschwelle = 35,
+ Dunkelschwelle = 22,
Anteil = 0.55,
Nachsicht = 10,
Schlusstafel = 20,
diff --git a/server/src/Flaro.Media/Bildabtaster.cs b/server/src/Flaro.Media/Bildabtaster.cs
index dd2e9ec..b6d989a 100644
--- a/server/src/Flaro.Media/Bildabtaster.cs
+++ b/server/src/Flaro.Media/Bildabtaster.cs
@@ -109,8 +109,20 @@ public sealed partial class Bildabtaster(FfmpegOptions optionen, ILoggerDieselbe Helligkeit, gleich welche Bittiefe die Datei hat.
+///
+///
+///
+/// Der Fehler, für den diese Prüfung steht. signalstats
+/// 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.
+///
+///
+/// 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.
+///
+///
+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.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");
+ });
+ }
+}