From 2e0f02dc9ddab52f1b64fded29d9046e4a3ea7f2 Mon Sep 17 00:00:00 2001 From: John Andrews Date: Mon, 15 Jan 2024 15:36:59 +1300 Subject: [PATCH] FF-1196 - keep original language --- FileFlows.Plugin.dll | Bin 128512 -> 129024 bytes FileFlows.Plugin.pdb | Bin 29524 -> 29524 bytes MetaNodes/TheMovieDb/MovieLookup.cs | 6 +- MetaNodes/TheMovieDb/TVEpisodeLookup.cs | 4 + MetaNodes/TheMovieDb/TVShowLookup.cs | 2 + .../FfmpegBuilderKeepOriginalLanguage.cs | 208 +++++++++++++ ...FfmpegBuilder_KeepOriginalLanguageTests.cs | 275 ++++++++++++++++++ VideoNodes/VideoNodes.en.json | 20 ++ 8 files changed, 514 insertions(+), 1 deletion(-) create mode 100644 VideoNodes/FfmpegBuilderNodes/FfmpegBuilderKeepOriginalLanguage.cs create mode 100644 VideoNodes/Tests/FfmpegBuilderTests/FfmpegBuilder_KeepOriginalLanguageTests.cs diff --git a/FileFlows.Plugin.dll b/FileFlows.Plugin.dll index 1b8cc7dc4a88e0c11f695b4b2fd12958498c249e..9f8899e5129b082775d7d586dc06faa53ca9eb4b 100644 GIT binary patch delta 5381 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 V>J4Gd^jnApJsVtCs7Y*T{XeKVvFrc< delta 5329 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 diff --git a/FileFlows.Plugin.pdb b/FileFlows.Plugin.pdb index e1b3b943c301d0fff7d62bbadf12a817bfb7dec1..1cbdb60feaaf649343262a1e1bfc5101c5361d53 100644 GIT binary patch delta 143 zcmccejPc4d#tAhdcK12s(wN%aH}NdEKkv1~-CND~_HCSSfR delta 143 zcmccejPc4d#tAhd>5+L?r}B0??cJubdAoN|q1=N%ts5tt;82uhU|`^9U}s=rU<9%l z7(jdhWd7z#&WkDvl2;hYFImQBemn5*{@yQ5b{z|Lu&J+K8?xc^dp;}w#wnY#^hC9! b^_duWfcll1)H&H0bYL7K5NGqOh+R?uNq8$! diff --git a/MetaNodes/TheMovieDb/MovieLookup.cs b/MetaNodes/TheMovieDb/MovieLookup.cs index ef315ca3..38697df7 100644 --- a/MetaNodes/TheMovieDb/MovieLookup.cs +++ b/MetaNodes/TheMovieDb/MovieLookup.cs @@ -147,7 +147,11 @@ public class MovieLookup : Node Variables["movie.Title"] = result.Title; Variables["movie.Year"] = result.ReleaseDate.Year; - Variables["VideoMetadata"] = GetVideoMetadata(movieApi, result.Id, args.TempPath); + var meta = GetVideoMetadata(movieApi, result.Id, args.TempPath); + Variables["VideoMetadata"] = meta; + if (string.IsNullOrWhiteSpace(meta.OriginalLanguage) == false) + Variables["OriginalLanguage"] = meta.OriginalLanguage; + Variables[Globals.MOVIE_INFO] = result; args.UpdateVariables(Variables); diff --git a/MetaNodes/TheMovieDb/TVEpisodeLookup.cs b/MetaNodes/TheMovieDb/TVEpisodeLookup.cs index 8df961c5..8a7fffa5 100644 --- a/MetaNodes/TheMovieDb/TVEpisodeLookup.cs +++ b/MetaNodes/TheMovieDb/TVEpisodeLookup.cs @@ -156,6 +156,10 @@ public class TVEpisodeLookup : Node Variables["tvepisode.Overview"] = epInfo.Overview; //Variables["VideoMetadata"] = GetVideoMetadata(movieApi, result.Id, args.TempPath); Variables[Globals.TV_SHOW_INFO] = result; + + if (string.IsNullOrWhiteSpace(result.OriginalLanguage) == false) + Variables["OriginalLanguage"] = result.OriginalLanguage; + args.UpdateVariables(Variables); return 1; diff --git a/MetaNodes/TheMovieDb/TVShowLookup.cs b/MetaNodes/TheMovieDb/TVShowLookup.cs index 5af82865..332e3e9b 100644 --- a/MetaNodes/TheMovieDb/TVShowLookup.cs +++ b/MetaNodes/TheMovieDb/TVShowLookup.cs @@ -107,6 +107,8 @@ public class TVShowLookup : Node Variables["tvshow.Year"] = result.FirstAirDate.Year; Variables["VideoMetadata"] = GetVideoMetadata(movieApi, result.Id, args.TempPath); Variables[Globals.TV_SHOW_INFO] = result; + if (string.IsNullOrWhiteSpace(result.OriginalLanguage) == false) + Variables["OriginalLanguage"] = result.OriginalLanguage; args.UpdateVariables(Variables); return 1; diff --git a/VideoNodes/FfmpegBuilderNodes/FfmpegBuilderKeepOriginalLanguage.cs b/VideoNodes/FfmpegBuilderNodes/FfmpegBuilderKeepOriginalLanguage.cs new file mode 100644 index 00000000..2eada97c --- /dev/null +++ b/VideoNodes/FfmpegBuilderNodes/FfmpegBuilderKeepOriginalLanguage.cs @@ -0,0 +1,208 @@ +using FileFlows.VideoNodes.FfmpegBuilderNodes.Models; + +namespace FileFlows.VideoNodes.FfmpegBuilderNodes; + +/// +/// FFmpeg Builder flow element to keep the original language track +/// +public class FfmpegBuilderKeepOriginalLanguage: FfmpegBuilderNode +{ + /// + /// Gets the help URL for the flow element + /// + public override string HelpUrl => "https://fileflows.com/docs/plugins/video-nodes/ffmpeg-builder/keep-original-language"; + + /// + /// Gets the number of outputs of the flow element + /// + public override int Outputs => 2; + + /// + /// Gets the icon of the flow element + /// + public override string Icon => "fas fa-globe"; + + /// + /// Gets or sets the stream type + /// + [Select(nameof(StreamTypeOptions), 1)] + public string StreamType { get; set; } + + private static List _StreamTypeOptions; + /// + /// Gets the stream options to show in the UI + /// + public static List StreamTypeOptions + { + get + { + if (_StreamTypeOptions == null) + { + _StreamTypeOptions = new List + { + new () { Label = "Audio", Value = "Audio" }, + new () { Label = "Subtitle", Value = "Subtitle" }, + new () { Label = "Both", Value = "Both" }, + }; + } + return _StreamTypeOptions; + } + } + + /// + /// Gets or sets the languages + /// + [StringArray(2)] + public List AdditionalLanguages { get; set; } + + /// + /// Gets or sets if only the first of each language should be kept + /// + [Boolean(3)] + public bool KeepOnlyFirst { get; set; } + + /// + /// Gets or sets if the first stream should be kept if no other streams match + /// + [Boolean(4)] + public bool FirstIfNone { get; set; } + + /// + /// Gets or sets if tracks with no language should be treated as the original langauge + /// + [Boolean(5)] + public bool TreatEmptyAsOriginal { get; set; } + + /// + /// Executes the flow element + /// + /// the flow parameters + /// the flow output to call next + public override int Execute(NodeParameters args) + { + string originalLanguage; + if (args.Variables.TryGetValue("OriginalLanguage", out object oValue) == false || + string.IsNullOrWhiteSpace(originalLanguage = oValue as string)) + { + args.Logger?.ILog("OriginalLanguage variable was not set."); + return 2; + } + args.Logger?.ILog("OriginalLanguage: " + originalLanguage); + + int changes = 0; + if(StreamType is "Audio" or "Both") + { + changes += ProcessStreams(args, Model.AudioStreams, originalLanguage); + } + if(StreamType is "Subtitle" or "Both") + { + changes += ProcessStreams(args, Model.SubtitleStreams, originalLanguage); + } + + return changes > 0 ? 1 : 2; + } + + private int ProcessStreams(NodeParameters args, List streams, string originalLanguage) where T : FfmpegStream + { + if (streams?.Any() != true) + return 0; + + int changed = 0; + bool firstStreamDeleted = streams[0].Deleted; + var foundLanguages = new List(); + foreach (var stream in streams) + { + bool deleted; + if (TreatEmptyAsOriginal && string.IsNullOrWhiteSpace(stream.Language)) + deleted = false; + else + deleted = KeepStream(originalLanguage, stream.Language) == false; + + if (deleted == false) + { + string lang = LanguageHelper.GetIso1Code(stream.Language?.EmptyAsNull() ?? originalLanguage); + if (foundLanguages.Contains(lang) == false) + foundLanguages.Add(lang); + else if (KeepOnlyFirst) + deleted = true; + } + + if (stream.Deleted == deleted) + continue; + stream.Deleted = deleted; + ++changed; + args.Logger?.ILog($"Stream '{stream.GetType().Name}' '{stream.Language}' " + (deleted ? "deleted" : "restored")); + } + + if (FirstIfNone && streams.Any(x => x.Deleted == false) == false) + { + if (firstStreamDeleted == false) + { + --changed; // remove the change + } + streams[0].Deleted = false; + args.Logger?.ILog($"Stream '{streams[0].GetType().Name}' '{streams[0].Language}' restored as only stream"); + } + + return changed; + } + + private bool KeepStream(string originalLanguage, string streamLanguage) + { + if (LanguageMatches(streamLanguage, originalLanguage)) + return true; + if (AdditionalLanguages?.Any() != true) + return false; + + foreach (var lang in this.AdditionalLanguages) + { + if (LanguageMatches(streamLanguage, lang)) + return true; + } + + return false; + } + + /// + /// Tests if a language matches + /// + /// the language of ths stream + /// the language to test + /// true if matches, otherwise false + private bool LanguageMatches(string streamLanguage, string testLanguage) + { + if (string.IsNullOrWhiteSpace(testLanguage)) + return false; + if (string.IsNullOrWhiteSpace(streamLanguage)) + return false; + if (testLanguage.ToLowerInvariant().Contains(streamLanguage.ToLowerInvariant())) + return true; + try + { + if (LanguageHelper.GetIso2Code(streamLanguage) == LanguageHelper.GetIso2Code(testLanguage)) + return true; + } + catch (Exception) + { + } + + try + { + if (LanguageHelper.GetIso1Code(streamLanguage) == LanguageHelper.GetIso1Code(testLanguage)) + return true; + } + catch (Exception) + { + } + + try + { + var rgx = new Regex(testLanguage, RegexOptions.IgnoreCase); + return rgx.IsMatch(streamLanguage); + } + catch (Exception) + { + return false; + } + } +} diff --git a/VideoNodes/Tests/FfmpegBuilderTests/FfmpegBuilder_KeepOriginalLanguageTests.cs b/VideoNodes/Tests/FfmpegBuilderTests/FfmpegBuilder_KeepOriginalLanguageTests.cs new file mode 100644 index 00000000..35cf1e6c --- /dev/null +++ b/VideoNodes/Tests/FfmpegBuilderTests/FfmpegBuilder_KeepOriginalLanguageTests.cs @@ -0,0 +1,275 @@ +#if(DEBUG) + +using FileFlows.VideoNodes.FfmpegBuilderNodes; +using FileFlows.VideoNodes.FfmpegBuilderNodes.Models; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using VideoNodes.Tests; + +namespace FileFlows.VideoNodes.Tests.FfmpegBuilderTests; + +[TestClass] +public class FfmpegBuilder_KeepOriginalLanguageTests +{ + VideoInfo vii; + NodeParameters args; + TestLogger logger = new TestLogger(); + private void Prepare() + { + const string file = @"D:\videos\unprocessed\basic.mkv"; + 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 = "fre", + Codec = "AAC", + Channels = 2 + }, + new AudioStream + { + Index = 5, + IndexString = "0:a:4", + Language = "deu", + Codec = "AAC", + Channels = 5.1f + } + }; + + vii.SubtitleStreams = new List + { + new() + { + Index = 2, + IndexString = "0:s:0", + Language = "en", + Codec = "AC3" + }, + new() + { + Index = 3, + IndexString = "0:s:1", + Language = "en", + Codec = "AAC" + }, + new() + { + Index = 4, + IndexString = "0:s:3", + Language = "fre", + Codec = "AAC", + }, + new() + { + Index = 5, + IndexString = "0:s:4", + Language = "deu", + Codec = "AAC" + } + }; + 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(); + ffStart.PreExecute(args); + Assert.AreEqual(1, ffStart.Execute(args)); + } + + private FfmpegModel GetFFmpegModel() + { + return args.Variables["FfmpegBuilderModel"] as FfmpegModel; + } + + [TestMethod] + public void FfmpegBuilder_Audio_German() + { + Prepare(); + + FfmpegBuilderKeepOriginalLanguage ffElement = new(); + ffElement.StreamType = "Audio"; + args.Variables["OriginalLanguage"] = "German"; + ffElement.PreExecute(args); + var result = ffElement.Execute(args); + var log = logger.ToString(); + + Assert.AreEqual(1, result); + var model = GetFFmpegModel(); + var kept = model.AudioStreams.Where(x => x.Deleted == false).ToList(); + Assert.AreEqual(1, kept.Count); + Assert.AreEqual("deu", kept[0].Language); + } + + [TestMethod] + public void FfmpegBuilder_Audio_None() + { + Prepare(); + + FfmpegBuilderKeepOriginalLanguage ffElement = new(); + ffElement.StreamType = "Audio"; + ffElement.FirstIfNone = true; + args.Variables["OriginalLanguage"] = "Maori"; + ffElement.PreExecute(args); + var result = ffElement.Execute(args); + var log = logger.ToString(); + + Assert.AreEqual(1, result); + var model = GetFFmpegModel(); + var kept = model.AudioStreams.Where(x => x.Deleted == false).ToList(); + Assert.AreEqual(1, kept.Count); + Assert.AreEqual("en", kept[0].Language); + } + + + [TestMethod] + public void FfmpegBuilder_Audio_OriginalAndEnglish() + { + Prepare(); + + FfmpegBuilderKeepOriginalLanguage ffElement = new(); + ffElement.StreamType = "Audio"; + ffElement.AdditionalLanguages = new List{ "English" }; + args.Variables["OriginalLanguage"] = "French"; + ffElement.PreExecute(args); + var result = ffElement.Execute(args); + var log = logger.ToString(); + + Assert.AreEqual(1, result); + var model = GetFFmpegModel(); + var kept = model.AudioStreams.Where(x => x.Deleted == false).ToList(); + Assert.AreEqual(3, kept.Count); + Assert.AreEqual("en", kept[0].Language); + Assert.AreEqual("en", kept[1].Language); + Assert.AreEqual("fre", kept[2].Language); + } + + + [TestMethod] + public void FfmpegBuilder_Both_German() + { + Prepare(); + + FfmpegBuilderKeepOriginalLanguage ffElement = new(); + ffElement.StreamType = "Both"; + args.Variables["OriginalLanguage"] = "German"; + ffElement.PreExecute(args); + var result = ffElement.Execute(args); + var log = logger.ToString(); + + Assert.AreEqual(1, result); + var model = GetFFmpegModel(); + var kept = model.AudioStreams.Where(x => x.Deleted == false).ToList(); + Assert.AreEqual(1, kept.Count); + Assert.AreEqual("deu", kept[0].Language); + + var subKept = model.SubtitleStreams.Where(x => x.Deleted == false).ToList(); + Assert.AreEqual(1, subKept.Count); + Assert.AreEqual("deu", subKept[0].Language); + } + + [TestMethod] + public void FfmpegBuilder_Both_None() + { + Prepare(); + + FfmpegBuilderKeepOriginalLanguage ffElement = new(); + ffElement.StreamType = "Both"; + ffElement.FirstIfNone = true; + args.Variables["OriginalLanguage"] = "Maori"; + ffElement.PreExecute(args); + var result = ffElement.Execute(args); + var log = logger.ToString(); + + Assert.AreEqual(1, result); + var model = GetFFmpegModel(); + var kept = model.AudioStreams.Where(x => x.Deleted == false).ToList(); + Assert.AreEqual(1, kept.Count); + Assert.AreEqual("en", kept[0].Language); + + var subKept = model.SubtitleStreams.Where(x => x.Deleted == false).ToList(); + Assert.AreEqual(1, subKept.Count); + Assert.AreEqual("en", subKept[0].Language); + } + + [TestMethod] + public void FfmpegBuilder_Both_OriginalAndEnglish() + { + Prepare(); + + FfmpegBuilderKeepOriginalLanguage ffElement = new(); + ffElement.StreamType = "Both"; + ffElement.AdditionalLanguages = new List{ "English" }; + args.Variables["OriginalLanguage"] = "French"; + ffElement.PreExecute(args); + var result = ffElement.Execute(args); + var log = logger.ToString(); + + Assert.AreEqual(1, result); + var model = GetFFmpegModel(); + var kept = model.AudioStreams.Where(x => x.Deleted == false).ToList(); + Assert.AreEqual(3, kept.Count); + Assert.AreEqual("en", kept[0].Language); + Assert.AreEqual("en", kept[1].Language); + Assert.AreEqual("fre", kept[2].Language); + + + var subKept = model.SubtitleStreams.Where(x => x.Deleted == false).ToList(); + Assert.AreEqual(3, subKept.Count); + Assert.AreEqual("en", subKept[0].Language); + Assert.AreEqual("en", subKept[1].Language); + Assert.AreEqual("fre", subKept[2].Language); + } + + [TestMethod] + public void FfmpegBuilder_Both_OriginalAndEnglish_OnlyFirst() + { + Prepare(); + + FfmpegBuilderKeepOriginalLanguage ffElement = new(); + ffElement.StreamType = "Both"; + ffElement.KeepOnlyFirst = true; + ffElement.AdditionalLanguages = new List{ "English" }; + args.Variables["OriginalLanguage"] = "French"; + ffElement.PreExecute(args); + var result = ffElement.Execute(args); + var log = logger.ToString(); + + Assert.AreEqual(1, result); + var model = GetFFmpegModel(); + var kept = model.AudioStreams.Where(x => x.Deleted == false).ToList(); + Assert.AreEqual(2, kept.Count); + Assert.AreEqual("en", kept[0].Language); + Assert.AreEqual("0:a:0", kept[0].Stream.IndexString); + Assert.AreEqual("fre", kept[1].Language); + + + var subKept = model.SubtitleStreams.Where(x => x.Deleted == false).ToList(); + Assert.AreEqual(2, subKept.Count); + Assert.AreEqual("en", subKept[0].Language); + Assert.AreEqual("0:s:0", subKept[0].Stream.IndexString); + Assert.AreEqual("fre", subKept[1].Language); + } +} + +#endif \ No newline at end of file diff --git a/VideoNodes/VideoNodes.en.json b/VideoNodes/VideoNodes.en.json index 07b9d202..445b1f6f 100644 --- a/VideoNodes/VideoNodes.en.json +++ b/VideoNodes/VideoNodes.en.json @@ -308,6 +308,26 @@ "2": "No HDR stream found" } }, + "FfmpegBuilderKeepOriginalLanguage": { + "Label": "FFMPEG Builder: Keep Original Language", + "Outputs": { + "1": "Tracks have been modified", + "2": "No tracks have been changed" + }, + "Description": "This flow element that will keep only the original language and any additional languages the user defines.\n\nAll other language streams will be removed/marked for deletion.", + "Fields": { + "StreamType": "Type", + "StreamType-Help": "The type of tracks that should be updated", + "AdditionalLanguages": "Additional Languages", + "AdditionalLanguages-Help": "An optional list of additional language codes to set on the tracks with missing languages.\n\nIt is recommended that an [ISO 639-2 language code](https://en.wikipedia.org/wiki/List_of_ISO_639-2_codes) are used.", + "KeepOnlyFirst": "Keep Only First", + "KeepOnlyFirst-Help": "When enabled only the first track of each language would be kept.\n\nFor example if there were 2 English tracks, 3 Spanish tracks and 1 German track. The original language was Spanish, additional languages was set to `eng`, then the result would be 1 English track and 1 Spanish track, the rest would be removed.", + "FirstIfNone": "First If None", + "FirstIfNone-Help": "When enabled, this ensures at least one track is kept. If no tracks matching the original language and no tracks matching the additional languages are found, the first track will be kept regardless.\n\nThis avoids any issues of no audio left on the video.", + "TreatEmptyAsOriginal": "Treat Empty As Original", + "TreatEmptyAsOriginal-Help": "When enabled, any track that has no language set, will be treated as if it were the original language.\n\nFor example, original language is Maori, and a track has no language set on it, it will be treated as Maori." + } + }, "FfmpegBuilderMetadataRemover": { "Label": "FFMPEG Builder: Metadata Remover", "Description": "Removes metadata from the FFMPEG Builder so when the file is processed the selected metadata will be removed.\n\nNote: Only the metadata when this node is effected, if metadata is added after this node runs, that will not be effected.",