diff --git a/.gitignore b/.gitignore index a56597c8..d80bc510 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,4 @@ Emby/settings.invalid.json Emby/settings.json VideoNodes/test.settings.dev.json Apprise/settings.json +build/utils/PluginInfoGenerator/ diff --git a/VideoNodes/LogicalNodes/VideoHasStream.cs b/VideoNodes/LogicalNodes/VideoHasStream.cs index 9c0c0254..18b2245a 100644 --- a/VideoNodes/LogicalNodes/VideoHasStream.cs +++ b/VideoNodes/LogicalNodes/VideoHasStream.cs @@ -1,120 +1,133 @@ -namespace FileFlows.VideoNodes +namespace FileFlows.VideoNodes; + +using System.Linq; +using System.ComponentModel; +using FileFlows.Plugin; +using FileFlows.Plugin.Attributes; +using System.ComponentModel.DataAnnotations; + +public class VideoHasStream : VideoNode { - using System.Linq; - using System.ComponentModel; - using FileFlows.Plugin; - using FileFlows.Plugin.Attributes; - using System.ComponentModel.DataAnnotations; + public override int Inputs => 1; + public override int Outputs => 2; + public override FlowElementType Type => FlowElementType.Logic; - public class VideoHasStream : VideoNode + [Select(nameof(StreamTypeOptions), 1)] + public string Stream { get; set; } + + private static List _StreamTypeOptions; + public static List StreamTypeOptions { - public override int Inputs => 1; - public override int Outputs => 2; - public override FlowElementType Type => FlowElementType.Logic; - - [Select(nameof(StreamTypeOptions), 1)] - public string Stream { get; set; } - - private static List _StreamTypeOptions; - public static List StreamTypeOptions + get { - get + if (_StreamTypeOptions == null) { - if (_StreamTypeOptions == null) + _StreamTypeOptions = new List { - _StreamTypeOptions = new List - { - new ListOption { Label = "Video", Value = "Video" }, - new ListOption { Label = "Audio", Value = "Audio" }, - new ListOption { Label = "Subtitle", Value = "Subtitle" } - }; - } - return _StreamTypeOptions; - } - } - - [TextVariable(2)] - public string Title { get; set; } - - [TextVariable(3)] - public string Codec { get; set; } - - [ConditionEquals(nameof(Stream), "Video", inverse: true)] - [TextVariable(4)] - public string Language { get; set; } - - [ConditionEquals(nameof(Stream), "Audio")] - [NumberInt(5)] - public float Channels { get; set; } - - public override int Execute(NodeParameters args) - { - var videoInfo = GetVideoInfo(args); - if (videoInfo == null) - return -1; - - bool found = false; - if (this.Stream == "Video") - { - found = videoInfo.VideoStreams.Where(x => - { - if (TitleMatches(x.Title)) - return false; - if (CodecMatches(x.Codec)) - return false; - return true; - }).Any(); - } - else if (this.Stream == "Audio") - { - found = videoInfo.AudioStreams.Where(x => - { - if (TitleMatches(x.Title)) - return false; - if (CodecMatches(x.Codec)) - return false; - if (LanguageMatches(x.Codec)) - return false; - if (this.Channels > 0 && Math.Abs(x.Channels - this.Channels) > 0.05f) - return false; - return true; - }).Any(); - } - else if (this.Stream == "Subtitle") - { - found = videoInfo.SubtitleStreams.Where(x => - { - if (TitleMatches(x.Title)) - return false; - if (CodecMatches(x.Codec)) - return false; - if (LanguageMatches(x.Codec)) - return false; - return true; - }).Any(); - } - - return found ? 1 : 2; - } - - private bool TitleMatches(string value) => ValueMatch(this.Title, value); - private bool CodecMatches(string value) => ValueMatch(this.Codec, value); - private bool LanguageMatches(string value) => ValueMatch(this.Language, value); - private bool ValueMatch(string pattern, string value) - { - if (string.IsNullOrWhiteSpace(pattern)) - return true; // no test, it matches - try - { - if (string.IsNullOrEmpty(value)) - return false; - var rgx = new Regex(pattern, RegexOptions.IgnoreCase); - return rgx.IsMatch(value); - } - catch (Exception) - { - return false; + new ListOption { Label = "Video", Value = "Video" }, + new ListOption { Label = "Audio", Value = "Audio" }, + new ListOption { Label = "Subtitle", Value = "Subtitle" } + }; } + return _StreamTypeOptions; } } -} \ No newline at end of file + + [TextVariable(2)] + public string Title { get; set; } + + [TextVariable(3)] + public string Codec { get; set; } + + [ConditionEquals(nameof(Stream), "Video", inverse: true)] + [TextVariable(4)] + public string Language { get; set; } + + [ConditionEquals(nameof(Stream), "Audio")] + [NumberInt(5)] + public float Channels { get; set; } + + public override int Execute(NodeParameters args) + { + var videoInfo = GetVideoInfo(args); + if (videoInfo == null) + return -1; + + bool found = false; + if (this.Stream == "Video") + { + found = videoInfo.VideoStreams.Where(x => + { + if (TitleMatches(x.Title) == MatchResult.NoMatch) + return false; + if (CodecMatches(x.Codec) == MatchResult.NoMatch) + return false; + return true; + }).Any(); + } + else if (this.Stream == "Audio") + { + found = videoInfo.AudioStreams.Where(x => + { + if (TitleMatches(x.Title) == MatchResult.NoMatch) + return false; + if (CodecMatches(x.Codec) == MatchResult.NoMatch) + return false; + if (LanguageMatches(x.Codec) == MatchResult.NoMatch) + return false; + if (this.Channels > 0 && Math.Abs(x.Channels - this.Channels) > 0.05f) + return false; + return true; + }).Any(); + } + else if (this.Stream == "Subtitle") + { + found = videoInfo.SubtitleStreams.Where(x => + { + if (TitleMatches(x.Title) == MatchResult.NoMatch) + return false; + if (CodecMatches(x.Codec) == MatchResult.NoMatch) + return false; + if (LanguageMatches(x.Codec) == MatchResult.NoMatch) + return false; + return true; + }).Any(); + } + + return found ? 1 : 2; + } + + private MatchResult TitleMatches(string value) => ValueMatch(this.Title, value); + private MatchResult CodecMatches(string value) => ValueMatch(this.Codec, value); + private MatchResult LanguageMatches(string value) => ValueMatch(this.Language, value); + private MatchResult ValueMatch(string pattern, string value) + { + if (string.IsNullOrWhiteSpace(pattern)) + return MatchResult.Skipped; + try + { + + if (string.IsNullOrEmpty(value)) + return MatchResult.NoMatch; + var rgx = new Regex(pattern, RegexOptions.IgnoreCase); + if(rgx.IsMatch(value)) + return MatchResult.Matched; + + if (value.ToLower() == "hevc" && pattern.ToLower() == "h265") + return MatchResult.Matched; // special case + + return MatchResult.NoMatch; + } + catch (Exception) + { + return MatchResult.NoMatch; + } + } + + private enum MatchResult + { + NoMatch = 0, + Matched = 1, + Skipped = 2 + } +} diff --git a/VideoNodes/Tests/VideoHasStreamTests.cs b/VideoNodes/Tests/VideoHasStreamTests.cs new file mode 100644 index 00000000..a59bbcea --- /dev/null +++ b/VideoNodes/Tests/VideoHasStreamTests.cs @@ -0,0 +1,166 @@ +#if(DEBUG) + +namespace VideoNodes.Tests; + +using FileFlows.VideoNodes; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +[TestClass] +public class VideoHasStreamTests : TestBase +{ + [TestMethod] + public void VideoHasStream_Video_H264() + { + string file = TestFile_BasicMkv; + var vi = new VideoInfoHelper(FfmpegPath, new TestLogger()); + var vii = vi.Read(file); + + VideoHasStream node = new(); + node.Codec = "h264"; + node.Stream = "Video"; + + var args = new NodeParameters(file, new TestLogger(), false, string.Empty); + args.GetToolPathActual = (string tool) => FfmpegPath; + args.TempPath = TempPath; + + var vf = new VideoFile(); + vf.PreExecute(args); + Assert.AreEqual(1, vf.Execute(args)); + + int output = node.Execute(args); + + Assert.AreEqual(1, output); + } + + [TestMethod] + public void VideoHasStream_Video_H265() + { + string file = TestFile_120_mbps_4k_uhd_hevc_10bit; + var vi = new VideoInfoHelper(FfmpegPath, new TestLogger()); + var vii = vi.Read(file); + + VideoHasStream node = new(); + node.Codec = "h265"; + node.Stream = "Video"; + + var args = new NodeParameters(file, new TestLogger(), false, string.Empty); + args.GetToolPathActual = (string tool) => FfmpegPath; + args.TempPath = TempPath; + + var vf = new VideoFile(); + vf.PreExecute(args); + Assert.AreEqual(1, vf.Execute(args)); + + int output = node.Execute(args); + + Assert.AreEqual(1, output); + } + + [TestMethod] + public void VideoHasStream_Video_Hevc() + { + string file = TestFile_120_mbps_4k_uhd_hevc_10bit; + var vi = new VideoInfoHelper(FfmpegPath, new TestLogger()); + var vii = vi.Read(file); + + VideoHasStream node = new(); + node.Codec = "h265"; + node.Stream = "Video"; + + var args = new NodeParameters(file, new TestLogger(), false, string.Empty); + args.GetToolPathActual = (string tool) => FfmpegPath; + args.TempPath = TempPath; + + var vf = new VideoFile(); + vf.PreExecute(args); + Assert.AreEqual(1, vf.Execute(args)); + + int output = node.Execute(args); + + Assert.AreEqual(1, output); + } + + + [TestMethod] + public void VideoHasStream_Audio_Vorbis() + { + string file = TestFile_BasicMkv; + var vi = new VideoInfoHelper(FfmpegPath, new TestLogger()); + var vii = vi.Read(file); + + VideoHasStream node = new(); + node.Codec = "vorbis"; + node.Stream = "Audio"; + + var args = new NodeParameters(file, new TestLogger(), false, string.Empty); + args.GetToolPathActual = (string tool) => FfmpegPath; + args.TempPath = TempPath; + + var vf = new VideoFile(); + vf.PreExecute(args); + Assert.AreEqual(1, vf.Execute(args)); + + int output = node.Execute(args); + + Assert.AreEqual(1, output); + } + + + + [TestMethod] + public void VideoHasStream_Audio_Channels_Pass() + { + string file = TestFile_BasicMkv; + var vi = new VideoInfoHelper(FfmpegPath, new TestLogger()); + var vii = vi.Read(file); + + VideoHasStream node = new(); + node.Channels = 5.1f; + node.Stream = "Audio"; + + var args = new NodeParameters(file, new TestLogger(), false, string.Empty); + args.GetToolPathActual = (string tool) => FfmpegPath; + args.TempPath = TempPath; + + var vf = new VideoFile(); + vf.PreExecute(args); + Assert.AreEqual(1, vf.Execute(args)); + + int output = node.Execute(args); + + Assert.AreEqual(1, output); + } + + + [TestMethod] + public void VideoHasStream_Audio_Channels_Fail() + { + string file = TestFile_BasicMkv; + var vi = new VideoInfoHelper(FfmpegPath, new TestLogger()); + var vii = vi.Read(file); + + VideoHasStream node = new(); + node.Channels = 2; + node.Stream = "Audio"; + + var args = new NodeParameters(file, new TestLogger(), false, string.Empty); + args.GetToolPathActual = (string tool) => FfmpegPath; + args.TempPath = TempPath; + + var vf = new VideoFile(); + vf.PreExecute(args); + Assert.AreEqual(1, vf.Execute(args)); + + int output = node.Execute(args); + + Assert.AreEqual(2, output); + } +} + + +#endif \ No newline at end of file