using Flaro.Core.Media; namespace Flaro.Core.Tests; /// Was zwei Folgen gemeinsam haben. /// /// /// Mit erfundenen Abdrücken statt mit Mediendateien. Was hier geprüft /// wird, ist die Suche nach dem gemeinsamen Stück — nicht, ob Chromaprint /// rechnen kann. Eine Prüfung, die eine Serie mit echten Folgen braucht, /// liefe nirgends ausser auf dem Rechner dessen, der sie geschrieben hat. /// public class TonabgleichTests { /// Chromaprint mit Standardeinstellungen. private const double JeAbdruck = 0.1238; /// Eine Folge von Abdrücken, die sich nicht wiederholt. /// /// /// Aus einem festen Startwert gerechnet, nicht gewürfelt: dieselbe /// Prüfung soll morgen dasselbe Ergebnis haben. /// private static uint[] Rauschen(int anzahl, int saat) { var werte = new uint[anzahl]; var stand = (uint)saat * 2654435761u + 1u; for (var i = 0; i < anzahl; i++) { stand ^= stand << 13; stand ^= stand >> 17; stand ^= stand << 5; werte[i] = stand; } return werte; } /// Hängt Abschnitte aneinander. private static uint[] Aneinander(params uint[][] teile) => teile.SelectMany(t => t).ToArray(); /// Wie viele Zeitscheiben so viele Sekunden sind. private static int Scheiben(double sekunden) => (int)Math.Round(sekunden / JeAbdruck); [Test] public void Ein_gemeinsamer_Vorspann_wird_gefunden() { var vorspann = Rauschen(Scheiben(30), saat: 1); // Folge 1: zehn Sekunden Handlung, dann der Vorspann, dann weiter. var a = Aneinander(Rauschen(Scheiben(10), 2), vorspann, Rauschen(Scheiben(60), 3)); // 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); Assert.That(treffer, Is.Not.Null); Assert.Multiple(() => { Assert.That(treffer!.AVon, Is.EqualTo(10).Within(1.0)); Assert.That(treffer.ABis, Is.EqualTo(40).Within(1.0)); Assert.That(treffer.BVon, Is.EqualTo(70).Within(1.0)); Assert.That(treffer.BBis, Is.EqualTo(100).Within(1.0)); }); } /// Zwei Folgen ohne Gemeinsamkeit ergeben keinen Fund. /// /// /// Der wichtigere der beiden Fälle. Ein Vorspann, der nicht /// gefunden wird, kostet einen Knopf. Ein Vorspann, der erfunden wird, /// schneidet dem Zuschauer eine halbe Minute Handlung weg. /// [Test] public void Ohne_Gemeinsamkeit_kommt_nichts() { var a = Rauschen(Scheiben(120), 11); var b = Rauschen(Scheiben(120), 12); Assert.That(Tonabgleich.Finden(a, b, JeAbdruck), Is.Null); } /// Ein zu kurzes gemeinsames Stück zählt nicht. [Test] public void Drei_Sekunden_sind_kein_Vorspann() { var kurz = Rauschen(Scheiben(3), 21); 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); } /// Ein paar abweichende Bits machen den Fund nicht kaputt. /// /// /// Derselbe Vorspann aus zwei Dateien ist nie bitgleich: andere Bitrate, /// anderer Codec, andere Lautstärke. Wäre Gleichheit verlangt, fände der /// Abgleich an echten Folgen nie etwas. /// [Test] public void Kleine_Abweichungen_stoeren_nicht() { var vorspann = Rauschen(Scheiben(30), 31); var leichtAnders = vorspann.Select((w, i) => i % 3 == 0 ? w ^ 0b101u : w).ToArray(); 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); Assert.That(treffer, Is.Not.Null); Assert.That(treffer!.Laenge, Is.EqualTo(30).Within(2.0)); } /// Eine eingeblendete Ansage mitten im Vorspann reisst ihn nicht entzwei. [Test] public void Eine_kurze_Luecke_haelt_den_Lauf_zusammen() { var vorspann = Rauschen(Scheiben(30), 41); // Eine Sekunde mittendrin ist in der einen Folge anders. var mitAnsage = (uint[])vorspann.Clone(); var mitte = mitAnsage.Length / 2; for (var i = mitte; i < mitte + Scheiben(1); i++) mitAnsage[i] = 0xDEADBEEF; 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); Assert.That(treffer, Is.Not.Null); Assert.That(treffer!.Laenge, Is.EqualTo(30).Within(2.0), "Die Lücke gehoert zum Vorspann, nicht dahinter"); } /// Eine ganze Staffel auf einmal. [Test] public void Jede_Folge_der_Staffel_bekommt_ihre_Stelle() { var vorspann = Rauschen(Scheiben(25), 51); var folgen = new List<(int, IReadOnlyList)>(); for (var i = 0; i < 5; i++) { folgen.Add((i, Aneinander( Rauschen(Scheiben(5 + i * 10), 60 + i), vorspann, Rauschen(Scheiben(30), 70 + i)))); } var gefunden = Tonabgleich.ReiheAbgleichen(folgen, JeAbdruck); Assert.That(gefunden, Has.Count.EqualTo(5), "auch die letzte Folge"); for (var i = 0; i < 5; i++) { Assert.That(gefunden[i].Von, Is.EqualTo(5 + i * 10).Within(1.5), $"Folge {i}"); Assert.That(gefunden[i].Bis - gefunden[i].Von, Is.EqualTo(25).Within(2.0), $"Folge {i}"); } } /// Eine einzelne Folge hat niemanden zum Vergleichen. [Test] public void Eine_Folge_allein_ergibt_nichts() { var eine = new List<(int, IReadOnlyList)> { (0, Rauschen(Scheiben(60), 81)) }; Assert.That(Tonabgleich.ReiheAbgleichen(eine, JeAbdruck), Is.Empty); } }