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:
Scarriffle
2026-10-16 16:26:57 +02:00
co-authored by Claude Opus 5
parent 32f592b83e
commit ed13d5fa43
73 changed files with 2137 additions and 694 deletions
@@ -8,41 +8,41 @@ namespace Flaro.Media.Tests;
/// <b>Ohne das Programm selbst.</b> Ob Chromaprint rechnen kann, ist nicht
/// unsere Frage; ob wir seine Ausgabe richtig verstehen, schon. Die Zahlen
/// kommen mit Vorzeichen, gemeint sind die Bitmuster — wer das verwechselt,
/// bekommt einen Abdruck, der nie zu einem zweiten passt.
/// bekommt einen Fingerprint, der nie zu einem zweiten passt.
/// </remarks>
public class TonabdruckleserTests
public class FingerprintleserTests
{
[Test]
public void Die_Zahlen_werden_als_Bitmuster_gelesen()
{
var abdruck = Tonabdruckleser.Lesen("DURATION=120\nFINGERPRINT=1,-1,2147483647,-2147483648\n");
var fingerprint = Fingerprintleser.Lesen("DURATION=120\nFINGERPRINT=1,-1,2147483647,-2147483648\n");
Assert.That(abdruck, Is.EqualTo(new uint[] { 1u, 0xFFFFFFFFu, 0x7FFFFFFFu, 0x80000000u }));
Assert.That(fingerprint, Is.EqualTo(new uint[] { 1u, 0xFFFFFFFFu, 0x7FFFFFFFu, 0x80000000u }));
}
[Test]
public void Ohne_Abdruckzeile_kommt_nichts()
public void Ohne_Fingerprintzeile_kommt_nichts()
{
Assert.That(Tonabdruckleser.Lesen("DURATION=120\n"), Is.Empty);
Assert.That(Fingerprintleser.Lesen("DURATION=120\n"), Is.Empty);
}
[Test]
public void Eine_leere_Zeile_ergibt_keinen_Abdruck()
public void Eine_leere_Zeile_ergibt_keinen_Fingerprint()
{
Assert.That(Tonabdruckleser.Lesen("FINGERPRINT=\n"), Is.Empty);
Assert.That(Fingerprintleser.Lesen("FINGERPRINT=\n"), Is.Empty);
}
/// <summary>Eine Zeitscheibe ist rund 124 Millisekunden.</summary>
///
/// <remarks>
/// Daran hängt jede Zeitangabe, die aus einem Abdruck entsteht. Stimmt
/// die Zahl nicht, liegt jede Marke um denselben Faktor daneben — und
/// Daran hängt jede Zeitangabe, die aus einem Fingerprint entsteht. Stimmt
/// die Zahl nicht, liegt jedes Segment um denselben Faktor daneben — und
/// das sähe aus wie ein Fehler in der Erkennung.
/// </remarks>
[Test]
public void Eine_Zeitscheibe_dauert_124_Millisekunden()
{
Assert.That(Tonabdruckleser.SekundenJeAbdruck, Is.EqualTo(0.1238).Within(0.0005));
Assert.That(Fingerprintleser.SekundenJeFingerprint, Is.EqualTo(0.1238).Within(0.0005));
}
}
@@ -59,27 +59,27 @@ public class TonabdruckleserTests
/// Sekunden: <c>-length 0</c> ergibt 21 786 Werte, also 0,1239 Sekunden je
/// Wert; <c>-length 120</c> bricht nach 948 Werten ab.
/// </remarks>
public class TonabdruckRateTests
public class FingerprintRateTests
{
[Test]
public void Die_Rate_stimmt_mit_der_Messung_ueberein()
{
// 21 786 Werte für 2700 Sekunden.
var gemessen = 2700.0 / 21786;
Assert.That(Math.Abs(gemessen - Tonabdruckleser.SekundenJeAbdruck), Is.LessThan(0.0005));
Assert.That(Math.Abs(gemessen - Fingerprintleser.SekundenJeFingerprint), Is.LessThan(0.0005));
}
/// <summary>Eine Stunde Abdruck passt in eine überschaubare Reihe.</summary>
/// <summary>Eine Stunde Fingerprint passt in eine überschaubare Reihe.</summary>
///
/// <remarks>
/// Damit niemand auf die Idee kommt, den Abdruck einer ganzen Folge für
/// Damit niemand auf die Idee kommt, den Fingerprint einer ganzen Folge für
/// zu gross zu halten und wieder in Ausschnitten zu lesen: eine Stunde
/// sind knapp 29 000 Werte, also 116 Kilobyte.
/// </remarks>
[Test]
public void Eine_Stunde_sind_rund_29000_Werte()
{
var werte = (int)(3600 / Tonabdruckleser.SekundenJeAbdruck);
var werte = (int)(3600 / Fingerprintleser.SekundenJeFingerprint);
Assert.That(werte, Is.InRange(28500, 29500));
}
}
@@ -6,7 +6,7 @@ using Microsoft.Extensions.Logging.Abstractions;
namespace Flaro.Media.Tests;
/// <summary>Die Ausgabe des Abtasters lesen — ohne ffmpeg.</summary>
public class BildabtasterLesenTests
public class FrameabtasterLesenTests
{
[Test]
public void Zeit_und_Wert_werden_zu_Proben()
@@ -19,12 +19,12 @@ public class BildabtasterLesenTests
""";
var proben = Bildabtaster.Lesen(ausgabe, versatz: 60);
var proben = Frameabtaster.Lesen(ausgabe, versatz: 60);
Assert.That(proben, Is.EqualTo(new[]
{
new Bildprobe(62.5, 3),
new Bildprobe(65, 84),
new Frameprobe(62.5, 3),
new Frameprobe(65, 84),
}));
}
@@ -38,13 +38,13 @@ public class BildabtasterLesenTests
[Test]
public void Ein_Wert_ohne_Zeit_faellt_heraus()
{
Assert.That(Bildabtaster.Lesen("lavfi.blackframe.pblack=84\n", 0), Is.Empty);
Assert.That(Frameabtaster.Lesen("lavfi.blackframe.pblack=84\n", 0), Is.Empty);
}
[Test]
public void Leere_Ausgabe_ergibt_nichts()
{
Assert.That(Bildabtaster.Lesen("", 0), Is.Empty);
Assert.That(Frameabtaster.Lesen("", 0), Is.Empty);
}
}
@@ -63,7 +63,7 @@ public class BildabtasterLesenTests
/// rotgemeldet — ein fehlendes Werkzeug ist kein Fehler im Quelltext.
/// </para>
/// </remarks>
public class BildabtasterLaufTests
public class FrameabtasterLaufTests
{
private string _ordner = "";
private string _datei = "";
@@ -117,8 +117,8 @@ public class BildabtasterLaufTests
try { Directory.Delete(_ordner, recursive: true); } catch (IOException) { }
}
private static Bildabtaster Abtaster() =>
new(FfmpegOptions.FromEnvironment(), NullLogger<Bildabtaster>.Instance);
private static Frameabtaster Abtaster() =>
new(FfmpegOptions.FromEnvironment(), NullLogger<Frameabtaster>.Instance);
/// <summary>Die Kante liegt da, wo sie erzeugt wurde: bei 90 Sekunden.</summary>
[Test]
@@ -198,7 +198,7 @@ public class BildabtasterLaufTests
/// 450 gewesen.
/// </para>
/// </remarks>
public class BildabtasterBittiefeTests
public class FrameabtasterBittiefeTests
{
private string _ordner = "";
@@ -246,8 +246,8 @@ public class BildabtasterBittiefeTests
[Test]
public async Task Acht_und_zehn_Bit_ergeben_dieselbe_Zahl()
{
var abtaster = new Bildabtaster(
FfmpegOptions.FromEnvironment(), NullLogger<Bildabtaster>.Instance);
var abtaster = new Frameabtaster(
FfmpegOptions.FromEnvironment(), NullLogger<Frameabtaster>.Instance);
var acht = await abtaster.DunkelanteilAsync(Erzeugen("yuv420p"), bei: 2);
var zehn = await abtaster.DunkelanteilAsync(Erzeugen("yuv420p10le"), bei: 2);
@@ -241,7 +241,7 @@ public class HlsArgumenteTests
/// <summary>Beim Rechnen wird jedes Segment gleich lang.</summary>
[Test]
public void Beim_Rechnen_stehen_die_Schluesselbilder_fest()
public void Beim_Rechnen_stehen_die_Keyframes_fest()
{
var zeile = Zeile([Bild("hevc"), Ton(1)]);
@@ -197,15 +197,15 @@ public class HlsLaufTests
/// <remarks>
/// <para>
/// <b>Das ist der Test, der gefehlt hat.</b> Die Quelle hat genau ein
/// Schlüsselbild, bei null; jeder Sprung landet also mitten in der
/// Keyframe, bei null; jeder Sprung landet also mitten in der
/// Bildgruppe — der schlimmste Fall. Genau gesprungen schnitte ffmpeg den
/// Ton auf die Sekunde, das kopierte Bild aber nicht, und dazwischen läge
/// Bild ohne Ton.
/// </para>
/// <para>
/// Zuerst hatte ich dafür den Sprung auf das Schlüsselbild gerundet. Das
/// Zuerst hatte ich dafür den Sprung auf das Keyframe gerundet. Das
/// war zu scharf geschliffen: eine Millisekunde zu klein, und ffmpeg nimmt
/// das Schlüsselbild davor — gemessen wurden aus 0,004 s Abstand dann
/// das Keyframe davor — gemessen wurden aus 0,004 s Abstand dann
/// 4,98. `-noaccurate_seek` braucht diese Genauigkeit nicht.
/// </para>
/// </remarks>
@@ -229,15 +229,15 @@ public class HlsLaufTests
///
/// <remarks>
/// <para>
/// <b>Die Quelle hat genau ein Schlüsselbild, bei null.</b> Ein Sprung auf
/// <b>Die Quelle hat genau ein Keyframe, bei null.</b> Ein Sprung auf
/// Sekunde 4 ist damit der schlimmste Fall: das kopierte Bild muss ganz
/// vorn anfangen, der neu gerechnete Ton könnte bei 4 anfangen — vier
/// Sekunden stummes Bild, und eine Uhr, die um vier Sekunden danebenliegt.
/// </para>
/// <para>
/// Gemessen an einer Quelle mit Schlüsselbildern alle fünf Sekunden und
/// Gemessen an einer Quelle mit Keyframesn alle fünf Sekunden und
/// einem Sprung auf Sekunde 12 war es genau so: Bild ab 0, Ton ab 1,98.
/// Deshalb rundet der Koordinator den Sprung auf das Schlüsselbild —
/// Deshalb rundet der Koordinator den Sprung auf das Keyframe —
/// hier wird geprüft, dass die Zeile das auch verträgt.
/// </para>
/// </remarks>
@@ -247,7 +247,7 @@ public class HlsLaufTests
var quelle = Path.Combine(_ordner, "lang.mkv");
Lang(quelle);
// Auf dem Schlüsselbild, so wie der Koordinator es rundet.
// Auf dem Keyframe, so wie der Koordinator es rundet.
var (bild, ton) = Anfang(Laufen(quelle, ab: 0));
Assert.That(Math.Abs(bild - ton), Is.LessThan(0.2),
@@ -5,7 +5,7 @@ using Microsoft.Extensions.Logging.Abstractions;
namespace Flaro.Media.Tests;
/// <summary>Sagt die Schlüsselbildsuche dasselbe wie ffmpeg?</summary>
/// <summary>Sagt die Keyframesuche dasselbe wie ffmpeg?</summary>
///
/// <remarks>
/// <para>
@@ -24,7 +24,7 @@ namespace Flaro.Media.Tests;
/// bleibt.
/// </para>
/// </remarks>
public class SchluesselbilderTests
public class KeyframesTests
{
private string _ordner = "";
private string _datei = "";
@@ -83,8 +83,8 @@ public class SchluesselbilderTests
[TestCase(100.2)]
public async Task Die_Suche_trifft_den_Anfang_des_Stroms(double wunsch)
{
var suche = new Schluesselbilder(
FfmpegOptions.FromEnvironment(), NullLogger<Schluesselbilder>.Instance);
var suche = new Keyframes(
FfmpegOptions.FromEnvironment(), NullLogger<Keyframes>.Instance);
var gemeldet = await suche.DavorAsync(_datei, wunsch);
var echt = EchterAnfang(wunsch);
@@ -102,8 +102,8 @@ public class SchluesselbilderTests
[TestCase(73.3)]
public async Task Nie_hinter_dem_Wunsch(double wunsch)
{
var suche = new Schluesselbilder(
FfmpegOptions.FromEnvironment(), NullLogger<Schluesselbilder>.Instance);
var suche = new Keyframes(
FfmpegOptions.FromEnvironment(), NullLogger<Keyframes>.Instance);
Assert.That(await suche.DavorAsync(_datei, wunsch), Is.LessThanOrEqualTo(wunsch));
}