Nicht wie hell das Bild ist, sondern wie viel davon schwarz
Gemessen wurde der Mittelwert über das ganze Bild, und daran scheiterte
die Abspannsuche wiederholt: weisse Schrift hebt den Mittelwert. 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 jeder Textzeile. Zweimal nachgezogen,
zweimal wieder getroffen.
Jellyfin zählt stattdessen Pixel, und das ist kein besserer Schwellenwert,
sondern ein besseres Merkmal. Nachgemessen an denselben erzeugten
Abspännen, durch dieselbe Kette wie im Betrieb:
Mittelwert Anteil dunkler Pixel
Handlung 128 0 %
Abspann, schriftreich 37 84 %
Abspann, schlank 19 87 %
dunkle Nachtszene 28 37 %
Schrift belegt ein bis zehn Prozent der Fläche; der Rest bleibt schwarz.
Der Anteil hängt deshalb nicht an der Schriftmenge, und der Abstand zur
Handlung ist nach beiden Seiten riesig.
`signalstats` weicht `blackframe=amount=0:threshold=24` — die Schwelle
für das einzelne Pixel über eine Reihe von Werten eingemessen: darunter
(16) fällt der Abspann selbst auf zehn Prozent ab, darüber (32) fängt die
Handlung an mitzuzählen. Die Ausgabe kommt über denselben
`metadata=print`-Weg wie vorher, nur unter einem anderen Schlüssel.
**Und die relative Schwelle fällt ersatzlos weg.** 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 Pixelanteil eicht
sich selbst — `Abspannkante.Schwelle` und `Abspannregeln.Anteil` sind
gestrichen, und mit ihnen die Rechnerei um den Grundton.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
121ee62be9
commit
791c00dacc
@@ -34,7 +34,26 @@ public sealed partial class Bildabtaster(FfmpegOptions optionen, ILogger<Bildabt
|
||||
/// </remarks>
|
||||
private const string Breite = "160";
|
||||
|
||||
/// <summary>Die Helligkeit an den Schlüsselbildern eines Abschnitts.</summary>
|
||||
/// <summary>Unter welchem Lumawert ein einzelnes Pixel dunkel heisst.</summary>
|
||||
///
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// <b>Nicht der Mittelwert des Bildes, sondern jedes Pixel für sich.</b>
|
||||
/// <c>blackframe</c> 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.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// 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.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
private const string Dunkelschwelle = "24";
|
||||
|
||||
/// <summary>Der Dunkelanteil an den Schlüsselbildern eines Abschnitts.</summary>
|
||||
///
|
||||
/// <remarks>
|
||||
/// <b>Nur Schlüsselbilder, und das ist der ganze Trick.</b> Gemessen an
|
||||
@@ -42,7 +61,7 @@ public sealed partial class Bildabtaster(FfmpegOptions optionen, ILogger<Bildabt
|
||||
/// Sekunden, nur die Schlüsselbilder 0,24. Dafür liegen die Messpunkte
|
||||
/// fünf bis zehn Sekunden auseinander — genug, um eine dunkle Strecke
|
||||
/// zu finden, zu wenig für ihre Kante. Die holt
|
||||
/// <see cref="HelligkeitAsync"/> nach.
|
||||
/// <see cref="DunkelanteilAsync"/> nach.
|
||||
/// </remarks>
|
||||
public async Task<IReadOnlyList<Bildprobe>> UeberblickAsync(
|
||||
string pfad, double ab, double dauer, CancellationToken ct = default)
|
||||
@@ -53,7 +72,7 @@ public sealed partial class Bildabtaster(FfmpegOptions optionen, ILogger<Bildabt
|
||||
return Lesen(ausgabe, ab);
|
||||
}
|
||||
|
||||
/// <summary>Die mittlere Helligkeit einer einzelnen Sekunde.</summary>
|
||||
/// <summary>Der mittlere Dunkelanteil einer einzelnen Sekunde.</summary>
|
||||
///
|
||||
/// <remarks>
|
||||
/// Alle Bilder, nicht nur Schlüsselbilder: eine Sekunde enthält sonst
|
||||
@@ -61,7 +80,7 @@ public sealed partial class Bildabtaster(FfmpegOptions optionen, ILogger<Bildabt
|
||||
/// an dekodieren muss, ist der Preis — bei zehn Sekunden Abstand sind
|
||||
/// das zehn Sekunden Bildmaterial, also Millisekunden.
|
||||
/// </remarks>
|
||||
public async Task<double?> HelligkeitAsync(
|
||||
public async Task<double?> DunkelanteilAsync(
|
||||
string pfad, double bei, CancellationToken ct = default)
|
||||
{
|
||||
var ausgabe = await LaufenAsync(
|
||||
@@ -70,7 +89,7 @@ public sealed partial class Bildabtaster(FfmpegOptions optionen, ILogger<Bildabt
|
||||
|
||||
var proben = Lesen(ausgabe, 0);
|
||||
if (proben.Count == 0) return null;
|
||||
return proben.Average(p => p.Helligkeit);
|
||||
return proben.Average(p => p.Dunkelanteil);
|
||||
}
|
||||
|
||||
private async Task<string> LaufenAsync(
|
||||
@@ -110,19 +129,15 @@ public sealed partial class Bildabtaster(FfmpegOptions optionen, ILogger<Bildabt
|
||||
start.ArgumentList.Add("-sn");
|
||||
start.ArgumentList.Add("-vf");
|
||||
|
||||
// **`format=gray` vor `signalstats`, und das ist nicht kosmetisch.**
|
||||
//
|
||||
// `signalstats` rechnet in der Skala des Bildformats: bei acht Bit
|
||||
// 0–255, bei zehn Bit 0–1023. Gemessen am selben erzeugten Bild
|
||||
// einmal als 8-Bit und einmal als 10-Bit: YAVG 127 gegen YAVG 500.
|
||||
// Alle Schwellen hier sind an acht Bit gemessen; bei einer
|
||||
// 10-Bit-Datei bedeutete `Dunkelschwelle = 35` plötzlich ein
|
||||
// Viertel davon, und im Protokoll standen Zahlen, die sich mit
|
||||
// denen anderer Dateien nicht vergleichen liessen — „Schwelle
|
||||
// 247,6" war der Hinweis, dem das hier zu verdanken ist.
|
||||
// **`format=gray`, weil die Skala sonst an der Bittiefe hängt.**
|
||||
// Gemessen am selben erzeugten Bild einmal als 8-Bit und einmal
|
||||
// als 10-Bit: 127 gegen 500. Ohne diese Umrechnung bedeutete
|
||||
// dieselbe Schwelle bei der einen Datei etwas anderes als bei der
|
||||
// nächsten — „Schwelle 247,6" im Protokoll war der Hinweis darauf.
|
||||
start.ArgumentList.Add(
|
||||
$"scale={Breite}:-2,format=gray,signalstats,"
|
||||
+ "metadata=print:key=lavfi.signalstats.YAVG:file=-");
|
||||
$"scale={Breite}:-2,format=gray,"
|
||||
+ $"blackframe=amount=0:threshold={Dunkelschwelle},"
|
||||
+ "metadata=print:key=lavfi.blackframe.pblack:file=-");
|
||||
start.ArgumentList.Add("-f");
|
||||
start.ArgumentList.Add("null");
|
||||
start.ArgumentList.Add("-");
|
||||
@@ -199,6 +214,6 @@ public sealed partial class Bildabtaster(FfmpegOptions optionen, ILogger<Bildabt
|
||||
[GeneratedRegex(@"pts_time:([\d.]+)")]
|
||||
private static partial Regex Zeitzeile();
|
||||
|
||||
[GeneratedRegex(@"lavfi\.signalstats\.YAVG=([\d.]+)")]
|
||||
[GeneratedRegex(@"lavfi\.blackframe\.pblack=([\d.]+)")]
|
||||
private static partial Regex Wertzeile();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user