Files
comic-img-compress/Program.cs
T
2024-03-10 01:20:22 +01:00

554 lines
21 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using System.IO;
using System.Threading.Tasks;
using System.Collections.Generic;
using McMaster.Extensions.CommandLineUtils;
using System.ComponentModel.DataAnnotations;
using System.Diagnostics;
using System.Threading;
using System.Drawing.Imaging;
using System.Drawing;
using System.Linq;
using System.Runtime.InteropServices;
using GroupDocs.Parser;
using GroupDocs.Parser.Data;
using iText.Kernel.Pdf;
using Imazen.WebP;
using SharpCompress.Archives;
using SharpCompress.Archives.Zip;
using SharpCompress.Common;
using itext.pdfimage.Extensions;
using org.omg.PortableInterceptor;
using org.apache.pdfbox.util.@operator;
using java.nio;
using ImageMagick;
namespace ComicCompressor
{
public class Program
{
public static int Main(string[] args) => CommandLineApplication.Execute<Program>(args);
[FileOrDirectoryExists]
[Option("-i|--input <FOLDER/FIlE>", Description = "The file or folder to convert")]
[Required]
public string Input { get; }
[Option("-o|--output <FOLDER>",
Description =
"Base path of the output (will be in output/subfolders if the recursive option is enabled), default: converted_comics")]
public string OutputFolder { get; } = "converted_comics";
[Option("-r|--recursive", Description = "Recursively traverse the input folder (include all subfolders)")]
public bool Recursive { get; } = false;
[Option("-s|--skip", Description = "Skip processing file if it already exists in the output folder")]
public bool Skip { get; } = false;
[Option("-q|--quality", Description = "Quality to use for the webp files (default: 75)")]
public int Quality { get; } = 75;
[Option("-p|--parallel", Description = "Run in parallel, utilizing all computing resources")]
public bool IsParallel { get; } = false;
[Option("-c|--cpu", Description = "Run in parallel, utilizing given computing resources")]
public int NumOfCpu { get; } = Environment.ProcessorCount;
[Option("-d|--pdf", Description = "process pdf only")]
public bool DoPdf { get; } = false;
[Option("-j|--image", Description = "process images only")]
public bool DoImage { get; } = false;
private Logger Logger { get; set; }
SimpleEncoder encoder = new SimpleEncoder();
private List<Stream> imageStreams = null;
public ZipArchive output = null;
private void OnExecute()
{
//ImageExtractor7.MainBox();
Stopwatch sw = Stopwatch.StartNew();
Logger = new Logger();
Logger.Debug = true;
Logger.LoggingLevel = LogLevel.Verbose;
IList<string> files = new FileParser().ListAllFiles(Input, Recursive);
Process(files);
long time_query = sw.ElapsedMilliseconds;
Logger.Log($"ElapsedTime {time_query} ms", LogLevel.Verbose, true);
Console.ReadKey();
}
private void Process(IList<string> files)
{
Compressor compressor = new Compressor(Logger);
compressor.Quality = Quality;
int filename_len = 0;
foreach (var file in files)
{
if(file.Length > filename_len)
filename_len = file.Length;
}
if (!IsParallel)
{
int n = 1;
foreach (var file in files)
{
Logger.Log($"{n,3}/{files.Count,3} - ", LogLevel.Verbose, false);
CompressTask(compressor, file, filename_len);
n++;
}
return;
}
ParallelOptions options = new ParallelOptions
{
MaxDegreeOfParallelism = (NumOfCpu > Environment.ProcessorCount) ? Environment.ProcessorCount : NumOfCpu
};
Parallel.ForEach(files, options, file => CompressTask(compressor, file, filename_len, true));
}
private void CompressTask(Compressor compressor, string filename, int max_name_len = -1, Boolean isPar = false)
{
var relativePath = Path.GetRelativePath(Input, filename);
var outputPath = Path.Join(OutputFolder, Path.ChangeExtension(relativePath, "cbz"));
var outputPath_webp = Path.Join(OutputFolder, Path.ChangeExtension(relativePath, "webp"));
if (File.Exists(outputPath) && Skip)
{
Logger.Log(outputPath, LogLevel.Verbose, true, Color.Yellow);
return;
}
if (DoImage && Skip && File.Exists(outputPath_webp))
{
Logger.Log(outputPath_webp, LogLevel.Verbose, true, Color.Yellow);
return;
}
if (DoPdf && filename.ToLower().EndsWith(".pdf"))
{
ExtractImageFromPDF(filename, outputPath);
//compressor.CompressPdf(filename, outputPath);
return;
}
if (DoPdf)
return;
if (DoImage &&
filename.ToLower().EndsWith("jpg") ||
filename.ToLower().EndsWith("jpeg") ||
filename.ToLower().EndsWith("tif") ||
filename.ToLower().EndsWith("png"))
{
outputPath = Path.Join(OutputFolder, Path.ChangeExtension(relativePath, "webp"));
CompressImage(filename, outputPath, max_name_len,isPar);
return;
}
//if (DoImage &&
// filename.ToLower().EndsWith("psd"))
//{
// outputPath = Path.Join(OutputFolder, Path.ChangeExtension(relativePath, "webp"));
// CompressImagePSD(filename, outputPath);
// return;
//}
if (!filename.ToLower().EndsWith(".cbr") && !filename.ToLower().EndsWith(".cbz"))
{
Logger.Log(relativePath, LogLevel.Verbose, true, Color.LightYellow);
return;
}
try
{
compressor.Compress(filename, outputPath);
}
catch (Exception e)
{
Logger.Log("Error processing file: " + filename, LogLevel.Error, Color.Red);
Logger.LogDebug(e, LogLevel.Error, col:Color.Red);
}
}
#region COMPRESS_PDF
#if false
public static string ExtractImageFromPDF(string sourcePdf)
{
iText.Kernel.Pdf.PdfReader reader = new iText.Kernel.Pdf.PdfReader(sourcePdf);
try
{
iText.Kernel.Pdf.PdfDocument document = new iText.Kernel.Pdf.PdfDocument(reader);
for (int pageNumber
= 1; pageNumber <= document.GetNumberOfPages(); pageNumber++)
{
iText.Kernel.Pdf.PdfDictionary obj =
(iText.Kernel.Pdf.PdfDictionary)document.GetPdfObject(pageNumber);
if (obj != null && obj.IsStream())
{
iText.Kernel.Pdf.PdfDictionary pd
= (iText.Kernel.Pdf.PdfDictionary)obj;
if (pd.ContainsKey(iText.Kernel.Pdf.PdfName.Subtype) && pd.Get(iText.Kernel.Pdf.PdfName.Subtype).ToString() == "/Image")
{
string filter
= pd.Get(iText.Kernel.Pdf.PdfName.Filter).ToString();
string width
= pd.Get(iText.Kernel.Pdf.PdfName.Width).ToString();
string height
= pd.Get(iText.Kernel.Pdf.PdfName.Height).ToString();
string bpp
= pd.Get(iText.Kernel.Pdf.PdfName.BitsPerComponent).ToString();
string extent
= ".";
byte[] img
= pd.Get(iText.Kernel.Pdf.PdfName.Image);
Debug.WriteLine(filter);
continue;
switch (filter)
{
case "/FlateDecode":
byte[] arr
= FlateDecodeFilter.FlateDecode(img, true);
Bitmap bmp
= new Bitmap(Int32.Parse(width), Int32.Parse(height), PixelFormat.Format24bppRgb);
BitmapData bmd
= bmp.LockBits(new Rectangle(0, 0, Int32.Parse(width), Int32.Parse(height)), ImageLockMode.WriteOnly,
PixelFormat.Format24bppRgb);
Marshal.Copy(arr, 0, bmd.Scan0, arr.Length);
bmp.UnlockBits(bmd);
bmp.Save("d:\\pdf\\bmp1.png", ImageFormat.Png);
break;
case "/CCITTFaxDecode":
break;
default:
break;
}
}
}
}
}
catch
{
throw;
}
return "";
}
#elif false
public static string ExtractImageFromPDF(string sourcePdf)
{
// Create an instance of Parser class
using (Parser parser = new Parser(sourcePdf))
{
// Extract images
var di = parser.GetDocumentInfo();
for (int p = 0; p < di.PageCount; p++)
{
IEnumerable<PageImageArea> images = parser.GetImages(p);
//IEnumerable<PageImageArea> images = parser.GetImages();
// Check if image extraction is supported
if (images == null)
{
Console.WriteLine("Images extraction isnt supported");
return "";
}
int counter = 1;
// Iterate over images
foreach (PageImageArea image in images)
{
// Save each image
Image.FromStream(image.GetImageStream())
.Save(string.Format($"{p.ToString("0000")}_{counter++.ToString("0000")}.jpg"), ImageFormat.Jpeg);
}
}
}
return "";
}
#else
public int GetPlace(int value, int place)
{
return ((value % (place * 10)) - (value % place)) / place;
}
private bool Cover = true;
int _currentPage = 1;
int _imageCount = 0;
public void ExtractImageFromPDF(string sourcePdf, string outputFolder)
{
long input_length = new System.IO.FileInfo(sourcePdf).Length;
var pdf = File.Open(sourcePdf, FileMode.Open);
var reader = new PdfReader(pdf);
var pdfDocument = new PdfDocument(reader);
int numOfPages = pdfDocument.GetNumberOfPages();
//var bitmaps = pdfDocument.ConvertToBitmaps();
Logger.Log($"Processing[{numOfPages}]: " + outputFolder, LogLevel.Verbose);
output = ZipArchive.Create();
imageStreams = new List<Stream>();
int count = 0;
Cover = true;
Stopwatch sw = Stopwatch.StartNew();
for (int p = 1; p <= numOfPages; p++)
{
PdfPage page = pdfDocument.GetPage(p);
//var bitmap = page.ConvertPageToBitmap();
MemoryStream jpg = (MemoryStream)page.ConvertPageToJpg();
//bitmap.Save(Path.Combine($"wave-{DateTime.Now.Ticks}.png"), ImageFormat.Png);
ProcessPdfImage(jpg);
//jpg.Dispose();
//page.Flush();
_currentPage++;
count++;
if (count % 10 == 0)
{
string digit = GetPlace(count, 10).ToString();
Logger.Log(digit, LogLevel.Verbose, false);
}
}
Directory.CreateDirectory(Path.GetDirectoryName(outputFolder));
output.SaveTo(outputFolder, CompressionType.Deflate);
//output.SaveTo(outputPath, CompressionType.None);
imageStreams.ForEach(i => i.Dispose());
output.Dispose();
long output_length = new System.IO.FileInfo(outputFolder).Length;
double save = 100 * (double)output_length / (double)input_length;
if (numOfPages == 0) numOfPages = 1;
long elapsed = sw.ElapsedMilliseconds;
Logger.Log($"\r\nFinished({save.ToString("0.##")}) [{elapsed}][{elapsed / numOfPages}]: " + outputFolder, LogLevel.Verbose);
}
private Bitmap ChangePixelFormat(Bitmap orig, System.Drawing.Imaging.PixelFormat format = System.Drawing.Imaging.PixelFormat.Format24bppRgb)
{
Bitmap clone = new Bitmap(orig.Width, orig.Height, format);
using (Graphics gr = Graphics.FromImage(clone))
{
gr.DrawImage(orig, new System.Drawing.Rectangle(0, 0, clone.Width, clone.Height));
}
return clone;
}
public static byte[] ImageToByte(Image img)
{
ImageConverter converter = new ImageConverter();
return (byte[])converter.ConvertTo(img, typeof(byte[]));
}
public void ProcessPdfImage(MemoryStream jpg)
{
Stopwatch sw = Stopwatch.StartNew();
try
{
var image = new Bitmap(jpg);
//var imageFileName = String.Format("{0}_{1}_{2}.{3}", _outputFilePrefix, _currentPage, _imageCount,
// imageObject.GetFileType());
var imageFileName = String.Format($"{_imageCount.ToString("0000")}");
//var imagePath = Path.Combine(_outputFolder, imageFileName);
//if (_overwriteExistingFiles || !File.Exists(imagePath))
{
//Logger.Log($"0 [{image.Width}][{image.Height}]" + sw.ElapsedMilliseconds.ToString(), LogLevel.Verbose);
//var imageRawBytes = ImageToByte(image);
//var image = imageObject.GetDrawingImage();
//Logger.Log("1 "+sw.ElapsedMilliseconds.ToString(), LogLevel.Verbose);
//File.WriteAllBytes(imagePath, imageRawBytes);
if (image.Width < 512 || image.Height < 512)
return;
if (image.PixelFormat != System.Drawing.Imaging.PixelFormat.Format24bppRgb)
{
var newBits = ChangePixelFormat((Bitmap)image);
image.Dispose();
image = newBits;
}
//Logger.Log("2 " + sw.ElapsedMilliseconds.ToString(), LogLevel.Verbose);
var ms = new MemoryStream();
encoder.Encode((Bitmap)image, ms, Quality);
//Logger.Log("3 " + sw.ElapsedMilliseconds.ToString(), LogLevel.Verbose);
if (!Cover && ms.Length < jpg.Length)
{
imageStreams.Add(ms);
output.AddEntry(imageFileName + ".webp", ms);
Logger.Log(".", LogLevel.Verbose, false);
//Logger.Log("4 " + sw.ElapsedMilliseconds.ToString(), LogLevel.Verbose);
}
else
{
//var mso = new MemoryStream();
//mso.Write(imageRawBytes);
// output.AddEntry(imageFileName + imageObject.GetFileType(), mso);
imageStreams.Add(ms);
output.AddEntry(imageFileName + ".jpg", jpg);
Logger.Log(Cover ? "C" : "=", LogLevel.Verbose, false);
Cover = false;
}
}
// Subtle: Always increment even if file is not written. This ensures consistency should only some
// of a PDF file's images actually exist.
_imageCount++;
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
//Logger.Log(sw.ElapsedMilliseconds.ToString(), LogLevel.Verbose);
}
#endif
#endregion
#region COMPRESS_IMAGE
public void CompressImage(string imagefile, string outputFolder, int max_name_len = -1, Boolean isPar = false)
{
string name_print = imagefile;
if(max_name_len > 0)
name_print = imagefile.PadRight(max_name_len);
if(!isPar)
Logger.Log(name_print, LogLevel.Verbose, false, col:Color.LightBlue);
try
{
Directory.CreateDirectory(Path.GetDirectoryName(outputFolder));
var encoder = new SimpleEncoder();
Bitmap mBitmap;
long length_in = 0;
FileStream outStream = new FileStream(outputFolder, FileMode.Create);
using (Stream BitmapStream = System.IO.File.Open(imagefile, System.IO.FileMode.Open))
{
length_in = BitmapStream.Length;
Image img = Image.FromStream(BitmapStream);
mBitmap = new Bitmap(img);
IntPtr result;
long length = 0;
encoder.Encode(mBitmap, outStream, 70);
outStream.Flush();
outStream.Close();
//encoder.Encode(mBitmap, 70, out result, out length);
//UnmanagedMemoryStream ustream = new UnmanagedMemoryStream((byte*)result, length);
//ustream.CopyTo(outStream);
//ustream.Close();
//outStrea.Close();
}
FileInfo finfo = new FileInfo(outputFolder);
long length_out = finfo.Length;
if (isPar)
Logger.Log($"{name_print} - IN[{length_in,10}] OUT[{length_out,10}] %[{(100.0 * length_out / (double)length_in),5:##.00}]", LogLevel.Verbose, true,col:Color.Green);
else
Logger.Log($" - IN[{length_in,10}] OUT[{length_out,10}] %[{(100.0 * length_out/(double)length_in),5:##.00}]", LogLevel.Verbose, true, col:Color.LightBlue);
}
catch(Exception e)
{
Logger.LogError(e, col: Color.Red);
}
}
public void CompressImagePSD(string imagefile, string outputFolder)
{
Logger.Log(imagefile, LogLevel.Verbose, false);
try
{
Directory.CreateDirectory(Path.GetDirectoryName(outputFolder));
var encoder = new SimpleEncoder();
Bitmap mBitmap;
long length_in = 0;
FileStream outStream = new FileStream(outputFolder, FileMode.Create);
using (Stream BitmapStream = System.IO.File.Open(imagefile, System.IO.FileMode.Open))
{
length_in = BitmapStream.Length;
//using var imageFromFile = new MagickImage(BitmapStream);
using (MagickImageCollection magickImages = new MagickImageCollection(BitmapStream))
{
foreach (var image in magickImages)
{
Image img = image.ToBitmap();
//Image img = Image.FromStream(BitmapStream);
//Image img = imageFromFile.ToBitmap();
mBitmap = new Bitmap(img);
IntPtr result;
long length = 0;
encoder.Encode(mBitmap, outStream, 70);
}
}
outStream.Flush();
outStream.Close();
//encoder.Encode(mBitmap, 70, out result, out length);
//UnmanagedMemoryStream ustream = new UnmanagedMemoryStream((byte*)result, length);
//ustream.CopyTo(outStream);
//ustream.Close();
//outStrea.Close();
}
FileInfo finfo = new FileInfo(outputFolder);
long length_out = finfo.Length;
Logger.Log($" - IN[{length_in}] OUT[{length_out}] %[{100.0 * length_out / (double)length_in}]", LogLevel.Verbose, true);
}
catch (Exception e)
{
Logger.LogError(e);
}
}
#endregion
}
}