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
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
|
||||
+44
-44
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
+10
-10
@@ -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));
|
||||
|
||||
+4
-4
@@ -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 +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)"));
|
||||
}
|
||||
|
||||
+11
-11
@@ -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(() =>
|
||||
{
|
||||
+16
-16
@@ -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));
|
||||
}
|
||||
}
|
||||
+12
-12
@@ -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),
|
||||
|
||||
+6
-6
@@ -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));
|
||||
}
|
||||
Reference in New Issue
Block a user