mirror of
https://github.com/revenz/FileFlowsPlugins.git
synced 2026-02-17 13:38:34 -06:00
FF-1196 - keep original language
This commit is contained in:
Binary file not shown.
Binary file not shown.
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -0,0 +1,208 @@
|
||||
using FileFlows.VideoNodes.FfmpegBuilderNodes.Models;
|
||||
|
||||
namespace FileFlows.VideoNodes.FfmpegBuilderNodes;
|
||||
|
||||
/// <summary>
|
||||
/// FFmpeg Builder flow element to keep the original language track
|
||||
/// </summary>
|
||||
public class FfmpegBuilderKeepOriginalLanguage: FfmpegBuilderNode
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the help URL for the flow element
|
||||
/// </summary>
|
||||
public override string HelpUrl => "https://fileflows.com/docs/plugins/video-nodes/ffmpeg-builder/keep-original-language";
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of outputs of the flow element
|
||||
/// </summary>
|
||||
public override int Outputs => 2;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the icon of the flow element
|
||||
/// </summary>
|
||||
public override string Icon => "fas fa-globe";
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the stream type
|
||||
/// </summary>
|
||||
[Select(nameof(StreamTypeOptions), 1)]
|
||||
public string StreamType { get; set; }
|
||||
|
||||
private static List<ListOption> _StreamTypeOptions;
|
||||
/// <summary>
|
||||
/// Gets the stream options to show in the UI
|
||||
/// </summary>
|
||||
public static List<ListOption> StreamTypeOptions
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_StreamTypeOptions == null)
|
||||
{
|
||||
_StreamTypeOptions = new List<ListOption>
|
||||
{
|
||||
new () { Label = "Audio", Value = "Audio" },
|
||||
new () { Label = "Subtitle", Value = "Subtitle" },
|
||||
new () { Label = "Both", Value = "Both" },
|
||||
};
|
||||
}
|
||||
return _StreamTypeOptions;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the languages
|
||||
/// </summary>
|
||||
[StringArray(2)]
|
||||
public List<string> AdditionalLanguages { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets if only the first of each language should be kept
|
||||
/// </summary>
|
||||
[Boolean(3)]
|
||||
public bool KeepOnlyFirst { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets if the first stream should be kept if no other streams match
|
||||
/// </summary>
|
||||
[Boolean(4)]
|
||||
public bool FirstIfNone { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets if tracks with no language should be treated as the original langauge
|
||||
/// </summary>
|
||||
[Boolean(5)]
|
||||
public bool TreatEmptyAsOriginal { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Executes the flow element
|
||||
/// </summary>
|
||||
/// <param name="args">the flow parameters</param>
|
||||
/// <returns>the flow output to call next</returns>
|
||||
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<T>(NodeParameters args, List<T> streams, string originalLanguage) where T : FfmpegStream
|
||||
{
|
||||
if (streams?.Any() != true)
|
||||
return 0;
|
||||
|
||||
int changed = 0;
|
||||
bool firstStreamDeleted = streams[0].Deleted;
|
||||
var foundLanguages = new List<string>();
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests if a language matches
|
||||
/// </summary>
|
||||
/// <param name="streamLanguage">the language of ths stream</param>
|
||||
/// <param name="testLanguage">the language to test</param>
|
||||
/// <returns>true if matches, otherwise false</returns>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<AudioStream>
|
||||
{
|
||||
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<SubtitleStream>
|
||||
{
|
||||
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<string>{ "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<string>{ "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<string>{ "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
|
||||
@@ -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.",
|
||||
|
||||
Reference in New Issue
Block a user