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);
}
}