diff --git a/BasicNodes/BasicNodes.csproj b/BasicNodes/BasicNodes.csproj index 234feee3..edb8d64c 100644 Binary files a/BasicNodes/BasicNodes.csproj and b/BasicNodes/BasicNodes.csproj differ diff --git a/BasicNodes/BasicNodes.en.json b/BasicNodes/BasicNodes.en.json index 6dfcd0f9..1a2e22d4 100644 --- a/BasicNodes/BasicNodes.en.json +++ b/BasicNodes/BasicNodes.en.json @@ -38,15 +38,15 @@ } }, "FileSize": { - "Description": "Checks if the file size matches the configured parameters.\n\nOutput 1: Matches\nOutput 2: Does not match", + "Description": "Checks if the file size matches the configured parameters. The values are in megabytes.\n\nOutput 1: Matches\nOutput 2: Does not match", "Fields": { "Comparison": "Comparison", "Lower": "Lower", "Lower-Suffix": "MB", - "Lower-Help": "The value it must bet greater than", + "Lower-Help": "The value it must be greater than this number of megabytes", "Upper": "Upper", "Upper-Suffix": "MB", - "Upper-Help": "The value it must be less than. Leave as 0 to not test the upper limit." + "Upper-Help": "The value it must be less than than this number of megabytes. Leave as 0 to not test the upper limit." } }, "Function": { @@ -82,11 +82,15 @@ "Pattern-Help": "A regular expression, using the C# specifciation for regular expressions." } }, - "RenameFile": { - "Description": "Renames the working file", + "Renamer": { + "Description": "Renames the working file.\nVariables can be used by entering the key '{' inside the Pattern field.", "Fields": { - "FileName": "File Name", - "FileName-Help": "The new filename" + "Pattern": "Pattern", + "Pattern-Help": "The pattern to use to rename the folder. Variables can be used, for example '{FileName}.{ext}'.", + "DestinationPath": "Destination Path", + "DestinationPath-Help": "If the file should be moved to a different directory.", + "LogOnly": "Log Only", + "LogOnly-Help": "Turn on if you just want to test this node without it actually renaming the file" } } } diff --git a/BasicNodes/File/Renamer.cs b/BasicNodes/File/Renamer.cs new file mode 100644 index 00000000..0a98715d --- /dev/null +++ b/BasicNodes/File/Renamer.cs @@ -0,0 +1,80 @@ +namespace FileFlows.BasicNodes.File +{ + using System.Text.RegularExpressions; + using FileFlows.Plugin; + using FileFlows.Plugin.Attributes; + + public class Renamer : Node + { + public override int Inputs => 1; + public override int Outputs => 1; + public override string Icon => "fas fa-font"; + + public string _Pattern = string.Empty; + + public override FlowElementType Type => FlowElementType.Process; + + [TextVariable(1)] + public string? Pattern + { + get => _Pattern; + set { _Pattern = value ?? ""; } + } + + private string _DestinationPath = string.Empty; + + [Folder(2)] + public string DestinationPath + { + get => _DestinationPath; + set { _DestinationPath = value ?? ""; } + } + + [Boolean(3)] + public bool LogOnly { get; set; } + + public override int Execute(NodeParameters args) + { + if(string.IsNullOrEmpty(Pattern)) + { + args.Logger?.ELog("No pattern specified"); + return -1; + } + + string newFile = Pattern; + // incase they set a linux path on windows or vice versa + newFile = newFile.Replace('\\', Path.DirectorySeparatorChar); + newFile = newFile.Replace('/', Path.DirectorySeparatorChar); + + if (args.Variables?.Any() == true) + { + { + foreach (string variable in args.Variables.Keys) + { + string strValue = args.Variables[variable]?.ToString() ?? ""; + newFile = ReplaceVariable(newFile, variable, strValue); + } + } + } + + string destFolder = DestinationPath; + if (string.IsNullOrEmpty(destFolder)) + destFolder = new FileInfo(args.WorkingFile).Directory?.FullName ?? ""; + + var dest = new FileInfo(Path.Combine(destFolder, newFile)); + + args.Logger?.ILog("Renaming file to: " + (string.IsNullOrEmpty(DestinationPath) ? "" : DestinationPath + Path.DirectorySeparatorChar) + newFile); + + + if (LogOnly) + return 1; + + return args.MoveFile(dest.FullName) ? 1 : -1; + } + + private string ReplaceVariable(string input, string variable, string value) + { + return Regex.Replace(input, @"{" + Regex.Escape(variable) + @"}", value, RegexOptions.IgnoreCase); + } + } +} diff --git a/BasicNodes/Tests/RenamerTests.cs b/BasicNodes/Tests/RenamerTests.cs new file mode 100644 index 00000000..d58ffa56 --- /dev/null +++ b/BasicNodes/Tests/RenamerTests.cs @@ -0,0 +1,40 @@ +#if(DEBUG) + +namespace BasicNodes.Tests +{ + using FileFlows.BasicNodes.File; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class RenamerTests + { + [TestMethod] + public void Renamer_Extension() + { + var args = new FileFlows.Plugin.NodeParameters(@"c:\test\testfile.mkv"); + var logger = new TestLogger(); + args.Logger = logger; + args.Variables = new Dictionary + { + { "mi.Title", "Ghostbusters" }, + { "mi.Year", 1984 }, + { "vi.Resolution", "1080P" } + }; + args.SetWorkingFile($@"c:\temp\{Guid.NewGuid()}.mkv", dontDelete: true); + + + Renamer node = new Renamer(); + node.Pattern = @"{mi.Title} ({mi.Year})\{mi.Title} [{vi.Resolution}].{ext}"; + node.LogOnly = true; + + var result = node.Execute(args); + Assert.AreEqual(1, result); + + string expectedShort = $"Ghostbusters (1984){Path.DirectorySeparatorChar}Ghostbusters [1080P].mkv"; + Assert.IsTrue(logger.Contains($"Renaming file to: " + expectedShort)); + } + + } +} + +#endif \ No newline at end of file diff --git a/BasicNodes/Tests/TestLogger.cs b/BasicNodes/Tests/TestLogger.cs index f300ec32..1543dc6f 100644 --- a/BasicNodes/Tests/TestLogger.cs +++ b/BasicNodes/Tests/TestLogger.cs @@ -1,4 +1,6 @@ -namespace BasicNodes.Tests +#if (DEBUG) + +namespace BasicNodes.Tests { using FileFlows.Plugin; using System; @@ -9,21 +11,37 @@ internal class TestLogger : ILogger { - public void DLog(params object[] args) + private List Messages = new List(); + + public void DLog(params object[] args) => Log("DBUG", args); + + public void ELog(params object[] args) => Log("ERRR", args); + + public void ILog(params object[] args) => Log("INFO", args); + + public void WLog(params object[] args) => Log("WARN", args); + + private void Log(string type, object[] args) { + if (args == null || args.Length == 0) + return; + string message = type + " -> " + + string.Join(", ", args.Select(x => + x == null ? "null" : + x.GetType().IsPrimitive || x is string ? x.ToString() : + System.Text.Json.JsonSerializer.Serialize(x))); + Messages.Add(message); } - public void ELog(params object[] args) - { - } - - public void ILog(params object[] args) - { - } - - public void WLog(params object[] args) + public bool Contains(string message) { + if (string.IsNullOrWhiteSpace(message)) + return false; + string log = string.Join(Environment.NewLine, Messages); + return log.Contains(message); } } } + +#endif \ No newline at end of file diff --git a/Builds/BasicNodes.zip b/Builds/BasicNodes.zip index ab046b5e..5bb55d69 100644 Binary files a/Builds/BasicNodes.zip and b/Builds/BasicNodes.zip differ diff --git a/Builds/MetaNodes.zip b/Builds/MetaNodes.zip index b941a156..2e08e253 100644 Binary files a/Builds/MetaNodes.zip and b/Builds/MetaNodes.zip differ diff --git a/Builds/VideoNodes.zip b/Builds/VideoNodes.zip index 45b12419..869d1de3 100644 Binary files a/Builds/VideoNodes.zip and b/Builds/VideoNodes.zip differ diff --git a/MetaNodes/MetaNodes.csproj b/MetaNodes/MetaNodes.csproj index c97fa416..1f683d32 100644 Binary files a/MetaNodes/MetaNodes.csproj and b/MetaNodes/MetaNodes.csproj differ diff --git a/MetaNodes/Tests/TestLogger.cs b/MetaNodes/Tests/TestLogger.cs index c3dbeb02..919d4524 100644 --- a/MetaNodes/Tests/TestLogger.cs +++ b/MetaNodes/Tests/TestLogger.cs @@ -1,4 +1,6 @@ -namespace BasicNodes.Tests +#if(DEBUG) + +namespace MetaNodes.Tests { using FileFlows.Plugin; using System; @@ -41,3 +43,5 @@ } } } + +#endif \ No newline at end of file diff --git a/MetaNodes/Tests/TheMovieDb/MovieLookupTests.cs b/MetaNodes/Tests/TheMovieDb/MovieLookupTests.cs index 20c8d81b..2b96cbb2 100644 --- a/MetaNodes/Tests/TheMovieDb/MovieLookupTests.cs +++ b/MetaNodes/Tests/TheMovieDb/MovieLookupTests.cs @@ -109,6 +109,23 @@ namespace MetaNodes.Tests.TheMovieDb Assert.AreEqual("Back to the Future Part II", mi.Title); Assert.AreEqual(1989, mi.ReleaseDate.Year); } + + [TestMethod] + public void MovieLookupTests_VariablesSet() + { + var args = new FileFlows.Plugin.NodeParameters(@"c:\test\Back To The Future (1989)\Jaws.mkv"); + args.Logger = new TestLogger(); + + MovieLookup ml = new MovieLookup(); + ml.UseFolderName = true; + + var result = ml.Execute(args); + Assert.AreEqual(1, result); + Assert.IsTrue(args.Variables.ContainsKey("mi.Title")); + Assert.IsTrue(args.Variables.ContainsKey("mi.Year")); + Assert.AreEqual("Back to the Future Part II", args.Variables["mi.Title"]); + Assert.AreEqual("1989", args.Variables["mi.Year"]); + } } } diff --git a/MetaNodes/Tests/TheMovieDb/MovieRenamerTests.cs b/MetaNodes/Tests/TheMovieDb/MovieRenamerTests.cs index cf7b3433..bb04489c 100644 --- a/MetaNodes/Tests/TheMovieDb/MovieRenamerTests.cs +++ b/MetaNodes/Tests/TheMovieDb/MovieRenamerTests.cs @@ -8,10 +8,10 @@ namespace MetaNodes.Tests.TheMovieDb using Microsoft.VisualStudio.TestTools.UnitTesting; [TestClass] - public class RenamerTests + public class MovieMovieRenamerTests { [TestMethod] - public void RenamerTests_File_TitleYearExt() + public void MovieRenamerTests_File_TitleYearExt() { var args = new FileFlows.Plugin.NodeParameters(@"c:\test\Ghostbusters.mkv"); var logger = new TestLogger(); @@ -22,7 +22,7 @@ namespace MetaNodes.Tests.TheMovieDb ReleaseDate = new DateTime(1989, 5, 5) }); - Renamer node = new Renamer(); + MovieRenamer node = new MovieRenamer(); node.Pattern = "{Title} ({Year}).{ext}"; node.LogOnly = true; @@ -33,7 +33,7 @@ namespace MetaNodes.Tests.TheMovieDb } [TestMethod] - public void RenamerTests_File_TitleExt() + public void MovieRenamerTests_File_TitleExt() { var args = new FileFlows.Plugin.NodeParameters(@"c:\test\Ghostbusters.mkv"); var logger = new TestLogger(); @@ -44,7 +44,7 @@ namespace MetaNodes.Tests.TheMovieDb ReleaseDate = new DateTime(1989, 5, 5) }); - Renamer node = new Renamer(); + MovieRenamer node = new MovieRenamer(); node.Pattern = "{Title}.{ext}"; node.LogOnly = true; @@ -55,7 +55,7 @@ namespace MetaNodes.Tests.TheMovieDb } [TestMethod] - public void RenamerTests_Folder_TitleYear_Windows() + public void MovieRenamerTests_Folder_TitleYear_Windows() { var args = new FileFlows.Plugin.NodeParameters(@"c:\test\Ghostbusters.mkv"); var logger = new TestLogger(); @@ -66,7 +66,7 @@ namespace MetaNodes.Tests.TheMovieDb ReleaseDate = new DateTime(1989, 5, 5) }); - Renamer node = new Renamer(); + MovieRenamer node = new MovieRenamer(); node.Pattern = @"{Title} ({Year})\{Title}.{ext}"; node.LogOnly = true; @@ -77,7 +77,7 @@ namespace MetaNodes.Tests.TheMovieDb } [TestMethod] - public void RenamerTests_Folder_TitleYear_Linux() + public void MovieRenamerTests_Folder_TitleYear_Linux() { var args = new FileFlows.Plugin.NodeParameters(@"c:\test\Ghostbusters.mkv"); var logger = new TestLogger(); @@ -88,7 +88,7 @@ namespace MetaNodes.Tests.TheMovieDb ReleaseDate = new DateTime(1989, 5, 5) }); - Renamer node = new Renamer(); + MovieRenamer node = new MovieRenamer(); node.Pattern = @"{Title} ({Year})/{Title}.{ext}"; node.LogOnly = true; @@ -99,7 +99,7 @@ namespace MetaNodes.Tests.TheMovieDb } [TestMethod] - public void RenamerTests_Folder_TitleYear_MoveActual() + public void MovieRenamerTests_Folder_TitleYear_MoveActual() { string tempFile = Path.Combine(Path.GetTempPath(), Guid.NewGuid() + ".mkv"); string path = new FileInfo(tempFile).DirectoryName; @@ -114,7 +114,7 @@ namespace MetaNodes.Tests.TheMovieDb ReleaseDate = new DateTime(1989, 5, 5) }); - Renamer node = new Renamer(); + MovieRenamer node = new MovieRenamer(); node.Pattern = @"{Title} ({Year})/{Title}.{ext}"; var result = node.Execute(args); diff --git a/MetaNodes/TheMovieDb/MovieLookup.cs b/MetaNodes/TheMovieDb/MovieLookup.cs index 8647733c..10ab4286 100644 --- a/MetaNodes/TheMovieDb/MovieLookup.cs +++ b/MetaNodes/TheMovieDb/MovieLookup.cs @@ -11,8 +11,24 @@ { public override int Inputs => 1; public override int Outputs => 2; + + public override FlowElementType Type => FlowElementType.Logic; + public override string Icon => "fas fa-film"; + private Dictionary _Variables; + + public override Dictionary Variables => _Variables; + + public MovieLookup() + { + _Variables = new Dictionary() + { + { "mi.Title", string.Empty }, + {"mi.Year", string.Empty } + }; + } + [Boolean(1)] public bool UseFolderName { get; set; } @@ -97,6 +113,11 @@ args.SetParameter(Globals.MOVIE_INFO, result); + Variables["mi.Title"] = result.Title; + Variables["mi.Year"] = result.ReleaseDate.Year.ToString(); + + args.UpdateVariables(Variables); + return 1; } diff --git a/MetaNodes/TheMovieDb/MovieRenamer.cs b/MetaNodes/TheMovieDb/MovieRenamer.cs index 2efbbed8..fa3ffb95 100644 --- a/MetaNodes/TheMovieDb/MovieRenamer.cs +++ b/MetaNodes/TheMovieDb/MovieRenamer.cs @@ -12,10 +12,11 @@ public override int Inputs => 1; public override int Outputs => 1; public override string Icon => "fas fa-font"; + public override FlowElementType Type => FlowElementType.Process; public string _Pattern = string.Empty; - [Text(1)] + [TextVariable(1)] public string? Pattern { get => _Pattern; diff --git a/Plugin.pdb b/Plugin.pdb deleted file mode 100644 index 11726bb1..00000000 Binary files a/Plugin.pdb and /dev/null differ diff --git a/VideoNodes/EncodingNode.cs b/VideoNodes/EncodingNode.cs index e62af9a2..ba75757b 100644 --- a/VideoNodes/EncodingNode.cs +++ b/VideoNodes/EncodingNode.cs @@ -33,7 +33,15 @@ namespace FileFlows.VideoNodes bool success = Encoder.Encode(args.WorkingFile, outputFile, ffmpegParameters); args.Logger.ILog("Encoding succesful: " + success); if (success) + { args.SetWorkingFile(outputFile); + + // get the new video info + var videoInfo = new VideoInfoHelper(ffmpegExe, args.Logger).Read(outputFile); + var newVariables = new Dictionary(); + SetVideoInfo(args, videoInfo, newVariables); + args.UpdateVariables(newVariables); + } Encoder.AtTime -= AtTimeEvent; Encoder = null; return success; diff --git a/VideoNodes/ExtensionMethods.cs b/VideoNodes/ExtensionMethods.cs new file mode 100644 index 00000000..01f63af9 --- /dev/null +++ b/VideoNodes/ExtensionMethods.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace VideoNodes +{ + internal static class ExtensionMethods + { + public static void AddOrUpdate(this Dictionary dict, string key, object value) { + if (dict.ContainsKey(key)) + dict[key] = value; + else + dict.Add(key, value); + } + } +} diff --git a/VideoNodes/VideoFile.cs b/VideoNodes/VideoFile.cs index 80a375ed..1372e0dc 100644 --- a/VideoNodes/VideoFile.cs +++ b/VideoNodes/VideoFile.cs @@ -9,6 +9,21 @@ namespace FileFlows.VideoNodes public override int Outputs => 1; public override FlowElementType Type => FlowElementType.Input; + private Dictionary _Variables; + public override Dictionary Variables => _Variables; + public VideoFile() + { + _Variables = new Dictionary() + { + { "vi.VideoCodec", string.Empty }, + { "vi.AudioCodec", string.Empty }, + { "vi.AudioCodecs", string.Empty }, + { "vi.AudioLanguage", string.Empty }, + { "vi.AudioLanguages", string.Empty }, + { "vi.Resolution", string.Empty }, + }; + } + public override int Execute(NodeParameters args) { string ffmpegExe = GetFFMpegExe(args); @@ -36,7 +51,30 @@ namespace FileFlows.VideoNodes args.Logger.ILog($"Audio stream '{vs.Codec}' '{vs.Index}' '{vs.Language}"); } - SetVideoInfo(args, videoInfo); + SetVideoInfo(args, videoInfo, Variables); + + Variables["vi.VideoCodec"] = videoInfo.VideoStreams[0].Codec; + if (videoInfo.AudioStreams?.Any() == true) + { + ; + if (string.IsNullOrEmpty(videoInfo.AudioStreams[0].Codec)) + Variables["vi.AudioCodec"] = videoInfo.AudioStreams[0].Codec; + if(string.IsNullOrEmpty(videoInfo.AudioStreams[0].Language)) + Variables["vi.AudioLanguage"] = videoInfo.AudioStreams[0].Language; + Variables["vi.AudioCodecs"] = string.Join(", ", videoInfo.AudioStreams.Select(x => x.Codec).Where(x => string.IsNullOrEmpty(x) == false)); + Variables["vi.AudioLanguages"] = string.Join(", ", videoInfo.AudioStreams.Select(x => x.Language).Where(x => string.IsNullOrEmpty(x) == false)); + } + + if (videoInfo.VideoStreams[0].Width == 1920) + Variables["vi.Resolution"] = "1080P"; + else if (videoInfo.VideoStreams[0].Width == 3840) + Variables["vi.Resolution"] = "4k"; + else if (videoInfo.VideoStreams[0].Width == 1280) + Variables["vi.Resolution"] = "720p"; + else if (videoInfo.VideoStreams[0].Width < 1280) + Variables["vi.Resolution"] = "SD"; + else + Variables["vi.Resolution"] = videoInfo.VideoStreams[0].Width + "x" + videoInfo.VideoStreams[0].Height; return 1; } diff --git a/VideoNodes/VideoNode.cs b/VideoNodes/VideoNode.cs index 8bd8df80..fc64c5b8 100644 --- a/VideoNodes/VideoNode.cs +++ b/VideoNodes/VideoNode.cs @@ -1,6 +1,8 @@ namespace FileFlows.VideoNodes { using FileFlows.Plugin; + using global::VideoNodes; + public abstract class VideoNode : Node { public override string Icon => "fas fa-video"; @@ -63,13 +65,37 @@ namespace FileFlows.VideoNodes } private const string VIDEO_INFO = "VideoInfo"; - protected void SetVideoInfo(NodeParameters args, VideoInfo info) + protected void SetVideoInfo(NodeParameters args, VideoInfo videoInfo, Dictionary variables) { if (args.Parameters.ContainsKey(VIDEO_INFO)) - args.Parameters[VIDEO_INFO] = info; + args.Parameters[VIDEO_INFO] = videoInfo; else - args.Parameters.Add(VIDEO_INFO, info); + args.Parameters.Add(VIDEO_INFO, videoInfo); + + variables.AddOrUpdate("vi.VideoCodec", videoInfo.VideoStreams[0].Codec); + if (videoInfo.AudioStreams?.Any() == true) + { + ; + if (string.IsNullOrEmpty(videoInfo.AudioStreams[0].Codec)) + Variables.AddOrUpdate("vi.AudioCodec", videoInfo.AudioStreams[0].Codec); + if (string.IsNullOrEmpty(videoInfo.AudioStreams[0].Language)) + Variables.AddOrUpdate("vi.AudioLanguage", videoInfo.AudioStreams[0].Language); + Variables.AddOrUpdate("vi.AudioCodecs", string.Join(", ", videoInfo.AudioStreams.Select(x => x.Codec).Where(x => string.IsNullOrEmpty(x) == false))); + Variables.AddOrUpdate("vi.AudioLanguages", string.Join(", ", videoInfo.AudioStreams.Select(x => x.Language).Where(x => string.IsNullOrEmpty(x) == false))); + } + + if (videoInfo.VideoStreams[0].Width == 1920) + Variables.AddOrUpdate("vi.Resolution", "1080P"); + else if (videoInfo.VideoStreams[0].Width == 3840) + Variables.AddOrUpdate("vi.Resolution", "4k"); + else if (videoInfo.VideoStreams[0].Width == 1280) + Variables.AddOrUpdate("vi.Resolution", "720P"); + else if (videoInfo.VideoStreams[0].Width < 1280) + Variables.AddOrUpdate("vi.Resolution", "SD"); + else + Variables.AddOrUpdate("vi.Resolution", videoInfo.VideoStreams[0].Width + "x" + videoInfo.VideoStreams[0].Height); } + protected VideoInfo GetVideoInfo(NodeParameters args) { if (args.Parameters.ContainsKey(VIDEO_INFO) == false) diff --git a/VideoNodes/VideoNodes.csproj b/VideoNodes/VideoNodes.csproj index a966e680..6931c5e1 100644 Binary files a/VideoNodes/VideoNodes.csproj and b/VideoNodes/VideoNodes.csproj differ diff --git a/plugins.json b/plugins.json index fa4884f6..8a96d6b0 100644 --- a/plugins.json +++ b/plugins.json @@ -1,17 +1,17 @@ [ { "Name": "BasicNodes", - "Version": "0.0.1.9", + "Version": "0.0.1.10", "Package": "https://github.com/revenz/FileFlowsPlugins/blob/master/Builds/BasicNodes.zip?raw=true" }, { "Name": "MetaNodes", - "Version": "0.0.1.9", + "Version": "0.0.1.10", "Package": "https://github.com/revenz/FileFlowsPlugins/blob/master/Builds/MetaNodes.zip?raw=true" }, { "Name": "VideoNodes", - "Version": "0.0.1.9", + "Version": "0.0.1.10", "Package": "https://github.com/revenz/FileFlowsPlugins/blob/master/Builds/VideoNodes.zip?raw=true" } ]