using Flaro.Core.Delivery; using Flaro.Core.Library; namespace Flaro.Media.Tests; /// Die ffmpeg-Zeile, betrachtet ohne sie auszuführen. public class RemuxArgumentsTests { private static ProbedStream Bild() => new(0, MediaStreamKind.Video, "h264", null, null, true, false, null, 1920, 1080); private static ProbedStream Ton(int i, string codec = "aac", int kanäle = 6) => new(i, MediaStreamKind.Audio, codec, "deu", null, false, false, kanäle, null, null); private static ProbedStream Text(int i, string codec = "subrip") => new(i, MediaStreamKind.Subtitle, codec, "deu", null, false, false, null, null, null); private static List Bauen(IReadOnlyList spuren, string schachtel = "mkv") { var plan = DeliveryDecider.ForDownload(schachtel, spuren); return RemuxArguments.For(new RemuxRequest("/medien/film.mkv", "/kratz/film.mp4", spuren, plan)); } private static string AlsZeile(List argumente) => string.Join(' ', argumente); /// Jede Spur wird einzeln benannt — und zwar alle. [Test] public void Alle_Spuren_werden_abgebildet() { var zeile = AlsZeile(Bauen([Bild(), Ton(1), Ton(2), Text(3)])); Assert.That(zeile, Does.Contain("-map 0:0")); Assert.That(zeile, Does.Contain("-map 0:1")); Assert.That(zeile, Does.Contain("-map 0:2")); Assert.That(zeile, Does.Contain("-map 0:3")); } [Test] public void Bild_und_Ton_werden_kopiert_nicht_gerechnet() { var zeile = AlsZeile(Bauen([Bild(), Ton(1), Ton(2)])); Assert.That(zeile, Does.Contain("-c copy")); Assert.That(zeile, Does.Not.Contain("-c:a:"), "Nichts davon muss neu gerechnet werden"); Assert.That(zeile, Does.Not.Contain("libx264"), "Das Bild wird nie angefasst"); } [Test] public void Textuntertitel_werden_nach_tx3g_umgeschrieben() { Assert.That(AlsZeile(Bauen([Bild(), Ton(1), Text(2)])), Does.Contain("-c:s mov_text")); } [Test] public void Ohne_Untertitel_steht_auch_keine_Untertitel_Anweisung_da() { Assert.That(AlsZeile(Bauen([Bild(), Ton(1)])), Does.Not.Contain("-c:s")); } [Test] public void Bilduntertitel_werden_gar_nicht_erst_abgebildet() { var zeile = AlsZeile(Bauen([Bild(), Ton(1), Text(2, "hdmv_pgs_subtitle"), Text(3, "subrip")])); Assert.That(zeile, Does.Not.Contain("-map 0:2"), "PGS passt in keine MP4"); Assert.That(zeile, Does.Contain("-map 0:3")); } /// Die Verwechslung, vor der der Kommentar an MediaStream.Index warnt. /// /// /// -c:a:1 meint die zweite Tonspur der Ausgabe, nicht die Spur /// Nummer 1 der Datei. Hier steht DTS an Dateinummer 2 und ist die /// zweite Tonspur — also -c:a:1. Wer die Dateinummer einsetzte, /// träfe eine andere Spur oder gar keine. /// [Test] public void Neu_gerechnet_wird_nach_der_Nummer_der_Ausgabe() { var zeile = AlsZeile(Bauen([Bild(), Ton(1, "aac"), Ton(2, "dts"), Ton(3, "aac")])); Assert.That(zeile, Does.Contain("-c:a:1 aac")); Assert.That(zeile, Does.Not.Contain("-c:a:0"), "Die erste Tonspur passt schon"); Assert.That(zeile, Does.Not.Contain("-c:a:2"), "Die dritte auch"); } /// Fällt ein Untertitel weg, verschieben sich die Tonnummern nicht. /// /// /// Tonspuren werden unter sich gezählt — ein weggelassener Untertitel /// ändert daran nichts. Die Prüfung steht hier, weil der umgekehrte Fehler /// naheliegt und erst an einer stummen Spur auffiele. /// [Test] public void Ein_weggelassener_Untertitel_verschiebt_die_Tonnummern_nicht() { var zeile = AlsZeile(Bauen([Bild(), Text(1, "hdmv_pgs_subtitle"), Ton(2, "aac"), Ton(3, "dts")])); Assert.That(zeile, Does.Contain("-c:a:1 aac")); } [Test] public void Mehr_Kanaele_bekommen_mehr_Bitrate() { Assert.That(AlsZeile(Bauen([Bild(), Ton(1, "dts", kanäle: 2)])), Does.Contain("-b:a:0 192000")); Assert.That(AlsZeile(Bauen([Bild(), Ton(1, "dts", kanäle: 6)])), Does.Contain("-b:a:0 576000")); Assert.That(AlsZeile(Bauen([Bild(), Ton(1, "dts", kanäle: 8)])), Does.Contain("-b:a:0 640000"), "Gedeckelt — acht Kanäle bekämen sonst mehr, als irgendein Gerät braucht"); } [Test] public void Das_Inhaltsverzeichnis_wandert_nach_vorn() { Assert.That(AlsZeile(Bauen([Bild(), Ton(1)])), Does.Contain("-movflags +faststart")); } [Test] public void Quelle_und_Ziel_stehen_an_der_richtigen_Stelle() { var argumente = Bauen([Bild(), Ton(1)]); Assert.That(argumente[argumente.IndexOf("-i") + 1], Is.EqualTo("/medien/film.mkv")); Assert.That(argumente[^1], Is.EqualTo("/kratz/film.mp4"), "Das Ziel steht bei ffmpeg immer zuletzt"); } /// Genau eine Tonspur ist die Vorgabe — sonst laufen alle zugleich. /// /// /// Gemeldet als „ich höre einfach alle Audiospuren gleichzeitig". /// MP4 ordnet Spuren derselben Art einer alternate_group zu, und /// ein Abspieler nimmt daraus genau eine. ffmpeg leitet diese Gruppe aus /// der Markierung der Quelle ab — sind dort zwei Tonspuren als Vorgabe /// markiert, legt es sie in getrennte Gruppen, und beide laufen. /// [Test] public void Nur_eine_Tonspur_bleibt_Vorgabe() { var zwei = new List { Bild(), Ton(1) with { IsDefault = true }, Ton(2) with { IsDefault = true }, }; var zeile = AlsZeile(Bauen(zwei)); Assert.Multiple(() => { Assert.That(zeile, Does.Contain("-disposition:a:0 default")); Assert.That(zeile, Does.Contain("-disposition:a:1 0"), "auch die Null gehört hingeschrieben: ffmpeg übernähme sonst die Markierung der Quelle"); }); } /// Die Vorgabe der Quelle bleibt die Vorgabe. [Test] public void Die_schon_vorgegebene_Spur_bleibt_es() { var zeile = AlsZeile(Bauen([Bild(), Ton(1), Ton(2) with { IsDefault = true }])); Assert.Multiple(() => { Assert.That(zeile, Does.Contain("-disposition:a:0 0")); Assert.That(zeile, Does.Contain("-disposition:a:1 default")); }); } /// „Erzwungen" überlebt die Behandlung. /// /// /// -disposition ersetzt die ganze Markierung, statt sie zu /// ergänzen. Wer hier nur „default" schreibt, nimmt einer erzwungenen /// Untertitelspur genau das, was sie ausmacht. /// [Test] public void Erzwungene_Untertitel_bleiben_erzwungen() { var zeile = AlsZeile(Bauen( [Bild(), Ton(1), Text(2) with { IsForced = true }, Text(3)])); Assert.That(zeile, Does.Contain("-disposition:s:0 forced")); } /// Ohne Vorgabe in der Quelle erscheint auch kein Untertitel von selbst. [Test] public void Untertitel_ohne_Vorgabe_bekommen_keine() { var zeile = AlsZeile(Bauen([Bild(), Ton(1), Text(2), Text(3)])); Assert.Multiple(() => { Assert.That(zeile, Does.Contain("-disposition:s:0 0")); Assert.That(zeile, Does.Not.Contain("-disposition:s:0 default")); }); } /// Bei einer einzigen Spur gibt es nichts zu entscheiden. [Test] public void Eine_einzige_Tonspur_wird_nicht_angefasst() { var zeile = AlsZeile(Bauen([Bild(), Ton(1)])); Assert.That(zeile, Does.Not.Contain("-disposition:a")); } }