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
@@ -220,7 +220,7 @@ public class EinladungTests
Assert.That(einladung.Token, Is.Not.Empty);
Assert.That(roh, Does.Not.Contain(einladung.Token),
"Gespeichert ist nur der Fingerabdruck — aus der Liste darf kein Link zu bauen sein.");
"Gespeichert ist nur der Fingerfingerprint — aus der Liste darf kein Link zu bauen sein.");
}
/// <summary>Ein Tippfehler im Namen darf den Link nicht verbrennen.</summary>
+2 -2
View File
@@ -30,8 +30,8 @@ public sealed class FlaroServer : IDisposable
///
/// <remarks>
/// <b>Sparsam benutzen.</b> Ein Test, der am Server vorbei schreibt,
/// prüft nicht mehr, ob der Server es könnte. Für Marken ist es nötig:
/// sie entstehen aus einem Tonabgleich, und der braucht Mediendateien
/// prüft nicht mehr, ob der Server es könnte. Für Segmente ist es nötig:
/// sie entstehen aus einem Fingerprintabgleich, und der braucht Mediendateien
/// mit echtem Ton, die es hier nicht gibt.
/// </remarks>
public async Task InDerDatenbank(Func<FlaroContext, Task> tun)
@@ -9,12 +9,12 @@ namespace Flaro.Api.Tests;
/// <summary>Vorspann und Abspann auf dem Weg zu den Spielern.</summary>
///
/// <remarks>
/// <b>Ohne Tonabgleich.</b> Ob er findet, was er finden soll, steht in
/// <c>TonabgleichTests</c> — mit erfundenen Abdrücken und ohne Mediendateien.
/// Hier geht es um das Stück danach: dass eine gefundene Marke auch bei dem
/// <b>Ohne Fingerprintabgleich.</b> Ob er findet, was er finden soll, steht in
/// <c>FingerprintabgleichTests</c> — mit erfundenen Abdrücken und ohne Mediendateien.
/// Hier geht es um das Stück danach: dass eine gefundene Segment auch bei dem
/// ankommt, der den Knopf zeichnen soll.
/// </remarks>
public class MarkenTests
public class SegmentTests
{
private string _bestand = "";
@@ -67,7 +67,7 @@ public class MarkenTests
}
[Test]
public async Task Eine_Marke_kommt_bei_der_Folge_an()
public async Task Eine_Segment_kommt_bei_der_Folge_an()
{
Datei("Dark/Season 01/Dark - S01E01 - Geheimnisse.mkv");
@@ -77,14 +77,14 @@ public class MarkenTests
await server.InDerDatenbank(async db =>
{
db.Marken.Add(new Marke
db.Segmente.Add(new Segment
{
EpisodeId = folge, Art = Markenart.Intro,
EpisodeId = folge, Art = Segmentart.Intro,
VonSekunden = 30, BisSekunden = 65, ErkanntAm = DateTimeOffset.UtcNow,
});
db.Marken.Add(new Marke
db.Segmente.Add(new Segment
{
EpisodeId = folge, Art = Markenart.Abspann,
EpisodeId = folge, Art = Segmentart.Abspann,
VonSekunden = 2500, BisSekunden = 2600, ErkanntAm = DateTimeOffset.UtcNow,
});
await db.SaveChangesAsync();
@@ -105,7 +105,7 @@ public class MarkenTests
/// <summary>Auch in der Folgenliste — dort holt die App sie beim Blättern.</summary>
[Test]
public async Task Die_Folgenliste_bringt_die_Marken_mit()
public async Task Die_Folgenliste_bringt_die_Segmente_mit()
{
Datei("Dark/Season 01/Dark - S01E01 - Geheimnisse.mkv");
Datei("Dark/Season 01/Dark - S01E02 - Luegen.mkv");
@@ -116,9 +116,9 @@ public class MarkenTests
await server.InDerDatenbank(async db =>
{
db.Marken.Add(new Marke
db.Segmente.Add(new Segment
{
EpisodeId = folge, Art = Markenart.Intro,
EpisodeId = folge, Art = Segmentart.Intro,
VonSekunden = 10, BisSekunden = 40, ErkanntAm = DateTimeOffset.UtcNow,
});
await db.SaveChangesAsync();
@@ -131,11 +131,11 @@ public class MarkenTests
{
Assert.That(liste!.Items.Single(f => f.Id == folge).Markers, Has.Count.EqualTo(1));
Assert.That(liste.Items.Single(f => f.Id != folge).Markers, Is.Empty,
"eine Folge ohne Marke bekommt eine leere Liste, nicht die der anderen");
"eine Folge ohne Segment bekommt eine leere Liste, nicht die der anderen");
});
}
/// <summary>Ohne erkannte Marken bleibt die Liste leer — kein Knopf, kein Fehler.</summary>
/// <summary>Ohne erkannte Segmente bleibt die Liste leer — kein Knopf, kein Fehler.</summary>
[Test]
public async Task Ohne_Erkennung_ist_die_Liste_leer()
{
@@ -196,11 +196,11 @@ public class MarkenTests
(await client.PostAsync($"/api/v1/series/{serie}/marken", null)).EnsureSuccessStatusCode();
// Der Lauf hängt im Hintergrund; ohne fpcalc ist er sofort durch.
List<Markensuche> vermerke = [];
List<Segmentsuche> vermerke = [];
for (var versuch = 0; versuch < 40; versuch++)
{
await server.InDerDatenbank(async db =>
vermerke = await db.Markensuchen.AsNoTracking()
vermerke = await db.Segmentsuchen.AsNoTracking()
.Where(x => x.SeriesId == serie).ToListAsync());
if (vermerke.Count > 0) break;
await Task.Delay(50);
@@ -217,9 +217,9 @@ public class MarkenTests
}
}
/// <summary>Ein Film trägt seine Abspannmarke genauso.</summary>
/// <summary>Ein Film trägt seine Abspannsegment genauso.</summary>
[Test]
public async Task Ein_Film_bekommt_seine_Marke()
public async Task Ein_Film_bekommt_seine_Segment()
{
Datei("Matrix (1999)/Matrix (1999).mkv");
@@ -230,9 +230,9 @@ public class MarkenTests
await server.InDerDatenbank(async db =>
{
db.Marken.Add(new Marke
db.Segmente.Add(new Segment
{
MovieId = film, Art = Markenart.Abspann,
MovieId = film, Art = Segmentart.Abspann,
VonSekunden = 6900, BisSekunden = 7200, ErkanntAm = DateTimeOffset.UtcNow,
});
await db.SaveChangesAsync();
@@ -249,7 +249,7 @@ public class MarkenTests
});
}
/// <summary>Eine falsche Marke lässt sich wieder wegnehmen.</summary>
/// <summary>Eine falsches Segment lässt sich wieder wegnehmen.</summary>
///
/// <remarks>
/// Ohne diesen Weg wäre die Erkennung eine Blackbox, die manchmal recht
@@ -257,7 +257,7 @@ public class MarkenTests
/// tun ausser die Datenbank anzufassen.
/// </remarks>
[Test]
public async Task Eine_falsche_Filmmarke_laesst_sich_verwerfen()
public async Task Eine_falsche_Filmsegment_laesst_sich_verwerfen()
{
Datei("Matrix (1999)/Matrix (1999).mkv");
@@ -268,14 +268,14 @@ public class MarkenTests
await server.InDerDatenbank(async db =>
{
db.Marken.Add(new Marke
db.Segmente.Add(new Segment
{
MovieId = film, Art = Markenart.Abspann,
MovieId = film, Art = Segmentart.Abspann,
VonSekunden = 60, BisSekunden = 7200, ErkanntAm = DateTimeOffset.UtcNow,
});
db.Markensuchen.Add(new Markensuche
db.Segmentsuchen.Add(new Segmentsuche
{
MovieId = film, Art = Markenart.Abspann, Folgen = 1,
MovieId = film, Art = Segmentart.Abspann, Folgen = 1,
GelaufenAm = DateTimeOffset.UtcNow,
});
await db.SaveChangesAsync();
@@ -286,14 +286,14 @@ public class MarkenTests
var vermerke = 0;
await server.InDerDatenbank(async db =>
vermerke = await db.Markensuchen.CountAsync(x => x.MovieId == film));
vermerke = await db.Segmentsuchen.CountAsync(x => x.MovieId == film));
Assert.Multiple(() =>
{
Assert.That(weg.StatusCode, Is.EqualTo(HttpStatusCode.NoContent));
Assert.That(danach!.Markers, Is.Empty);
Assert.That(vermerke, Is.EqualTo(1),
"der Vermerk bleibt, sonst legte der nächste Scan dieselbe Marke wieder an");
"der Vermerk bleibt, sonst legte der nächste Scan dieselbe Segment wieder an");
});
}
@@ -301,9 +301,9 @@ public class MarkenTests
///
/// <remarks>
/// <para>
/// <b>„Schon durchsucht" und „hat eine Marke" sind zwei verschiedene
/// <b>„Schon durchsucht" und „hat ein Segment" sind zwei verschiedene
/// Dinge.</b> Ein Film, bei dem die Suche einmal nichts fand, trägt
/// einen Vermerk und keine Marke — und war damit für jeden weiteren
/// einen Vermerk und kein Segment — und war damit für jeden weiteren
/// Lauf erledigt, obwohl er der interessanteste Fall ist.
/// </para>
/// <para>
@@ -313,9 +313,9 @@ public class MarkenTests
/// </para>
/// </remarks>
[TestCase("Neue", false, TestName = "Neue überspringt, was schon durchsucht ist")]
[TestCase("Luecken", true, TestName = "Luecken nimmt sich den Film ohne Marke vor")]
[TestCase("Luecken", true, TestName = "Luecken nimmt sich den Film ohne Segment vor")]
[TestCase("Alles", true, TestName = "Alles nimmt sich alles vor")]
public async Task Der_Umfang_entscheidet_bei_einem_Film_ohne_Marke(
public async Task Der_Umfang_entscheidet_bei_einem_Film_ohne_Segment(
string umfang, bool erwartetAngefasst)
{
Datei("Matrix (1999)/Matrix (1999).mkv");
@@ -325,7 +325,7 @@ public class MarkenTests
var filme = await client.GetFromJsonAsync<Page<MovieResponse>>("/api/v1/movies");
var film = filme!.Items[0].Id;
// Gesucht wurde schon, gefunden nichts: Vermerk ja, Marke nein.
// Gesucht wurde schon, gefunden nichts: Vermerk ja, Segment nein.
var vorher = new DateTimeOffset(2020, 1, 1, 0, 0, 0, TimeSpan.Zero);
await server.InDerDatenbank(async db =>
{
@@ -334,9 +334,9 @@ public class MarkenTests
var datei = await db.MediaFiles.FirstAsync(d => d.MovieId == film);
datei.Duration = TimeSpan.FromMinutes(120);
db.Markensuchen.Add(new Markensuche
db.Segmentsuchen.Add(new Segmentsuche
{
MovieId = film, Art = Markenart.Abspann, Folgen = 1, GelaufenAm = vorher,
MovieId = film, Art = Segmentart.Abspann, Folgen = 1, GelaufenAm = vorher,
});
await db.SaveChangesAsync();
});
@@ -348,7 +348,7 @@ public class MarkenTests
for (var versuch = 0; versuch < 40; versuch++)
{
await server.InDerDatenbank(async db =>
gelaufen = await db.Markensuchen.AsNoTracking()
gelaufen = await db.Segmentsuchen.AsNoTracking()
.Where(x => x.MovieId == film).Select(x => x.GelaufenAm).FirstAsync());
if (gelaufen != vorher) break;
await Task.Delay(50);
@@ -360,9 +360,9 @@ public class MarkenTests
: "dieser Umfang muss ihn überspringen");
}
/// <summary>Ein Film mit Marke bleibt bei „nur Lücken" unangetastet.</summary>
/// <summary>Ein Film mit Segment bleibt bei „nur Lücken" unangetastet.</summary>
[Test]
public async Task Luecken_laesst_einen_Film_mit_Marke_in_Ruhe()
public async Task Luecken_laesst_einen_Film_mit_Segment_in_Ruhe()
{
Datei("Matrix (1999)/Matrix (1999).mkv");
@@ -377,14 +377,14 @@ public class MarkenTests
var datei = await db.MediaFiles.FirstAsync(d => d.MovieId == film);
datei.Duration = TimeSpan.FromMinutes(120);
db.Marken.Add(new Marke
db.Segmente.Add(new Segment
{
MovieId = film, Art = Markenart.Abspann,
MovieId = film, Art = Segmentart.Abspann,
VonSekunden = 6900, BisSekunden = 7200, ErkanntAm = vorher,
});
db.Markensuchen.Add(new Markensuche
db.Segmentsuchen.Add(new Segmentsuche
{
MovieId = film, Art = Markenart.Abspann, Folgen = 1, GelaufenAm = vorher,
MovieId = film, Art = Segmentart.Abspann, Folgen = 1, GelaufenAm = vorher,
});
await db.SaveChangesAsync();
});
@@ -395,10 +395,10 @@ public class MarkenTests
var gelaufen = DateTimeOffset.MinValue;
await server.InDerDatenbank(async db =>
gelaufen = await db.Markensuchen.AsNoTracking()
gelaufen = await db.Segmentsuchen.AsNoTracking()
.Where(x => x.MovieId == film).Select(x => x.GelaufenAm).FirstAsync());
Assert.That(gelaufen, Is.EqualTo(vorher),
"wo schon eine Marke steht, ist nichts zu füllen");
"wo schon ein Segment steht, ist nichts zu füllen");
}
}
+6 -6
View File
@@ -333,12 +333,12 @@ public class StreamTests
/// Strom fängt bei null an, der Film bei Minute 30.
/// </para>
/// <para>
/// <b>Und „echt" heisst: beim Schlüsselbild.</b> Dieser Test stand vorher
/// <b>Und „echt" heisst: beim Keyframe.</b> Dieser Test stand vorher
/// auf „genau zwei Sekunden" und hielt damit einen Fehler fest. Ein
/// kopierter Bildstrom lässt sich nicht mitten in einer Bildgruppe
/// aufschneiden: der Strom fängt beim Schlüsselbild davor an, und wer
/// aufschneiden: der Strom fängt beim Keyframe davor an, und wer
/// trotzdem „zwei" zurückmeldet, verschiebt die Uhr des Spielers um den
/// Unterschied. Diese Testdatei hat genau ein Schlüsselbild, bei null —
/// Unterschied. Diese Testdatei hat genau ein Keyframe, bei null —
/// also ist null die richtige Antwort.
/// </para>
/// </remarks>
@@ -353,10 +353,10 @@ public class StreamTests
Assert.That(strom.StartSeconds, Is.LessThanOrEqualTo(2),
"Weiter als gewünscht darf der Strom nie anfangen — das überspränge Film.");
Assert.That(strom.StartSeconds, Is.EqualTo(0),
"Diese Datei hat nur ein Schlüsselbild, bei null.");
"Diese Datei hat nur ein Keyframe, bei null.");
}
/// <summary>Schliessen räumt auf — sonst füllt sich der Kratzplatz.</summary>
/// <summary>Schliessen räumt auf — sonst füllt sich der Transcode-Cache.</summary>
[Test]
public async Task Schliessen_raeumt_die_Sitzung_weg()
{
@@ -501,7 +501,7 @@ public class StreamTests
/// <para>
/// <b>Verglichen wird mit dem gemeldeten Vorlauf, nicht mit dem
/// gewünschten</b> — und das ist keine Bequemlichkeit. Beim Kopieren
/// beginnt ffmpeg am Schlüsselbild vor dem Wunsch; in einem Prüfclip von
/// beginnt ffmpeg am Keyframe vor dem Wunsch; in einem Prüfclip von
/// vier Sekunden ist das die Null. Der Untertitel muss zu dem passen, was
/// wirklich läuft.
/// </para>
@@ -6,7 +6,7 @@ using Flaro.Core.Delivery;
namespace Flaro.Api.Tests;
/// <summary>Was der Server auf dem Kratzplatz wegräumt — und was nicht.</summary>
/// <summary>Was der Server auf dem Transcode-Cache wegräumt — und was nicht.</summary>
///
/// <remarks>
/// <para>
@@ -17,13 +17,13 @@ namespace Flaro.Api.Tests;
/// <para>
/// <b>Hier wird die Verdrahtung geprüft</b>, nicht die Regel — die steht
/// ohne Platte und ohne Server in
/// <c>Flaro.Core.Tests/KratzplatzregelTests</c>. Was hier zählt: findet der
/// <c>Flaro.Core.Tests/TranscoderegelTests</c>. Was hier zählt: findet der
/// Aufräumer die Dateien, erkennt er, was zur Mediathek gehört, und lässt
/// er das Gültige stehen.
/// </para>
/// </remarks>
[TestFixture]
public sealed class KratzplatzTests
public sealed class TranscodeTests
{
private string _bestand = "";
@@ -41,8 +41,8 @@ public sealed class KratzplatzTests
try { Directory.Delete(_bestand, recursive: true); } catch (IOException) { /* Kür */ }
}
/// <summary>Ein Server mit einem eingelesenen Film und bekanntem Kratzplatz.</summary>
private async Task<(HttpClient Client, string Kratzplatz, Guid DateiId)> Aufgesetzt(FlaroServer server)
/// <summary>Ein Server mit einem eingelesenen Film und bekanntem Transcode-Cache.</summary>
private async Task<(HttpClient Client, string Ordner, Guid DateiId)> Aufgesetzt(FlaroServer server)
{
var voll = Path.Combine(_bestand, "Probe (2026)", "Probe (2026).mkv");
Directory.CreateDirectory(Path.GetDirectoryName(voll)!);
@@ -244,18 +244,18 @@ public sealed class KratzplatzTests
Paketname.Bauen(dateiId, Gestempelt, PackageCoordinator.Bauart), kilobyte: 8);
var orte = await client.GetFromJsonAsync<List<Speicherort>>("/api/v1/system/storage");
var kratzplatz = orte!.First(o => o.Variable == "FLARO_TRANSCODE");
var transcode = orte!.First(o => o.Variable == "FLARO_TRANSCODE");
Assert.Multiple(() =>
{
Assert.That(kratzplatz.Teile, Is.Not.Null);
Assert.That(kratzplatz.Teile!.Select(t => t.Name),
Assert.That(transcode.Teile, Is.Not.Null);
Assert.That(transcode.Teile!.Select(t => t.Name),
Is.EquivalentTo(new[] { "stroeme", "packages", "untertitel" }));
var pakete = kratzplatz.Teile!.First(t => t.Name == "packages");
var pakete = transcode.Teile!.First(t => t.Name == "packages");
Assert.That(pakete.Dateien, Is.EqualTo(1));
Assert.That(pakete.Frei, Is.EqualTo(8 * 1024), "das Feld Frei trägt hier die Belegung des Ordners");
Assert.That(orte!.First(o => o.Variable == "FLARO_DATA").Teile, Is.Null,
"nur der Kratzplatz wird aufgeschlüsselt");
"nur der Transcode-Cache wird aufgeschlüsselt");
});
}
@@ -261,7 +261,7 @@ public sealed class WartungsplanTests
Assert.That(nachher!.BackupDays, Is.EqualTo(1));
Assert.That(nachher.BackupKeep, Is.EqualTo(10));
Assert.That(nachher.OptimizeDays, Is.EqualTo(7), "was nicht geschickt wurde, bleibt");
Assert.That(nachher.CapGb, Is.EqualTo(20).Within(0.01), "und der Kratzplatz auch");
Assert.That(nachher.CapGb, Is.EqualTo(20).Within(0.01), "und der Transcode-Cache auch");
});
}
}
@@ -28,11 +28,11 @@ public class AbspannkanteTests
/// <summary>Eine Reihe von Messpunkten im Abstand von zehn Sekunden.</summary>
///
/// <param name="abspannAb">Ab wann dunkel. <c>null</c> heisst: nie.</param>
private static List<Bildprobe> Reihe(double von, double bis, double? abspannAb)
private static List<Frameprobe> Reihe(double von, double bis, double? abspannAb)
{
var proben = new List<Bildprobe>();
var proben = new List<Frameprobe>();
for (var t = von; t <= bis; t += 10)
proben.Add(new Bildprobe(t, abspannAb is { } ab && t >= ab ? 85 : 0));
proben.Add(new Frameprobe(t, abspannAb is { } ab && t >= ab ? 85 : 0));
return proben;
}
@@ -71,7 +71,7 @@ public class AbspannkanteTests
/// <summary>Eine Szene nach dem Abspann nimmt den Fund weg.</summary>
///
/// <remarks>
/// Lieber keine Marke als eine, die über die Szene hinwegspringt.
/// Lieber kein Segment als eine, die über die Szene hinwegspringt.
/// </remarks>
[Test]
public void Eine_Szene_nach_dem_Abspann_nimmt_den_Fund_weg()
@@ -223,14 +223,14 @@ public class AbspannAnteilTests
///
/// <remarks>
/// Zwanzig Minuten vor dem Ende einer Folge von fünfundvierzig, an den
/// Schlüsselbildern abgetastet — also alle fünf Sekunden eine Probe.
/// Keyframesn abgetastet — also alle fünf Sekunden eine Probe.
/// </remarks>
private static List<Bildprobe> Fenster(
private static List<Frameprobe> Fenster(
double abspannAb, double imAbspann, double imInhalt = 0)
{
var proben = new List<Bildprobe>();
var proben = new List<Frameprobe>();
for (var t = 1500.0; t <= 2700; t += 5)
proben.Add(new Bildprobe(t, t < abspannAb ? imInhalt : imAbspann));
proben.Add(new Frameprobe(t, t < abspannAb ? imInhalt : imAbspann));
return proben;
}
@@ -300,13 +300,13 @@ public class AbspannSchlusstafelTests
private static double? Anfang(IReadOnlyList<Abspannstück> stücke) =>
stücke.Count == 0 ? null : stücke[0].Von;
private static List<Bildprobe> Fenster(double abspannAb, double tafelAb = double.MaxValue)
private static List<Frameprobe> Fenster(double abspannAb, double tafelAb = double.MaxValue)
{
var proben = new List<Bildprobe>();
var proben = new List<Frameprobe>();
for (var t = 1500.0; t <= 2700; t += 5)
{
var dunkel = t >= tafelAb ? 0 : t < abspannAb ? 0 : 85;
proben.Add(new Bildprobe(t, dunkel));
proben.Add(new Frameprobe(t, dunkel));
}
return proben;
}
@@ -336,20 +336,20 @@ public class AbspannSchlusstafelTests
}
}
/// <summary>Dieselbe Szene, zweierlei Schlüsselbilddichte.</summary>
/// <summary>Dieselbe Szene, zweierlei Keyframedichte.</summary>
///
/// <remarks>
/// <para>
/// <b>Der Fehler, für den diese Prüfungen stehen.</b> Die Nachsicht
/// gegenüber hellen Stellen mitten im Abspann war in <i>Proben</i>
/// angegeben, nicht in Sekunden. Was das bedeutet, hängt allein daran,
/// wie dicht eine Datei ihre Schlüsselbilder setzt — und das schwankt um
/// wie dicht eine Datei ihre Keyframes setzt — und das schwankt um
/// eine Zehnerpotenz.
/// </para>
/// <para>
/// Gemessen am Bestand: „(T)Raumschiff Surprise" liefert 1318 Proben auf
/// zwanzig Minuten, also eine alle 0,9 Sekunden. Zwei Proben Nachsicht
/// waren dort 1,8 Sekunden; bei einer Datei mit einem Schlüsselbild alle
/// waren dort 1,8 Sekunden; bei einer Datei mit einem Keyframe alle
/// zehn Sekunden waren dieselben zwei Proben zwanzig Sekunden. Ein
/// fünfsekündiges Logo mitten im Abspann zerriss die Strecke deshalb nur
/// bei der dicht abgetasteten Datei — und genau die fiel durch.
@@ -370,13 +370,13 @@ public class AbspannNachsichtTests
stücke.Count == 0 ? null : stücke[0].Von;
/// <summary>Abspann ab 2340 s, darin fünf Sekunden helles Logo.</summary>
private static List<Bildprobe> Fenster(double takt)
private static List<Frameprobe> Fenster(double takt)
{
var proben = new List<Bildprobe>();
var proben = new List<Frameprobe>();
for (var t = 1500.0; t <= 2700; t += takt)
{
var imLogo = t >= 2500 && t < 2505;
proben.Add(new Bildprobe(t, t < 2340 ? 0 : imLogo ? 5 : 85));
proben.Add(new Frameprobe(t, t < 2340 ? 0 : imLogo ? 5 : 85));
}
return proben;
}
@@ -408,12 +408,12 @@ public class AbspannNachsichtTests
[Test]
public void Eine_Minute_hell_mittendrin_teilt_den_Abspann_in_zwei_Stuecke()
{
var proben = new List<Bildprobe>();
var proben = new List<Frameprobe>();
for (var t = 1500.0; t <= 2700; t += 1)
{
// 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 ? 0 : 85));
proben.Add(new Frameprobe(t, hell ? 0 : 85));
}
var stücke = Abspannkante.Suchen(proben, Regeln);
@@ -433,15 +433,15 @@ public class AbspannNachsichtTests
[Test]
public void Der_Grund_wird_benannt()
{
var hell = new List<Bildprobe>();
for (var t = 1500.0; t <= 2700; t += 5) hell.Add(new Bildprobe(t, 0));
var hell = new List<Frameprobe>();
for (var t = 1500.0; t <= 2700; t += 5) hell.Add(new Frameprobe(t, 0));
Abspannkante.Suchen(hell, Regeln, out var grund);
Assert.That(grund, Does.Contain("keine dunkle Strecke"));
var kurz = new List<Bildprobe>();
var kurz = new List<Frameprobe>();
for (var t = 1500.0; t <= 2700; t += 5)
kurz.Add(new Bildprobe(t, t < 2680 ? 0 : 85));
kurz.Add(new Frameprobe(t, t < 2680 ? 0 : 85));
Abspannkante.Suchen(kurz, Regeln, out var zuKurz);
Assert.That(zuKurz, Does.Contain("zusammen nur"));
@@ -469,13 +469,13 @@ public class AbspannstueckeTests
private static readonly Abspannregeln Regeln = Abspannregeln.Vorgabe;
/// <summary>Eine Reihe im Sekundentakt, dunkel in den angegebenen Spannen.</summary>
private static List<Bildprobe> Reihe(double bis, params (double Von, double Bis)[] dunkel)
private static List<Frameprobe> Reihe(double bis, params (double Von, double Bis)[] dunkel)
{
var proben = new List<Bildprobe>();
var proben = new List<Frameprobe>();
for (var t = 0.0; t <= bis; t += 1)
{
var istDunkel = Array.Exists(dunkel, d => t >= d.Von && t < d.Bis);
proben.Add(new Bildprobe(t, istDunkel ? 85 : 0));
proben.Add(new Frameprobe(t, istDunkel ? 85 : 0));
}
return proben;
}
@@ -22,7 +22,7 @@ public class SecretsTests
}
[Test]
public void Der_Fingerabdruck_ist_gleichbleibend_und_unterscheidet()
public void Der_Fingerfingerprint_ist_gleichbleibend_und_unterscheidet()
{
var a = Secrets.Create();
var b = Secrets.Create();
@@ -35,9 +35,9 @@ public class SecretsTests
Assert.That(ersterLauf, Has.Length.EqualTo(64));
}
/// <summary>Der Fingerabdruck darf das Geheimnis nicht enthalten.</summary>
/// <summary>Der Fingerfingerprint darf das Geheimnis nicht enthalten.</summary>
[Test]
public void Der_Fingerabdruck_verraet_das_Geheimnis_nicht()
public void Der_Fingerfingerprint_verraet_das_Geheimnis_nicht()
{
var s = Secrets.Create();
Assert.That(Secrets.Fingerprint(s), Does.Not.Contain(s));
@@ -16,13 +16,13 @@ namespace Flaro.Core.Tests;
/// verschiedener Bitrate, durch <c>fpcalc</c> gedreht. Zwanzig Kilobyte
/// Text — der Preis dafür, dass diese Zusage nicht bloss behauptet ist.
/// </remarks>
public class EchteAbdrueckeTests
public class EchteFingerprintsTests
{
private const double JeAbdruck = 4096.0 / 3.0 / 11025.0;
private const double JeFingerprint = 4096.0 / 3.0 / 11025.0;
private static IReadOnlyList<uint>? Lesen(string name)
{
var pfad = Path.Combine(TestContext.CurrentContext.TestDirectory, "Abdruecke", name);
var pfad = Path.Combine(TestContext.CurrentContext.TestDirectory, "Fingerprints", name);
if (!File.Exists(pfad)) return null;
return File.ReadAllText(pfad)
@@ -38,7 +38,7 @@ public class EchteAbdrueckeTests
var b = Lesen("folgeB.txt");
if (a is null || b is null) Assert.Ignore("Keine Abdrücke zum Prüfen da.");
var treffer = Tonabgleich.Finden(a, b, JeAbdruck);
var treffer = Fingerprintabgleich.Finden(a, b, JeFingerprint);
TestContext.Out.WriteLine($"A {a.Count} Werte, B {b.Count} Werte");
TestContext.Out.WriteLine(treffer is null
@@ -10,10 +10,10 @@ namespace Flaro.Core.Tests;
/// rechnen kann. Eine Prüfung, die eine Serie mit echten Folgen braucht,
/// liefe nirgends ausser auf dem Rechner dessen, der sie geschrieben hat.
/// </remarks>
public class TonabgleichTests
public class FingerprintabgleichTests
{
/// <summary>Chromaprint mit Standardeinstellungen.</summary>
private const double JeAbdruck = 0.1238;
private const double JeFingerprint = 0.1238;
/// <summary>Eine Folge von Abdrücken, die sich nicht wiederholt.</summary>
///
@@ -40,7 +40,7 @@ public class TonabgleichTests
teile.SelectMany(t => t).ToArray();
/// <summary>Wie viele Zeitscheiben so viele Sekunden sind.</summary>
private static int Scheiben(double sekunden) => (int)Math.Round(sekunden / JeAbdruck);
private static int Scheiben(double sekunden) => (int)Math.Round(sekunden / JeFingerprint);
[Test]
public void Ein_gemeinsamer_Vorspann_wird_gefunden()
@@ -52,7 +52,7 @@ public class TonabgleichTests
// Folge 2: der Vorspann liegt viel später.
var b = Aneinander(Rauschen(Scheiben(70), 4), vorspann, Rauschen(Scheiben(20), 5));
var treffer = Tonabgleich.Finden(a, b, JeAbdruck);
var treffer = Fingerprintabgleich.Finden(a, b, JeFingerprint);
Assert.That(treffer, Is.Not.Null);
Assert.Multiple(() =>
@@ -77,7 +77,7 @@ public class TonabgleichTests
var a = Rauschen(Scheiben(120), 11);
var b = Rauschen(Scheiben(120), 12);
Assert.That(Tonabgleich.Finden(a, b, JeAbdruck), Is.Null);
Assert.That(Fingerprintabgleich.Finden(a, b, JeFingerprint), Is.Null);
}
/// <summary>Ein zu kurzes gemeinsames Stück zählt nicht.</summary>
@@ -88,7 +88,7 @@ public class TonabgleichTests
var a = Aneinander(Rauschen(Scheiben(30), 22), kurz, Rauschen(Scheiben(30), 23));
var b = Aneinander(Rauschen(Scheiben(50), 24), kurz, Rauschen(Scheiben(10), 25));
Assert.That(Tonabgleich.Finden(a, b, JeAbdruck), Is.Null);
Assert.That(Fingerprintabgleich.Finden(a, b, JeFingerprint), Is.Null);
}
/// <summary>Ein paar abweichende Bits machen den Fund nicht kaputt.</summary>
@@ -107,7 +107,7 @@ public class TonabgleichTests
var a = Aneinander(Rauschen(Scheiben(5), 32), vorspann, Rauschen(Scheiben(40), 33));
var b = Aneinander(Rauschen(Scheiben(20), 34), leichtAnders, Rauschen(Scheiben(40), 35));
var treffer = Tonabgleich.Finden(a, b, JeAbdruck);
var treffer = Fingerprintabgleich.Finden(a, b, JeFingerprint);
Assert.That(treffer, Is.Not.Null);
Assert.That(treffer!.Laenge, Is.EqualTo(30).Within(2.0));
@@ -126,7 +126,7 @@ public class TonabgleichTests
var a = Aneinander(Rauschen(Scheiben(5), 42), vorspann, Rauschen(Scheiben(30), 43));
var b = Aneinander(Rauschen(Scheiben(15), 44), mitAnsage, Rauschen(Scheiben(30), 45));
var treffer = Tonabgleich.Finden(a, b, JeAbdruck);
var treffer = Fingerprintabgleich.Finden(a, b, JeFingerprint);
Assert.That(treffer, Is.Not.Null);
Assert.That(treffer!.Laenge, Is.EqualTo(30).Within(2.0),
@@ -145,7 +145,7 @@ public class TonabgleichTests
Rauschen(Scheiben(5 + i * 10), 60 + i), vorspann, Rauschen(Scheiben(30), 70 + i))));
}
var gefunden = Tonabgleich.ReiheAbgleichen(folgen, JeAbdruck);
var gefunden = Fingerprintabgleich.ReiheAbgleichen(folgen, JeFingerprint);
Assert.That(gefunden, Has.Count.EqualTo(5), "auch die letzte Folge");
for (var i = 0; i < 5; i++)
@@ -160,6 +160,6 @@ public class TonabgleichTests
public void Eine_Folge_allein_ergibt_nichts()
{
var eine = new List<(int, IReadOnlyList<uint>)> { (0, Rauschen(Scheiben(60), 81)) };
Assert.That(Tonabgleich.ReiheAbgleichen(eine, JeAbdruck), Is.Empty);
Assert.That(Fingerprintabgleich.ReiheAbgleichen(eine, JeFingerprint), Is.Empty);
}
}
@@ -25,7 +25,7 @@
</ItemGroup>
<ItemGroup>
<None Include="Abdruecke\**" CopyToOutputDirectory="PreserveNewest" />
<None Include="Fingerprints\**" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
</Project>
@@ -295,15 +295,15 @@ public sealed class ProgressRuleTests
});
}
/// <summary>Eine Marke hinter der Schwelle verschiebt nichts nach hinten.</summary>
/// <summary>Ein Segment hinter der Schwelle verschiebt nichts nach hinten.</summary>
///
/// <remarks>
/// Sonst machte eine falsch erkannte Marke aus einem Titel, der bei
/// Sonst machte eine falsch erkannte Segment aus einem Titel, der bei
/// neunzig Prozent fertig war, einen, der es erst bei fünfundneunzig
/// ist — und niemand sähe, warum.
/// </remarks>
[Test]
public void Eine_spaete_Marke_schiebt_nichts_nach_hinten()
public void Eine_spaete_Segment_schiebt_nichts_nach_hinten()
{
var laenge = TimeSpan.FromMinutes(45);
var regeln = new Wiedergaberegeln { GesehenAnteil = 0.9 };
@@ -314,7 +314,7 @@ public sealed class ProgressRuleTests
Assert.That(ergebnis.Watched, Is.True);
}
/// <summary>Ohne Marke bleibt alles, wie es war.</summary>
/// <summary>Ohne Segment bleibt alles, wie es war.</summary>
[Test]
public void Ohne_Abspann_gilt_der_Anteil()
{
@@ -5,7 +5,7 @@ namespace Flaro.Core.Tests;
/// <summary>Der Satz, der in der Verwaltung mitläuft.</summary>
///
/// <remarks>
/// <b>Warum das geprüft wird.</b> Die Markensuche läuft über Stunden und
/// <b>Warum das geprüft wird.</b> Die Segmentsuche läuft über Stunden und
/// hat bisher gar nichts gemeldet — in der Verwaltung stand das Ergebnis
/// des längst beendeten Scans, während der Server noch jede Folge
/// durchhörte. Sie benutzt jetzt denselben Weg wie der Scan, und der Satz
@@ -15,10 +15,10 @@ public class ScanschrittTests
{
/// <summary>Mit Zähler, wenn die Gesamtzahl bekannt ist.</summary>
[Test]
public void Die_Markensuche_nennt_Titel_und_Zaehler()
public void Die_Segmentsuche_nennt_Titel_und_Zaehler()
{
var schritt = new Scanschritt(
Scanphase.Marken, "Biohackers S02E01 · Neuanfang", 12, 95);
Scanphase.Segmente, "Biohackers S02E01 · Neuanfang", 12, 95);
Assert.That(schritt.Text(),
Is.EqualTo("Suche Vorspann und Abspann: Biohackers S02E01 · Neuanfang — 12 von 95"));
@@ -28,7 +28,7 @@ public class ScanschrittTests
[Test]
public void Ohne_Gesamtzahl_steht_nur_der_Titel()
{
var schritt = new Scanschritt(Scanphase.Marken, "Arrival (2016)");
var schritt = new Scanschritt(Scanphase.Segmente, "Arrival (2016)");
Assert.That(schritt.Text(), Is.EqualTo("Suche Vorspann und Abspann: Arrival (2016)"));
}
@@ -5,12 +5,12 @@ namespace Flaro.Core.Tests;
/// <summary>Welche umgepackten Dateien weggeräumt werden.</summary>
///
/// <remarks>
/// <b>Der Anlass: 39 Gigabyte Kratzplatz.</b> Für jede Mitnahme baut der
/// <b>Der Anlass: 39 Gigabyte Transcode-Cache.</b> Für jede Mitnahme baut der
/// Server eine MP4 in Quellgrösse und löschte sie nie. Hier steht die Regel,
/// die das beendet — ohne Platte, ohne Server, damit die Entscheidung selbst
/// nachlesbar ist und nicht nur ihr Ergebnis.
/// </remarks>
public class KratzplatzregelTests
public class TranscoderegelTests
{
private static readonly DateTimeOffset Jetzt = new(2026, 10, 8, 12, 0, 0, TimeSpan.Zero);
private const int Bauart = 2;
@@ -20,7 +20,7 @@ public class KratzplatzregelTests
private const long Gigabyte = 1024L * 1024 * 1024;
/// <summary>Eine Datei, wie sie im Kratzplatz läge.</summary>
/// <summary>Eine Datei, wie sie im Transcode-Cache läge.</summary>
private static Paketakte Akte(
Guid id, long gigabyte, TimeSpan vorWieLange, long? geaendertMs = null, int bauart = Bauart)
{
@@ -36,12 +36,12 @@ public class KratzplatzregelTests
private static IReadOnlyList<string> Weg(
IReadOnlyList<Paketakte> akten,
Kratzplatzregeln? regeln = null,
Transcoderegeln? regeln = null,
IReadOnlyDictionary<Guid, long>? bekannt = null,
Func<string, bool>? inBenutzung = null) =>
Kratzplatzauswahl.Wegzuwerfen(
Transcodeauswahl.Wegzuwerfen(
akten, bekannt ?? Bekannt, Bauart,
regeln ?? Kratzplatzregeln.Vorgabe, Jetzt,
regeln ?? Transcoderegeln.Vorgabe, Jetzt,
inBenutzung ?? (_ => false))
.Select(a => a.Pfad).ToList();
@@ -161,7 +161,7 @@ public class KratzplatzregelTests
[Test]
public void Eine_Obergrenze_von_null_heisst_unbegrenzt()
{
var regeln = Kratzplatzregeln.Vorgabe with { ObergrenzeBytes = 0 };
var regeln = Transcoderegeln.Vorgabe with { ObergrenzeBytes = 0 };
var akten = new[]
{
Akte(EinsId, 500, TimeSpan.FromHours(2)),
@@ -174,7 +174,7 @@ public class KratzplatzregelTests
[Test]
public void Ohne_Altersabbau_bleibt_auch_sehr_Altes_liegen()
{
var regeln = Kratzplatzregeln.Vorgabe with { Unbenutzt = TimeSpan.Zero };
var regeln = Transcoderegeln.Vorgabe with { Unbenutzt = TimeSpan.Zero };
Assert.That(Weg([Akte(EinsId, 1, TimeSpan.FromDays(400))], regeln), Is.Empty);
}
@@ -187,7 +187,7 @@ public class KratzplatzregelTests
[Test]
public void Verwaistes_geht_auch_bei_abgeschalteten_Grenzen()
{
var regeln = new Kratzplatzregeln(0, TimeSpan.Zero, TimeSpan.FromMinutes(30));
var regeln = new Transcoderegeln(0, TimeSpan.Zero, TimeSpan.FromMinutes(30));
Assert.That(Weg([Akte(EinsId, 1, TimeSpan.FromHours(2), bauart: 1)], regeln), Has.Count.EqualTo(1));
}
@@ -195,7 +195,7 @@ public class KratzplatzregelTests
[Test]
public void Eine_zu_kurze_Schonfrist_wird_hochgesetzt()
{
var regeln = Kratzplatzregeln.Geprueft(20, 7, 1);
var regeln = Transcoderegeln.Geprueft(20, 7, 1);
Assert.That(regeln.Schonfrist, Is.EqualTo(TimeSpan.FromMinutes(5)),
"eine Minute löschte die Datei mitten im Herunterladen weg");
@@ -204,7 +204,7 @@ public class KratzplatzregelTests
[Test]
public void Null_und_negative_Werte_heissen_abgeschaltet()
{
var regeln = Kratzplatzregeln.Geprueft(0, -3, 30);
var regeln = Transcoderegeln.Geprueft(0, -3, 30);
Assert.Multiple(() =>
{
@@ -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));
}