code sources

This commit is contained in:
Marco Giuntoni
2024-03-01 16:05:06 +01:00
parent b9ac7f8d72
commit b73c859b94
7 changed files with 1444 additions and 3 deletions
+435
View File
@@ -0,0 +1,435 @@
using System;
using System.Linq;
using System.Drawing;
using Imazen.WebP;
using SharpCompress.Archives;
using SharpCompress.Archives.Rar;
using SharpCompress.Archives.Zip;
using System.IO;
using SharpCompress.Common;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Diagnostics;
using javax.swing.plaf;
using SharpCompress.Readers;
using SharpCompress.Writers;
using SharpCompress.Compressors.Xz;
namespace ComicCompressor
{
public class Compressor
{
private long last_mem = 0;
private Logger Logger { get; set; }
public int Quality { get; set; } = 75;
public Compressor(Logger logger)
{
Logger = logger;
}
public void Compress(string filename, string outputFile = null)
{
Stopwatch sw = Stopwatch.StartNew();
outputFile = Path.ChangeExtension(outputFile ?? filename, "cbz");
Process proc = Process.GetCurrentProcess();
Logger.LogDebug($"M {proc.PrivateMemorySize64}", LogLevel.Verbose, col: Color.Green);
last_mem = proc.PrivateMemorySize64;
//Logger.Log("Processing: " + filename, LogLevel.Verbose);
IArchive archive = null;
//SharpCompress.Readers.ReaderOptions opt = new ReaderOptions()
//{
//}
ArchiveType ArchiveType = ArchiveType.Zip;
using (Stream stream = File.OpenRead(filename))
{
using (var reader = ReaderFactory.Open(stream))
ArchiveType = reader.ArchiveType;
}
bool isSolid = false;
//if (filename.EndsWith(".cbr"))
if (ArchiveType == ArchiveType.Rar)
{
archive = RarArchive.Open(filename);
//foreach (var entry in archive.Entries)
//{
// isSolid = isSolid || entry.IsSolid;
//}
isSolid = archive.IsSolid;
}
//else if (filename.EndsWith(".cbz"))
else if (ArchiveType == ArchiveType.Zip)
{
archive = ZipArchive.Open(filename);
}
//Logger.Log($"Processing[{archive.Entries.Count()}]: " + filename, LogLevel.Verbose);
Logger.Log($"Processing[{archive.Entries.Count()}]: ", LogLevel.Verbose,false);
Logger.Log( filename, LogLevel.Verbose,col:Color.Green);
double save = 0;
int num_pages =0;
if (isSolid)
{
using (Stream stream = File.OpenRead(filename))
{
using (var reader = ReaderFactory.Open(stream))
{
(save, num_pages) = ProcessReader(archive,reader, outputFile);
}
}
}
else
(save, num_pages) = ProcessArchive(archive, outputFile);
if (num_pages == 0) num_pages = 1;
long elapsed = sw.ElapsedMilliseconds;
Logger.Log($"\r\nFinished({save.ToString("0.##")}%) [{elapsed}][{elapsed/num_pages}]: " + filename, LogLevel.Verbose, col: Color.Green);
}
private (double, int) ProcessReader(IArchive archive, IReader reader, string outputPath)
{
var encoder = new SimpleEncoder();
bool cover = true;
double save = 0.0;
int nop = 0;
using (var output = ZipArchive.Create())
{
var imageStreams = new List<Stream>();
while (reader.MoveToNextEntry())
{
if (!reader.Entry.IsDirectory)
{
//Console.WriteLine(reader.Entry.Key);
if (reader.Entry.Key.StartsWith("__MACOSX"))
{
continue;
}
using (var entryStream = reader.OpenEntryStream())
{
string entry_key_lower = reader.Entry.Key.ToLower();
if (!entry_key_lower.EndsWith(".jpg") &&
!entry_key_lower.EndsWith(".png") &&
!entry_key_lower.EndsWith(".bmp") &&
!entry_key_lower.EndsWith(".gif") &&
!entry_key_lower.EndsWith(".jpeg"))
{
var ms = new MemoryStream();
imageStreams.Add(ms);
entryStream.CopyTo(ms);
output.AddEntry(reader.Entry.Key, ms);
Logger.Log("o", LogLevel.Verbose, false, col: Color.PaleGreen);
continue;
}
nop++;
if (cover)
{
var ms = new MemoryStream();
imageStreams.Add(ms);
entryStream.CopyTo(ms);
output.AddEntry(reader.Entry.Key, ms);
Logger.Log("C", LogLevel.Verbose, false, col: Color.Green);
cover = false;
continue;
}
ProcessReaderEntry(reader, entryStream, imageStreams, output, encoder);
}
}
}
reader.Dispose();
Directory.CreateDirectory(Path.GetDirectoryName(outputPath));
output.SaveTo(outputPath, CompressionType.Deflate);
//output.SaveTo(outputPath, CompressionType.None);
imageStreams.ForEach(i => i.Dispose());
}
long output_length = new System.IO.FileInfo(outputPath).Length;
save = 100 * (double)output_length / (double)archive.TotalSize;
return (save,nop);
}
public int GetPlace(int value, int place)
{
return ((value % (place * 10)) - (value % place)) / place;
}
private (double, int) ProcessArchive(IArchive archive, string outputPath)
{
//var fileEntries = archive.Entries.Where(e => !e.IsDirectory);
var fileEntries = archive.Entries.Where(e => !e.IsDirectory).OrderBy(e => e.Key);
var encoder = new SimpleEncoder();
bool cover = true;
double save = 0.0;
int nop = 0;
using (var output = ZipArchive.Create())
{
var imageStreams = new List<Stream>();
int count = 0;
foreach (var entry in fileEntries)
{
(bool res, bool skipped) = ProcessEntry(entry, imageStreams, output, encoder, cover);
if (res)
cover = false;
count++;
if (!skipped)
nop++;
if (count % 10 == 0)
{
string digit = GetPlace(count, 10).ToString();
Logger.Log(digit, LogLevel.Verbose, false, col: Color.Green);
}
}
archive.Dispose();
Directory.CreateDirectory(Path.GetDirectoryName(outputPath));
output.SaveTo(outputPath, CompressionType.Deflate);
//output.SaveTo(outputPath, CompressionType.None);
imageStreams.ForEach(i => i.Dispose());
output.Dispose();
}
long output_length = new System.IO.FileInfo(outputPath).Length;
save = 100 * (double)output_length / (double)archive.TotalSize;
return (save, nop);
}
private (bool, bool) ProcessEntry(IArchiveEntry entry, IList<Stream> imageStreams, ZipArchive output, SimpleEncoder encoder, bool cover)
{
try
{
if (entry.IsEncrypted)
{
Logger.Log("X", LogLevel.Verbose, false, col: Color.Yellow);
return (false,true);
}
using (var stream = entry.OpenEntryStream())
{
if (entry.Key.StartsWith("__MACOSX"))
{
return (false,true);
}
string entry_key_lower = entry.Key.ToLower();
if (!entry_key_lower.EndsWith(".jpg") &&
!entry_key_lower.EndsWith(".png") &&
!entry_key_lower.EndsWith(".bmp") &&
!entry_key_lower.EndsWith(".gif") &&
!entry_key_lower.EndsWith(".jpeg"))
{
var ms = new MemoryStream();
imageStreams.Add(ms);
stream.CopyTo(ms);
output.AddEntry(entry.Key, ms);
Logger.Log("o", LogLevel.Verbose, false, col: Color.Green);
return (false, true);
}
if (cover)
{
var ms = new MemoryStream();
imageStreams.Add(ms);
stream.CopyTo(ms);
output.AddEntry(entry.Key, ms);
Logger.Log("C", LogLevel.Verbose, false,col:Color.Green);
return (true, false);
}
Bitmap bits;
var raw_ms = new MemoryStream();
stream.CopyTo(raw_ms);
try
{
bits = new Bitmap(raw_ms);
}
catch (Exception e)
{
Logger.LogError("Error parsing bitmap: " + entry.Key, col: Color.Red);
Logger.LogDebug(e, LogLevel.Error, col:Color.Red);
return (false, true);
}
if (bits.PixelFormat != System.Drawing.Imaging.PixelFormat.Format24bppRgb)
{
var newBits = ChangePixelFormat(bits);
bits.Dispose();
bits = newBits;
}
try
{
var ms = new MemoryStream();
encoder.Encode(bits, ms, Quality);
if (ms.Length < entry.Size)
{
imageStreams.Add(ms);
output.AddEntry(entry.Key + ".webp", ms);
Logger.Log(".", LogLevel.Verbose, false, col: Color.PaleGreen);
// release not used stream
raw_ms.Dispose();
}
else
{
imageStreams.Add(raw_ms);
raw_ms.Seek(0, SeekOrigin.Begin);
//stream.Seek(0,SeekOrigin.Begin);
output.AddEntry(entry.Key, raw_ms);
Logger.Log("=", LogLevel.Verbose, false, col: Color.Green);
// release not used stream
ms.Dispose();
}
}
catch (Exception e)
{
Logger.LogError("Error encoding entry: " + entry.Key, col: Color.Red);
Logger.LogDebug(e, LogLevel.Error, col: Color.Red);
}
finally
{
bits.Dispose();
}
}
}
catch (Exception e)
{
Logger.LogError("Error Archive Extraction: " + entry.Key, col: Color.Red);
Logger.LogDebug(e, LogLevel.Error, col: Color.Red);
return (false, true);
}
Process proc = Process.GetCurrentProcess();
//Logger.LogDebug($"M {proc.PrivateMemorySize64} - D {proc.PrivateMemorySize64 - last_mem}", LogLevel.Verbose);
//last_mem = proc.PrivateMemorySize64;
return (false,false);
}
private void ProcessReaderEntry(IReader reader,EntryStream entry, IList<Stream> imageStreams, ZipArchive output, SimpleEncoder encoder)
{
try
{
var stream = entry;
// using (var stream = entry.OpenEntryStream())
{
Bitmap bits;
var raw_ms = new MemoryStream();
imageStreams.Add(raw_ms);
stream.CopyTo(raw_ms);
try
{
bits = new Bitmap(raw_ms);
}
catch (Exception e)
{
Logger.LogError("Error parsing bitmap: " + reader.Entry.Key, col: Color.Red);
Logger.LogDebug(e, LogLevel.Error, col: Color.Red);
return ;
}
if (bits.PixelFormat != System.Drawing.Imaging.PixelFormat.Format24bppRgb)
{
var newBits = ChangePixelFormat(bits);
bits.Dispose();
bits = newBits;
}
try
{
var ms = new MemoryStream();
encoder.Encode(bits, ms, Quality);
if (ms.Length < reader.Entry.Size)
{
imageStreams.Add(ms);
output.AddEntry(reader.Entry.Key + ".webp", ms);
Logger.Log("s", LogLevel.Verbose, false, col: Color.Green);
}
else
{
raw_ms.Seek(0, SeekOrigin.Begin);
//stream.Seek(0,SeekOrigin.Begin);
output.AddEntry(reader.Entry.Key, raw_ms);
Logger.Log("=", LogLevel.Verbose, false, col: Color.Green);
}
}
catch (Exception e)
{
Logger.LogError("Error encoding entry: " + reader.Entry.Key, col: Color.Red);
Logger.LogDebug(e, LogLevel.Error, col: Color.Red);
}
finally
{
bits.Dispose();
}
}
}
catch (Exception e)
{
Logger.LogError("Error Archive Extraction: " + reader.Entry.Key, col: Color.Red);
Logger.LogDebug(e, LogLevel.Error, col: Color.Red);
return;
}
return ;
}
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 Rectangle(0, 0, clone.Width, clone.Height));
}
return clone;
}
public void CompressPdf(string filename, string outputFile = null)
{
new ImageExtractor(Logger).ExtractImagesFromFile(filename, outputFile);
}
}
}
+46
View File
@@ -0,0 +1,46 @@
using System.Collections.Generic;
using System.IO;
namespace ComicCompressor
{
public class FileParser
{
public IList<string> ListAllFiles(string input, bool recursive)
{
Queue<string> unexplored;
IList<string> files = new List<string>();
if (Directory.Exists(input))
{
unexplored = new Queue<string>(Directory.GetFileSystemEntries(input));
}
else
{
// No directory but must exist so it is a file
unexplored = new Queue<string>();
unexplored.Enqueue(input);
}
while(unexplored.Count > 0)
{
var file = unexplored.Dequeue();
if (File.Exists(file))
{
files.Add(file);
}
else if (recursive)
{
foreach (var f in Directory.GetFileSystemEntries(file))
{
unexplored.Enqueue(f);
}
}
}
return files;
}
}
}
+205
View File
@@ -0,0 +1,205 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Runtime.ConstrainedExecution;
using ikvm.extensions;
using Imazen.WebP;
using iTextSharp.text.pdf;
using iTextSharp.text.pdf.parser;
using SharpCompress.Archives;
using SharpCompress.Archives.Zip;
using SharpCompress.Common;
using Path = System.IO.Path;
namespace ComicCompressor
{
/// <summary>
/// Helper lass to dump all images from a PDF into separate files
/// </summary>
public class ImageExtractor : IRenderListener
{
int _currentPage = 1;
int _imageCount = 0;
readonly string _outputFilePrefix = "";
//readonly string _outputFolder;
//readonly bool _overwriteExistingFiles;
private bool Cover = true;
private Logger Logger { get; set; }
SimpleEncoder encoder = new SimpleEncoder();
public int Quality { get; set; } = 75;
private List<Stream> imageStreams = null;
public ZipArchive output = null;
public ImageExtractor(Logger logger)
{
Logger = logger;
//_outputFilePrefix = outputFilePrefix;
//_outputFolder = outputFolder;
//_overwriteExistingFiles = overwriteExistingFiles;
}
public int GetPlace(int value, int place)
{
return ((value % (place * 10)) - (value % place)) / place;
}
/// <summary>
/// Extract all images from a PDF file
/// </summary>
/// <param name="pdfPath">Full path and file name of PDF file</param>
/// <param name="outputFilePrefix">Basic name of exported files. If null then uses same name as PDF file.</param>
/// <param name="outputFolder">Where to save images. If null or empty then uses same folder as PDF file.</param>
/// <param name="overwriteExistingFiles">True to overwrite existing image files, false to skip past them</param>
/// <returns>Count of number of images extracted.</returns>
public int ExtractImagesFromFile(string pdfPath, string outputFolder, int quality = 75)
{
// Handle setting of any default values
//outputFilePrefix = outputFilePrefix ?? Path.GetFileNameWithoutExtension(pdfPath);
//outputFolder = String.IsNullOrEmpty(outputFolder) ? Path.GetDirectoryName(pdfPath) : outputFolder;
quality = quality;
Stopwatch sw = Stopwatch.StartNew();
using (var pdfReader = new PdfReader(pdfPath))
{
Logger.Log($"Processing[{pdfReader.NumberOfPages}]: " + pdfPath, LogLevel.Verbose);
if (pdfReader.IsEncrypted())
throw new ApplicationException(pdfPath + " is encrypted.");
var pdfParser = new PdfReaderContentParser(pdfReader);
output = ZipArchive.Create();
{
imageStreams = new List<Stream>();
int count = 0;
Cover = true;
while (_currentPage <= pdfReader.NumberOfPages)
{
pdfParser.ProcessContent(_currentPage, this);
_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();
int nop = pdfReader.NumberOfPages;
if (nop == 0) nop = 1;
long elapsed = sw.ElapsedMilliseconds;
//Logger.Log($"\r\nFinished({save.ToString("0.##")}): " + pdfPath, LogLevel.Verbose);
Logger.Log($"\r\nFinished({0.ToString("0.##")} [{elapsed}][{elapsed / nop}]): " + pdfPath, LogLevel.Verbose);
}
return _imageCount;
}
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 Rectangle(0, 0, clone.Width, clone.Height));
}
return clone;
}
#region Implementation of IRenderListener
public void BeginTextBlock()
{
}
public void EndTextBlock()
{
}
public void RenderText(TextRenderInfo renderInfo)
{
var text = renderInfo.GetText();
}
public void RenderImage(ImageRenderInfo renderInfo)
{
try
{
var type = renderInfo.GetType();
var str = renderInfo.toString();
//var ftype = renderInfo.GetFileType();
var imageObject = renderInfo.GetImage();
//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))
{
var imageRawBytes = imageObject.GetImageAsBytes();
var image = imageObject.GetDrawingImage();
//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;
}
var ms = new MemoryStream();
encoder.Encode((Bitmap)image, ms, Quality);
if (!Cover && ms.Length < imageRawBytes.Length)
{
imageStreams.Add(ms);
output.AddEntry(imageFileName + ".webp", ms);
Logger.Log(".", LogLevel.Verbose, false);
}
else
{
var mso = new MemoryStream();
mso.Write(imageRawBytes);
output.AddEntry(imageFileName + imageObject.GetFileType(), mso);
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;
}
}
#endregion // Implementation of IRenderListener
}
}
+89
View File
@@ -0,0 +1,89 @@
using System;
using System.IO;
using System.Linq;
using iText.Kernel.Geom;
using iText.Kernel.Pdf;
using iText.Kernel.Pdf.Xobject;
using iText.Layout;
using iText.Layout.Element;
using java.util;
using org.apache.pdfbox.pdmodel.graphics.xobject;
using org.apache.pdfbox.pdmodel;
namespace ComicCompressor
{
public class ImageExtractor7
{
public static readonly String DEST = "K:\\_COMIX\\_TOOLS_\\comic-compress-master\\bin\\Debug\\net6.0\\_NEW_PDF_\\Fan SUP 18.cbz";
public static readonly String SRC = "K:\\_COMIX\\_TOOLS_\\comic-compress-master\\bin\\Debug\\net6.0\\_PDF_\\Fan SUP 18.pdf";
public static void Main7()
{
FileInfo file = new FileInfo(DEST);
file.Directory.Create();
new ImageExtractor7().ManipulatePdf(DEST);
}
protected void ManipulatePdf(String dest)
{
PdfDocument resultDoc = new PdfDocument(new PdfWriter(dest));
PdfDocument srcDoc = new PdfDocument(new PdfReader(SRC));
// Assume that there is a single XObject in the source document
// and this single object is an image.
PdfDictionary pageDict = srcDoc.GetFirstPage().GetPdfObject();
PdfDictionary pageResources = pageDict.GetAsDictionary(PdfName.Resources);
PdfDictionary pageXObjects = pageResources.GetAsDictionary(PdfName.XObject);
var ks = pageXObjects.KeySet();
PdfName imgRef = pageXObjects.KeySet().First();
PdfStream imgStream = pageXObjects.GetAsStream(imgRef);
PdfImageXObject imgObject = new PdfImageXObject((PdfStream)imgStream.CopyTo(resultDoc));
Image image = new Image(imgObject);
image.ScaleToFit(14400, 14400);
image.SetFixedPosition(0, 0);
srcDoc.Close();
PageSize pageSize = new PageSize(image.GetImageScaledWidth(), image.GetImageScaledHeight());
Document doc = new Document(resultDoc, pageSize);
doc.Add(image);
doc.Close();
}
public static void MainBox()
{
new ImageExtractor7().ManipulatePdf(DEST);
}
protected void ManipulatePdfBox(String dest)
{
PDDocument document = null;
document = PDDocument.load(SRC);
java.util.List pages = document.getDocumentCatalog().getAllPages();
Iterator iter = pages.iterator();
while (iter.hasNext())
{
PDPage page = (PDPage)iter.next();
PDResources resources = page.getResources();
Map pageImages = resources.getImages();
if (pageImages != null)
{
Iterator imageIter = pageImages.keySet().iterator();
while (imageIter.hasNext())
{
String key = (String)imageIter.next();
PDXObjectImage image = (PDXObjectImage)pageImages.get(key);
//image.write2OutputStream(/* some output stream */);
}
}
}
}
}
}
+93
View File
@@ -0,0 +1,93 @@
using System;
using System.Drawing;
using System.IO;
using System.Runtime.CompilerServices;
using Pastel;
namespace ComicCompressor
{
public class Logger
{
public LogLevel LoggingLevel { get; set; } = LogLevel.Warning;
public bool Debug = false;
// Set to null for stdout
public string LogFile { get; set; } = null;
public void Log(object message, LogLevel logLevel, Color? col = null)
{
Log(message.ToString(), logLevel, col:col);
}
public void Log(string message, LogLevel logLevel,bool newLine = true, Color? col = null)
{
if (logLevel > LoggingLevel) return;
if (LogFile == null)
{
if(newLine)
Console.WriteLine(message.Pastel(col ?? Color.FromArgb(0,204,204,204)));
else
Console.Write(message.Pastel(col ?? Color.FromArgb(0, 204, 204, 204)));
return;
}
WriteToFile(message);
}
public void LogError(string message, Color? col = null)
{
Log(message, LogLevel.Error, col:col);
}
public void LogError(object message, Color? col = null)
{
Log(message, LogLevel.Error, col:col);
}
public void LogDebug(string message, LogLevel logLevel, Color? col = null)
{
if (!Debug) return;
Log(message, logLevel,col:col);
}
public void LogDebug(object message, LogLevel logLevel, Color? col = null)
{
LogDebug(message.ToString(), logLevel, col:col);
}
[MethodImpl(MethodImplOptions.Synchronized)]
private void WriteToFile(string message)
{
if (LogFile == null) return;
if (!Path.IsPathRooted(LogFile) && !LogFile.StartsWith("."))
{
LogFile = Path.Join(".", LogFile);
}
try
{
Directory.CreateDirectory(Path.GetDirectoryName(LogFile));
File.AppendAllText(Path.GetFullPath(LogFile), message + "\n");
}
catch (UnauthorizedAccessException)
{
Console.WriteLine("Error: User does not have permissions for log file: " + LogFile);
return;
}
catch (Exception)
{
Console.WriteLine("Error: could not write to log file: " + LogFile);
return;
}
}
}
public enum LogLevel
{
Error = 2, Warning = 3, Info = 4, Verbose = 5
}
}
+552
View File
@@ -0,0 +1,552 @@
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("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
}
}
+24 -3
View File
@@ -1,4 +1,25 @@
# comic_img-compress
#comic-img-compress
**#** About
A simple command line interface to change .cbr and .cbz comic files with png or jpeg images to webp, improving compression markedly
The tool is able to convert also single jpg or png images to webp
# Setup
This project uses [libwebp-net](https://github.com/imazen/libwebp-net), in order to use download the libwebp.dll from [here](https://s3.amazonaws.com/resizer-dynamic-downloads/webp/0.5.2/x86_64/libwebp.dll) and add to the project root
# Running
Run `dotnet run -- [args]`
`dotnet run -- --help` for help
```
-i|--input <FOLDER/FIlE> The file or folder to convert
-o|--output <FOLDER> Base path of the output (will be in output/subfolders if the recursive option is enabled), default: converted_comics
-r|--recursive Recursively traverse the input folder (include all subfolders)
-s|--skip Skip processing file if it already exists in the output folder
-q|--quality Quality to use for the webp files (default: 75)
-p|--parallel Run in parallel, utilizing all computing resources
-?|-h|--help Show help information
```
# Building
Run `dotnet publish -r win-x64 -c Release`
.NET console tool to compress CBZ, CBR archives with jpeg or png files to CBZ with WEBP converted images
The tool is able to convert also single jpg or png images to webp