diff --git a/VideoNodes/InputNodes/VideoFile.cs b/VideoNodes/InputNodes/VideoFile.cs index c1d669e1..be1f0a0b 100644 --- a/VideoNodes/InputNodes/VideoFile.cs +++ b/VideoNodes/InputNodes/VideoFile.cs @@ -154,6 +154,8 @@ public class VideoFile : VideoNode /// the node parameters private void PrintFFmpegVersion(NodeParameters args) { + if(string.IsNullOrEmpty(FFMPEG)) + return; string firstLine = string.Empty; try diff --git a/VideoNodes/Tests/SubtitleExtractorTests.cs b/VideoNodes/Tests/SubtitleExtractorTests.cs index efdd1e28..2873e52b 100644 --- a/VideoNodes/Tests/SubtitleExtractorTests.cs +++ b/VideoNodes/Tests/SubtitleExtractorTests.cs @@ -45,9 +45,10 @@ namespace VideoNodes.Tests var vi = new VideoInfoHelper(FfmpegPath, new TestLogger()); var vii = vi.Read(file); - foreach (string ext in new[] { String.Empty, ".srt", ".sup" }) + foreach (string ext in new[] { string.Empty, ".srt", ".sup" }) { SubtitleExtractor node = new(); + node.ForcedOnly = true; node.OutputFile = Path.Combine(TempPath, "subtitle.en" + ext); node.Language = "eng"; @@ -55,7 +56,9 @@ namespace VideoNodes.Tests args.GetToolPathActual = (string tool) => FfmpegPath; args.TempPath = TempPath; - Assert.AreEqual(1, new VideoFile().Execute(args)); + var vf = new VideoFile(); + vf.PreExecute(args); + Assert.AreEqual(1, vf.Execute(args)); int output = node.Execute(args); diff --git a/VideoNodes/VideoNodes.en.json b/VideoNodes/VideoNodes.en.json index b3f96761..4968a8dc 100644 --- a/VideoNodes/VideoNodes.en.json +++ b/VideoNodes/VideoNodes.en.json @@ -498,7 +498,9 @@ "OutputFile": "Output File", "OutputFile-Help": "Where to save the the output file to, e.g. \"'{folder.Orig.FullName}\\{file.Orig.FileName}.srt'\" to save it with the original file as a srt output.\nIf left blank an srt subtitle will be created in the same folder as the original input file.", "SetWorkingFile": "Set as Working File", - "SetWorkingFile-Help": "When this is checked, if a subtitle is extracted, the working file will be changed to this extracted subtitle. The original working file will NOT be deleted." + "SetWorkingFile-Help": "When this is checked, if a subtitle is extracted, the working file will be changed to this extracted subtitle. The original working file will NOT be deleted.", + "ForcedOnly": "Forced Only", + "ForcedOnly-Help": "If only forced subtitles should be extracted." } }, "VideoHasStream": { diff --git a/VideoNodes/VideoNodes/SubtitleExtractor.cs b/VideoNodes/VideoNodes/SubtitleExtractor.cs index 7ebde434..ab6fe86f 100644 --- a/VideoNodes/VideoNodes/SubtitleExtractor.cs +++ b/VideoNodes/VideoNodes/SubtitleExtractor.cs @@ -1,145 +1,187 @@ -namespace FileFlows.VideoNodes +using System.Net; + +namespace FileFlows.VideoNodes; + +/// +/// Element that extracts subtitles +/// +public class SubtitleExtractor : EncodingNode { - using FileFlows.Plugin; - using FileFlows.Plugin.Attributes; - using System; - using System.Collections.Generic; - using System.Linq; - using System.Threading.Tasks; - - public class SubtitleExtractor : EncodingNode + /// + /// Gets the number of outputs + /// + public override int Outputs => 2; + /// + /// Gets the icon for the element + /// + public override string Icon => "fas fa-comment-dots"; + /// + /// Gets or sets the language to extract + /// + [Text(1)] + public string Language { get; set; } + /// + /// Gets or sets the destination output file + /// + [File(2)] + public string OutputFile { get; set; } + /// + /// Gets or sets if the new file should be set as the current working file + /// + [Boolean(3)] + public bool SetWorkingFile { get; set; } + /// + /// Gets or sets if only forced subtitles should be extracted + /// + [Boolean(4)] + public bool ForcedOnly { get; set; } + + + private Dictionary _Variables; + /// + /// Gets or sets the variables + /// + public override Dictionary Variables => _Variables; + + /// + /// Creates an instance of the subtitle extractor + /// + public SubtitleExtractor() { - public override int Outputs => 2; - - public override string Icon => "fas fa-comment-dots"; - - [Text(1)] - public string Language { get; set; } - - [File(2)] - public string OutputFile { get; set; } - - [Boolean(3)] - public bool SetWorkingFile { get; set; } - private Dictionary _Variables; - public override Dictionary Variables => _Variables; - public SubtitleExtractor() + _Variables = new Dictionary() { - _Variables = new Dictionary() - { - { "sub.FileName", "/path/to/subtitle.sub" } - }; - } + { "sub.FileName", "/path/to/subtitle.sub" } + }; + } - public override int Execute(NodeParameters args) + /// + /// Executes the subtitle extractor + /// + /// the node parameters + /// the output to call next + public override int Execute(NodeParameters args) + { + try { - try - { - VideoInfo videoInfo = GetVideoInfo(args); - if (videoInfo == null) - return -1; - - // ffmpeg -i input.mkv -map "0:m:language:eng" -map "-0:v" -map "-0:a" output.srt - var subTrack = videoInfo.SubtitleStreams?.Where(x => string.IsNullOrEmpty(Language) || x.Language?.ToLower() == Language.ToLower()).FirstOrDefault(); - if (subTrack == null) - { - args.Logger?.ILog("No subtitles found to extract"); - return 2; - } - - if (string.IsNullOrEmpty(OutputFile) == false) - { - OutputFile = args.ReplaceVariables(OutputFile, true); - } - else - { - var file = new FileInfo(args.FileName); - - - OutputFile = file.FullName.Substring(0, file.FullName.LastIndexOf(file.Extension)); - } - OutputFile = args.MapPath(OutputFile); - - string extension = "srt"; - if (subTrack.Codec?.ToLower()?.Contains("pgs") == true) - extension = "sup"; - if (OutputFile.ToLower().EndsWith(".srt") || OutputFile.ToLower().EndsWith(".sup")) - OutputFile = OutputFile[0..^4]; - - OutputFile += "." + extension; - //bool textSubtitles = Regex.IsMatch(OutputFile, @"\.(sup)$") == false; - - - var extracted = ExtractSubtitle(args, FFMPEG, "0:s:" + subTrack.TypeIndex, OutputFile); - if(extracted) - { - args.UpdateVariables(new Dictionary - { - { "sub.FileName", OutputFile } - }); - if (SetWorkingFile) - args.SetWorkingFile(OutputFile, dontDelete: true); - - return 1; - } - + VideoInfo videoInfo = GetVideoInfo(args); + if (videoInfo == null) return -1; - } - catch (Exception ex) + + // ffmpeg -i input.mkv -map "0:m:language:eng" -map "-0:v" -map "-0:a" output.srt + var subTrack = videoInfo.SubtitleStreams?.Where(x => { - args.Logger?.ELog("Failed processing VideoFile: " + ex.Message); - return -1; - } - } - - internal bool ExtractSubtitle(NodeParameters args, string ffmpegExe, string subtitleStream, string output) - { - if (File.Exists(OutputFile)) - { - args.Logger?.ILog("File already exists, deleting file: " + OutputFile); - File.Delete(OutputFile); - } - - bool textSubtitles = Regex.IsMatch(OutputFile.ToLower(), @"\.(sup)$") == false; - // -y means it will overwrite a file if output already exists - var result = args.Process.ExecuteShellCommand(new ExecuteArgs - { - Command = ffmpegExe, - ArgumentList = textSubtitles ? - new[] { - - "-i", args.WorkingFile, - "-map", subtitleStream, - output - } : - new[] { - - "-i", args.WorkingFile, - "-map", subtitleStream, - "-c:s", "copy", - output - } - }).Result; - - var of = new FileInfo(OutputFile); - if (result.ExitCode != 0) - { - args.Logger?.ELog("FFMPEG process failed to extract subtitles"); - args.Logger?.ILog("Unexpected exit code: " + result.ExitCode); - args.Logger?.ILog(result.StandardOutput ?? String.Empty); - args.Logger?.ILog(result.StandardError ?? String.Empty); - if (of.Exists && of.Length == 0) - { - // delete the output file if it created an empty file - try - { - of.Delete(); - } - catch (Exception) { } - } + if (ForcedOnly && x.Forced == false) + return false; + if(string.IsNullOrEmpty(Language)) + return true; + if (x.Language?.ToLowerInvariant() == Language.ToLowerInvariant()) + return true; return false; + }).FirstOrDefault(); + if (subTrack == null) + { + args.Logger?.ILog("No subtitles found to extract"); + return 2; } - return of.Exists; + + if (string.IsNullOrEmpty(OutputFile) == false) + { + OutputFile = args.ReplaceVariables(OutputFile, true); + } + else + { + var file = new FileInfo(args.FileName); + + + OutputFile = file.FullName.Substring(0, file.FullName.LastIndexOf(file.Extension)); + } + OutputFile = args.MapPath(OutputFile); + + string extension = "srt"; + if (subTrack.Codec?.ToLower()?.Contains("pgs") == true) + extension = "sup"; + if (OutputFile.ToLower().EndsWith(".srt") || OutputFile.ToLower().EndsWith(".sup")) + OutputFile = OutputFile[0..^4]; + + OutputFile += "." + extension; + + var extracted = ExtractSubtitle(args, FFMPEG, "0:s:" + subTrack.TypeIndex, OutputFile); + if(extracted) + { + args.UpdateVariables(new Dictionary + { + { "sub.FileName", OutputFile } + }); + if (SetWorkingFile) + args.SetWorkingFile(OutputFile, dontDelete: true); + + return 1; + } + + return -1; + } + catch (Exception ex) + { + args.Logger?.ELog("Failed processing VideoFile: " + ex.Message); + return -1; } } + + /// + /// Extracts a subtitle + /// + /// the node parameters + /// the FFmpeg executable + /// the FFmpeg subtitle stream to extract + /// the destination file of the extracted subtitle + /// if it was successful or not + internal bool ExtractSubtitle(NodeParameters args, string ffmpegExe, string subtitleStream, string output) + { + if (File.Exists(OutputFile)) + { + args.Logger?.ILog("File already exists, deleting file: " + OutputFile); + File.Delete(OutputFile); + } + + bool textSubtitles = Regex.IsMatch(OutputFile.ToLower(), @"\.(sup)$") == false; + // -y means it will overwrite a file if output already exists + var result = args.Process.ExecuteShellCommand(new ExecuteArgs + { + Command = ffmpegExe, + ArgumentList = textSubtitles ? + new[] { + + "-i", args.WorkingFile, + "-map", subtitleStream, + output + } : + new[] { + + "-i", args.WorkingFile, + "-map", subtitleStream, + "-c:s", "copy", + output + } + }).Result; + + var of = new FileInfo(OutputFile); + if (result.ExitCode != 0) + { + args.Logger?.ELog("FFMPEG process failed to extract subtitles"); + args.Logger?.ILog("Unexpected exit code: " + result.ExitCode); + args.Logger?.ILog(result.StandardOutput ?? String.Empty); + args.Logger?.ILog(result.StandardError ?? String.Empty); + if (of.Exists && of.Length == 0) + { + // delete the output file if it created an empty file + try + { + of.Delete(); + } + catch (Exception) { } + } + return false; + } + return of.Exists; + } }