diff --git a/server/src/Flaro.Api/Markenerkennung.cs b/server/src/Flaro.Api/Markenerkennung.cs index 6f6734a..50ca154 100644 --- a/server/src/Flaro.Api/Markenerkennung.cs +++ b/server/src/Flaro.Api/Markenerkennung.cs @@ -510,17 +510,17 @@ public sealed class Markenerkennung( // weiterbauen lohnt — und das sieht man nur an vielen Dateien. log.LogInformation( "„{Titel}“: kein Abspann — {Grund}. {Proben} Probe(n), " - + "Schwelle {Schwelle:0.#}, letzte Probe {Letzte:0.#}. {Kapitel}", - name, grund, proben.Count, Abspannkante.Schwelle(proben, regeln), - proben.Count > 0 ? proben[^1].Helligkeit : double.NaN, - Kapitellage(kapitel)); + + "letzte Probe {Letzte:0.#} % dunkel (nötig: {Noetig} %). {Kapitel}", + name, grund, proben.Count, + proben.Count > 0 ? proben[^1].Dunkelanteil : double.NaN, + regeln.Mindestanteil, Kapitellage(kapitel)); return false; } var hell = Abspannkante.HellDavor(proben, stelle, regeln) ?? ab; var kante = await Abspannkante.VerfeinernAsync( - hell, stelle, Abspannkante.Schwelle(proben, regeln), - wo => abtaster.HelligkeitAsync(pfad, wo, ct)); + hell, stelle, regeln.Mindestanteil, + wo => abtaster.DunkelanteilAsync(pfad, wo, ct)); log.LogInformation("„{Titel}“: Abspann im Bild ab {Stelle:h\\:mm\\:ss}.", name, TimeSpan.FromSeconds(kante)); diff --git a/server/src/Flaro.Core/Media/Abspannkante.cs b/server/src/Flaro.Core/Media/Abspannkante.cs index 36c32c7..e43ab0b 100644 --- a/server/src/Flaro.Core/Media/Abspannkante.cs +++ b/server/src/Flaro.Core/Media/Abspannkante.cs @@ -1,10 +1,26 @@ namespace Flaro.Core.Media; -/// Ein Messpunkt aus dem Bild: wann, und wie hell. +/// Ein Messpunkt aus dem Bild: wann, und wie schwarz. +/// +/// +/// +/// Der Anteil, nicht der Mittelwert — und das ist der Unterschied +/// zwischen „funktioniert manchmal" und „funktioniert". Ein Mittelwert +/// über das ganze Bild steigt mit jeder Textzeile: derselbe Abspann misst +/// 19, wenn wenig Schrift darin steht, und 37, wenn viel darin steht. Die +/// Schwelle musste zwischen diesen Wert und die Handlung passen, und der +/// Abstand schrumpfte mit jedem Namen. +/// +/// +/// Der Anteil dunkler Pixel tut das nicht: Schrift belegt ein bis zehn +/// Prozent der Fläche, der Rest bleibt schwarz. Gemessen an denselben +/// zwei Abspännen — 84 % und 87 % gegen 0 % in der Handlung. +/// +/// /// /// Die Stelle im Film. -/// Mittlere Leuchtdichte, 0 bis 255. -public readonly record struct Bildprobe(double Sekunde, double Helligkeit); +/// Wie viel Prozent des Bildes dunkel sind, 0 bis 100. +public readonly record struct Bildprobe(double Sekunde, double Dunkelanteil); /// Die Schwellen, nach denen eine dunkle Strecke ein Abspann ist. /// @@ -15,43 +31,29 @@ public readonly record struct Bildprobe(double Sekunde, double Helligkeit); /// public readonly record struct Abspannregeln { - /// Die unterste Schwelle, unter der immer „dunkel" gilt (0–255). - /// - /// - /// Nur noch der Boden, nicht mehr die Schwelle. Sie stand lange - /// allein da, und das war falsch gemessen: das Prüfbild hatte 0,8 - /// Prozent weisse Fläche und kam auf 18. Ein echter Abspann hat mehr - /// Schrift. Nachgemessen an 1080p mit zwei Spalten Namen: bei - /// 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; } - - /// Wie dunkel eine Strecke gegenüber dem Film sein muss. + /// Wie viel Prozent des Bildes dunkel sein müssen (0–100). /// /// /// - /// Dunkel ist keine absolute Grösse. Entscheidend ist nicht, wie - /// dunkel der Abspann ist, sondern dass er viel dunkler ist als das, - /// was davor lief — und das misst sich am Film selbst. Ein heller Film - /// (Grundton 126) erkennt damit auch einen schriftreichen Abspann bei - /// 47; ein durchweg dunkler Film rutscht nicht in Fehlalarme, weil - /// die Schwelle nie unter fällt. + /// Sechzig, und der Abstand ist nach beiden Seiten gross. + /// Gemessen an erzeugtem 1080p-Material mit zwei Spalten Namen, durch + /// dieselbe ffmpeg-Kette wie im Betrieb: /// + /// + /// Abspann, schriftreich: 84 % + /// Abspann, schlank: 87 % + /// dunkle Nachtszene: 37 % + /// Handlung: 0 % + /// /// - /// 0,55 heisst: knapp unter die Hälfte. Bei 126 ergibt das 69 — weit - /// über beiden gemessenen Abspannwerten und weit unter dem Inhalt. + /// Und damit entfällt die relative Schwelle. Sie war nötig, + /// solange der Mittelwert gemessen wurde: wie hell ein Abspann ist, + /// hing am Film, also musste sich die Schwelle am Film ausrichten. Der + /// Anteil dunkler Pixel eicht sich selbst — ein heller Film und ein + /// dunkler ergeben in der Handlung beide nahezu null. /// /// - public double Anteil { get; init; } + public double Mindestanteil { get; init; } /// Wie viele Sekunden hell eine dunkle Strecke nicht brechen. /// @@ -90,8 +92,7 @@ public readonly record struct Abspannregeln public static Abspannregeln Vorgabe => new() { - Dunkelschwelle = 22, - Anteil = 0.55, + Mindestanteil = 60, Nachsicht = 10, Schlusstafel = 20, Mindestlänge = 45, @@ -129,33 +130,6 @@ public static class Abspannkante /// Messpunkte über das Ende des Films, aufsteigend nach Zeit. Sie dürfen /// ungleichmässig liegen: Schlüsselbilder tun das. /// - /// Ab welcher Helligkeit eine Probe in diesem Film dunkel ist. - /// - /// - /// - /// Das obere Viertel als Grundton. Nicht der Mittelwert: der - /// zöge den Abspann selbst mit hinein. Nicht der Höchstwert: ein - /// einzelner Blitz verschöbe alles. Drei Viertel ist robust, solange - /// der Abspann nicht das ganze Fenster füllt — und das kann er nicht, - /// lässt ihm höchstens gut die - /// Hälfte davon. - /// - /// - /// Öffentlich, weil das Nachmessen dieselbe Schwelle braucht. Zweimal - /// gerechnet wären es zwei Schwellen, und die Kante läge woanders als - /// der Fund. - /// - /// - public static double Schwelle(IReadOnlyList proben, Abspannregeln regeln) - { - if (proben.Count == 0) return regeln.Dunkelschwelle; - - var sortiert = proben.Select(p => p.Helligkeit).OrderBy(h => h).ToArray(); - var grundton = sortiert[Math.Min(sortiert.Length - 1, (int)(sortiert.Length * 0.75))]; - - return Math.Max(regeln.Dunkelschwelle, grundton * regeln.Anteil); - } - public static double? Suchen(IReadOnlyList proben, Abspannregeln regeln) => Suchen(proben, regeln, out _); @@ -175,8 +149,6 @@ public static class Abspannkante return null; } - var schwelle = Schwelle(proben, regeln); - // **Von hinten.** Der Abspann endet am Dateiende; alles, was davor // dunkel war und wieder hell wurde, war eine Schwarzblende. // @@ -187,7 +159,7 @@ public static class Abspannkante // einziges weisses Schlussbild macht den ganzen Abspann unsichtbar. var letzte = proben.Count - 1; var ende = proben[letzte].Sekunde; - while (letzte > 0 && proben[letzte].Helligkeit >= schwelle) + while (letzte > 0 && proben[letzte].Dunkelanteil < regeln.Mindestanteil) { if (ende - proben[letzte].Sekunde >= regeln.Schlusstafel) { @@ -196,7 +168,7 @@ public static class Abspannkante } letzte--; } - if (proben[letzte].Helligkeit >= schwelle) + if (proben[letzte].Dunkelanteil < regeln.Mindestanteil) { grund = "das Ende ist hell"; return null; @@ -208,7 +180,7 @@ public static class Abspannkante var anfang = letzte; for (var i = letzte - 1; i >= 0; i--) { - if (proben[i].Helligkeit < schwelle) { anfang = i; continue; } + if (proben[i].Dunkelanteil >= regeln.Mindestanteil) { anfang = i; continue; } if (proben[anfang].Sekunde - proben[i].Sekunde > regeln.Nachsicht) break; } @@ -235,12 +207,11 @@ public static class Abspannkante public static double? HellDavor( IReadOnlyList proben, double dunkelAb, Abspannregeln regeln) { - var schwelle = Schwelle(proben, regeln); double? letzteHelle = null; foreach (var p in proben) { if (p.Sekunde >= dunkelAb) break; - if (p.Helligkeit >= schwelle) letzteHelle = p.Sekunde; + if (p.Dunkelanteil < regeln.Mindestanteil) letzteHelle = p.Sekunde; } return letzteHelle; } @@ -263,7 +234,7 @@ public static class Abspannkante /// /// public static async Task VerfeinernAsync( - double hell, double dunkel, double schwelle, + double hell, double dunkel, double mindestanteil, Func> messen, double genauigkeit = 4) { if (genauigkeit <= 0) genauigkeit = 1; @@ -272,9 +243,9 @@ public static class Abspannkante { var mitte = hell + (dunkel - hell) / 2; var wert = await messen(mitte); - if (wert is not { } helligkeit) return dunkel; + if (wert is not { } anteil) return dunkel; - if (helligkeit < schwelle) dunkel = mitte; + if (anteil >= mindestanteil) dunkel = mitte; else hell = mitte; } diff --git a/server/src/Flaro.Media/Bildabtaster.cs b/server/src/Flaro.Media/Bildabtaster.cs index b6d989a..ccc6360 100644 --- a/server/src/Flaro.Media/Bildabtaster.cs +++ b/server/src/Flaro.Media/Bildabtaster.cs @@ -34,7 +34,26 @@ public sealed partial class Bildabtaster(FfmpegOptions optionen, ILogger private const string Breite = "160"; - /// Die Helligkeit an den Schlüsselbildern eines Abschnitts. + /// Unter welchem Lumawert ein einzelnes Pixel dunkel heisst. + /// + /// + /// + /// Nicht der Mittelwert des Bildes, sondern jedes Pixel für sich. + /// blackframe zählt, wie viel Prozent eines Bildes darunter + /// liegen — und das ist ein ganz anderes Merkmal als der Mittelwert: + /// weisse Schrift hebt den Mittelwert, belegt aber nur ein paar + /// Prozent der Fläche. + /// + /// + /// Vierundzwanzig, gemessen über eine Reihe von Schwellen. Darunter + /// (16) fällt der Abspann selbst auf zehn Prozent ab, darüber (32) + /// fängt die Handlung an mitzuzählen. Bei 24: Abspann 84 bis 87 + /// Prozent, Handlung 0, eine dunkle Nachtszene 37. + /// + /// + private const string Dunkelschwelle = "24"; + + /// Der Dunkelanteil an den Schlüsselbildern eines Abschnitts. /// /// /// Nur Schlüsselbilder, und das ist der ganze Trick. Gemessen an @@ -42,7 +61,7 @@ public sealed partial class Bildabtaster(FfmpegOptions optionen, ILogger nach. + /// nach. /// public async Task> UeberblickAsync( string pfad, double ab, double dauer, CancellationToken ct = default) @@ -53,7 +72,7 @@ public sealed partial class Bildabtaster(FfmpegOptions optionen, ILoggerDie mittlere Helligkeit einer einzelnen Sekunde. + /// Der mittlere Dunkelanteil einer einzelnen Sekunde. /// /// /// Alle Bilder, nicht nur Schlüsselbilder: eine Sekunde enthält sonst @@ -61,7 +80,7 @@ public sealed partial class Bildabtaster(FfmpegOptions optionen, ILogger - public async Task HelligkeitAsync( + public async Task DunkelanteilAsync( string pfad, double bei, CancellationToken ct = default) { var ausgabe = await LaufenAsync( @@ -70,7 +89,7 @@ public sealed partial class Bildabtaster(FfmpegOptions optionen, ILogger p.Helligkeit); + return proben.Average(p => p.Dunkelanteil); } private async Task LaufenAsync( @@ -110,19 +129,15 @@ public sealed partial class Bildabtaster(FfmpegOptions optionen, ILogger /// Mit erfundenen Messreihen statt mit Filmen. Geprüft wird das -/// Beurteilen, nicht ob ffmpeg rechnen kann. Die Zahlen stammen aus einer -/// Messung an einem erzeugten Film: Handlung 125, Abspann 18. +/// Beurteilen, nicht ob ffmpeg rechnen kann. Die Zahlen sind der Anteil +/// dunkler Pixel je Bild und stammen aus einer Messung an erzeugtem +/// 1080p-Material: Handlung 0 %, Abspann 84 bis 87 %, dunkle Nachtszene +/// 37 %. /// public class AbspannkanteTests { @@ -20,7 +22,7 @@ public class AbspannkanteTests { var proben = new List(); for (var t = von; t <= bis; t += 10) - proben.Add(new Bildprobe(t, abspannAb is { } ab && t >= ab ? 18 : 125)); + proben.Add(new Bildprobe(t, abspannAb is { } ab && t >= ab ? 85 : 0)); return proben; } @@ -50,7 +52,7 @@ public class AbspannkanteTests public void Eine_Schwarzblende_in_der_Mitte_zaehlt_nicht() { var proben = Reihe(0, 600, abspannAb: null); - for (var i = 20; i < 30; i++) proben[i] = proben[i] with { Helligkeit = 10 }; + for (var i = 20; i < 30; i++) proben[i] = proben[i] with { Dunkelanteil = 92 }; Assert.That(Abspannkante.Suchen(proben, Regeln), Is.Null, "sie hält nicht bis zum Ende durch"); @@ -66,7 +68,7 @@ public class AbspannkanteTests { var proben = Reihe(0, 600, abspannAb: 420); for (var i = proben.Count - 5; i < proben.Count; i++) - proben[i] = proben[i] with { Helligkeit = 120 }; + proben[i] = proben[i] with { Dunkelanteil = 0 }; Assert.That(Abspannkante.Suchen(proben, Regeln), Is.Null); } @@ -85,7 +87,7 @@ public class AbspannkanteTests public void Eine_kurze_helle_Tafel_bricht_nichts() { var proben = Reihe(0, 600, abspannAb: 420); - proben[50] = proben[50] with { Helligkeit = 140 }; + proben[50] = proben[50] with { Dunkelanteil = 0 }; Assert.That(Abspannkante.Suchen(proben, Regeln), Is.EqualTo(420)); } @@ -121,10 +123,10 @@ public class AbspannkanteTests const double echteKante = 417.3; var sonden = 0; - var kante = await Abspannkante.VerfeinernAsync(410, 420, Regeln.Dunkelschwelle, stelle => + var kante = await Abspannkante.VerfeinernAsync(410, 420, Regeln.Mindestanteil, stelle => { sonden++; - return Task.FromResult(stelle >= echteKante ? 18 : 125); + return Task.FromResult(stelle >= echteKante ? 85 : 0); }); Assert.Multiple(() => @@ -141,7 +143,7 @@ public class AbspannkanteTests public async Task Ohne_Antwort_bleibt_es_beim_groben_Fund() { var kante = await Abspannkante.VerfeinernAsync( - 410, 420, Regeln.Dunkelschwelle, _ => Task.FromResult(null)); + 410, 420, Regeln.Mindestanteil, _ => Task.FromResult(null)); Assert.That(kante, Is.EqualTo(420)); } @@ -151,10 +153,10 @@ public class AbspannkanteTests public async Task Ein_enger_Bereich_wird_nicht_angefasst() { var sonden = 0; - var kante = await Abspannkante.VerfeinernAsync(418, 420, Regeln.Dunkelschwelle, _ => + var kante = await Abspannkante.VerfeinernAsync(418, 420, Regeln.Mindestanteil, _ => { sonden++; - return Task.FromResult(18); + return Task.FromResult(85); }); Assert.Multiple(() => @@ -165,32 +167,35 @@ public class AbspannkanteTests } } -/// Die Schwelle an gemessenen Abspannwerten, nicht an erfundenen. +/// Der Anteil dunkler Pixel, an gemessenen Werten. /// /// /// -/// Der Fehler, für den diese Prüfungen stehen. Die Erkennung lief -/// gegen eine feste Schwelle von 35, und die stammte aus einem Prüfbild -/// mit 0,8 Prozent weisser Fläche — Helligkeit 18. Ein echter Abspann hat -/// mehr Schrift. +/// Wofür diese Prüfungen stehen. Gemessen wurde lange der +/// Mittelwert der Helligkeit, und daran scheiterte die Erkennung +/// wiederholt: derselbe Abspann misst 19, wenn wenig Schrift darin +/// steht, und 37, wenn viel darin steht — die Schwelle musste zwischen +/// diesen Wert und die Handlung passen, und der Abstand schrumpfte mit +/// jedem Namen. Zweimal nachgezogen, zweimal wieder getroffen. /// /// -/// Nachgemessen an erzeugtem 1080p-Material mit zwei Spalten Namen, +/// Der Anteil dunkler Pixel kennt dieses Problem nicht. Schrift +/// belegt ein bis zehn Prozent der Fläche; der Rest bleibt schwarz. +/// Nachgemessen an erzeugtem 1080p-Material mit zwei Spalten Namen, /// durch dieselbe ffmpeg-Kette wie im Betrieb -/// (scale=160:-2,signalstats): +/// (format=gray,blackframe=amount=0:threshold=24): /// /// -/// Handlung: 126 -/// Abspann, realistische Schriftdichte (1,3 % Weiss): 32 -/// Abspann, dicht gesetzt (9,5 % Weiss): 47 +/// Handlung: 0 % +/// Abspann, schriftreich: 84 % +/// Abspann, schlank: 87 % +/// dunkle Nachtszene: 37 % /// /// -/// Die Fünfunddreissig lag also mitten zwischen den beiden Abspannfällen. -/// Seitdem ist sie nur noch der Boden; gemessen wird gegen den Grundton -/// des Films. +/// Sechzig Prozent trennen das sauber, und zwar nach beiden Seiten. /// /// -public class AbspannSchwellenTests +public class AbspannAnteilTests { private static readonly Abspannregeln Regeln = Abspannregeln.Vorgabe; @@ -200,7 +205,8 @@ public class AbspannSchwellenTests /// Zwanzig Minuten vor dem Ende einer Folge von fünfundvierzig, an den /// Schlüsselbildern abgetastet — also alle fünf Sekunden eine Probe. /// - private static List Fenster(double abspannAb, double imAbspann, double imInhalt = 126) + private static List Fenster( + double abspannAb, double imAbspann, double imInhalt = 0) { var proben = new List(); for (var t = 1500.0; t <= 2700; t += 5) @@ -208,62 +214,44 @@ public class AbspannSchwellenTests return proben; } - /// Der Fall, an dem es scheiterte: ein schriftreicher Abspann. + /// Der schriftreiche Abspann — der Fall, an dem es scheiterte. + [Test] + public void Ein_dichter_Abspann_bei_vierundachtzig_Prozent_wird_gefunden() + { + var proben = Fenster(abspannAb: 2340, imAbspann: 84); + + Assert.That(Abspannkante.Suchen(proben, Regeln), Is.EqualTo(2340.0).Within(5)); + } + + [Test] + public void Ein_schlanker_Abspann_bei_siebenundachtzig_Prozent_auch() + { + var proben = Fenster(abspannAb: 2340, imAbspann: 87); + + Assert.That(Abspannkante.Suchen(proben, Regeln), Is.EqualTo(2340.0).Within(5)); + } + + /// **Eine dunkle Nachtszene ist kein Abspann.** /// /// - /// Mit der alten festen Schwelle von 35 ist diese Prüfung rot: 47 gilt - /// dort als hell, und die Folge bekam keine Marke. - /// - [Test] - public void Ein_dichter_Abspann_bei_siebenundvierzig_wird_gefunden() - { - var proben = Fenster(abspannAb: 2340, imAbspann: 47); - - Assert.That(Abspannkante.Suchen(proben, Regeln), Is.EqualTo(2340).Within(5)); - } - - [Test] - public void Ein_schlanker_Abspann_bei_zweiunddreissig_wird_gefunden() - { - var proben = Fenster(abspannAb: 2340, imAbspann: 32); - - Assert.That(Abspannkante.Suchen(proben, Regeln), Is.EqualTo(2340).Within(5)); - } - - /// Und die Schwelle liegt zwischen beidem, nicht daneben. - [Test] - public void Die_Schwelle_liegt_zwischen_Handlung_und_Abspann() - { - var schwelle = Abspannkante.Schwelle(Fenster(2340, 47), Regeln); - - Assert.Multiple(() => - { - Assert.That(schwelle, Is.GreaterThan(47), "ein dichter Abspann muss darunter liegen"); - Assert.That(schwelle, Is.LessThan(126), "die Handlung muss darüber liegen"); - }); - } - - /// **Ein dunkler Film wird dadurch nicht zum Dauerabspann.** - /// - /// - /// Eine relative Schwelle kann nach unten entgleisen: bei einem Film, - /// der durchweg dämmrig ist, wäre „etwas mehr als halb so hell" bald - /// jede Nachtszene. Deshalb bleibt Dunkelschwelle als Boden — - /// hier sorgt sie dafür, dass eine Schlussszene bei 40 nichts auslöst. + /// Mit dem Mittelwert war das die heikelste Grenze: eine Nachtszene + /// misst 28, ein schlanker Abspann 19 — zwischen diesen neun Punkten + /// musste die Schwelle liegen. Nach Pixelanteilen sind es 37 gegen 87, + /// und die Frage stellt sich nicht mehr. /// [Test] public void Eine_duestere_Schlussszene_ist_kein_Abspann() { - var proben = Fenster(abspannAb: 2340, imAbspann: 40, imInhalt: 55); + var proben = Fenster(abspannAb: 2340, imAbspann: 37, imInhalt: 12); Assert.That(Abspannkante.Suchen(proben, Regeln), Is.Null); } - /// Helle Handlung bis zum Ende bleibt Handlung. + /// Und ein heller Film bis zum Ende bleibt Handlung. [Test] public void Ohne_dunkles_Ende_kein_Fund() { - var proben = Fenster(abspannAb: double.MaxValue, imAbspann: 0); + var proben = Fenster(abspannAb: double.MaxValue, imAbspann: 99); Assert.That(Abspannkante.Suchen(proben, Regeln), Is.Null); } @@ -287,8 +275,8 @@ public class AbspannSchlusstafelTests var proben = new List(); for (var t = 1500.0; t <= 2700; t += 5) { - var hell = t >= tafelAb ? 210 : t < abspannAb ? 126 : 32; - proben.Add(new Bildprobe(t, hell)); + var dunkel = t >= tafelAb ? 0 : t < abspannAb ? 0 : 85; + proben.Add(new Bildprobe(t, dunkel)); } return proben; } @@ -348,7 +336,7 @@ public class AbspannNachsichtTests for (var t = 1500.0; t <= 2700; t += takt) { var imLogo = t >= 2500 && t < 2505; - proben.Add(new Bildprobe(t, t < 2340 ? 126 : imLogo ? 180 : 32)); + proben.Add(new Bildprobe(t, t < 2340 ? 0 : imLogo ? 5 : 85)); } return proben; } @@ -383,7 +371,7 @@ public class AbspannNachsichtTests { // Dunkel ab 2340, aber von 2400 bis 2460 eine Minute hell. var hell = t < 2340 || (t >= 2400 && t < 2460); - proben.Add(new Bildprobe(t, hell ? 126 : 32)); + proben.Add(new Bildprobe(t, hell ? 0 : 85)); } // Gefunden wird nur das Stück hinter der hellen Minute. @@ -395,14 +383,14 @@ public class AbspannNachsichtTests public void Der_Grund_wird_benannt() { var hell = new List(); - for (var t = 1500.0; t <= 2700; t += 5) hell.Add(new Bildprobe(t, 126)); + for (var t = 1500.0; t <= 2700; t += 5) hell.Add(new Bildprobe(t, 0)); Abspannkante.Suchen(hell, Regeln, out var grund); Assert.That(grund, Does.Contain("hell")); var kurz = new List(); for (var t = 1500.0; t <= 2700; t += 5) - kurz.Add(new Bildprobe(t, t < 2680 ? 126 : 32)); + kurz.Add(new Bildprobe(t, t < 2680 ? 0 : 85)); Abspannkante.Suchen(kurz, Regeln, out var zuKurz); Assert.That(zuKurz, Does.Contain("kurz")); diff --git a/server/tests/Flaro.Media.Tests/BildabtasterTests.cs b/server/tests/Flaro.Media.Tests/BildabtasterTests.cs index 8a04d5e..04da549 100644 --- a/server/tests/Flaro.Media.Tests/BildabtasterTests.cs +++ b/server/tests/Flaro.Media.Tests/BildabtasterTests.cs @@ -13,9 +13,9 @@ public class BildabtasterLesenTests { var ausgabe = """ frame:0 pts:30720 pts_time:2.5 - lavfi.signalstats.YAVG=125.065 + lavfi.blackframe.pblack=3 frame:1 pts:61440 pts_time:5 - lavfi.signalstats.YAVG=18.4 + lavfi.blackframe.pblack=84 """; @@ -23,8 +23,8 @@ public class BildabtasterLesenTests Assert.That(proben, Is.EqualTo(new[] { - new Bildprobe(62.5, 125.065), - new Bildprobe(65, 18.4), + new Bildprobe(62.5, 3), + new Bildprobe(65, 84), })); } @@ -38,7 +38,7 @@ public class BildabtasterLesenTests [Test] public void Ein_Wert_ohne_Zeit_faellt_heraus() { - Assert.That(Bildabtaster.Lesen("lavfi.signalstats.YAVG=18.4\n", 0), Is.Empty); + Assert.That(Bildabtaster.Lesen("lavfi.blackframe.pblack=84\n", 0), Is.Empty); } [Test] @@ -133,8 +133,8 @@ public class BildabtasterLaufTests var hell = Abspannkante.HellDavor(proben, grob!.Value, Abspannregeln.Vorgabe) ?? 0; var abtaster = Abtaster(); var kante = await Abspannkante.VerfeinernAsync( - hell, grob.Value, Abspannkante.Schwelle(proben, Abspannregeln.Vorgabe), - stelle => abtaster.HelligkeitAsync(_datei, stelle)); + hell, grob.Value, Abspannregeln.Vorgabe.Mindestanteil, + stelle => abtaster.DunkelanteilAsync(_datei, stelle)); Assert.That(kante, Is.EqualTo(90).Within(4), "erzeugt wurde der Übergang bei genau 90 Sekunden"); @@ -153,39 +153,36 @@ public class BildabtasterLaufTests /// /// /// - /// Hier stand der Fehler, und hier wurde er sichtbar. Diese - /// Prüfung verlangte früher, dass der Abspann unter der festen - /// Schwelle von 35 liegt — mit dem schmalen Balken von damals lag er - /// bei 18, und alles schien zu stimmen. Mit realistischer - /// Schriftdichte misst dieselbe Stelle 40. Die feste Schwelle - /// hätte diesen Abspann also für Handlung gehalten, und genau das - /// geschah im Bestand. + /// Hier wurde der alte Fehler sichtbar. Gemessen wurde lange + /// der Mittelwert der Helligkeit; diese Prüfung verlangte, dass der + /// Abspann unter einer festen Schwelle von 35 liegt. Mit dem schmalen + /// Balken von damals lag er bei 18, und alles schien zu stimmen — mit + /// realistischer Schriftdichte bei 40. Die feste Schwelle hielt + /// diesen Abspann also für Handlung, und genau das geschah im + /// Bestand. /// /// - /// Gemessen wird deshalb gegen die Schwelle, die der Film selbst - /// vorgibt — und die liegt hier zwischen 40 und der Handlung. + /// Nach Pixelanteilen stellt sich die Frage nicht mehr: gemessen + /// werden hier rund 90 Prozent im Abspann gegen null in der Handlung. /// /// [Test] - public async Task Eine_Sekunde_im_Abspann_liegt_unter_der_Schwelle() + public async Task Eine_Sekunde_im_Abspann_ist_fast_ganz_dunkel() { - var proben = await Abtaster().UeberblickAsync(_datei, ab: 30, dauer: 120); - var schwelle = Abspannkante.Schwelle(proben, Abspannregeln.Vorgabe); + var noetig = Abspannregeln.Vorgabe.Mindestanteil; - var dunkel = await Abtaster().HelligkeitAsync(_datei, bei: 120); - var hell = await Abtaster().HelligkeitAsync(_datei, bei: 40); + var dunkel = await Abtaster().DunkelanteilAsync(_datei, bei: 120); + var hell = await Abtaster().DunkelanteilAsync(_datei, bei: 40); Assert.Multiple(() => { - Assert.That(dunkel, Is.Not.Null.And.LessThan(schwelle)); - Assert.That(hell, Is.Not.Null.And.GreaterThan(schwelle)); - Assert.That(dunkel, Is.GreaterThan(Abspannregeln.Vorgabe.Dunkelschwelle), - "dieser Abspann ist heller als der alte feste Wert — darum ging es"); + Assert.That(dunkel, Is.Not.Null.And.GreaterThan(noetig)); + Assert.That(hell, Is.Not.Null.And.LessThan(noetig)); }); } } -/// Dieselbe Helligkeit, gleich welche Bittiefe die Datei hat. +/// Derselbe Dunkelanteil, gleich welche Bittiefe die Datei hat. /// /// /// @@ -251,8 +248,8 @@ public class BildabtasterBittiefeTests 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); + var acht = await abtaster.DunkelanteilAsync(Erzeugen("yuv420p"), bei: 2); + var zehn = await abtaster.DunkelanteilAsync(Erzeugen("yuv420p10le"), bei: 2); Assert.Multiple(() => {