diff --git a/VideoNodes/FfmpegBuilderNodes/Audio/FfmpegBuilderAudioAddTrack.cs b/VideoNodes/FfmpegBuilderNodes/Audio/FfmpegBuilderAudioAddTrack.cs index 288f958f..53b8ca73 100644 --- a/VideoNodes/FfmpegBuilderNodes/Audio/FfmpegBuilderAudioAddTrack.cs +++ b/VideoNodes/FfmpegBuilderNodes/Audio/FfmpegBuilderAudioAddTrack.cs @@ -97,27 +97,32 @@ public class FfmpegBuilderAudioAddTrack : FfmpegBuilderNode var audio = new FfmpegAudioStream(); -#pragma warning disable IL2026 // Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code - var bestAudio = Model.AudioStreams.Where(x => System.Text.Json.JsonSerializer.Serialize(x.Stream).ToLower().Contains("commentary") == false) -#pragma warning restore IL2026 // Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code - .OrderBy(x => + var bestAudio = GetBestAudioTrack(args, Model.AudioStreams.Select(x => x.Stream)); + if (bestAudio == null) { - if (Language != string.Empty) - { - args.Logger?.ILog("Language: " + x.Stream.Language, x); - if (string.IsNullOrEmpty(x.Stream.Language)) - return 50; // no language specified - if (x.Stream.Language?.ToLower() != Language) - return 100; // low priority not the desired language - } - return 0; - }) - .ThenByDescending(x => x.Stream.Channels) - .ThenBy(x => x.Index) - .FirstOrDefault(); - audio.Stream = bestAudio.Stream; + args.Logger.WLog("No source audio track found"); + return -1; + } - audio.EncodingParameters.AddRange(GetNewAudioTrackParameters("0:a:" + (bestAudio.Stream.TypeIndex))); + audio.Stream = bestAudio; + + bool directCopy = false; + if(bestAudio.Codec.ToLower() == this.Codec.ToLower()) + { + if(this.Channels == 0 || this.Channels == bestAudio.Channels) + { + directCopy = true; + } + } + + if (directCopy) + { + args.Logger?.ILog($"Source audio is already in appropriate format, just copying that track: {bestAudio.IndexString}, Channels: {bestAudio.Channels}, Codec: {bestAudio.Codec}"); + } + else + { + audio.EncodingParameters.AddRange(GetNewAudioTrackParameters("0:a:" + (bestAudio.TypeIndex))); + } if (Index > Model.AudioStreams.Count - 1) Model.AudioStreams.Add(audio); else @@ -126,6 +131,54 @@ public class FfmpegBuilderAudioAddTrack : FfmpegBuilderNode return 1; } + internal AudioStream GetBestAudioTrack(NodeParameters args, IEnumerable streams) + { +#pragma warning disable IL2026 // Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code + var bestAudio = streams.Where(x => System.Text.Json.JsonSerializer.Serialize(x).ToLower().Contains("commentary") == false) +#pragma warning restore IL2026 // Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code + .OrderBy(x => + { + if (Language != string.Empty) + { + args.Logger?.ILog("Language: " + x.Language, x); + if (string.IsNullOrEmpty(x.Language)) + return 50; // no language specified + if (x.Language.ToLower() != Language) + return 100; // low priority not the desired language + } + return 0; + }) + .ThenByDescending(x => { + if(this.Channels == 2) + { + if (x.Channels == 2) + return 1_000_000_000; + // compare codecs + if (x.Codec?.ToLower() == this.Codec?.ToLower()) + return 1_000_000; + } + if(this.Channels == 1) + { + if (x.Channels == 1) + return 1_000_000_000; + if (x.Channels <= 2.1f) + return 5_000_000; + if (x.Codec?.ToLower() == this.Codec?.ToLower()) + return 1_000_000; + } + + // now we want best channels, but to prefer matching codec + if (x.Codec?.ToLower() == this.Codec?.ToLower()) + { + return 1_000 + x.Channels; + } + return x.Channels; + }) + .ThenBy(x => x.Index) + .FirstOrDefault(); + return bestAudio; + } + private string[] GetNewAudioTrackParameters(string source) { diff --git a/VideoNodes/Tests/FfmpegBuilderTests/FfmpegBuilder_AddAudioTests.cs b/VideoNodes/Tests/FfmpegBuilderTests/FfmpegBuilder_AddAudioTests.cs new file mode 100644 index 00000000..fa21a374 --- /dev/null +++ b/VideoNodes/Tests/FfmpegBuilderTests/FfmpegBuilder_AddAudioTests.cs @@ -0,0 +1,169 @@ +#if(DEBUG) + +using FileFlows.VideoNodes.FfmpegBuilderNodes; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using VideoNodes.Tests; + +namespace FileFlows.VideoNodes.Tests.FfmpegBuilderTests; + +[TestClass] +public class FfmpegBuilder_AddAudioTests +{ + VideoInfo vii; + NodeParameters args; + private void Prepare() + { + const string file = @"D:\videos\unprocessed\basic.mkv"; + var logger = new TestLogger(); + const string ffmpeg = @"C:\utils\ffmpeg\ffmpeg.exe"; + var vi = new VideoInfoHelper(ffmpeg, logger); + vii = vi.Read(file); + vii.AudioStreams = new List + { + new AudioStream + { + Index = 2, + IndexString = "0:a:0", + Language = "en", + Codec = "AC3", + Channels = 5.1f + }, + new AudioStream + { + Index = 3, + IndexString = "0:a:1", + Language = "en", + Codec = "AAC", + Channels = 2 + }, + new AudioStream + { + Index = 4, + IndexString = "0:a:3", + Language = "en", + Codec = "AAC", + Channels = 2 + }, + new AudioStream + { + Index = 5, + IndexString = "0:a:4", + Language = "en", + Codec = "AAC", + Channels = 5.1f + } + }; + args = new NodeParameters(file, logger, false, string.Empty); + args.GetToolPathActual = (string tool) => ffmpeg; + args.TempPath = @"D:\videos\temp"; + args.Parameters.Add("VideoInfo", vii); + + + FfmpegBuilderStart ffStart = new(); + Assert.AreEqual(1, ffStart.Execute(args)); + } + + [TestMethod] + public void FfmpegBuilder_AddAudio_AacStereo() + { + Prepare(); + + FfmpegBuilderAudioAddTrack ffAddAudio = new(); + ffAddAudio.Codec = "aac"; + ffAddAudio.Channels = 2; + var best = ffAddAudio.GetBestAudioTrack(args, vii.AudioStreams); + + Assert.IsNotNull(best); + + Assert.AreEqual(3, best.Index); + Assert.AreEqual("AAC", best.Codec); + Assert.AreEqual(2f, best.Channels); + } + + [TestMethod] + public void FfmpegBuilder_AddAudio_AacSameAsSource() + { + Prepare(); + + FfmpegBuilderAudioAddTrack ffAddAudio = new(); + ffAddAudio.Codec = "aac"; + ffAddAudio.Channels = 0; + var best = ffAddAudio.GetBestAudioTrack(args, vii.AudioStreams); + + Assert.IsNotNull(best); + + Assert.AreEqual(5, best.Index); + Assert.AreEqual("AAC", best.Codec); + Assert.AreEqual(5.1f, best.Channels); + } + + [TestMethod] + public void FfmpegBuilder_AddAudio_Ac3SameAsSource() + { + Prepare(); + + FfmpegBuilderAudioAddTrack ffAddAudio = new(); + ffAddAudio.Codec = "ac3"; + ffAddAudio.Channels = 0; + ffAddAudio.Index = 1; + var best = ffAddAudio.GetBestAudioTrack(args, vii.AudioStreams); + + Assert.IsNotNull(best); + + Assert.AreEqual(2, best.Index); + Assert.AreEqual("AC3", best.Codec); + Assert.AreEqual(5.1f, best.Channels); + } + + [TestMethod] + public void FfmpegBuilder_AddAudio_DtsSame() + { + Prepare(); + + FfmpegBuilderAudioAddTrack ffAddAudio = new(); + ffAddAudio.Codec = "dts"; + ffAddAudio.Channels = 0; + var best = ffAddAudio.GetBestAudioTrack(args, vii.AudioStreams); + + Assert.IsNotNull(best); + + Assert.AreEqual(2, best.Index); + Assert.AreEqual("AC3", best.Codec); + Assert.AreEqual(5.1f, best.Channels); + } + + [TestMethod] + public void FfmpegBuilder_AddAudio_DtsStereo() + { + Prepare(); + + FfmpegBuilderAudioAddTrack ffAddAudio = new(); + ffAddAudio.Codec = "dts"; + ffAddAudio.Channels = 2; + var best = ffAddAudio.GetBestAudioTrack(args, vii.AudioStreams); + + Assert.IsNotNull(best); + + Assert.AreEqual(3, best.Index); + Assert.AreEqual("AAC", best.Codec); + Assert.AreEqual(2f, best.Channels); + } + [TestMethod] + public void FfmpegBuilder_AddAudio_DtsMono() + { + Prepare(); + + FfmpegBuilderAudioAddTrack ffAddAudio = new(); + ffAddAudio.Codec = "dts"; + ffAddAudio.Channels = 1; + var best = ffAddAudio.GetBestAudioTrack(args, vii.AudioStreams); + + Assert.IsNotNull(best); + + Assert.AreEqual(3, best.Index); + Assert.AreEqual("AAC", best.Codec); + Assert.AreEqual(2f, best.Channels); + } +} + +#endif \ No newline at end of file