Mein Prüfbild war dunkler als jeder echte Abspann
Die Abspannsuche lief gegen eine feste Schwelle von 35. Die stammte aus
einem erzeugten Prüfbild mit 0,8 Prozent weisser Fläche — Helligkeit 18,
weit unter 35, alles schien zu stimmen. Ein echter Abspann hat mehr
Schrift.
Nachgemessen an 1080p mit zwei Spalten Namen, durch dieselbe ffmpeg-Kette
wie im Betrieb:
Handlung 126
Abspann, realistische Schriftdichte 32
Abspann, dicht gesetzt 47
Die Fünfunddreissig lag mitten zwischen den beiden Abspannfällen und
schnitt sie auseinander. Dasselbe noch einmal an der erzeugten Prüfdatei,
nachdem ihr Abspann realistische Dichte bekam: 40,2 — und die Prüfung,
die vorher grün war, wurde rot. Das ist der Fehler, lokal reproduziert.
**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. Gemessen wird jetzt gegen den Grundton des Films — das obere
Viertel der Proben, mal 0,55. Die alte Schwelle bleibt als Boden, damit
ein durchweg dämmriger Film nicht in Fehlalarme rutscht.
**Und die Logotafel.** Die Suche verlangte, dass die allerletzte Probe
dunkel ist. Hinter dem Abspann steht bei vielen Produktionen noch eine
Tafel — Sender, Studio, Förderer —, oft hell. Ein einziges weisses
Schlussbild machte damit den ganzen Abspann unsichtbar. Zwanzig Sekunden
sind jetzt erlaubt, in Sekunden gezählt und nicht in Proben: wie viele
Messpunkte auf eine Tafel fallen, hängt allein an der Schlüsselbilddichte
der Datei.
**Ein Lauf ohne Fund sagt jetzt, warum.** Anzahl Proben, Schwelle,
letzte Probe — ohne diese drei Zahlen ist „kein Abspann erkannt" keine
Auskunft, und man rät wieder.
Sieben neue Prüfungen mit den gemessenen Werten. Die beiden
entscheidenden sind mit der alten Schwelle rot; gegengeprüft, nicht
behauptet.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
7e5b3fb0ee
commit
70ab4cc421
@@ -299,11 +299,25 @@ public sealed class Markenerkennung(
|
|||||||
var proben = await abtaster.UeberblickAsync(pfad, ab, laufzeit - ab, ct);
|
var proben = await abtaster.UeberblickAsync(pfad, ab, laufzeit - ab, ct);
|
||||||
|
|
||||||
var regeln = Abspannregeln.Vorgabe;
|
var regeln = Abspannregeln.Vorgabe;
|
||||||
if (Abspannkante.Suchen(proben, regeln) is not { } stelle) return false;
|
|
||||||
|
// **Ein Lauf ohne Fund soll sagen, warum.** „Kein Abspann erkannt"
|
||||||
|
// allein ist keine Auskunft: ob die Datei zu wenige Schlüsselbilder
|
||||||
|
// hat, ob der Abspann zu hell war oder ob am Schluss eine Tafel
|
||||||
|
// steht, sieht man nur an diesen drei Zahlen — und sonst gar nicht.
|
||||||
|
if (Abspannkante.Suchen(proben, regeln) is not { } stelle)
|
||||||
|
{
|
||||||
|
log.LogInformation(
|
||||||
|
"Kein Abspann in {Pfad}: {Proben} Proben, Schwelle {Schwelle:0.#}, "
|
||||||
|
+ "letzte Probe {Letzte:0.#}.",
|
||||||
|
pfad, proben.Count, Abspannkante.Schwelle(proben, regeln),
|
||||||
|
proben.Count > 0 ? proben[^1].Helligkeit : double.NaN);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
var hell = Abspannkante.HellDavor(proben, stelle, regeln) ?? ab;
|
var hell = Abspannkante.HellDavor(proben, stelle, regeln) ?? ab;
|
||||||
var kante = await Abspannkante.VerfeinernAsync(
|
var kante = await Abspannkante.VerfeinernAsync(
|
||||||
hell, stelle, regeln, wo => abtaster.HelligkeitAsync(pfad, wo, ct));
|
hell, stelle, Abspannkante.Schwelle(proben, regeln),
|
||||||
|
wo => abtaster.HelligkeitAsync(pfad, wo, ct));
|
||||||
|
|
||||||
return await SchreibenAsync(db, titelId, istFilm, Markenart.Abspann,
|
return await SchreibenAsync(db, titelId, istFilm, Markenart.Abspann,
|
||||||
kante, laufzeit, ct);
|
kante, laufzeit, ct);
|
||||||
|
|||||||
@@ -15,14 +15,37 @@ public readonly record struct Bildprobe(double Sekunde, double Helligkeit);
|
|||||||
/// </remarks>
|
/// </remarks>
|
||||||
public readonly record struct Abspannregeln
|
public readonly record struct Abspannregeln
|
||||||
{
|
{
|
||||||
/// <summary>Ab wann ein Bild als dunkel gilt (0–255).</summary>
|
/// <summary>Die unterste Schwelle, unter der immer „dunkel" gilt (0–255).</summary>
|
||||||
///
|
///
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// Gemessen an einem erzeugten Film: Handlung 125, Abspann 18.
|
/// <b>Nur noch der Boden, nicht mehr die Schwelle.</b> Sie stand lange
|
||||||
/// Fünfunddreissig liegt weit von beiden.
|
/// 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.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public double Dunkelschwelle { get; init; }
|
public double Dunkelschwelle { get; init; }
|
||||||
|
|
||||||
|
/// <summary>Wie dunkel eine Strecke gegenüber dem Film sein muss.</summary>
|
||||||
|
///
|
||||||
|
/// <remarks>
|
||||||
|
/// <para>
|
||||||
|
/// <b>Dunkel ist keine absolute Grösse.</b> 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 <see cref="Dunkelschwelle"/> fällt.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// 0,55 heisst: knapp unter die Hälfte. Bei 126 ergibt das 69 — weit
|
||||||
|
/// über beiden gemessenen Abspannwerten und weit unter dem Inhalt.
|
||||||
|
/// </para>
|
||||||
|
/// </remarks>
|
||||||
|
public double Anteil { get; init; }
|
||||||
|
|
||||||
/// <summary>Wie viele helle Proben am Stück eine Strecke nicht brechen.</summary>
|
/// <summary>Wie viele helle Proben am Stück eine Strecke nicht brechen.</summary>
|
||||||
///
|
///
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
@@ -32,13 +55,26 @@ public readonly record struct Abspannregeln
|
|||||||
/// </remarks>
|
/// </remarks>
|
||||||
public int Lücke { get; init; }
|
public int Lücke { get; init; }
|
||||||
|
|
||||||
|
/// <summary>Wie viele Sekunden hell hinter dem Abspann erlaubt sind.</summary>
|
||||||
|
///
|
||||||
|
/// <remarks>
|
||||||
|
/// <b>In Sekunden, nicht in Proben.</b> Eine Logotafel dauert, was sie
|
||||||
|
/// dauert; wie viele Messpunkte darauf fallen, hängt allein daran, wie
|
||||||
|
/// dicht die Datei ihre Schlüsselbilder setzt. In Proben gezählt wäre
|
||||||
|
/// dieselbe Tafel bei der einen Datei verziehen und bei der nächsten
|
||||||
|
/// ein Abbruch.
|
||||||
|
/// </remarks>
|
||||||
|
public double Schlusstafel { get; init; }
|
||||||
|
|
||||||
public double Mindestlänge { get; init; }
|
public double Mindestlänge { get; init; }
|
||||||
public double Höchstlänge { get; init; }
|
public double Höchstlänge { get; init; }
|
||||||
|
|
||||||
public static Abspannregeln Vorgabe => new()
|
public static Abspannregeln Vorgabe => new()
|
||||||
{
|
{
|
||||||
Dunkelschwelle = 35,
|
Dunkelschwelle = 35,
|
||||||
|
Anteil = 0.55,
|
||||||
Lücke = 2,
|
Lücke = 2,
|
||||||
|
Schlusstafel = 20,
|
||||||
Mindestlänge = 45,
|
Mindestlänge = 45,
|
||||||
Höchstlänge = 12 * 60,
|
Höchstlänge = 12 * 60,
|
||||||
};
|
};
|
||||||
@@ -74,21 +110,62 @@ public static class Abspannkante
|
|||||||
/// Messpunkte über das Ende des Films, aufsteigend nach Zeit. Sie dürfen
|
/// Messpunkte über das Ende des Films, aufsteigend nach Zeit. Sie dürfen
|
||||||
/// ungleichmässig liegen: Schlüsselbilder tun das.
|
/// ungleichmässig liegen: Schlüsselbilder tun das.
|
||||||
/// </param>
|
/// </param>
|
||||||
|
/// <summary>Ab welcher Helligkeit eine Probe in diesem Film dunkel ist.</summary>
|
||||||
|
///
|
||||||
|
/// <remarks>
|
||||||
|
/// <para>
|
||||||
|
/// <b>Das obere Viertel als Grundton.</b> 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,
|
||||||
|
/// <see cref="Abspannregeln.Höchstlänge"/> lässt ihm höchstens gut die
|
||||||
|
/// Hälfte davon.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// Öffentlich, weil das Nachmessen dieselbe Schwelle braucht. Zweimal
|
||||||
|
/// gerechnet wären es zwei Schwellen, und die Kante läge woanders als
|
||||||
|
/// der Fund.
|
||||||
|
/// </para>
|
||||||
|
/// </remarks>
|
||||||
|
public static double Schwelle(IReadOnlyList<Bildprobe> 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<Bildprobe> proben, Abspannregeln regeln)
|
public static double? Suchen(IReadOnlyList<Bildprobe> proben, Abspannregeln regeln)
|
||||||
{
|
{
|
||||||
if (proben.Count < 2) return null;
|
if (proben.Count < 2) return null;
|
||||||
|
var schwelle = Schwelle(proben, regeln);
|
||||||
|
|
||||||
// **Von hinten.** Der Abspann endet am Dateiende; alles, was davor
|
// **Von hinten.** Der Abspann endet am Dateiende; alles, was davor
|
||||||
// dunkel war und wieder hell wurde, war eine Schwarzblende.
|
// dunkel war und wieder hell wurde, war eine Schwarzblende.
|
||||||
|
//
|
||||||
|
// **Das allerletzte Bild muss aber nicht dunkel sein.** Hinter dem
|
||||||
|
// Abspann steht bei vielen Produktionen noch eine Logotafel —
|
||||||
|
// Sender, Studio, Förderer —, oft hell und ein paar Sekunden lang.
|
||||||
|
// Darauf zu bestehen, dass die letzte Probe dunkel ist, hiess: ein
|
||||||
|
// einziges weisses Schlussbild macht den ganzen Abspann unsichtbar.
|
||||||
|
// Dieselbe Nachsicht wie innerhalb der Strecke, also `Lücke`.
|
||||||
var letzte = proben.Count - 1;
|
var letzte = proben.Count - 1;
|
||||||
if (proben[letzte].Helligkeit >= regeln.Dunkelschwelle) return null;
|
var ende = proben[letzte].Sekunde;
|
||||||
|
while (letzte > 0 && proben[letzte].Helligkeit >= schwelle)
|
||||||
|
{
|
||||||
|
if (ende - proben[letzte].Sekunde >= regeln.Schlusstafel) return null;
|
||||||
|
letzte--;
|
||||||
|
}
|
||||||
|
if (proben[letzte].Helligkeit >= schwelle) return null;
|
||||||
|
|
||||||
var anfang = letzte;
|
var anfang = letzte;
|
||||||
var hellAmStück = 0;
|
var hellAmStück = 0;
|
||||||
|
|
||||||
for (var i = letzte - 1; i >= 0; i--)
|
for (var i = letzte - 1; i >= 0; i--)
|
||||||
{
|
{
|
||||||
if (proben[i].Helligkeit < regeln.Dunkelschwelle)
|
if (proben[i].Helligkeit < schwelle)
|
||||||
{
|
{
|
||||||
anfang = i;
|
anfang = i;
|
||||||
hellAmStück = 0;
|
hellAmStück = 0;
|
||||||
@@ -112,11 +189,12 @@ public static class Abspannkante
|
|||||||
public static double? HellDavor(
|
public static double? HellDavor(
|
||||||
IReadOnlyList<Bildprobe> proben, double dunkelAb, Abspannregeln regeln)
|
IReadOnlyList<Bildprobe> proben, double dunkelAb, Abspannregeln regeln)
|
||||||
{
|
{
|
||||||
|
var schwelle = Schwelle(proben, regeln);
|
||||||
double? letzteHelle = null;
|
double? letzteHelle = null;
|
||||||
foreach (var p in proben)
|
foreach (var p in proben)
|
||||||
{
|
{
|
||||||
if (p.Sekunde >= dunkelAb) break;
|
if (p.Sekunde >= dunkelAb) break;
|
||||||
if (p.Helligkeit >= regeln.Dunkelschwelle) letzteHelle = p.Sekunde;
|
if (p.Helligkeit >= schwelle) letzteHelle = p.Sekunde;
|
||||||
}
|
}
|
||||||
return letzteHelle;
|
return letzteHelle;
|
||||||
}
|
}
|
||||||
@@ -139,7 +217,7 @@ public static class Abspannkante
|
|||||||
/// </para>
|
/// </para>
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public static async Task<double> VerfeinernAsync(
|
public static async Task<double> VerfeinernAsync(
|
||||||
double hell, double dunkel, Abspannregeln regeln,
|
double hell, double dunkel, double schwelle,
|
||||||
Func<double, Task<double?>> messen, double genauigkeit = 4)
|
Func<double, Task<double?>> messen, double genauigkeit = 4)
|
||||||
{
|
{
|
||||||
if (genauigkeit <= 0) genauigkeit = 1;
|
if (genauigkeit <= 0) genauigkeit = 1;
|
||||||
@@ -150,7 +228,7 @@ public static class Abspannkante
|
|||||||
var wert = await messen(mitte);
|
var wert = await messen(mitte);
|
||||||
if (wert is not { } helligkeit) return dunkel;
|
if (wert is not { } helligkeit) return dunkel;
|
||||||
|
|
||||||
if (helligkeit < regeln.Dunkelschwelle) dunkel = mitte;
|
if (helligkeit < schwelle) dunkel = mitte;
|
||||||
else hell = mitte;
|
else hell = mitte;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ public class AbspannkanteTests
|
|||||||
const double echteKante = 417.3;
|
const double echteKante = 417.3;
|
||||||
var sonden = 0;
|
var sonden = 0;
|
||||||
|
|
||||||
var kante = await Abspannkante.VerfeinernAsync(410, 420, Regeln, stelle =>
|
var kante = await Abspannkante.VerfeinernAsync(410, 420, Regeln.Dunkelschwelle, stelle =>
|
||||||
{
|
{
|
||||||
sonden++;
|
sonden++;
|
||||||
return Task.FromResult<double?>(stelle >= echteKante ? 18 : 125);
|
return Task.FromResult<double?>(stelle >= echteKante ? 18 : 125);
|
||||||
@@ -134,7 +134,7 @@ public class AbspannkanteTests
|
|||||||
public async Task Ohne_Antwort_bleibt_es_beim_groben_Fund()
|
public async Task Ohne_Antwort_bleibt_es_beim_groben_Fund()
|
||||||
{
|
{
|
||||||
var kante = await Abspannkante.VerfeinernAsync(
|
var kante = await Abspannkante.VerfeinernAsync(
|
||||||
410, 420, Regeln, _ => Task.FromResult<double?>(null));
|
410, 420, Regeln.Dunkelschwelle, _ => Task.FromResult<double?>(null));
|
||||||
|
|
||||||
Assert.That(kante, Is.EqualTo(420));
|
Assert.That(kante, Is.EqualTo(420));
|
||||||
}
|
}
|
||||||
@@ -144,7 +144,7 @@ public class AbspannkanteTests
|
|||||||
public async Task Ein_enger_Bereich_wird_nicht_angefasst()
|
public async Task Ein_enger_Bereich_wird_nicht_angefasst()
|
||||||
{
|
{
|
||||||
var sonden = 0;
|
var sonden = 0;
|
||||||
var kante = await Abspannkante.VerfeinernAsync(418, 420, Regeln, _ =>
|
var kante = await Abspannkante.VerfeinernAsync(418, 420, Regeln.Dunkelschwelle, _ =>
|
||||||
{
|
{
|
||||||
sonden++;
|
sonden++;
|
||||||
return Task.FromResult<double?>(18);
|
return Task.FromResult<double?>(18);
|
||||||
@@ -157,3 +157,156 @@ public class AbspannkanteTests
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>Die Schwelle an gemessenen Abspannwerten, nicht an erfundenen.</summary>
|
||||||
|
///
|
||||||
|
/// <remarks>
|
||||||
|
/// <para>
|
||||||
|
/// <b>Der Fehler, für den diese Prüfungen stehen.</b> 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.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// <b>Nachgemessen</b> an erzeugtem 1080p-Material mit zwei Spalten Namen,
|
||||||
|
/// durch dieselbe ffmpeg-Kette wie im Betrieb
|
||||||
|
/// (<c>scale=160:-2,signalstats</c>):
|
||||||
|
/// </para>
|
||||||
|
/// <list type="table">
|
||||||
|
/// <item><description>Handlung: <b>126</b></description></item>
|
||||||
|
/// <item><description>Abspann, realistische Schriftdichte (1,3 % Weiss): <b>32</b></description></item>
|
||||||
|
/// <item><description>Abspann, dicht gesetzt (9,5 % Weiss): <b>47</b></description></item>
|
||||||
|
/// </list>
|
||||||
|
/// <para>
|
||||||
|
/// 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.
|
||||||
|
/// </para>
|
||||||
|
/// </remarks>
|
||||||
|
public class AbspannSchwellenTests
|
||||||
|
{
|
||||||
|
private static readonly Abspannregeln Regeln = Abspannregeln.Vorgabe;
|
||||||
|
|
||||||
|
/// <summary>Ein Suchfenster, wie es im Betrieb entsteht.</summary>
|
||||||
|
///
|
||||||
|
/// <remarks>
|
||||||
|
/// Zwanzig Minuten vor dem Ende einer Folge von fünfundvierzig, an den
|
||||||
|
/// Schlüsselbildern abgetastet — also alle fünf Sekunden eine Probe.
|
||||||
|
/// </remarks>
|
||||||
|
private static List<Bildprobe> Fenster(double abspannAb, double imAbspann, double imInhalt = 126)
|
||||||
|
{
|
||||||
|
var proben = new List<Bildprobe>();
|
||||||
|
for (var t = 1500.0; t <= 2700; t += 5)
|
||||||
|
proben.Add(new Bildprobe(t, t < abspannAb ? imInhalt : imAbspann));
|
||||||
|
return proben;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Der Fall, an dem es scheiterte: ein schriftreicher Abspann.</summary>
|
||||||
|
///
|
||||||
|
/// <remarks>
|
||||||
|
/// Mit der alten festen Schwelle von 35 ist diese Prüfung rot: 47 gilt
|
||||||
|
/// dort als hell, und die Folge bekam keine Marke.
|
||||||
|
/// </remarks>
|
||||||
|
[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));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Und die Schwelle liegt zwischen beidem, nicht daneben.</summary>
|
||||||
|
[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");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>**Ein dunkler Film wird dadurch nicht zum Dauerabspann.**</summary>
|
||||||
|
///
|
||||||
|
/// <remarks>
|
||||||
|
/// 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 <c>Dunkelschwelle</c> als Boden —
|
||||||
|
/// hier sorgt sie dafür, dass eine Schlussszene bei 40 nichts auslöst.
|
||||||
|
/// </remarks>
|
||||||
|
[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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Helle Handlung bis zum Ende bleibt Handlung.</summary>
|
||||||
|
[Test]
|
||||||
|
public void Ohne_dunkles_Ende_kein_Fund()
|
||||||
|
{
|
||||||
|
var proben = Fenster(abspannAb: double.MaxValue, imAbspann: 0);
|
||||||
|
|
||||||
|
Assert.That(Abspannkante.Suchen(proben, Regeln), Is.Null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Was hinter dem Abspann noch kommt.</summary>
|
||||||
|
///
|
||||||
|
/// <remarks>
|
||||||
|
/// <b>Der Fehler, für den diese Prüfungen stehen.</b> 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.
|
||||||
|
/// </remarks>
|
||||||
|
public class AbspannSchlusstafelTests
|
||||||
|
{
|
||||||
|
private static readonly Abspannregeln Regeln = Abspannregeln.Vorgabe;
|
||||||
|
|
||||||
|
private static List<Bildprobe> Fenster(double abspannAb, double tafelAb = double.MaxValue)
|
||||||
|
{
|
||||||
|
var proben = new List<Bildprobe>();
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Eine helle Tafel am Schluss verdeckt den Abspann nicht.</summary>
|
||||||
|
[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));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Eine lange helle Strecke am Ende schon.</summary>
|
||||||
|
///
|
||||||
|
/// <remarks>
|
||||||
|
/// Die Nachsicht hat eine Grenze, sonst fände die Suche hinter jeder
|
||||||
|
/// Schwarzblende einen Abspann und hinge die halbe Handlung daran.
|
||||||
|
/// </remarks>
|
||||||
|
[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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -82,7 +82,13 @@ public class BildabtasterLaufTests
|
|||||||
"-hide_banner", "-loglevel", "error", "-y",
|
"-hide_banner", "-loglevel", "error", "-y",
|
||||||
"-f", "lavfi", "-i", "testsrc2=size=640x360:rate=24:duration=90",
|
"-f", "lavfi", "-i", "testsrc2=size=640x360:rate=24:duration=90",
|
||||||
"-f", "lavfi", "-i", "color=c=black:s=640x360:r=24:d=60",
|
"-f", "lavfi", "-i", "color=c=black:s=640x360:r=24:d=60",
|
||||||
"-f", "lavfi", "-i", "color=c=white:s=180x14:r=24:d=60",
|
// **So viel weisse Fläche hat ein echter Abspann.**
|
||||||
|
// Nachgemessen an 1080p mit zwei Spalten Namen: rund ein
|
||||||
|
// Zehntel der Fläche, und damit YAVG um 45 statt um 18.
|
||||||
|
// Mit dem schmalen Balken von vorher war dieses Prüfbild
|
||||||
|
// dunkler als jeder wirkliche Abspann — und genau deshalb
|
||||||
|
// blieb die zu enge Schwelle so lange unbemerkt.
|
||||||
|
"-f", "lavfi", "-i", "color=c=white:s=400x76:r=24:d=60",
|
||||||
"-filter_complex",
|
"-filter_complex",
|
||||||
"[1][2]overlay=x=230:y='H-mod(t*45,H+h)'[cr];[0][cr]concat=n=2:v=1:a=0[v]",
|
"[1][2]overlay=x=230:y='H-mod(t*45,H+h)'[cr];[0][cr]concat=n=2:v=1:a=0[v]",
|
||||||
"-map", "[v]", "-c:v", "libx264", "-preset", "ultrafast",
|
"-map", "[v]", "-c:v", "libx264", "-preset", "ultrafast",
|
||||||
@@ -127,7 +133,7 @@ public class BildabtasterLaufTests
|
|||||||
var hell = Abspannkante.HellDavor(proben, grob!.Value, Abspannregeln.Vorgabe) ?? 0;
|
var hell = Abspannkante.HellDavor(proben, grob!.Value, Abspannregeln.Vorgabe) ?? 0;
|
||||||
var abtaster = Abtaster();
|
var abtaster = Abtaster();
|
||||||
var kante = await Abspannkante.VerfeinernAsync(
|
var kante = await Abspannkante.VerfeinernAsync(
|
||||||
hell, grob.Value, Abspannregeln.Vorgabe,
|
hell, grob.Value, Abspannkante.Schwelle(proben, Abspannregeln.Vorgabe),
|
||||||
stelle => abtaster.HelligkeitAsync(_datei, stelle));
|
stelle => abtaster.HelligkeitAsync(_datei, stelle));
|
||||||
|
|
||||||
Assert.That(kante, Is.EqualTo(90).Within(4),
|
Assert.That(kante, Is.EqualTo(90).Within(4),
|
||||||
@@ -143,17 +149,38 @@ public class BildabtasterLaufTests
|
|||||||
Assert.That(Abspannkante.Suchen(proben, Abspannregeln.Vorgabe), Is.Null);
|
Assert.That(Abspannkante.Suchen(proben, Abspannregeln.Vorgabe), Is.Null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Eine einzelne Sekunde lässt sich messen.</summary>
|
/// <summary>Eine einzelne Sekunde lässt sich messen — und liegt richtig.</summary>
|
||||||
|
///
|
||||||
|
/// <remarks>
|
||||||
|
/// <para>
|
||||||
|
/// <b>Hier stand der Fehler, und hier wurde er sichtbar.</b> 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 <b>40</b>. Die feste Schwelle
|
||||||
|
/// hätte diesen Abspann also für Handlung gehalten, und genau das
|
||||||
|
/// geschah im Bestand.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// Gemessen wird deshalb gegen die Schwelle, die der Film selbst
|
||||||
|
/// vorgibt — und die liegt hier zwischen 40 und der Handlung.
|
||||||
|
/// </para>
|
||||||
|
/// </remarks>
|
||||||
[Test]
|
[Test]
|
||||||
public async Task Eine_Sekunde_im_Abspann_ist_dunkel()
|
public async Task Eine_Sekunde_im_Abspann_liegt_unter_der_Schwelle()
|
||||||
{
|
{
|
||||||
|
var proben = await Abtaster().UeberblickAsync(_datei, ab: 30, dauer: 120);
|
||||||
|
var schwelle = Abspannkante.Schwelle(proben, Abspannregeln.Vorgabe);
|
||||||
|
|
||||||
var dunkel = await Abtaster().HelligkeitAsync(_datei, bei: 120);
|
var dunkel = await Abtaster().HelligkeitAsync(_datei, bei: 120);
|
||||||
var hell = await Abtaster().HelligkeitAsync(_datei, bei: 40);
|
var hell = await Abtaster().HelligkeitAsync(_datei, bei: 40);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
Assert.Multiple(() =>
|
||||||
{
|
{
|
||||||
Assert.That(dunkel, Is.Not.Null.And.LessThan(Abspannregeln.Vorgabe.Dunkelschwelle));
|
Assert.That(dunkel, Is.Not.Null.And.LessThan(schwelle));
|
||||||
Assert.That(hell, Is.Not.Null.And.GreaterThan(Abspannregeln.Vorgabe.Dunkelschwelle));
|
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");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user