From 03a6cc6cdf26f935f44e768fa98c42852226a6a6 Mon Sep 17 00:00:00 2001 From: John Andrews Date: Sun, 20 Oct 2024 20:42:29 +1300 Subject: [PATCH] FF-1886: Create video thumbnail flow element --- FileFlows.Plugin.dll | Bin 159232 -> 159232 bytes FileFlows.Plugin.pdb | Bin 40920 -> 40924 bytes VideoNodes/Tests/CreateThumbnailTests.cs | 51 +++++ VideoNodes/VideoNodes/CreateThumbnail.cs | 240 +++++++++++++++++++++++ 4 files changed, 291 insertions(+) create mode 100644 VideoNodes/Tests/CreateThumbnailTests.cs create mode 100644 VideoNodes/VideoNodes/CreateThumbnail.cs diff --git a/FileFlows.Plugin.dll b/FileFlows.Plugin.dll index fa67346692710e0cb94069c02ff6f0ad98eb8f53..8113a193963dac7b242639aff797affbacc9abd5 100644 GIT binary patch delta 43947 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 V9MzPX`*HVe&A#tk)Jwa>{D0ldd2Rpz delta 43917 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%0Dp9gZj%%Pvgj<`z8tIj))#zrR5z%4 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 BZ&m;R diff --git a/FileFlows.Plugin.pdb b/FileFlows.Plugin.pdb index 06a4020e423f9fea8ad9b0937eb8ae7dd7cc8491..b5a7bbf82c99f15d8f1b4e9104211ab2a7783db8 100644 GIT binary patch delta 7062 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 diff --git a/VideoNodes/Tests/CreateThumbnailTests.cs b/VideoNodes/Tests/CreateThumbnailTests.cs new file mode 100644 index 00000000..80db8031 --- /dev/null +++ b/VideoNodes/Tests/CreateThumbnailTests.cs @@ -0,0 +1,51 @@ +#if(DEBUG) + +using Microsoft.VisualStudio.TestTools.UnitTesting; +using FileFlows.VideoNodes; +using Moq; + +namespace VideoNodes.Tests; + +/// +/// Thumbnail tests +/// +[TestClass] +public class CreateThumbnailTests : VideoTestBase +{ + private Mock _imageHelper; + private int ImageDarkness = 50; + + /// + protected override void TestStarting() + { + base.TestStarting(); + _imageHelper = new Mock(); + _imageHelper.Setup(x => x.CalculateImageDarkness(It.IsAny())).Returns(() => ImageDarkness); + } + + /// + /// Tests creating a basic thumbnail + /// + [TestMethod] + public void BasicThumbnail() + { + var args = GetVideoNodeParameters(VideoMkv); + args.ImageHelper = _imageHelper.Object; + + VideoFile vf = new(); + vf.PreExecute(args); + vf.Execute(args); + + CreateThumbnail element = new(); + element.PreExecute(args); + element.Width = 320; + element.Height = 240; + element.SkipBlackFrames = false; + int output = element.Execute(args); + + Assert.AreEqual(1, output); + } + +} + +#endif \ No newline at end of file diff --git a/VideoNodes/VideoNodes/CreateThumbnail.cs b/VideoNodes/VideoNodes/CreateThumbnail.cs new file mode 100644 index 00000000..6911f4a3 --- /dev/null +++ b/VideoNodes/VideoNodes/CreateThumbnail.cs @@ -0,0 +1,240 @@ +using System.IO; + +namespace FileFlows.VideoNodes; + +/// +/// Node that creates a video thumbnail with resizing options and aspect ratio considerations. +/// +public class CreateThumbnail : VideoNode +{ + /// + /// Gets or sets the destination path for zipping. + /// + [TextVariable(1)] + public string Destination { get; set; } = string.Empty; + + /// + /// The width of the thumbnail. + /// + [NumberInt(2)] + public int Width { get; set; } = 1280; + + /// + /// The height of the thumbnail. + /// + [NumberInt(3)] + public int Height { get; set; } = 720; + + /// + /// The time in the video to capture the thumbnail (in seconds or percentage of the video duration). + /// + [Time(4)] + public TimeSpan Time { get; set; } + + + /// + /// The image resize mode to use. + /// + [Select(nameof(ResizeModeOptions), 5)] + public ResizeMode ResizeMode { get; set; } = ResizeMode.Contain; + + /// + /// Indicates whether to detect black frames or credits and skip them. + /// + [Boolean(6)] + public bool SkipBlackFrames { get; set; } = true; + + private static List _ResizeModeOptions; + public static List ResizeModeOptions + { + get + { + if (_ResizeModeOptions == null) + { + _ResizeModeOptions = new List + { + new () { Label = "Fill", Value = ResizeMode.Fill }, + new () { Label = "Contain", Value = ResizeMode.Contain }, + new () { Label = "Cover", Value = ResizeMode.Cover }, + new () { Label = "Pad", Value = ResizeMode.Pad }, + new () { Label = "Min", Value = ResizeMode.Min }, + new () { Label = "Max", Value = ResizeMode.Max } + }; + } + return _ResizeModeOptions; + } + } + + /// + /// Executes the node, creating a thumbnail for the video. + /// + /// The node parameters. + /// The result code: 1 for success, 2 for failure. + public override int Execute(NodeParameters args) + { + try + { + VideoInfo videoInfo = GetVideoInfo(args); + if (videoInfo == null) + { + args.FailureReason = "No Video Information found."; + args.Logger?.ELog(args.FailureReason); + return -1; + } + var lfResult = args.FileService.GetLocalPath(args.WorkingFile); + if (lfResult.Failed(out var error)) + { + args.FailureReason = "Failed to get local file: " + error; + args.Logger?.ELog(args.FailureReason); + return -1; + } + string localFile = lfResult.Value; + + // Ensure time is within bounds + TimeSpan captureTime = GetValidCaptureTime(videoInfo.VideoStreams[0].Duration); + + // Generate a thumbnail + string thumbnailPath = Path.Combine(args.TempPath, Guid.NewGuid() + ".png"); + if (CaptureThumbnail(args, localFile, captureTime, thumbnailPath) == false) + { + args.Logger?.WLog("Failed to generate a thumbnail"); + return 2; + } + + // Check for black frames or credits and skip if necessary + if (SkipBlackFrames && IsBlackOrCredits(thumbnailPath, args)) + { + captureTime = AdjustCaptureTime(captureTime, videoInfo.VideoStreams[0].Duration); + if (CaptureThumbnail(args, localFile, captureTime, thumbnailPath) == false) + { + args.Logger?.WLog("Failed to generate a thumbnail 2"); + return 2; + } + } + + // Resize the thumbnail + string resizedThumbnailPath = Path.Combine(args.TempPath, Guid.NewGuid() + ".png"); + var resizeResult = args.ImageHelper.Resize(thumbnailPath, resizedThumbnailPath, Width, Height, ResizeMode); + if (resizeResult.Failed(out error)) + { + args.Logger?.ELog("Failed to resize thumbnail: " + error); + return 2; + } + + + string dest = args.ReplaceVariables(Destination, stripMissing: true); + if (string.IsNullOrWhiteSpace(dest)) + { + dest = resizedThumbnailPath; + } + else + { + if (args.FileService.FileMove(resizedThumbnailPath, dest).Failed(out error)) + { + args.Logger?.WLog("Failed to move file: " + error); + return 2; + } + } + args.Logger?.ILog("Thumbnail Path: " + dest); + // Set output variable + args.UpdateVariables(new Dictionary { { "ThumbnailPath", dest } }); + return 1; + } + catch (Exception ex) + { + args.FailureReason = "Failed to create thumbnail: " + ex.Message; + args.Logger?.ELog(args.FailureReason); + return -1; + } + } + + /// + /// Gets a valid capture time ensuring it's within the bounds of the video duration. + /// + /// The video duration. + /// The valid capture time as a TimeSpan. + private TimeSpan GetValidCaptureTime(TimeSpan duration) + { + // If Time is set to a value less than 1 second, treat it as a percentage (e.g., 0.1 means 10% of the video) + if (Time.TotalSeconds < 1) + return TimeSpan.FromTicks((long)(duration.Ticks * Time.TotalSeconds)); + + // Otherwise, treat it as an absolute value in seconds + return Time < duration ? Time : duration; // Cap Time to the video duration + } + + /// + /// Captures a thumbnail using ffmpeg. + /// + /// The node parameters. + /// The local file path of the video. + /// The time to capture the thumbnail in the video. + /// The output file path for the thumbnail. + /// True if successful, otherwise false. + private bool CaptureThumbnail(NodeParameters args, string localFile, TimeSpan time, string outputPath) + { + var result = args.Process.ExecuteShellCommand(new ExecuteArgs + { + Command = FFMPEG, + ArgumentList = [ + "-ss", ((int)time.TotalSeconds).ToString(), // seek to the time + "-i", localFile, // input file + "-frames:v", "1", // capture just one frame + "-update", "1", // allow overwriting the file + outputPath // output single image, no sequence pattern + ] + }).Result; + + if (result.ExitCode != 0 || File.Exists(outputPath) == false) + { + args.Logger?.ELog("FFmpeg failed to capture thumbnail."); + return false; + } + + return true; + } + + /// + /// Checks if the captured thumbnail is mostly black or contains credits. + /// + /// The path to the thumbnail image. + /// The node parameters. + /// True if the image is mostly black or contains credits, otherwise false. + private bool IsBlackOrCredits(string thumbnailPath, NodeParameters args) + { + // Example logic for checking if an image is mostly black or very small (e.g., credits) + try + { + var result = args.ImageHelper.CalculateImageDarkness(thumbnailPath); + if (result.Failed(out var error)) + { + args.Logger?.WLog("Falied to calculate darkness: " + error); + return false; + } + + args.Logger?.ILog($"Darkness level detected: {result.Value}"); + return result.Value < 20; + } + catch (Exception ex) + { + args.Logger?.WLog($"Error analyzing thumbnail {thumbnailPath}: {ex.Message}"); + return false; + } + } + + /// + /// Adjusts the capture time if the thumbnail is black or contains credits. + /// + /// The current time of the thumbnail. + /// The total duration of the video. + /// The adjusted capture time. + private TimeSpan AdjustCaptureTime(TimeSpan currentTime, TimeSpan duration) + { + // Move the capture time by 10% of the video length forwards or backwards + TimeSpan shift = TimeSpan.FromTicks((long)(duration.Ticks * 0.1)); + if (currentTime + shift < duration) + return currentTime + shift; + + return currentTime > shift ? currentTime - shift : TimeSpan.Zero; // Shift backwards if near the end + } +}