diff --git a/BasicNodes/Tools/Executor.cs b/BasicNodes/Tools/Executor.cs index ce768184..0ae8e114 100644 --- a/BasicNodes/Tools/Executor.cs +++ b/BasicNodes/Tools/Executor.cs @@ -3,6 +3,7 @@ using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.Diagnostics; + using System.Text.RegularExpressions; using FileFlows.Plugin; using FileFlows.Plugin.Attributes; @@ -13,6 +14,8 @@ public override FlowElementType Type => FlowElementType.Process; public override string Icon => "fas fa-terminal"; + private const string VariablePattern = "^([a-zA-Z_]+)[a-zA-Z_0-9]*$"; + [Required] [File(1)] public string FileName { get; set; } @@ -31,6 +34,13 @@ [NumberInt(5)] public int Timeout { get; set; } + [Text(6)] + [System.ComponentModel.DataAnnotations.RegularExpression(VariablePattern)] + public string OutputVariable { get; set; } + [Text(7)] + [System.ComponentModel.DataAnnotations.RegularExpression(VariablePattern)] + public string OutputErrorVariable { get; set; } + private NodeParameters args; public override async Task Cancel() @@ -58,6 +68,20 @@ return -1; } bool success = task.Result.ExitCode == this.SuccessCode; + if(Regex.IsMatch(OutputVariable ?? string.Empty, VariablePattern)) + { + args.UpdateVariables(new Dictionary + { + { OutputVariable, task.Result.StandardOutput } + }); + } + if (Regex.IsMatch(OutputErrorVariable ?? string.Empty, VariablePattern)) + { + args.UpdateVariables(new Dictionary + { + { OutputErrorVariable, task.Result.StandardError } + }); + } if (success) return 1; else diff --git a/VideoNodes/VideoNodes.en.json b/VideoNodes/VideoNodes.en.json index 00881dc7..01000e86 100644 --- a/VideoNodes/VideoNodes.en.json +++ b/VideoNodes/VideoNodes.en.json @@ -54,13 +54,13 @@ "Description": "A generic video encoding node, this lets you customize how to encode a video file using ffmpeg.\n\nOutput 1: Video was processed\nOutput 2: No processing required", "Fields": { "Extension": "Extension", - "Extension-Help": "The file extension to use on the newly created file", + "Extension-Help": "The file extension to use on the newly created file.", "VideoCodec": "Video Codec", - "VideoCodec-Help": "The video codec the video should be in, for example hevc, h264", + "VideoCodec-Help": "The video codec the video should be in, for example hevc, h264.\nIf left empty all original video tracks will be copied.", "VideoCodecParameters": "Video Codec Parameters", "VideoCodecParameters-Help": "The parameters to use to encode the video, eg. \"hevc_nvenc -preset hq -crf 23\" to encode into hevc using the HQ preset a constant rate factor of 23 and using NVIDIA hardware acceleration.", "AudioCodec": "Audio Codec", - "AudioCodec-Help": "The audio codec to encode the video with", + "AudioCodec-Help": "The audio codec to encode the video with.\nIf left empty all original audio tracks will be copied.", "Language": "Language", "Language-Help": "Optional ISO 639-2 language code to use. Will attempt to find an audio track with this language code if not the best audio track will be used.\nhttps://en.wikipedia.org/wiki/List_of_ISO_639-2_codes" } diff --git a/VideoNodes/VideoNodes/VideoEncode.cs b/VideoNodes/VideoNodes/VideoEncode.cs index bf1f30ae..1c3376b3 100644 --- a/VideoNodes/VideoNodes/VideoEncode.cs +++ b/VideoNodes/VideoNodes/VideoEncode.cs @@ -34,21 +34,21 @@ namespace FileFlows.VideoNodes public override int Execute(NodeParameters args) { - if (string.IsNullOrEmpty(VideoCodec)) + if (VideoCodec == "COPY") + VideoCodec = string.Empty; + if (AudioCodec == "COPY") + AudioCodec = string.Empty; + + if (string.IsNullOrEmpty(VideoCodec) && string.IsNullOrEmpty(AudioCodec)) { - args.Logger?.ELog("Video codec not set"); + args.Logger?.ELog("Video codec or Audio codec must be set"); return -1; } - if (string.IsNullOrEmpty(AudioCodec)) - { - args.Logger?.ELog("Audio codec not set"); - return -1; - } - VideoCodec = args.ReplaceVariables(VideoCodec); - VideoCodecParameters = args.ReplaceVariables(VideoCodecParameters); - AudioCodec = args.ReplaceVariables(AudioCodec); + + VideoCodec = args.ReplaceVariables(VideoCodec ?? string.Empty); + VideoCodecParameters = args.ReplaceVariables(VideoCodecParameters ?? string.Empty); + AudioCodec = args.ReplaceVariables(AudioCodec ?? string.Empty); Language = args.ReplaceVariables(Language); - Extension = args.ReplaceVariables(Extension)?.EmptyAsNull() ?? "mkv"; VideoCodec = VideoCodec.ToLower(); AudioCodec = AudioCodec.ToLower(); @@ -62,71 +62,87 @@ namespace FileFlows.VideoNodes Language = Language?.ToLower() ?? ""; - // ffmpeg is one based for stream index, so video should be 1, audio should be 2 - - var videoIsRightCodec = videoInfo.VideoStreams.FirstOrDefault(x => IsSameVideoCodec(x.Codec ?? string.Empty, VideoCodec)); - var videoTrack = videoIsRightCodec ?? videoInfo.VideoStreams[0]; - args.Logger?.ILog("Video: ", videoTrack); - - var bestAudio = videoInfo.AudioStreams.Where(x => System.Text.Json.JsonSerializer.Serialize(x).ToLower().Contains("commentary") == false) - .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 => x.Channels) - //.ThenBy(x => x.CodecName.ToLower() == "ac3" ? 0 : 1) // if we do this we can get commentary tracks... - .ThenBy(x => x.Index) - .FirstOrDefault(); - - bool audioRightCodec = bestAudio?.Codec?.ToLower() == AudioCodec && videoInfo.AudioStreams[0] == bestAudio; - args.Logger?.ILog("Best Audio: ", bestAudio == null ? "null" : (object)bestAudio); - - string crop = args.GetParameter(DetectBlackBars.CROP_KEY) ?? ""; - if (crop != string.Empty) - crop = " -vf crop=" + crop; - - if (audioRightCodec == true && videoIsRightCodec != null) - { - if (crop == string.Empty) - { - args.Logger?.DLog($"File is {VideoCodec} with the first audio track is {AudioCodec}"); - return 2; - } - else - { - args.Logger?.ILog($"Video is {VideoCodec} and audio is {AudioCodec} but needs to be cropped"); - } - } - string ffmpegExe = GetFFMpegExe(args); if (string.IsNullOrEmpty(ffmpegExe)) return -1; + // ffmpeg is one based for stream index, so video should be 1, audio should be 2 + + string encodeVideoParameters = string.Empty, encodeAudioParameters = string.Empty; + const string copyVideoStream = "-map 0:v -c:v copy"; + const string copyAudioStream = "-map 0:a -c:a copy"; + + if (string.IsNullOrEmpty(VideoCodec) == false) + { + + var videoIsRightCodec = videoInfo.VideoStreams.FirstOrDefault(x => IsSameVideoCodec(x.Codec ?? string.Empty, VideoCodec)); + var videoTrack = videoIsRightCodec ?? videoInfo.VideoStreams[0]; + args.Logger?.ILog("Video: ", videoTrack); + + string crop = args.GetParameter(DetectBlackBars.CROP_KEY) ?? ""; + if (crop != string.Empty) + crop = " -vf crop=" + crop; + + if (videoIsRightCodec == null || crop != string.Empty) + { + string codecParameters = CheckVideoCodec(ffmpegExe, VideoCodecParameters); + encodeVideoParameters = $"-map 0:v:0 -c:v {codecParameters} {crop}"; + } + Extension = args.ReplaceVariables(Extension)?.EmptyAsNull() ?? "mkv"; + } + else if(string.IsNullOrEmpty(Extension) == false) + { + // vidoe is being copied so use the same extension + Extension = new FileInfo(args.WorkingFile).Extension; + if(Extension.StartsWith(".")) + Extension = Extension.Substring(1); + } + + + if (string.IsNullOrEmpty(AudioCodec) == false) + { + + var bestAudio = videoInfo.AudioStreams.Where(x => System.Text.Json.JsonSerializer.Serialize(x).ToLower().Contains("commentary") == false) + .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 => x.Channels) + .ThenBy(x => x.Index) + .FirstOrDefault(); + + bool audioRightCodec = bestAudio?.Codec?.ToLower() == AudioCodec && videoInfo.AudioStreams[0] == bestAudio; + args.Logger?.ILog("Best Audio: ", bestAudio == null ? "null" : (object)bestAudio); + + + if (audioRightCodec == false) + encodeAudioParameters = $"-map 0:{bestAudio!.Index} -c:a {AudioCodec}"; + else if(videoInfo.AudioStreams.Count > 1) + encodeAudioParameters = $"-map 0:{bestAudio!.Index} -c:a copy"; + } + + if(string.IsNullOrEmpty(encodeVideoParameters) && string.IsNullOrEmpty(encodeAudioParameters)) + { + args.Logger?.ILog("Video and Audio does not need to be reencoded"); + return 2; + } + + List ffArgs = new List(); - if (videoIsRightCodec == null || crop != string.Empty) - { - string codecParameters = CheckVideoCodec(ffmpegExe, VideoCodecParameters); - ffArgs.Add($"-map 0:v:0 -c:v {codecParameters} {crop}"); - } - else - ffArgs.Add($"-map 0:v:0 -c:v copy"); + ffArgs.Add(encodeVideoParameters?.EmptyAsNull() ?? copyVideoStream); + ffArgs.Add(encodeAudioParameters?.EmptyAsNull() ?? copyAudioStream); TotalTime = videoInfo.VideoStreams[0].Duration; - if (audioRightCodec == false) - ffArgs.Add($"-map 0:{bestAudio!.Index} -c:a {AudioCodec}"); - else - ffArgs.Add($"-map 0:{bestAudio!.Index} -c:a copy"); - if (videoInfo?.SubtitleStreams?.Any() == true) { if (SupportsSubtitles(args, videoInfo, Extension))