mirror of
https://github.com/revenz/FileFlowsPlugins.git
synced 2026-05-08 08:39:41 -05:00
FF-1493: Added ability to save images to the file log
This commit is contained in:
@@ -29,7 +29,7 @@ public class Apprise: Node
|
|||||||
[Select(nameof(MessageTypeOptions), 2)]
|
[Select(nameof(MessageTypeOptions), 2)]
|
||||||
public string MessageType { get; set; } = string.Empty;
|
public string MessageType { get; set; } = string.Empty;
|
||||||
|
|
||||||
private static List<ListOption> _MessageTypeOptions;
|
private static List<ListOption>? _MessageTypeOptions;
|
||||||
public static List<ListOption> MessageTypeOptions
|
public static List<ListOption> MessageTypeOptions
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
@@ -54,9 +54,9 @@ public class Apprise: Node
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
[Required]
|
[Required]
|
||||||
[Template(3, nameof(MessageTemplates))]
|
[Template(3, nameof(MessageTemplates))]
|
||||||
public string Message { get; set; }
|
public string Message { get; set; } = string.Empty;
|
||||||
|
|
||||||
private static List<ListOption> _MessageTemplates;
|
private static List<ListOption>? _MessageTemplates;
|
||||||
public static List<ListOption> MessageTemplates
|
public static List<ListOption> MessageTemplates
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
|||||||
@@ -165,7 +165,7 @@ public class FFprobeTimeSpanConverter : JsonConverter<TimeSpan>
|
|||||||
{
|
{
|
||||||
public override TimeSpan Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
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)
|
if (double.TryParse(stringValue, out double seconds) == false)
|
||||||
return default;
|
return default;
|
||||||
|
|
||||||
|
|||||||
@@ -111,7 +111,9 @@ public class AudioFileNormalization : AudioNode
|
|||||||
args.Logger?.WLog("Failed to parse TwoPass json\"");
|
args.Logger?.WLog("Failed to parse TwoPass json\"");
|
||||||
return (false, string.Empty);
|
return (false, string.Empty);
|
||||||
}
|
}
|
||||||
LoudNormStats stats = JsonSerializer.Deserialize<LoudNormStats>(json);
|
var stats = JsonSerializer.Deserialize<LoudNormStats>(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}";
|
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);
|
return (true, ar);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -314,10 +314,10 @@ namespace FileFlows.AudioNodes
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
var ffArgs = GetArguments(args, out string extension);
|
var ffArgs = GetArguments(args, out var extension);
|
||||||
string actualExt = args.ReplaceVariables(CustomExtension, stripMissing: true)?.EmptyAsNull() ??
|
var actualExt = args.ReplaceVariables(CustomExtension, stripMissing: true)?.EmptyAsNull() ??
|
||||||
extension?.EmptyAsNull() ?? DefaultExtension;
|
extension?.EmptyAsNull() ?? DefaultExtension;
|
||||||
string outputFile = FileHelper.Combine(args.TempPath, Guid.NewGuid() + "." + actualExt.TrimStart('.'));
|
var outputFile = FileHelper.Combine(args.TempPath, Guid.NewGuid() + "." + actualExt.TrimStart('.'));
|
||||||
|
|
||||||
ffArgs.Insert(0, "-hide_banner");
|
ffArgs.Insert(0, "-hide_banner");
|
||||||
ffArgs.Insert(1, "-y"); // tells ffmpeg to replace the file if already exists, which it shouldnt but just incase
|
ffArgs.Insert(1, "-y"); // tells ffmpeg to replace the file if already exists, which it shouldnt but just incase
|
||||||
|
|||||||
@@ -23,23 +23,23 @@ public class Discord: Node
|
|||||||
/// Gets or sets the title
|
/// Gets or sets the title
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[TextVariable(1)]
|
[TextVariable(1)]
|
||||||
public string Title { get; set; }
|
public string Title { get; set; } = string.Empty;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the message type
|
/// Gets or sets the message type
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DefaultValue("standard")]
|
[DefaultValue("standard")]
|
||||||
[Select(nameof(MessageTypeOptions), 2)]
|
[Select(nameof(MessageTypeOptions), 2)]
|
||||||
public string MessageType { get; set; }
|
public string MessageType { get; set; } = "standard";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the message
|
/// Gets or sets the message
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Required]
|
[Required]
|
||||||
[Template(3, nameof(MessageTemplates))]
|
[Template(3, nameof(MessageTemplates))]
|
||||||
public string Message { get; set; }
|
public string Message { get; set; } = string.Empty;
|
||||||
|
|
||||||
private static List<ListOption> _MessageTypeOptions;
|
private static List<ListOption>? _MessageTypeOptions;
|
||||||
public static List<ListOption> MessageTypeOptions
|
public static List<ListOption> MessageTypeOptions
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
@@ -60,7 +60,7 @@ public class Discord: Node
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static List<ListOption> _MessageTemplates;
|
private static List<ListOption>? _MessageTemplates;
|
||||||
public static List<ListOption> MessageTemplates
|
public static List<ListOption> MessageTemplates
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
|||||||
@@ -10,12 +10,12 @@ public class PluginSettings:IPluginSettings
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
[Text(1)]
|
[Text(1)]
|
||||||
[Required]
|
[Required]
|
||||||
public string WebhookId { get; set; }
|
public string WebhookId { get; set; } = string.Empty;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the webhook token for this plugin
|
/// Gets or sets the webhook token for this plugin
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Text(2)]
|
[Text(2)]
|
||||||
[Required]
|
[Required]
|
||||||
public string WebhookToken { get; set; }
|
public string WebhookToken { get; set; } = string.Empty;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,21 +9,21 @@
|
|||||||
{
|
{
|
||||||
[Required]
|
[Required]
|
||||||
[Text(1)]
|
[Text(1)]
|
||||||
public string SmtpServer { get; set; }
|
public string SmtpServer { get; set; } = string.Empty;
|
||||||
|
|
||||||
[Range(1, 6555)]
|
[Range(1, 6555)]
|
||||||
[NumberInt(1)]
|
[NumberInt(1)]
|
||||||
public int SmtpPort { get; set; }
|
public int SmtpPort { get; set; }
|
||||||
|
|
||||||
[Text(2)]
|
[Text(2)]
|
||||||
public string SmtpUsername { get; set; }
|
public string SmtpUsername { get; set; } = string.Empty;
|
||||||
|
|
||||||
[Password(3)]
|
[Password(3)]
|
||||||
public string SmtpPassword { get; set; }
|
public string SmtpPassword { get; set; } = string.Empty;
|
||||||
|
|
||||||
[Text(4)]
|
[Text(4)]
|
||||||
[Required]
|
[Required]
|
||||||
[System.ComponentModel.DataAnnotations.RegularExpression(@"^\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$")]
|
[System.ComponentModel.DataAnnotations.RegularExpression(@"^\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$")]
|
||||||
public string Sender { get; set; }
|
public string Sender { get; set; } = string.Empty;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,10 +17,10 @@ public class EmbyUpdater: Node
|
|||||||
public override bool NoEditorOnAdd => true;
|
public override bool NoEditorOnAdd => true;
|
||||||
|
|
||||||
[Text(1)]
|
[Text(1)]
|
||||||
public string ServerUrl { get; set; }
|
public string ServerUrl { get; set; } = string.Empty;
|
||||||
|
|
||||||
[Text(2)]
|
[Text(2)]
|
||||||
public string AccessToken { get; set; }
|
public string AccessToken { get; set; } = string.Empty;
|
||||||
|
|
||||||
[KeyValue(3, null)]
|
[KeyValue(3, null)]
|
||||||
public List<KeyValuePair<string, string>> Mapping { get; set; }
|
public List<KeyValuePair<string, string>> Mapping { get; set; }
|
||||||
|
|||||||
@@ -8,13 +8,13 @@
|
|||||||
{
|
{
|
||||||
[Text(1)]
|
[Text(1)]
|
||||||
[Required]
|
[Required]
|
||||||
public string ServerUrl { get; set; }
|
public string ServerUrl { get; set; } = string.Empty;
|
||||||
|
|
||||||
[Text(2)]
|
[Text(2)]
|
||||||
[Required]
|
[Required]
|
||||||
public string AccessToken { get; set; }
|
public string AccessToken { get; set; } = string.Empty;
|
||||||
|
|
||||||
[KeyValue(3, null)]
|
[KeyValue(3, null)]
|
||||||
public List<KeyValuePair<string, string>> Mapping { get; set; }
|
public List<KeyValuePair<string, string>>? Mapping { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@@ -1,4 +1,5 @@
|
|||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
|
||||||
namespace FileFlows.Gotify.Communication;
|
namespace FileFlows.Gotify.Communication;
|
||||||
@@ -42,7 +43,7 @@ public class Gotify: Node
|
|||||||
/// Gets or sets the title of the message
|
/// Gets or sets the title of the message
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[TextVariable(1)]
|
[TextVariable(1)]
|
||||||
public string Title { get; set; }
|
public string Title { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -58,9 +59,9 @@ public class Gotify: Node
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
[Required]
|
[Required]
|
||||||
[Template(3, nameof(MessageTemplates))]
|
[Template(3, nameof(MessageTemplates))]
|
||||||
public string Message { get; set; }
|
public string Message { get; set; } = string.Empty;
|
||||||
|
|
||||||
private static List<ListOption> _MessageTemplates;
|
private static List<ListOption>? _MessageTemplates;
|
||||||
public static List<ListOption> MessageTemplates
|
public static List<ListOption> MessageTemplates
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
@@ -96,6 +97,7 @@ File shrunk in size by: {{ difference | file_size }} / {{ percent }}%
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="args">the node parameters</param>
|
/// <param name="args">the node parameters</param>
|
||||||
/// <returns>the output to call next</returns>
|
/// <returns>the output to call next</returns>
|
||||||
|
[UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "<Pending>")]
|
||||||
public override int Execute(NodeParameters args)
|
public override int Execute(NodeParameters args)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|||||||
@@ -10,12 +10,12 @@ public class PluginSettings : IPluginSettings
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
[Text(1)]
|
[Text(1)]
|
||||||
[Required]
|
[Required]
|
||||||
public string ServerUrl { get; set; }
|
public string ServerUrl { get; set; } = string.Empty;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the Access Token for the server
|
/// Gets or sets the Access Token for the server
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Text(2)]
|
[Text(2)]
|
||||||
[Required]
|
[Required]
|
||||||
public string AccessToken { get; set; }
|
public string AccessToken { get; set; } = string.Empty;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,13 +9,13 @@
|
|||||||
{
|
{
|
||||||
[Text(1)]
|
[Text(1)]
|
||||||
[Required]
|
[Required]
|
||||||
public string ServerUrl { get; set; }
|
public string ServerUrl { get; set; } = string.Empty;
|
||||||
|
|
||||||
[Text(2)]
|
[Text(2)]
|
||||||
[Required]
|
[Required]
|
||||||
public string AccessToken { get; set; }
|
public string AccessToken { get; set; } = string.Empty;
|
||||||
|
|
||||||
[KeyValue(3, null)]
|
[KeyValue(3, null)]
|
||||||
public List<KeyValuePair<string, string>> Mapping { get; set; }
|
public List<KeyValuePair<string, string>> Mapping { get; set; } = new();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,16 +42,16 @@ public class Pushbullet: Node
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
[TextVariable(1)]
|
[TextVariable(1)]
|
||||||
[Required]
|
[Required]
|
||||||
public string Title { get; set; }
|
public string Title { get; set; } = string.Empty;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the message
|
/// Gets or sets the message
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Required]
|
[Required]
|
||||||
[Template(2, nameof(MessageTemplates))]
|
[Template(2, nameof(MessageTemplates))]
|
||||||
public string Message { get; set; }
|
public string Message { get; set; } = string.Empty;
|
||||||
|
|
||||||
private static List<ListOption> _MessageTemplates;
|
private static List<ListOption>? _MessageTemplates;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a list of message templates
|
/// Gets a list of message templates
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -10,5 +10,5 @@ public class PluginSettings : IPluginSettings
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
[Text(2)]
|
[Text(2)]
|
||||||
[Required]
|
[Required]
|
||||||
public string ApiToken { get; set; }
|
public string ApiToken { get; set; } = string.Empty;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ public class Pushover: Node
|
|||||||
[Range(30, 86400)]
|
[Range(30, 86400)]
|
||||||
public int Retry { get; set; } = 2;
|
public int Retry { get; set; } = 2;
|
||||||
|
|
||||||
private static List<ListOption> _Priorities;
|
private static List<ListOption>? _Priorities;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a list of message templates
|
/// Gets a list of message templates
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -83,16 +83,16 @@ public class Pushover: Node
|
|||||||
return _Priorities;
|
return _Priorities;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the message
|
/// Gets or sets the message
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Required]
|
[Required]
|
||||||
[Template(3, nameof(MessageTemplates))]
|
[Template(3, nameof(MessageTemplates))]
|
||||||
public string Message { get; set; }
|
public string Message { get; set; } = string.Empty;
|
||||||
|
|
||||||
private static List<ListOption> _MessageTemplates;
|
private static List<ListOption>? _MessageTemplates;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a list of message templates
|
/// Gets a list of message templates
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -10,12 +10,12 @@ public class PluginSettings : IPluginSettings
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
[Text(1)]
|
[Text(1)]
|
||||||
[Required]
|
[Required]
|
||||||
public string UserKey { get; set; }
|
public string UserKey { get; set; } = string.Empty;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the API Token
|
/// Gets or sets the API Token
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Text(2)]
|
[Text(2)]
|
||||||
[Required]
|
[Required]
|
||||||
public string ApiToken { get; set; }
|
public string ApiToken { get; set; } = string.Empty;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,9 +39,9 @@ public class Telegram: Node
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
[Required]
|
[Required]
|
||||||
[Template(1, nameof(MessageTemplates))]
|
[Template(1, nameof(MessageTemplates))]
|
||||||
public string Message { get; set; }
|
public string Message { get; set; } = string.Empty;
|
||||||
|
|
||||||
private static List<ListOption> _MessageTemplates;
|
private static List<ListOption>? _MessageTemplates;
|
||||||
public static List<ListOption> MessageTemplates
|
public static List<ListOption> MessageTemplates
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
|||||||
@@ -10,12 +10,12 @@ public class PluginSettings : IPluginSettings
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
[Text(1)]
|
[Text(1)]
|
||||||
[Required]
|
[Required]
|
||||||
public string BotToken { get; set; }
|
public string BotToken { get; set; } = string.Empty;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the chat ID
|
/// Gets or sets the chat ID
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Text(1)]
|
[Text(1)]
|
||||||
[Required]
|
[Required]
|
||||||
public string ChatId { get; set; }
|
public string ChatId { get; set; } = string.Empty;
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
namespace FileFlows.VideoNodes.FfmpegBuilderNodes;
|
namespace FileFlows.VideoNodes.FfmpegBuilderNodes;
|
||||||
public class FfmpegBuilderCropBlackBars : FfmpegBuilderNode
|
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
|
try
|
||||||
{
|
{
|
||||||
@@ -88,17 +89,19 @@ public class FfmpegBuilderCropBlackBars : FfmpegBuilderNode
|
|||||||
var intervals = GetTimeIntervals(seconds);
|
var intervals = GetTimeIntervals(seconds);
|
||||||
if (intervals.Length == 0)
|
if (intervals.Length == 0)
|
||||||
return string.Empty;
|
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())
|
using (var process = new Process())
|
||||||
{
|
{
|
||||||
process.StartInfo = new ProcessStartInfo();
|
process.StartInfo = new ProcessStartInfo();
|
||||||
process.StartInfo.FileName = ffplay;
|
process.StartInfo.FileName = ffmpeg;
|
||||||
process.StartInfo.UseShellExecute = false;
|
process.StartInfo.UseShellExecute = false;
|
||||||
process.StartInfo.RedirectStandardOutput = true;
|
process.StartInfo.RedirectStandardOutput = true;
|
||||||
process.StartInfo.RedirectStandardError = true;
|
process.StartInfo.RedirectStandardError = true;
|
||||||
process.StartInfo.CreateNoWindow = 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);
|
args.Logger?.DLog("Executing ffmpeg " + process.StartInfo.Arguments);
|
||||||
process.Start();
|
process.Start();
|
||||||
string output = process.StandardError.ReadToEnd();
|
string output = process.StandardError.ReadToEnd();
|
||||||
@@ -106,7 +109,8 @@ public class FfmpegBuilderCropBlackBars : FfmpegBuilderNode
|
|||||||
string error = process.StandardError.ReadToEnd();
|
string error = process.StandardError.ReadToEnd();
|
||||||
process.WaitForExit();
|
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)
|
if (dimMatch.Success == false)
|
||||||
{
|
{
|
||||||
args.Logger?.WLog("Can't find dimensions for video");
|
args.Logger?.WLog("Can't find dimensions for video");
|
||||||
@@ -117,12 +121,24 @@ public class FfmpegBuilderCropBlackBars : FfmpegBuilderNode
|
|||||||
foreach (Match match in matches)
|
foreach (Match match in matches)
|
||||||
{
|
{
|
||||||
int[] parts = match.Value.Split(':').Select(x => int.Parse(x)).ToArray();
|
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]);
|
x = Math.Min(x, parts[2]);
|
||||||
y = Math.Min(y, parts[3]);
|
y = Math.Min(y, parts[3]);
|
||||||
width = Math.Max(width, parts[0]);
|
width = Math.Max(width, parts[0]);
|
||||||
height = Math.Max(height, parts[1]);
|
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)
|
if (width == 0 || height == 0)
|
||||||
@@ -161,6 +177,63 @@ public class FfmpegBuilderCropBlackBars : FfmpegBuilderNode
|
|||||||
{
|
{
|
||||||
int diff = (vidWidth - detectedWidth) + (vidHeight - detectedHeight);
|
int diff = (vidWidth - detectedWidth) + (vidHeight - detectedHeight);
|
||||||
return (diff > threshold, diff);
|
return (diff > threshold, diff);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Extracts a frame from a video at a specified time, scales it to 640x480 resolution, and saves it as a JPEG image using FFmpeg.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="args">the node parameters</param>
|
||||||
|
/// <param name="ffmpeg">The path to the FFmpeg executable.</param>
|
||||||
|
/// <param name="inputFile">The input video file.</param>
|
||||||
|
/// <param name="ss">The time offset in seconds.</param>
|
||||||
|
/// <param name="destination">The output image file path.</param>
|
||||||
|
/// <returns>True if the frame was extracted and saved successfully, otherwise false.</returns>
|
||||||
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user