Kratzplatz, Markenerkennung, Tonabdruck — erfundene Wörter für Fachbegriffe
„Scratch disk" wörtlich zu übersetzen ist derselbe Fehler wie „Wachhund"
für Watchdog und „Keks" für Cookie. Und „Marke" ist kein Fachbegriff für
das, was gemeint ist: Jellyfin nennt diese Abschnitte Media Segments, und
das ist auch das Wort, in dem sich darüber reden lässt.
Kratzplatz* → Transcode* (was es ist, steht in FLARO_TRANSCODE)
Marke, Marken → Segment, Segmente
Markenerkennung → Segmenterkennung
Markenart/-suche → Segmentart/-suche
Tonabdruck → Fingerprint (Chromaprint nennt es so)
Tonabgleich → Fingerprintabgleich
Schlüsselbild → Keyframe
Bildabtaster → Frameabtaster
Gewöhnliches Deutsch bleibt gewöhnliches Deutsch: Wartung, Freigabe,
Meldung, Abspann, Vorspann. Das war nie das Problem.
**Was sich nicht ändert, und zwar mit Absicht.** Die drei
Einstellungsschlüssel `wartung.kratzplatz.paket.*` bleiben, wie sie sind —
sie stehen in der Datenbank, und ein neuer Name hiesse, dass die Werte
des Nutzers stillschweigend auf die Vorgabe zurückfallen. Dasselbe für
die Endpunktpfade `/marken`: sie sind nicht sichtbar, und ein Umbenennen
wäre Diff ohne Gewinn.
**Die Migration ist von Hand geschrieben.** `dotnet ef` hat ein Gerüst
erzeugt, das beide Tabellen löscht und neu anlegt — es sieht eine
verschwundene und eine neue Tabelle, nicht eine umbenannte. Damit wäre
jedes erkannte Segment und jeder Vermerk „hier wurde schon gesucht" weg
gewesen. `RenameTable` und `RenameIndex` tun dasselbe, ohne eine Zeile
anzufassen; nachgemessen an einer echten SQLite-Datei: Zeilen vorher 1
und 1, nachher 1 und 1, Inhalt unverändert.
Nachgezogen: in `bilder.ts` und `main.ts` heisst „marke" ein Häkchen und
das Logo — die blieben unberührt. Und „Segment" ist sächlich, was eine
Blindersetzung nicht weiss: „eine Segment" stand danach an vierzehn
Stellen.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
32f592b83e
commit
ed13d5fa43
@@ -20,7 +20,7 @@ namespace Flaro.Core.Media;
|
||||
///
|
||||
/// <param name="Sekunde">Die Stelle im Film.</param>
|
||||
/// <param name="Dunkelanteil">Wie viel Prozent des Bildes dunkel sind, 0 bis 100.</param>
|
||||
public readonly record struct Bildprobe(double Sekunde, double Dunkelanteil);
|
||||
public readonly record struct Frameprobe(double Sekunde, double Dunkelanteil);
|
||||
|
||||
/// <summary>Ein Stück Abspann: von wann bis wann.</summary>
|
||||
///
|
||||
@@ -80,10 +80,10 @@ public readonly record struct Abspannregeln
|
||||
/// <b>In Sekunden, nicht in Proben</b> — dieselbe Lehre wie bei
|
||||
/// <see cref="Schlusstafel"/>, hier eine Zeile höher übersehen. Als
|
||||
/// Probenzahl hing die Nachsicht daran, wie dicht die Datei ihre
|
||||
/// Schlüsselbilder setzt: gemessen an „(T)Raumschiff Surprise" kommen
|
||||
/// Keyframes setzt: gemessen an „(T)Raumschiff Surprise" kommen
|
||||
/// 1318 Proben auf zwanzig Minuten, also eine alle 0,9 Sekunden —
|
||||
/// zwei Proben waren dort 1,8 Sekunden Nachsicht. Bei einer Datei mit
|
||||
/// einem Schlüsselbild alle zehn Sekunden waren es zwanzig. Dasselbe
|
||||
/// einem Keyframe alle zehn Sekunden waren es zwanzig. Dasselbe
|
||||
/// Zugeständnis, zehnfach verschieden.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
@@ -94,7 +94,7 @@ public readonly record struct Abspannregeln
|
||||
/// <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
|
||||
/// dicht die Datei ihre Keyframes setzt. In Proben gezählt wäre
|
||||
/// dieselbe Tafel bei der einen Datei verziehen und bei der nächsten
|
||||
/// ein Abbruch.
|
||||
/// </remarks>
|
||||
@@ -156,7 +156,7 @@ public readonly record struct Abspannregeln
|
||||
/// <b>Reine Rechnung, ohne Datei.</b> Das Abtasten braucht ffmpeg; das
|
||||
/// Beurteilen braucht nichts. Getrennt lässt sich das Schwierige prüfen,
|
||||
/// ohne einen Film vorzuhalten — dieselbe Naht wie bei
|
||||
/// <see cref="Tonabgleich"/>.
|
||||
/// <see cref="Fingerprintabgleich"/>.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public static class Abspannkante
|
||||
@@ -165,10 +165,10 @@ public static class Abspannkante
|
||||
///
|
||||
/// <param name="proben">
|
||||
/// Messpunkte über das Ende des Films, aufsteigend nach Zeit. Sie dürfen
|
||||
/// ungleichmässig liegen: Schlüsselbilder tun das.
|
||||
/// ungleichmässig liegen: Keyframes tun das.
|
||||
/// </param>
|
||||
public static IReadOnlyList<Abspannstück> Suchen(
|
||||
IReadOnlyList<Bildprobe> proben, Abspannregeln regeln) =>
|
||||
IReadOnlyList<Frameprobe> proben, Abspannregeln regeln) =>
|
||||
Suchen(proben, regeln, out _);
|
||||
|
||||
/// <param name="grund">
|
||||
@@ -178,7 +178,7 @@ public static class Abspannkante
|
||||
/// und ohne diese Zeile beginnt jede Nachfrage wieder beim Raten.
|
||||
/// </param>
|
||||
public static IReadOnlyList<Abspannstück> Suchen(
|
||||
IReadOnlyList<Bildprobe> proben, Abspannregeln regeln, out string grund)
|
||||
IReadOnlyList<Frameprobe> proben, Abspannregeln regeln, out string grund)
|
||||
{
|
||||
grund = "gefunden";
|
||||
if (proben.Count < 2)
|
||||
@@ -244,11 +244,11 @@ public static class Abspannkante
|
||||
/// <remarks>
|
||||
/// <b>Die Nachsicht zählt Sekunden, nicht Proben.</b> Eine Schrifttafel
|
||||
/// blitzt auf, ein Logo steht zwei Sekunden im Bild; wie viele
|
||||
/// Messpunkte darauf fallen, hängt allein an der Schlüsselbilddichte
|
||||
/// Messpunkte darauf fallen, hängt allein an der Keyframedichte
|
||||
/// der Datei und schwankt um eine Zehnerpotenz.
|
||||
/// </remarks>
|
||||
private static List<Abspannstück> Läufe(
|
||||
IReadOnlyList<Bildprobe> proben, Abspannregeln regeln)
|
||||
IReadOnlyList<Frameprobe> proben, Abspannregeln regeln)
|
||||
{
|
||||
var läufe = new List<Abspannstück>();
|
||||
double? von = null;
|
||||
@@ -281,7 +281,7 @@ public static class Abspannkante
|
||||
|
||||
/// <summary>Die letzte helle Probe vor dem Fund — der Suchbereich fürs Nachmessen.</summary>
|
||||
public static double? HellDavor(
|
||||
IReadOnlyList<Bildprobe> proben, double dunkelAb, Abspannregeln regeln)
|
||||
IReadOnlyList<Frameprobe> proben, double dunkelAb, Abspannregeln regeln)
|
||||
{
|
||||
double? letzteHelle = null;
|
||||
foreach (var p in proben)
|
||||
@@ -297,7 +297,7 @@ public static class Abspannkante
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// <b>Binäre Suche mit kurzen Sonden, nicht noch ein Durchlauf.</b> Die
|
||||
/// grobe Reihe stammt aus Schlüsselbildern, und die liegen je nach
|
||||
/// grobe Reihe stammt aus Keyframesn, und die liegen je nach
|
||||
/// Aufnahme fünf bis zehn Sekunden auseinander. Den ganzen Bereich fein
|
||||
/// abzutasten hiesse, ihn zu dekodieren; zehn Sonden von je einer
|
||||
/// Sekunde kosten einen Bruchteil davon. Denselben Weg geht Jellyfins
|
||||
|
||||
+17
-17
@@ -24,7 +24,7 @@ public sealed record Gemeinsamkeit(double AVon, double ABis, double BVon, double
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// <b>Verglichen werden Tonabdrücke, keine Töne.</b> Was hereinkommt, ist je
|
||||
/// Folge eine Reihe von 32-Bit-Zahlen, eine je Zeitscheibe — der Abdruck,
|
||||
/// Folge eine Reihe von 32-Bit-Zahlen, eine je Zeitscheibe — der Fingerprint,
|
||||
/// den Chromaprint aus dem Ton rechnet. Zwei Aufnahmen desselben Stücks
|
||||
/// ergeben darin fast dieselben Zahlen, auch bei anderer Lautstärke, anderem
|
||||
/// Codec und anderer Bitrate. Deshalb wird nicht auf Gleichheit geprüft,
|
||||
@@ -36,7 +36,7 @@ public sealed record Gemeinsamkeit(double AVon, double ABis, double BVon, double
|
||||
/// sich das Schwierige prüfen, ohne eine Mediendatei vorzuhalten.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public static class Tonabgleich
|
||||
public static class Fingerprintabgleich
|
||||
{
|
||||
/// <summary>Wie viele Bits zweier Abdrücke sich unterscheiden dürfen.</summary>
|
||||
///
|
||||
@@ -67,7 +67,7 @@ public static class Tonabgleich
|
||||
|
||||
/// <summary>Sucht das längste Stück, das <paramref name="a"/> und <paramref name="b"/> teilen.</summary>
|
||||
///
|
||||
/// <param name="sekundenJeAbdruck">
|
||||
/// <param name="sekundenJeFingerprint">
|
||||
/// Wie viel Zeit eine Zeitscheibe abdeckt. Chromaprint rechnet mit
|
||||
/// 11025 Hz und 4096 Abtastwerten bei zwei Dritteln Überlappung — das
|
||||
/// sind rund 0,1238 Sekunden.
|
||||
@@ -75,14 +75,14 @@ public static class Tonabgleich
|
||||
public static Gemeinsamkeit? Finden(
|
||||
IReadOnlyList<uint> a,
|
||||
IReadOnlyList<uint> b,
|
||||
double sekundenJeAbdruck,
|
||||
double sekundenJeFingerprint,
|
||||
double mindestlänge = StandardMindestlänge,
|
||||
int bitabstand = StandardBitabstand,
|
||||
int lücke = StandardLücke)
|
||||
{
|
||||
if (a.Count == 0 || b.Count == 0 || sekundenJeAbdruck <= 0) return null;
|
||||
if (a.Count == 0 || b.Count == 0 || sekundenJeFingerprint <= 0) return null;
|
||||
|
||||
var mindestens = (int)Math.Ceiling(mindestlänge / sekundenJeAbdruck);
|
||||
var mindestens = (int)Math.Ceiling(mindestlänge / sekundenJeFingerprint);
|
||||
if (mindestens <= 0) mindestens = 1;
|
||||
|
||||
Gemeinsamkeit? beste = null;
|
||||
@@ -90,7 +90,7 @@ public static class Tonabgleich
|
||||
// **Jede Verschiebung durchprobieren.** Das Intro steht in beiden
|
||||
// Folgen an einer anderen Stelle; gesucht ist die Verschiebung, bei
|
||||
// der die meisten Zeitscheiben aufeinanderpassen. Bei zwei Minuten
|
||||
// Abdruck sind das ein paar Millionen Vergleiche — ein Wimpernschlag
|
||||
// Fingerprint sind das ein paar Millionen Vergleiche — ein Wimpernschlag
|
||||
// gegen das Lesen der Datei, aus der sie stammen.
|
||||
for (var versatz = -(b.Count - 1); versatz < a.Count; versatz++)
|
||||
{
|
||||
@@ -102,13 +102,13 @@ public static class Tonabgleich
|
||||
var lauf = LängsterLauf(a, b, vonA, vonB, länge, bitabstand, lücke);
|
||||
if (lauf is not var (start, dauer) || dauer < mindestens) continue;
|
||||
|
||||
if (beste is not null && dauer * sekundenJeAbdruck <= beste.Laenge) continue;
|
||||
if (beste is not null && dauer * sekundenJeFingerprint <= beste.Laenge) continue;
|
||||
|
||||
beste = new Gemeinsamkeit(
|
||||
(vonA + start) * sekundenJeAbdruck,
|
||||
(vonA + start + dauer) * sekundenJeAbdruck,
|
||||
(vonB + start) * sekundenJeAbdruck,
|
||||
(vonB + start + dauer) * sekundenJeAbdruck);
|
||||
(vonA + start) * sekundenJeFingerprint,
|
||||
(vonA + start + dauer) * sekundenJeFingerprint,
|
||||
(vonB + start) * sekundenJeFingerprint,
|
||||
(vonB + start + dauer) * sekundenJeFingerprint);
|
||||
}
|
||||
|
||||
return beste is not null && beste.Laenge >= mindestlänge ? beste : null;
|
||||
@@ -176,8 +176,8 @@ public static class Tonabgleich
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public static IReadOnlyDictionary<TSchlüssel, (double Von, double Bis)> ReiheAbgleichen<TSchlüssel>(
|
||||
IReadOnlyList<(TSchlüssel Schlüssel, IReadOnlyList<uint> Abdruck)> folgen,
|
||||
double sekundenJeAbdruck,
|
||||
IReadOnlyList<(TSchlüssel Schlüssel, IReadOnlyList<uint> Fingerprint)> folgen,
|
||||
double sekundenJeFingerprint,
|
||||
double mindestlänge = StandardMindestlänge)
|
||||
where TSchlüssel : notnull
|
||||
{
|
||||
@@ -186,10 +186,10 @@ public static class Tonabgleich
|
||||
|
||||
for (var i = 0; i + 1 < folgen.Count; i++)
|
||||
{
|
||||
var (schlüsselA, abdruckA) = folgen[i];
|
||||
var (schlüsselB, abdruckB) = folgen[i + 1];
|
||||
var (schlüsselA, fingerprintA) = folgen[i];
|
||||
var (schlüsselB, fingerprintB) = folgen[i + 1];
|
||||
|
||||
var treffer = Finden(abdruckA, abdruckB, sekundenJeAbdruck, mindestlänge);
|
||||
var treffer = Finden(fingerprintA, fingerprintB, sekundenJeFingerprint, mindestlänge);
|
||||
if (treffer is null) continue;
|
||||
|
||||
// Der erste Fund je Folge gewinnt: er stammt vom Vergleich mit
|
||||
Reference in New Issue
Block a user