mirror of
https://github.com/revenz/FileFlowsPlugins.git
synced 2026-02-14 11:08:24 -06:00
FF-273 - added auto crop image node
fixed issue with mp4 image based subtitles
This commit is contained in:
@@ -10,6 +10,7 @@ public class ComicConverter: Node
|
||||
public override string Icon => "fas fa-book";
|
||||
public override bool FailureNode => true;
|
||||
|
||||
CancellationTokenSource cancellation = new CancellationTokenSource();
|
||||
|
||||
[DefaultValue("cbz")]
|
||||
[Select(nameof(FormatOptions), 1)]
|
||||
@@ -24,9 +25,9 @@ public class ComicConverter: Node
|
||||
{
|
||||
_FormatOptions = new List<ListOption>
|
||||
{
|
||||
new ListOption { Label = "CBZ", Value = "cbz"},
|
||||
new ListOption { Label = "CBZ", Value = "CBZ"},
|
||||
//new ListOption { Label = "CB7", Value = "cb7"},
|
||||
new ListOption { Label = "PDF", Value = "pdf" }
|
||||
new ListOption { Label = "PDF", Value = "PDF" }
|
||||
};
|
||||
}
|
||||
return _FormatOptions;
|
||||
@@ -41,9 +42,11 @@ public class ComicConverter: Node
|
||||
args.Logger?.ELog("Could not detect format for: " + args.WorkingFile);
|
||||
return -1;
|
||||
}
|
||||
if(currentFormat[0] == '.')
|
||||
Format = Format?.ToUpper() ?? string.Empty;
|
||||
|
||||
if (currentFormat[0] == '.')
|
||||
currentFormat = currentFormat[1..]; // remove the dot
|
||||
currentFormat = currentFormat.ToLower();
|
||||
currentFormat = currentFormat.ToUpper();
|
||||
|
||||
|
||||
var metadata = new Dictionary<string, object>();
|
||||
@@ -62,8 +65,9 @@ public class ComicConverter: Node
|
||||
}
|
||||
|
||||
string destinationPath = Path.Combine(args.TempPath, Guid.NewGuid().ToString());
|
||||
|
||||
Directory.CreateDirectory(destinationPath);
|
||||
if (Helpers.ComicExtractor.Extract(args, args.WorkingFile, destinationPath, halfProgress: true) == false)
|
||||
if (Helpers.ComicExtractor.Extract(args, args.WorkingFile, destinationPath, halfProgress: true, cancellation: cancellation.Token) == false)
|
||||
return -1;
|
||||
|
||||
string newFile = CreateComic(args, destinationPath, this.Format);
|
||||
@@ -73,14 +77,20 @@ public class ComicConverter: Node
|
||||
return 1;
|
||||
}
|
||||
|
||||
public override Task Cancel()
|
||||
{
|
||||
cancellation.Cancel();
|
||||
return base.Cancel();
|
||||
}
|
||||
|
||||
private int GetPageCount(string format, string workingFile)
|
||||
{
|
||||
if (format == null)
|
||||
return 0;
|
||||
format = format.ToLower().Trim();
|
||||
format = format.ToUpper().Trim();
|
||||
switch (format)
|
||||
{
|
||||
case "pdf":
|
||||
case "PDF":
|
||||
return Helpers.PdfHelper.GetPageCount(workingFile);
|
||||
default:
|
||||
return Helpers.GenericExtractor.GetImageCount(workingFile);
|
||||
|
||||
@@ -9,6 +9,8 @@ public class ComicExtractor : Node
|
||||
public override FlowElementType Type => FlowElementType.Process;
|
||||
public override string Icon => "fas fa-file-pdf";
|
||||
|
||||
CancellationTokenSource cancellation = new CancellationTokenSource();
|
||||
|
||||
[Required]
|
||||
[Folder(1)]
|
||||
public string DestinationPath { get; set; }
|
||||
@@ -24,14 +26,19 @@ public class ComicExtractor : Node
|
||||
args.Result = NodeResult.Failure;
|
||||
return -1;
|
||||
}
|
||||
Helpers.ComicExtractor.Extract(args, args.WorkingFile, dest, halfProgress: false);
|
||||
Helpers.ComicExtractor.Extract(args, args.WorkingFile, dest, halfProgress: false, cancellation: cancellation.Token);
|
||||
|
||||
var metadata = new Dictionary<string, object>();
|
||||
metadata.Add("Format", args.WorkingFile.Substring(args.WorkingFile.LastIndexOf(".") + 1));
|
||||
metadata.Add("Format", args.WorkingFile.Substring(args.WorkingFile.LastIndexOf(".") + 1).ToUpper());
|
||||
var rgxImages = new Regex(@"\.(jpeg|jpg|jpe|png|bmp|tiff|webp|gif)$");
|
||||
metadata.Add("Pages", Directory.GetFiles(dest, "*.*", SearchOption.AllDirectories).Where(x => rgxImages.IsMatch(x)).Count());
|
||||
args.SetMetadata(metadata);
|
||||
|
||||
return 1;
|
||||
}
|
||||
public override Task Cancel()
|
||||
{
|
||||
cancellation.Cancel();
|
||||
return base.Cancel();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,36 +4,35 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FileFlows.ComicNodes.Helpers
|
||||
namespace FileFlows.ComicNodes.Helpers;
|
||||
|
||||
internal class ComicExtractor
|
||||
{
|
||||
internal class ComicExtractor
|
||||
internal static bool Extract(NodeParameters args, string file, string destinationPath, bool halfProgress, CancellationToken cancellation)
|
||||
{
|
||||
internal static bool Extract(NodeParameters args, string file, string destinationPath, bool halfProgress)
|
||||
|
||||
string currentFormat = new FileInfo(args.WorkingFile).Extension;
|
||||
if (string.IsNullOrEmpty(currentFormat))
|
||||
{
|
||||
|
||||
string currentFormat = new FileInfo(args.WorkingFile).Extension;
|
||||
if (string.IsNullOrEmpty(currentFormat))
|
||||
{
|
||||
args.Logger?.ELog("Could not detect format for: " + args.WorkingFile);
|
||||
return false;
|
||||
}
|
||||
if (currentFormat[0] == '.')
|
||||
currentFormat = currentFormat[1..]; // remove the dot
|
||||
currentFormat = currentFormat.ToLower();
|
||||
|
||||
Directory.CreateDirectory(destinationPath);
|
||||
args.Logger?.ILog("Extracting comic pages to: " + destinationPath);
|
||||
|
||||
if (currentFormat == "pdf")
|
||||
PdfHelper.Extract(args, args.WorkingFile, destinationPath, "page", halfProgress: halfProgress);
|
||||
else if (currentFormat == "cbz")
|
||||
ZipHelper.Extract(args, args.WorkingFile, destinationPath, halfProgress: halfProgress);
|
||||
else if (currentFormat == "cb7" || currentFormat == "cbr" || currentFormat == "gz" || currentFormat == "bz2")
|
||||
GenericExtractor.Extract(args, args.WorkingFile, destinationPath, halfProgress: halfProgress);
|
||||
else
|
||||
throw new Exception("Unknown format:" + currentFormat);
|
||||
|
||||
return true;
|
||||
args.Logger?.ELog("Could not detect format for: " + args.WorkingFile);
|
||||
return false;
|
||||
}
|
||||
if (currentFormat[0] == '.')
|
||||
currentFormat = currentFormat[1..]; // remove the dot
|
||||
currentFormat = currentFormat.ToUpper();
|
||||
|
||||
Directory.CreateDirectory(destinationPath);
|
||||
args.Logger?.ILog("Extracting comic pages to: " + destinationPath);
|
||||
|
||||
if (currentFormat == "PDF")
|
||||
PdfHelper.Extract(args, args.WorkingFile, destinationPath, "page", halfProgress: halfProgress, cancellation: cancellation);
|
||||
else if (currentFormat == "CBZ")
|
||||
ZipHelper.Extract(args, args.WorkingFile, destinationPath, halfProgress: halfProgress);
|
||||
else if (currentFormat == "CB7" || currentFormat == "CBR" || currentFormat == "GZ" || currentFormat == "BZ2")
|
||||
GenericExtractor.Extract(args, args.WorkingFile, destinationPath, halfProgress: halfProgress);
|
||||
else
|
||||
throw new Exception("Unknown format:" + currentFormat);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace FileFlows.ComicNodes.Helpers;
|
||||
|
||||
internal class PdfHelper
|
||||
{
|
||||
public static void Extract(NodeParameters args, string pdfFile, string destinationDirectory, string filePrefix, bool halfProgress = true)
|
||||
public static void Extract(NodeParameters args, string pdfFile, string destinationDirectory, string filePrefix, bool halfProgress, CancellationToken cancellation)
|
||||
{
|
||||
using var library = DocLib.Instance;
|
||||
using var docReader = library.GetDocReader(pdfFile, new PageDimensions(1080, 1920));
|
||||
@@ -37,6 +37,8 @@ internal class PdfHelper
|
||||
percent = (percent / 2);
|
||||
args?.PartPercentageUpdate(percent);
|
||||
}
|
||||
if (cancellation.IsCancellationRequested)
|
||||
return;
|
||||
}
|
||||
if (args?.PartPercentageUpdate != null)
|
||||
args?.PartPercentageUpdate(halfProgress ? 50 : 0);
|
||||
|
||||
@@ -1,6 +1,19 @@
|
||||
{
|
||||
"Flow": {
|
||||
"Parts": {
|
||||
"AutoCropImage": {
|
||||
"Outputs": {
|
||||
"1": "Image cropped, saved to new temporary file",
|
||||
"2": "Image was not cropped"
|
||||
},
|
||||
"Description": "Automatically crops an image",
|
||||
"Fields": {
|
||||
"Format": "Format",
|
||||
"Format-Help": "The image format to convert to",
|
||||
"Threshold": "Threshold",
|
||||
"Threshold-Help": "Threshold for entropic density, default is 50. Must be between 0 and 100."
|
||||
}
|
||||
},
|
||||
"ImageFlip": {
|
||||
"Outputs": {
|
||||
"1": "Image flipped, saved to new temporary file"
|
||||
|
||||
42
ImageNodes/Images/AutoCropImage.cs
Normal file
42
ImageNodes/Images/AutoCropImage.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using SixLabors.ImageSharp.Formats;
|
||||
using SixLabors.ImageSharp.Processing;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace FileFlows.ImageNodes.Images;
|
||||
|
||||
public class AutoCropImage : ImageNode
|
||||
{
|
||||
public override int Inputs => 1;
|
||||
public override int Outputs => 2;
|
||||
public override FlowElementType Type => FlowElementType.Process;
|
||||
public override string HelpUrl => "https://docs.fileflows.com/plugins/image-nodes/auto-crop-image";
|
||||
public override string Icon => "fas fa-crop";
|
||||
|
||||
[Slider(1)]
|
||||
[Range(1, 100)]
|
||||
[DefaultValue(50)]
|
||||
public int Threshold { get; set; }
|
||||
|
||||
|
||||
public override int Execute(NodeParameters args)
|
||||
{
|
||||
using var image = Image.Load(args.WorkingFile, out IImageFormat format);
|
||||
int originalWidth = image.Width;
|
||||
int originalHeight= image.Height;
|
||||
float threshold = Threshold / 100f;
|
||||
if (threshold < 0)
|
||||
threshold = 0.5f;
|
||||
|
||||
args.Logger?.ILog("Attempting to auto crop using threshold: " + threshold);
|
||||
image.Mutate(c => c.EntropyCrop(threshold));
|
||||
|
||||
if (image.Width == originalWidth && image.Height == originalHeight)
|
||||
return 2;
|
||||
|
||||
var formatOpts = GetFormat(args);
|
||||
SaveImage(args, image, formatOpts.file, formatOpts.format ?? format);
|
||||
args.Logger?.ILog($"Image cropped from '{originalWidth}x{originalHeight}' to '{image.Width}x{image.Height}'");
|
||||
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@ public class ImageNodesTests
|
||||
string TestImage1;
|
||||
string TestImage2;
|
||||
string TempDir;
|
||||
string TestCropImage1, TestCropImage2, TestCropImage3, TestCropImageNoCrop;
|
||||
|
||||
public ImageNodesTests()
|
||||
{
|
||||
@@ -21,6 +22,10 @@ public class ImageNodesTests
|
||||
TestImage1 = @"D:\videos\pictures\image1.jpg";
|
||||
TestImage2 = @"D:\videos\pictures\image2.png";
|
||||
TempDir = @"D:\videos\temp";
|
||||
TestCropImage1 = @"D:\images\testimages\crop01.jpg";
|
||||
TestCropImage2 = @"D:\images\testimages\crop02.jpg";
|
||||
TestCropImage3 = @"D:\images\testimages\crop03.jpg";
|
||||
TestCropImageNoCrop = @"D:\images\testimages\nocrop.jpg";
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -109,6 +114,72 @@ public class ImageNodesTests
|
||||
node.Angle = 270;
|
||||
Assert.AreEqual(1, node.Execute(args));
|
||||
}
|
||||
|
||||
|
||||
[TestMethod]
|
||||
public void ImageNodes_Basic_AutoCrop_01()
|
||||
{
|
||||
var logger = new TestLogger();
|
||||
var args = new NodeParameters(TestCropImage1, logger, false, string.Empty)
|
||||
{
|
||||
TempPath = TempDir
|
||||
};
|
||||
|
||||
var node = new AutoCropImage();
|
||||
int result = node.Execute(args);
|
||||
|
||||
string log = logger.ToString();
|
||||
Assert.AreEqual(1, result);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ImageNodes_Basic_AutoCrop_02()
|
||||
{
|
||||
var logger = new TestLogger();
|
||||
var args = new NodeParameters(TestCropImage2, logger, false, string.Empty)
|
||||
{
|
||||
TempPath = TempDir
|
||||
};
|
||||
|
||||
var node = new AutoCropImage();
|
||||
node.Threshold = 95;
|
||||
int result = node.Execute(args);
|
||||
|
||||
string log = logger.ToString();
|
||||
Assert.AreEqual(1, result);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ImageNodes_Basic_AutoCrop_03()
|
||||
{
|
||||
var logger = new TestLogger();
|
||||
var args = new NodeParameters(TestCropImage3, logger, false, string.Empty)
|
||||
{
|
||||
TempPath = TempDir
|
||||
};
|
||||
|
||||
var node = new AutoCropImage();
|
||||
int result = node.Execute(args);
|
||||
|
||||
string log = logger.ToString();
|
||||
Assert.AreEqual(1, result);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ImageNodes_Basic_AutoCrop_NoCrop()
|
||||
{
|
||||
var logger = new TestLogger();
|
||||
var args = new NodeParameters(TestCropImageNoCrop, logger, false, string.Empty)
|
||||
{
|
||||
TempPath = TempDir
|
||||
};
|
||||
|
||||
var node = new AutoCropImage();
|
||||
int result = node.Execute(args);
|
||||
|
||||
string log = logger.ToString();
|
||||
Assert.AreEqual(2, result);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -25,7 +25,14 @@
|
||||
break;
|
||||
case "mp4":
|
||||
{
|
||||
results.Add("mov_text");
|
||||
if (Helpers.SubtitleHelper.IsImageSubtitle(Stream.Codec))
|
||||
{
|
||||
results.Add("copy");
|
||||
}
|
||||
else
|
||||
{
|
||||
results.Add("mov_text");
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
|
||||
21
VideoNodes/Helpers/SubtitleHelper.cs
Normal file
21
VideoNodes/Helpers/SubtitleHelper.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FileFlows.VideoNodes.Helpers;
|
||||
|
||||
/// <summary>
|
||||
/// Helper for Subtitles
|
||||
/// </summary>
|
||||
internal class SubtitleHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Tests if a subtitle is an image based subtitle
|
||||
/// </summary>
|
||||
/// <param name="codec">the subtitle codec</param>
|
||||
/// <returns>true if the subtitle is an image based subtitle</returns>
|
||||
internal static bool IsImageSubtitle(string codec)
|
||||
=> Regex.IsMatch(codec.Replace("_", ""), "dvbsub|dvdsub|pgs|xsub", RegexOptions.IgnoreCase);
|
||||
}
|
||||
@@ -277,7 +277,9 @@ namespace FileFlows.VideoNodes
|
||||
audio.Title = "";
|
||||
// this isnt type index, this is overall index
|
||||
audio.TypeIndex = int.Parse(Regex.Match(line, @"#([\d]+):([\d]+)").Groups[2].Value) - 1;
|
||||
audio.Codec = parts[0].Substring(parts[0].IndexOf("Audio: ") + "Audio: ".Length).Trim().Split(' ').First().ToLower() ?? "";
|
||||
audio.Codec = parts[0].Substring(parts[0].IndexOf("Audio: ") + "Audio: ".Length).Trim().Split(' ').First().ToLower() ?? string.Empty;
|
||||
if (audio.Codec.EndsWith(","))
|
||||
audio.Codec = audio.Codec[..^1].Trim();
|
||||
|
||||
audio.Language = GetLanguage(line);
|
||||
if (info.IndexOf("0 channels") >= 0)
|
||||
@@ -336,6 +338,8 @@ namespace FileFlows.VideoNodes
|
||||
SubtitleStream sub = new SubtitleStream();
|
||||
sub.TypeIndex = int.Parse(Regex.Match(line, @"#([\d]+):([\d]+)").Groups[2].Value);
|
||||
sub.Codec = line.Substring(line.IndexOf("Subtitle: ") + "Subtitle: ".Length).Trim().Split(' ').First().ToLower();
|
||||
if (sub.Codec.EndsWith(","))
|
||||
sub.Codec = sub.Codec[..^1].Trim();
|
||||
sub.Language = GetLanguage(line);
|
||||
|
||||
if (rgxTitle.IsMatch(info))
|
||||
|
||||
Reference in New Issue
Block a user