using Flaro.Core.Media; namespace Flaro.Core.Tests; /// Wo der Abspann eines Films anfängt. /// /// /// 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. /// public class AbspannkanteTests { private static readonly Abspannregeln Regeln = Abspannregeln.Vorgabe; /// Eine Reihe von Messpunkten im Abstand von zehn Sekunden. /// /// Ab wann dunkel. null heisst: nie. private static List Reihe(double von, double bis, double? abspannAb) { var proben = new List(); for (var t = von; t <= bis; t += 10) proben.Add(new Bildprobe(t, abspannAb is { } ab && t >= ab ? 18 : 125)); return proben; } [Test] public void Eine_dunkle_Strecke_bis_zum_Ende_ist_der_Abspann() { var proben = Reihe(0, 600, abspannAb: 420); Assert.That(Abspannkante.Suchen(proben, Regeln), Is.EqualTo(420)); } /// Ein Film, der hell endet, hat keinen erkennbaren Abspann. /// /// /// Namen über laufendem Bild — Marvel, Pixar, jede zweite Komödie. Kein /// Fund ist hier die richtige Antwort: ein geratener Knopf schnitte /// Handlung weg. /// [Test] public void Ohne_dunkles_Ende_kein_Fund() { Assert.That(Abspannkante.Suchen(Reihe(0, 600, abspannAb: null), Regeln), Is.Null); } /// Eine Schwarzblende mitten im Film zählt nicht. [Test] 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 }; Assert.That(Abspannkante.Suchen(proben, Regeln), Is.Null, "sie hält nicht bis zum Ende durch"); } /// Eine Szene nach dem Abspann nimmt den Fund weg. /// /// /// Lieber keine Marke als eine, die über die Szene hinwegspringt. /// [Test] public void Eine_Szene_nach_dem_Abspann_nimmt_den_Fund_weg() { var proben = Reihe(0, 600, abspannAb: 420); for (var i = proben.Count - 5; i < proben.Count; i++) proben[i] = proben[i] with { Helligkeit = 120 }; Assert.That(Abspannkante.Suchen(proben, Regeln), Is.Null); } /// Eine helle Schrifttafel mittendrin bricht die Strecke nicht. [Test] public void Eine_kurze_helle_Tafel_bricht_nichts() { var proben = Reihe(0, 600, abspannAb: 420); // Zwei Proben, also zwanzig Sekunden, sind hell. proben[50] = proben[50] with { Helligkeit = 140 }; proben[51] = proben[51] with { Helligkeit = 140 }; Assert.That(Abspannkante.Suchen(proben, Regeln), Is.EqualTo(420)); } /// Zwanzig Sekunden Dunkelheit sind kein Abspann. [Test] public void Zu_kurz_zaehlt_nicht() { Assert.That(Abspannkante.Suchen(Reihe(0, 600, abspannAb: 580), Regeln), Is.Null); } /// Ein halber Film im Dunkeln ist ein Fehlfund. [Test] public void Zu_lang_zaehlt_auch_nicht() { Assert.That(Abspannkante.Suchen(Reihe(0, 3600, abspannAb: 600), Regeln), Is.Null); } [Test] public void Die_letzte_helle_Probe_begrenzt_die_Suche() { var proben = Reihe(0, 600, abspannAb: 420); Assert.That(Abspannkante.HellDavor(proben, 420, Regeln), Is.EqualTo(410)); } // ---------- Nachmessen ---------- /// Die binäre Suche findet die Kante auf vier Sekunden genau. [Test] public async Task Die_Kante_wird_eingegrenzt() { const double echteKante = 417.3; var sonden = 0; var kante = await Abspannkante.VerfeinernAsync(410, 420, Regeln.Dunkelschwelle, stelle => { sonden++; return Task.FromResult(stelle >= echteKante ? 18 : 125); }); Assert.Multiple(() => { Assert.That(kante, Is.EqualTo(echteKante).Within(4)); Assert.That(kante, Is.GreaterThanOrEqualTo(echteKante), "lieber ein paar Sekunden Abspann zu viel als eine Sekunde Handlung"); Assert.That(sonden, Is.LessThanOrEqualTo(4), "zehn Sekunden auf vier: zwei Halbierungen"); }); } /// Antwortet eine Sonde nicht, gilt der grobe Fund. [Test] public async Task Ohne_Antwort_bleibt_es_beim_groben_Fund() { var kante = await Abspannkante.VerfeinernAsync( 410, 420, Regeln.Dunkelschwelle, _ => Task.FromResult(null)); Assert.That(kante, Is.EqualTo(420)); } /// Ein schon enger Bereich braucht keine Sonde. [Test] public async Task Ein_enger_Bereich_wird_nicht_angefasst() { var sonden = 0; var kante = await Abspannkante.VerfeinernAsync(418, 420, Regeln.Dunkelschwelle, _ => { sonden++; return Task.FromResult(18); }); Assert.Multiple(() => { Assert.That(kante, Is.EqualTo(420)); Assert.That(sonden, Is.Zero); }); } } /// Die Schwelle an gemessenen Abspannwerten, nicht an erfundenen. /// /// /// /// 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. /// /// /// Nachgemessen an erzeugtem 1080p-Material mit zwei Spalten Namen, /// durch dieselbe ffmpeg-Kette wie im Betrieb /// (scale=160:-2,signalstats): /// /// /// Handlung: 126 /// Abspann, realistische Schriftdichte (1,3 % Weiss): 32 /// Abspann, dicht gesetzt (9,5 % Weiss): 47 /// /// /// 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. /// /// public class AbspannSchwellenTests { private static readonly Abspannregeln Regeln = Abspannregeln.Vorgabe; /// Ein Suchfenster, wie es im Betrieb entsteht. /// /// /// 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) { var proben = new List(); for (var t = 1500.0; t <= 2700; t += 5) proben.Add(new Bildprobe(t, t < abspannAb ? imInhalt : imAbspann)); return proben; } /// Der Fall, an dem es scheiterte: ein schriftreicher 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. /// [Test] public void Eine_duestere_Schlussszene_ist_kein_Abspann() { var proben = Fenster(abspannAb: 2340, imAbspann: 40, imInhalt: 55); Assert.That(Abspannkante.Suchen(proben, Regeln), Is.Null); } /// Helle Handlung bis zum Ende bleibt Handlung. [Test] public void Ohne_dunkles_Ende_kein_Fund() { var proben = Fenster(abspannAb: double.MaxValue, imAbspann: 0); Assert.That(Abspannkante.Suchen(proben, Regeln), Is.Null); } } /// Was hinter dem Abspann noch kommt. /// /// /// Der Fehler, für den diese Prüfungen stehen. Die Suche verlangte, /// dass die allerletzte Probe dunkel ist. Hinter dem Abspann steht bei /// vielen Produktionen aber noch eine Logotafel — Sender, Studio, /// Förderer —, oft hell. Ein einziges weisses Schlussbild machte damit den /// ganzen Abspann unsichtbar. /// public class AbspannSchlusstafelTests { private static readonly Abspannregeln Regeln = Abspannregeln.Vorgabe; private static List Fenster(double abspannAb, double tafelAb = double.MaxValue) { 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)); } return proben; } /// Eine helle Tafel am Schluss verdeckt den Abspann nicht. [Test] public void Eine_Logotafel_am_Ende_zaehlt_nicht_dagegen() { // Fünfzehn Sekunden weiss hinter dem Abspann. var proben = Fenster(abspannAb: 2340, tafelAb: 2690); Assert.That(Abspannkante.Suchen(proben, Regeln), Is.EqualTo(2340).Within(5)); } /// Eine lange helle Strecke am Ende schon. /// /// /// Die Nachsicht hat eine Grenze, sonst fände die Suche hinter jeder /// Schwarzblende einen Abspann und hinge die halbe Handlung daran. /// [Test] public void Eine_lange_helle_Strecke_am_Ende_bricht_ab() { var proben = Fenster(abspannAb: 2340, tafelAb: 2600); Assert.That(Abspannkante.Suchen(proben, Regeln), Is.Null); } }