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
@@ -12,7 +12,7 @@ namespace Flaro.Core.Auth;
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// <b>Der Code allein holt nie ein Token ab.</b> Dafür gibt es den Abholschein,
|
||||
/// den nur das anfragende Gerät kennt und der hier nur als Fingerabdruck
|
||||
/// den nur das anfragende Gerät kennt und der hier nur als Fingerfingerprint
|
||||
/// liegt. Ohne ihn könnte jemand sechsstellige Zahlen durchprobieren — eine
|
||||
/// Million Versuche sind nichts, und am Ende stünde ein fremdes Gerät in der
|
||||
/// Mediathek.
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace Flaro.Core.Auth;
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// <b>Übernommen ist die Geheimnisdisziplin.</b> Wie bei
|
||||
/// <see cref="DeviceToken"/> liegt hier nur der Fingerabdruck; den Link
|
||||
/// <see cref="DeviceToken"/> liegt hier nur der Fingerfingerprint; den Link
|
||||
/// selbst bekommt man genau einmal zu sehen, beim Erstellen. Wer die
|
||||
/// Datenbank liest, kann daraus keine Einladung bauen.
|
||||
/// </para>
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace Flaro.Core.Auth;
|
||||
/// <summary>Erzeugt und verfingert Geheimnisse, die Clients vorzeigen.</summary>
|
||||
///
|
||||
/// <remarks>
|
||||
/// <b>Abgelegt wird nur der Fingerabdruck, nie das Geheimnis.</b> Wer die
|
||||
/// <b>Abgelegt wird nur der Fingerfingerprint, nie das Geheimnis.</b> Wer die
|
||||
/// Datenbank erbeutet, hat damit Hashes und keine Zugänge. Dass das überhaupt
|
||||
/// erwähnt werden muss: bei Medienservern liegen Token regelmässig im
|
||||
/// Klartext, und ein einziges Datenbankleck gibt dann jedem alles.
|
||||
@@ -23,7 +23,7 @@ public static class Secrets
|
||||
public static string Create() =>
|
||||
Base64UrlEncode(RandomNumberGenerator.GetBytes(Bytes));
|
||||
|
||||
/// <summary>Der Fingerabdruck, wie er in der Datenbank steht.</summary>
|
||||
/// <summary>Der Fingerfingerprint, wie er in der Datenbank steht.</summary>
|
||||
public static string Fingerprint(string secret) =>
|
||||
Convert.ToHexStringLower(SHA256.HashData(Encoding.UTF8.GetBytes(secret)));
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace Flaro.Core.Delivery;
|
||||
///
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// <b>Warum der Name alles trägt.</b> Im Kratzplatz liegen Dateien, zu denen
|
||||
/// <b>Warum der Name alles trägt.</b> Im Transcode-Cache liegen Dateien, zu denen
|
||||
/// es keine Tabelle gibt. Ein Aufräumer muss allein am Namen erkennen können,
|
||||
/// wozu eine Datei gehört und ob sie noch gilt — sonst bliebe ihm nur,
|
||||
/// alles zu behalten oder alles wegzuwerfen.
|
||||
|
||||
+6
-6
@@ -21,7 +21,7 @@ namespace Flaro.Core.Delivery;
|
||||
/// <param name="ObergrenzeBytes">Wie viel insgesamt liegen bleiben darf; <c>0</c> heisst unbegrenzt.</param>
|
||||
/// <param name="Unbenutzt">Ab wann eine unbenutzte Datei auch unterhalb der Grenze geht; <c>Zero</c> heisst nie.</param>
|
||||
/// <param name="Schonfrist">Wie lange eine frisch gebrauchte Datei in jedem Fall bleibt.</param>
|
||||
public sealed record Kratzplatzregeln(
|
||||
public sealed record Transcoderegeln(
|
||||
long ObergrenzeBytes,
|
||||
TimeSpan Unbenutzt,
|
||||
TimeSpan Schonfrist)
|
||||
@@ -34,7 +34,7 @@ public sealed record Kratzplatzregeln(
|
||||
/// Schonfrist decken die Lücke zwischen zwei Teilanfragen und einen
|
||||
/// Neustart, der den Benutzungszähler verliert.
|
||||
/// </remarks>
|
||||
public static Kratzplatzregeln Vorgabe { get; } =
|
||||
public static Transcoderegeln Vorgabe { get; } =
|
||||
new(20L * 1024 * 1024 * 1024, TimeSpan.FromDays(7), TimeSpan.FromMinutes(30));
|
||||
|
||||
/// <summary>Nimmt entgegen, was eingestellt wurde, und biegt Unsinn zurecht.</summary>
|
||||
@@ -45,13 +45,13 @@ public sealed record Kratzplatzregeln(
|
||||
/// Datei weg — deshalb eine Untergrenze von fünf Minuten. Negative Werte
|
||||
/// gelten als „aus".
|
||||
/// </remarks>
|
||||
public static Kratzplatzregeln Geprueft(double obergrenzeGb, int unbenutztTage, int schonfristMinuten) =>
|
||||
public static Transcoderegeln Geprueft(double obergrenzeGb, int unbenutztTage, int schonfristMinuten) =>
|
||||
new(obergrenzeGb <= 0 ? 0 : (long)(obergrenzeGb * 1024 * 1024 * 1024),
|
||||
unbenutztTage <= 0 ? TimeSpan.Zero : TimeSpan.FromDays(Math.Min(unbenutztTage, 3650)),
|
||||
TimeSpan.FromMinutes(Math.Clamp(schonfristMinuten, 5, 1440)));
|
||||
}
|
||||
|
||||
/// <summary>Eine Datei im Kratzplatz, so wie sie dort liegt.</summary>
|
||||
/// <summary>Eine Datei im Transcode-Cache, so wie sie dort liegt.</summary>
|
||||
///
|
||||
/// <param name="Benutzt">
|
||||
/// Wann sie zuletzt gebraucht wurde — die Änderungszeit der Datei selbst.
|
||||
@@ -66,7 +66,7 @@ public readonly record struct Paketakte(
|
||||
Paketname.Teile? Name);
|
||||
|
||||
/// <summary>Welche Dateien weggeräumt werden — reine Rechnung, ohne Platte.</summary>
|
||||
public static class Kratzplatzauswahl
|
||||
public static class Transcodeauswahl
|
||||
{
|
||||
/// <param name="bekannt">Kennung → Änderungszeitpunkt der Quelle, aus der Mediathek.</param>
|
||||
/// <param name="bauart">Wie der Server heute baut.</param>
|
||||
@@ -75,7 +75,7 @@ public static class Kratzplatzauswahl
|
||||
IReadOnlyList<Paketakte> akten,
|
||||
IReadOnlyDictionary<Guid, long> bekannt,
|
||||
int bauart,
|
||||
Kratzplatzregeln regeln,
|
||||
Transcoderegeln regeln,
|
||||
DateTimeOffset jetzt,
|
||||
Func<string, bool> inBenutzung)
|
||||
{
|
||||
@@ -124,8 +124,8 @@ public sealed class Stichwortlink
|
||||
public Guid? SeriesId { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>Was für ein Abschnitt eine Marke bezeichnet.</summary>
|
||||
public enum Markenart
|
||||
/// <summary>Was für ein Abschnitt ein Segment bezeichnet.</summary>
|
||||
public enum Segmentart
|
||||
{
|
||||
/// <summary>Der Vorspann.</summary>
|
||||
Intro,
|
||||
@@ -148,7 +148,7 @@ public enum Markenart
|
||||
/// im Film ist dieselbe.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public sealed class Marke
|
||||
public sealed class Segment
|
||||
{
|
||||
public Guid Id { get; init; } = Guid.CreateVersion7();
|
||||
/// <summary>Zu welcher Folge sie gehört — oder zu welchem Film.</summary>
|
||||
@@ -162,7 +162,7 @@ public sealed class Marke
|
||||
/// </remarks>
|
||||
public Guid? EpisodeId { get; init; }
|
||||
public Guid? MovieId { get; init; }
|
||||
public required Markenart Art { get; init; }
|
||||
public required Segmentart Art { get; init; }
|
||||
public required double VonSekunden { get; set; }
|
||||
public required double BisSekunden { get; set; }
|
||||
|
||||
@@ -195,21 +195,21 @@ public sealed class Marke
|
||||
/// Lauf.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public sealed class Markensuche
|
||||
public sealed class Segmentsuche
|
||||
{
|
||||
public Guid Id { get; init; } = Guid.CreateVersion7();
|
||||
|
||||
/// <summary>Eine Staffel einer Serie — oder ein Film.</summary>
|
||||
///
|
||||
/// <remarks>
|
||||
/// Genau eines von beiden, wie bei <see cref="Marke"/>. Bei einer
|
||||
/// Genau eines von beiden, wie bei <see cref="Segment"/>. Bei einer
|
||||
/// Staffel steht die Nummer daneben; ein Film hat keine.
|
||||
/// </remarks>
|
||||
public Guid? SeriesId { get; init; }
|
||||
public int? Staffel { get; init; }
|
||||
public Guid? MovieId { get; init; }
|
||||
|
||||
public required Markenart Art { get; init; }
|
||||
public required Segmentart Art { get; init; }
|
||||
|
||||
/// <summary>Wie viele Folgen die Staffel hatte, als gesucht wurde.</summary>
|
||||
///
|
||||
|
||||
@@ -28,7 +28,7 @@ public enum Scanphase
|
||||
/// über Stunden, und ohne eine Zeile, die mitzählt, sieht die
|
||||
/// Verwaltung minutenlang so aus, als täte sich nichts.
|
||||
/// </remarks>
|
||||
Marken,
|
||||
Segmente,
|
||||
|
||||
/// <summary>Eine Notiz fürs Protokoll — nicht für die Fortschrittszeile.</summary>
|
||||
///
|
||||
@@ -79,7 +79,7 @@ public readonly record struct Scanschritt(Scanphase Phase, string Was, int Erled
|
||||
Scanphase.Holen => $"Hole Metadaten zu {Was}{zaehler}",
|
||||
Scanphase.Bilder => $"Lade Bilder zu {Was}{zaehler}",
|
||||
Scanphase.Aufraeumen => "Räume auf und lege Doppeltes zusammen",
|
||||
Scanphase.Marken => $"Suche Vorspann und Abspann: {Was}{zaehler}",
|
||||
Scanphase.Segmente => $"Suche Vorspann und Abspann: {Was}{zaehler}",
|
||||
Scanphase.Vermerk => Was,
|
||||
_ => Was,
|
||||
};
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace Flaro.Core.Library;
|
||||
/// <para>
|
||||
/// <b>Transcode-Zwischenstände gehören ausdrücklich nicht hierher.</b> Die
|
||||
/// liegen unter <c>FLARO_TRANSCODE</c>, auf einem eigenen Volume. Ein
|
||||
/// Medienordner ist kein Kratzplatz.
|
||||
/// Medienordner ist kein Transcode-Cache.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public static class SidecarNames
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace Flaro.Core.Media;
|
||||
///
|
||||
/// <param name="Sekunde">Die Stelle im Film.</param>
|
||||
/// <param name="Dunkelanteil">Wie viel Prozent des Bildes dunkel sind, 0 bis 100.</param>
|
||||
public readonly record struct Bildprobe(double Sekunde, double Dunkelanteil);
|
||||
public readonly record struct Frameprobe(double Sekunde, double Dunkelanteil);
|
||||
|
||||
/// <summary>Ein Stück Abspann: von wann bis wann.</summary>
|
||||
///
|
||||
@@ -80,10 +80,10 @@ public readonly record struct Abspannregeln
|
||||
/// <b>In Sekunden, nicht in Proben</b> — dieselbe Lehre wie bei
|
||||
/// <see cref="Schlusstafel"/>, hier eine Zeile höher übersehen. Als
|
||||
/// Probenzahl hing die Nachsicht daran, wie dicht die Datei ihre
|
||||
/// Schlüsselbilder setzt: gemessen an „(T)Raumschiff Surprise" kommen
|
||||
/// Keyframes setzt: gemessen an „(T)Raumschiff Surprise" kommen
|
||||
/// 1318 Proben auf zwanzig Minuten, also eine alle 0,9 Sekunden —
|
||||
/// zwei Proben waren dort 1,8 Sekunden Nachsicht. Bei einer Datei mit
|
||||
/// einem Schlüsselbild alle zehn Sekunden waren es zwanzig. Dasselbe
|
||||
/// einem Keyframe alle zehn Sekunden waren es zwanzig. Dasselbe
|
||||
/// Zugeständnis, zehnfach verschieden.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
@@ -94,7 +94,7 @@ public readonly record struct Abspannregeln
|
||||
/// <remarks>
|
||||
/// <b>In Sekunden, nicht in Proben.</b> Eine Logotafel dauert, was sie
|
||||
/// dauert; wie viele Messpunkte darauf fallen, hängt allein daran, wie
|
||||
/// dicht die Datei ihre Schlüsselbilder setzt. In Proben gezählt wäre
|
||||
/// dicht die Datei ihre Keyframes setzt. In Proben gezählt wäre
|
||||
/// dieselbe Tafel bei der einen Datei verziehen und bei der nächsten
|
||||
/// ein Abbruch.
|
||||
/// </remarks>
|
||||
@@ -156,7 +156,7 @@ public readonly record struct Abspannregeln
|
||||
/// <b>Reine Rechnung, ohne Datei.</b> Das Abtasten braucht ffmpeg; das
|
||||
/// Beurteilen braucht nichts. Getrennt lässt sich das Schwierige prüfen,
|
||||
/// ohne einen Film vorzuhalten — dieselbe Naht wie bei
|
||||
/// <see cref="Tonabgleich"/>.
|
||||
/// <see cref="Fingerprintabgleich"/>.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public static class Abspannkante
|
||||
@@ -165,10 +165,10 @@ public static class Abspannkante
|
||||
///
|
||||
/// <param name="proben">
|
||||
/// Messpunkte über das Ende des Films, aufsteigend nach Zeit. Sie dürfen
|
||||
/// ungleichmässig liegen: Schlüsselbilder tun das.
|
||||
/// ungleichmässig liegen: Keyframes tun das.
|
||||
/// </param>
|
||||
public static IReadOnlyList<Abspannstück> Suchen(
|
||||
IReadOnlyList<Bildprobe> proben, Abspannregeln regeln) =>
|
||||
IReadOnlyList<Frameprobe> proben, Abspannregeln regeln) =>
|
||||
Suchen(proben, regeln, out _);
|
||||
|
||||
/// <param name="grund">
|
||||
@@ -178,7 +178,7 @@ public static class Abspannkante
|
||||
/// und ohne diese Zeile beginnt jede Nachfrage wieder beim Raten.
|
||||
/// </param>
|
||||
public static IReadOnlyList<Abspannstück> Suchen(
|
||||
IReadOnlyList<Bildprobe> proben, Abspannregeln regeln, out string grund)
|
||||
IReadOnlyList<Frameprobe> proben, Abspannregeln regeln, out string grund)
|
||||
{
|
||||
grund = "gefunden";
|
||||
if (proben.Count < 2)
|
||||
@@ -244,11 +244,11 @@ public static class Abspannkante
|
||||
/// <remarks>
|
||||
/// <b>Die Nachsicht zählt Sekunden, nicht Proben.</b> Eine Schrifttafel
|
||||
/// blitzt auf, ein Logo steht zwei Sekunden im Bild; wie viele
|
||||
/// Messpunkte darauf fallen, hängt allein an der Schlüsselbilddichte
|
||||
/// Messpunkte darauf fallen, hängt allein an der Keyframedichte
|
||||
/// der Datei und schwankt um eine Zehnerpotenz.
|
||||
/// </remarks>
|
||||
private static List<Abspannstück> Läufe(
|
||||
IReadOnlyList<Bildprobe> proben, Abspannregeln regeln)
|
||||
IReadOnlyList<Frameprobe> proben, Abspannregeln regeln)
|
||||
{
|
||||
var läufe = new List<Abspannstück>();
|
||||
double? von = null;
|
||||
@@ -281,7 +281,7 @@ public static class Abspannkante
|
||||
|
||||
/// <summary>Die letzte helle Probe vor dem Fund — der Suchbereich fürs Nachmessen.</summary>
|
||||
public static double? HellDavor(
|
||||
IReadOnlyList<Bildprobe> proben, double dunkelAb, Abspannregeln regeln)
|
||||
IReadOnlyList<Frameprobe> proben, double dunkelAb, Abspannregeln regeln)
|
||||
{
|
||||
double? letzteHelle = null;
|
||||
foreach (var p in proben)
|
||||
@@ -297,7 +297,7 @@ public static class Abspannkante
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// <b>Binäre Suche mit kurzen Sonden, nicht noch ein Durchlauf.</b> Die
|
||||
/// grobe Reihe stammt aus Schlüsselbildern, und die liegen je nach
|
||||
/// grobe Reihe stammt aus Keyframesn, und die liegen je nach
|
||||
/// Aufnahme fünf bis zehn Sekunden auseinander. Den ganzen Bereich fein
|
||||
/// abzutasten hiesse, ihn zu dekodieren; zehn Sonden von je einer
|
||||
/// Sekunde kosten einen Bruchteil davon. Denselben Weg geht Jellyfins
|
||||
|
||||
+17
-17
@@ -24,7 +24,7 @@ public sealed record Gemeinsamkeit(double AVon, double ABis, double BVon, double
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// <b>Verglichen werden Tonabdrücke, keine Töne.</b> Was hereinkommt, ist je
|
||||
/// Folge eine Reihe von 32-Bit-Zahlen, eine je Zeitscheibe — der Abdruck,
|
||||
/// Folge eine Reihe von 32-Bit-Zahlen, eine je Zeitscheibe — der Fingerprint,
|
||||
/// den Chromaprint aus dem Ton rechnet. Zwei Aufnahmen desselben Stücks
|
||||
/// ergeben darin fast dieselben Zahlen, auch bei anderer Lautstärke, anderem
|
||||
/// Codec und anderer Bitrate. Deshalb wird nicht auf Gleichheit geprüft,
|
||||
@@ -36,7 +36,7 @@ public sealed record Gemeinsamkeit(double AVon, double ABis, double BVon, double
|
||||
/// sich das Schwierige prüfen, ohne eine Mediendatei vorzuhalten.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public static class Tonabgleich
|
||||
public static class Fingerprintabgleich
|
||||
{
|
||||
/// <summary>Wie viele Bits zweier Abdrücke sich unterscheiden dürfen.</summary>
|
||||
///
|
||||
@@ -67,7 +67,7 @@ public static class Tonabgleich
|
||||
|
||||
/// <summary>Sucht das längste Stück, das <paramref name="a"/> und <paramref name="b"/> teilen.</summary>
|
||||
///
|
||||
/// <param name="sekundenJeAbdruck">
|
||||
/// <param name="sekundenJeFingerprint">
|
||||
/// Wie viel Zeit eine Zeitscheibe abdeckt. Chromaprint rechnet mit
|
||||
/// 11025 Hz und 4096 Abtastwerten bei zwei Dritteln Überlappung — das
|
||||
/// sind rund 0,1238 Sekunden.
|
||||
@@ -75,14 +75,14 @@ public static class Tonabgleich
|
||||
public static Gemeinsamkeit? Finden(
|
||||
IReadOnlyList<uint> a,
|
||||
IReadOnlyList<uint> b,
|
||||
double sekundenJeAbdruck,
|
||||
double sekundenJeFingerprint,
|
||||
double mindestlänge = StandardMindestlänge,
|
||||
int bitabstand = StandardBitabstand,
|
||||
int lücke = StandardLücke)
|
||||
{
|
||||
if (a.Count == 0 || b.Count == 0 || sekundenJeAbdruck <= 0) return null;
|
||||
if (a.Count == 0 || b.Count == 0 || sekundenJeFingerprint <= 0) return null;
|
||||
|
||||
var mindestens = (int)Math.Ceiling(mindestlänge / sekundenJeAbdruck);
|
||||
var mindestens = (int)Math.Ceiling(mindestlänge / sekundenJeFingerprint);
|
||||
if (mindestens <= 0) mindestens = 1;
|
||||
|
||||
Gemeinsamkeit? beste = null;
|
||||
@@ -90,7 +90,7 @@ public static class Tonabgleich
|
||||
// **Jede Verschiebung durchprobieren.** Das Intro steht in beiden
|
||||
// Folgen an einer anderen Stelle; gesucht ist die Verschiebung, bei
|
||||
// der die meisten Zeitscheiben aufeinanderpassen. Bei zwei Minuten
|
||||
// Abdruck sind das ein paar Millionen Vergleiche — ein Wimpernschlag
|
||||
// Fingerprint sind das ein paar Millionen Vergleiche — ein Wimpernschlag
|
||||
// gegen das Lesen der Datei, aus der sie stammen.
|
||||
for (var versatz = -(b.Count - 1); versatz < a.Count; versatz++)
|
||||
{
|
||||
@@ -102,13 +102,13 @@ public static class Tonabgleich
|
||||
var lauf = LängsterLauf(a, b, vonA, vonB, länge, bitabstand, lücke);
|
||||
if (lauf is not var (start, dauer) || dauer < mindestens) continue;
|
||||
|
||||
if (beste is not null && dauer * sekundenJeAbdruck <= beste.Laenge) continue;
|
||||
if (beste is not null && dauer * sekundenJeFingerprint <= beste.Laenge) continue;
|
||||
|
||||
beste = new Gemeinsamkeit(
|
||||
(vonA + start) * sekundenJeAbdruck,
|
||||
(vonA + start + dauer) * sekundenJeAbdruck,
|
||||
(vonB + start) * sekundenJeAbdruck,
|
||||
(vonB + start + dauer) * sekundenJeAbdruck);
|
||||
(vonA + start) * sekundenJeFingerprint,
|
||||
(vonA + start + dauer) * sekundenJeFingerprint,
|
||||
(vonB + start) * sekundenJeFingerprint,
|
||||
(vonB + start + dauer) * sekundenJeFingerprint);
|
||||
}
|
||||
|
||||
return beste is not null && beste.Laenge >= mindestlänge ? beste : null;
|
||||
@@ -176,8 +176,8 @@ public static class Tonabgleich
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public static IReadOnlyDictionary<TSchlüssel, (double Von, double Bis)> ReiheAbgleichen<TSchlüssel>(
|
||||
IReadOnlyList<(TSchlüssel Schlüssel, IReadOnlyList<uint> Abdruck)> folgen,
|
||||
double sekundenJeAbdruck,
|
||||
IReadOnlyList<(TSchlüssel Schlüssel, IReadOnlyList<uint> Fingerprint)> folgen,
|
||||
double sekundenJeFingerprint,
|
||||
double mindestlänge = StandardMindestlänge)
|
||||
where TSchlüssel : notnull
|
||||
{
|
||||
@@ -186,10 +186,10 @@ public static class Tonabgleich
|
||||
|
||||
for (var i = 0; i + 1 < folgen.Count; i++)
|
||||
{
|
||||
var (schlüsselA, abdruckA) = folgen[i];
|
||||
var (schlüsselB, abdruckB) = folgen[i + 1];
|
||||
var (schlüsselA, fingerprintA) = folgen[i];
|
||||
var (schlüsselB, fingerprintB) = folgen[i + 1];
|
||||
|
||||
var treffer = Finden(abdruckA, abdruckB, sekundenJeAbdruck, mindestlänge);
|
||||
var treffer = Finden(fingerprintA, fingerprintB, sekundenJeFingerprint, mindestlänge);
|
||||
if (treffer is null) continue;
|
||||
|
||||
// Der erste Fund je Folge gewinnt: er stammt vom Vergleich mit
|
||||
@@ -79,8 +79,8 @@ public static class ProgressRule
|
||||
var schwelle = laenge * regeln.GesehenAnteil;
|
||||
|
||||
// Der Abspann zieht die Schwelle nach vorn, nie nach hinten: eine
|
||||
// falsch erkannte Marke bei Minute vierzig darf nicht dazu führen,
|
||||
// dass eine Folge erst nach der Marke als gesehen gilt.
|
||||
// falsch erkannte Segment bei Minute vierzig darf nicht dazu führen,
|
||||
// dass eine Folge erst nach dem Segment als gesehen gilt.
|
||||
if (abspannAb is { } ab && ab > TimeSpan.Zero && ab < schwelle) schwelle = ab;
|
||||
|
||||
return new Outcome(stelle, Watched: stelle >= schwelle);
|
||||
|
||||
Reference in New Issue
Block a user