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(() =>
{