diff --git a/Apprise/Communication/Apprise.cs b/Apprise/Communication/Apprise.cs index e86b9f03..2595798f 100644 --- a/Apprise/Communication/Apprise.cs +++ b/Apprise/Communication/Apprise.cs @@ -29,7 +29,7 @@ public class Apprise: Node [Select(nameof(MessageTypeOptions), 2)] public string MessageType { get; set; } = string.Empty; - private static List _MessageTypeOptions; + private static List? _MessageTypeOptions; public static List MessageTypeOptions { get @@ -54,9 +54,9 @@ public class Apprise: Node /// [Required] [Template(3, nameof(MessageTemplates))] - public string Message { get; set; } + public string Message { get; set; } = string.Empty; - private static List _MessageTemplates; + private static List? _MessageTemplates; public static List MessageTemplates { get diff --git a/AudioNodes/AudioFormatInfo.cs b/AudioNodes/AudioFormatInfo.cs index 11c764d8..20440909 100644 --- a/AudioNodes/AudioFormatInfo.cs +++ b/AudioNodes/AudioFormatInfo.cs @@ -165,7 +165,7 @@ public class FFprobeTimeSpanConverter : JsonConverter { public override TimeSpan Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - string stringValue = reader.GetString(); + var stringValue = reader.GetString() ?? string.Empty; if (double.TryParse(stringValue, out double seconds) == false) return default; diff --git a/AudioNodes/Nodes/AudioFileNormalization.cs b/AudioNodes/Nodes/AudioFileNormalization.cs index 55efe340..42109af2 100644 --- a/AudioNodes/Nodes/AudioFileNormalization.cs +++ b/AudioNodes/Nodes/AudioFileNormalization.cs @@ -111,7 +111,9 @@ public class AudioFileNormalization : AudioNode args.Logger?.WLog("Failed to parse TwoPass json\""); return (false, string.Empty); } - LoudNormStats stats = JsonSerializer.Deserialize(json); + var stats = JsonSerializer.Deserialize(json); + if (stats == null) + return (false, string.Empty); string ar = $"loudnorm=print_format=summary:linear=true:{LOUDNORM_TARGET}:measured_I={stats.input_i}:measured_LRA={stats.input_lra}:measured_tp={stats.input_tp}:measured_thresh={stats.input_thresh}:offset={stats.target_offset}"; return (true, ar); } diff --git a/AudioNodes/Nodes/ConvertFlowElements/ConvertNode.cs b/AudioNodes/Nodes/ConvertFlowElements/ConvertNode.cs index 33bb8285..ef27cc05 100644 --- a/AudioNodes/Nodes/ConvertFlowElements/ConvertNode.cs +++ b/AudioNodes/Nodes/ConvertFlowElements/ConvertNode.cs @@ -314,10 +314,10 @@ namespace FileFlows.AudioNodes } - var ffArgs = GetArguments(args, out string extension); - string actualExt = args.ReplaceVariables(CustomExtension, stripMissing: true)?.EmptyAsNull() ?? - extension?.EmptyAsNull() ?? DefaultExtension; - string outputFile = FileHelper.Combine(args.TempPath, Guid.NewGuid() + "." + actualExt.TrimStart('.')); + var ffArgs = GetArguments(args, out var extension); + var actualExt = args.ReplaceVariables(CustomExtension, stripMissing: true)?.EmptyAsNull() ?? + extension?.EmptyAsNull() ?? DefaultExtension; + var outputFile = FileHelper.Combine(args.TempPath, Guid.NewGuid() + "." + actualExt.TrimStart('.')); ffArgs.Insert(0, "-hide_banner"); ffArgs.Insert(1, "-y"); // tells ffmpeg to replace the file if already exists, which it shouldnt but just incase diff --git a/DiscordNodes/Communication/Discord.cs b/DiscordNodes/Communication/Discord.cs index ebd2d53c..9aaa4e68 100644 --- a/DiscordNodes/Communication/Discord.cs +++ b/DiscordNodes/Communication/Discord.cs @@ -23,23 +23,23 @@ public class Discord: Node /// Gets or sets the title /// [TextVariable(1)] - public string Title { get; set; } + public string Title { get; set; } = string.Empty; /// /// Gets or sets the message type /// [DefaultValue("standard")] [Select(nameof(MessageTypeOptions), 2)] - public string MessageType { get; set; } + public string MessageType { get; set; } = "standard"; /// /// Gets or sets the message /// [Required] [Template(3, nameof(MessageTemplates))] - public string Message { get; set; } + public string Message { get; set; } = string.Empty; - private static List _MessageTypeOptions; + private static List? _MessageTypeOptions; public static List MessageTypeOptions { get @@ -60,7 +60,7 @@ public class Discord: Node } } - private static List _MessageTemplates; + private static List? _MessageTemplates; public static List MessageTemplates { get diff --git a/DiscordNodes/PluginSettings.cs b/DiscordNodes/PluginSettings.cs index 005cb43a..2f269f7c 100644 --- a/DiscordNodes/PluginSettings.cs +++ b/DiscordNodes/PluginSettings.cs @@ -10,12 +10,12 @@ public class PluginSettings:IPluginSettings /// [Text(1)] [Required] - public string WebhookId { get; set; } + public string WebhookId { get; set; } = string.Empty; /// /// Gets or sets the webhook token for this plugin /// [Text(2)] [Required] - public string WebhookToken { get; set; } + public string WebhookToken { get; set; } = string.Empty; } diff --git a/EmailNodes/PluginSettings.cs b/EmailNodes/PluginSettings.cs index e43fd6e2..164ad9dd 100644 --- a/EmailNodes/PluginSettings.cs +++ b/EmailNodes/PluginSettings.cs @@ -9,21 +9,21 @@ { [Required] [Text(1)] - public string SmtpServer { get; set; } + public string SmtpServer { get; set; } = string.Empty; [Range(1, 6555)] [NumberInt(1)] public int SmtpPort { get; set; } [Text(2)] - public string SmtpUsername { get; set; } + public string SmtpUsername { get; set; } = string.Empty; [Password(3)] - public string SmtpPassword { get; set; } + public string SmtpPassword { get; set; } = string.Empty; [Text(4)] [Required] [System.ComponentModel.DataAnnotations.RegularExpression(@"^\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$")] - public string Sender { get; set; } + public string Sender { get; set; } = string.Empty; } } diff --git a/Emby/MediaManagement/EmbyUpdater.cs b/Emby/MediaManagement/EmbyUpdater.cs index f7a59a76..5c187f35 100644 --- a/Emby/MediaManagement/EmbyUpdater.cs +++ b/Emby/MediaManagement/EmbyUpdater.cs @@ -17,10 +17,10 @@ public class EmbyUpdater: Node public override bool NoEditorOnAdd => true; [Text(1)] - public string ServerUrl { get; set; } + public string ServerUrl { get; set; } = string.Empty; [Text(2)] - public string AccessToken { get; set; } + public string AccessToken { get; set; } = string.Empty; [KeyValue(3, null)] public List> Mapping { get; set; } diff --git a/Emby/PluginSettings.cs b/Emby/PluginSettings.cs index f46c0b45..7eebbd3e 100644 --- a/Emby/PluginSettings.cs +++ b/Emby/PluginSettings.cs @@ -8,13 +8,13 @@ { [Text(1)] [Required] - public string ServerUrl { get; set; } + public string ServerUrl { get; set; } = string.Empty; [Text(2)] [Required] - public string AccessToken { get; set; } + public string AccessToken { get; set; } = string.Empty; [KeyValue(3, null)] - public List> Mapping { get; set; } + public List>? Mapping { get; set; } } } diff --git a/FileFlows.Plugin.dll b/FileFlows.Plugin.dll index bc913680..f7b57ae5 100644 Binary files a/FileFlows.Plugin.dll and b/FileFlows.Plugin.dll differ diff --git a/FileFlows.Plugin.pdb b/FileFlows.Plugin.pdb index 2e7c9914..0e5cf857 100644 Binary files a/FileFlows.Plugin.pdb and b/FileFlows.Plugin.pdb differ diff --git a/Gotify/Communication/Gotify.cs b/Gotify/Communication/Gotify.cs index de7c41ff..43438618 100644 --- a/Gotify/Communication/Gotify.cs +++ b/Gotify/Communication/Gotify.cs @@ -1,4 +1,5 @@ using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; using System.Text.Json; namespace FileFlows.Gotify.Communication; @@ -42,7 +43,7 @@ public class Gotify: Node /// Gets or sets the title of the message /// [TextVariable(1)] - public string Title { get; set; } + public string Title { get; set; } = string.Empty; /// @@ -58,9 +59,9 @@ public class Gotify: Node /// [Required] [Template(3, nameof(MessageTemplates))] - public string Message { get; set; } + public string Message { get; set; } = string.Empty; - private static List _MessageTemplates; + private static List? _MessageTemplates; public static List MessageTemplates { get @@ -96,6 +97,7 @@ File shrunk in size by: {{ difference | file_size }} / {{ percent }}% /// /// the node parameters /// the output to call next + [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "")] public override int Execute(NodeParameters args) { try diff --git a/Gotify/PluginSettings.cs b/Gotify/PluginSettings.cs index 73d8a105..a3ce8e44 100644 --- a/Gotify/PluginSettings.cs +++ b/Gotify/PluginSettings.cs @@ -10,12 +10,12 @@ public class PluginSettings : IPluginSettings /// [Text(1)] [Required] - public string ServerUrl { get; set; } + public string ServerUrl { get; set; } = string.Empty; /// /// Gets or sets the Access Token for the server /// [Text(2)] [Required] - public string AccessToken { get; set; } + public string AccessToken { get; set; } = string.Empty; } diff --git a/Plex/PluginSettings.cs b/Plex/PluginSettings.cs index f4ebec41..97013d21 100644 --- a/Plex/PluginSettings.cs +++ b/Plex/PluginSettings.cs @@ -9,13 +9,13 @@ { [Text(1)] [Required] - public string ServerUrl { get; set; } + public string ServerUrl { get; set; } = string.Empty; [Text(2)] [Required] - public string AccessToken { get; set; } + public string AccessToken { get; set; } = string.Empty; - [KeyValue(3, null)] - public List> Mapping { get; set; } + [KeyValue(3, null)] + public List> Mapping { get; set; } = new(); } } diff --git a/Pushbullet/Communication/Pushbullet.cs b/Pushbullet/Communication/Pushbullet.cs index 159ca0b8..35714e07 100644 --- a/Pushbullet/Communication/Pushbullet.cs +++ b/Pushbullet/Communication/Pushbullet.cs @@ -42,16 +42,16 @@ public class Pushbullet: Node /// [TextVariable(1)] [Required] - public string Title { get; set; } + public string Title { get; set; } = string.Empty; /// /// Gets or sets the message /// [Required] [Template(2, nameof(MessageTemplates))] - public string Message { get; set; } + public string Message { get; set; } = string.Empty; - private static List _MessageTemplates; + private static List? _MessageTemplates; /// /// Gets a list of message templates /// diff --git a/Pushbullet/PluginSettings.cs b/Pushbullet/PluginSettings.cs index ecc9d7f0..46b86d6b 100644 --- a/Pushbullet/PluginSettings.cs +++ b/Pushbullet/PluginSettings.cs @@ -10,5 +10,5 @@ public class PluginSettings : IPluginSettings /// [Text(2)] [Required] - public string ApiToken { get; set; } + public string ApiToken { get; set; } = string.Empty; } diff --git a/Pushover/Communication/Pushover.cs b/Pushover/Communication/Pushover.cs index 75e4b2b3..e2c3af82 100644 --- a/Pushover/Communication/Pushover.cs +++ b/Pushover/Communication/Pushover.cs @@ -62,7 +62,7 @@ public class Pushover: Node [Range(30, 86400)] public int Retry { get; set; } = 2; - private static List _Priorities; + private static List? _Priorities; /// /// Gets a list of message templates /// @@ -83,16 +83,16 @@ public class Pushover: Node return _Priorities; } } - - + + /// /// Gets or sets the message /// [Required] [Template(3, nameof(MessageTemplates))] - public string Message { get; set; } + public string Message { get; set; } = string.Empty; - private static List _MessageTemplates; + private static List? _MessageTemplates; /// /// Gets a list of message templates /// diff --git a/Pushover/PluginSettings.cs b/Pushover/PluginSettings.cs index 8c0d720c..c5b14a9f 100644 --- a/Pushover/PluginSettings.cs +++ b/Pushover/PluginSettings.cs @@ -10,12 +10,12 @@ public class PluginSettings : IPluginSettings /// [Text(1)] [Required] - public string UserKey { get; set; } + public string UserKey { get; set; } = string.Empty; /// /// Gets or sets the API Token /// [Text(2)] [Required] - public string ApiToken { get; set; } + public string ApiToken { get; set; } = string.Empty; } diff --git a/Telegram/Communication/Telegram.cs b/Telegram/Communication/Telegram.cs index 0d8f82f4..0a9dc13a 100644 --- a/Telegram/Communication/Telegram.cs +++ b/Telegram/Communication/Telegram.cs @@ -39,9 +39,9 @@ public class Telegram: Node /// [Required] [Template(1, nameof(MessageTemplates))] - public string Message { get; set; } + public string Message { get; set; } = string.Empty; - private static List _MessageTemplates; + private static List? _MessageTemplates; public static List MessageTemplates { get diff --git a/Telegram/PluginSettings.cs b/Telegram/PluginSettings.cs index 930d3f34..d9c7075c 100644 --- a/Telegram/PluginSettings.cs +++ b/Telegram/PluginSettings.cs @@ -10,12 +10,12 @@ public class PluginSettings : IPluginSettings /// [Text(1)] [Required] - public string BotToken { get; set; } + public string BotToken { get; set; } = string.Empty; /// /// Gets or sets the chat ID /// [Text(1)] [Required] - public string ChatId { get; set; } + public string ChatId { get; set; } = string.Empty; } \ No newline at end of file diff --git a/VideoNodes/FfmpegBuilderNodes/Video/FfmpegBuilderCropBlackBars.cs b/VideoNodes/FfmpegBuilderNodes/Video/FfmpegBuilderCropBlackBars.cs index 73251d7a..ec3c0acb 100644 --- a/VideoNodes/FfmpegBuilderNodes/Video/FfmpegBuilderCropBlackBars.cs +++ b/VideoNodes/FfmpegBuilderNodes/Video/FfmpegBuilderCropBlackBars.cs @@ -1,4 +1,5 @@ using System.Diagnostics; +using System.IO; namespace FileFlows.VideoNodes.FfmpegBuilderNodes; public class FfmpegBuilderCropBlackBars : FfmpegBuilderNode @@ -77,7 +78,7 @@ public class FfmpegBuilderCropBlackBars : FfmpegBuilderNode }; } - string Execute(string ffplay, string file, NodeParameters args, int vidWidth, int vidHeight, int threshold, int seconds) + string Execute(string ffmpeg, string file, NodeParameters args, int vidWidth, int vidHeight, int threshold, int seconds) { try { @@ -88,17 +89,19 @@ public class FfmpegBuilderCropBlackBars : FfmpegBuilderNode var intervals = GetTimeIntervals(seconds); if (intervals.Length == 0) return string.Empty; - foreach (int ss in intervals) // check at multiple times + foreach (int ss in intervals) // check at multiple times { + int intervalX = 0, intervalY = 0, intervalWidth = 0, intervalHeight = 0; using (var process = new Process()) { process.StartInfo = new ProcessStartInfo(); - process.StartInfo.FileName = ffplay; + process.StartInfo.FileName = ffmpeg; process.StartInfo.UseShellExecute = false; process.StartInfo.RedirectStandardOutput = true; process.StartInfo.RedirectStandardError = true; process.StartInfo.CreateNoWindow = true; - process.StartInfo.Arguments = $" -ss {ss} -i \"{file}\" -hide_banner -vframes 25 -vf cropdetect -f null -"; + process.StartInfo.Arguments = + $" -ss {ss} -i \"{file}\" -hide_banner -vframes 25 -vf cropdetect -f null -"; args.Logger?.DLog("Executing ffmpeg " + process.StartInfo.Arguments); process.Start(); string output = process.StandardError.ReadToEnd(); @@ -106,7 +109,8 @@ public class FfmpegBuilderCropBlackBars : FfmpegBuilderNode string error = process.StandardError.ReadToEnd(); process.WaitForExit(); - var dimMatch = Regex.Match(output, @"Stream #[\d]+:[\d]+(.*?)Video:(.*?)([\d]+)x([\d]+)", RegexOptions.Multiline); + var dimMatch = Regex.Match(output, @"Stream #[\d]+:[\d]+(.*?)Video:(.*?)([\d]+)x([\d]+)", + RegexOptions.Multiline); if (dimMatch.Success == false) { args.Logger?.WLog("Can't find dimensions for video"); @@ -117,12 +121,24 @@ public class FfmpegBuilderCropBlackBars : FfmpegBuilderNode foreach (Match match in matches) { int[] parts = match.Value.Split(':').Select(x => int.Parse(x)).ToArray(); + intervalX = parts[2]; + intervalY = parts[3]; + intervalWidth = parts[0]; + intervalHeight = parts[1]; x = Math.Min(x, parts[2]); y = Math.Min(y, parts[3]); width = Math.Max(width, parts[0]); height = Math.Max(height, parts[1]); } } + + string imgFile = Path.Combine(args.TempPath, Guid.NewGuid() + ".jpg"); + if (ExtractFrameWithFFmpeg(args, ffmpeg, file, ss, imgFile) && File.Exists(imgFile)) + { + // draw rectangle on frame + args.ImageHelper?.DrawRectangleOnImage(imgFile, intervalX, intervalY, intervalWidth, intervalHeight); + args.LogImage(imgFile); + } } if (width == 0 || height == 0) @@ -161,6 +177,63 @@ public class FfmpegBuilderCropBlackBars : FfmpegBuilderNode { int diff = (vidWidth - detectedWidth) + (vidHeight - detectedHeight); return (diff > threshold, diff); + } + + + /// + /// Extracts a frame from a video at a specified time, scales it to 640x480 resolution, and saves it as a JPEG image using FFmpeg. + /// + /// the node parameters + /// The path to the FFmpeg executable. + /// The input video file. + /// The time offset in seconds. + /// The output image file path. + /// True if the frame was extracted and saved successfully, otherwise false. + static bool ExtractFrameWithFFmpeg(NodeParameters args, string ffmpeg, string inputFile, int ss, string destination) + { try + { + // Create process start info + + // Create process start info + ProcessStartInfo psi = new ProcessStartInfo + { + FileName = ffmpeg, + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = true + }; + // Specify arguments using ArgumentList to avoid escaping issues + psi.ArgumentList.Add("-ss"); + psi.ArgumentList.Add(ss.ToString()); + psi.ArgumentList.Add("-i"); + psi.ArgumentList.Add(inputFile); + // psi.ArgumentList.Add("-vf"); + // psi.ArgumentList.Add("select=eq(n\\,0),scale=640:480"); + psi.ArgumentList.Add("-vframes"); + psi.ArgumentList.Add("1"); + psi.ArgumentList.Add(destination); + + // Start the process + using (Process process = Process.Start(psi)) + { + // Capture and display the output + string output = process.StandardOutput.ReadToEnd(); + string error = process.StandardError.ReadToEnd(); + + // Wait for the process to exit + process.WaitForExit(); + + if (process.ExitCode == 0) + return true; + args.Logger?.WLog($"Error extracting frame: {error}"); + return false; + } + } + catch (Exception ex) + { + args.Logger?.ELog($"An error occurred: {ex.Message}"); + return false; + } } } \ No newline at end of file