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, filename); 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(); 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(".ppm") && !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, string inputPath) { //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(); 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; Program.file_stat fs = null; Program.File_statistics.TryGetValue(inputPath,out fs); if(fs != null) { fs.Uncompressed_file_len = archive.TotalSize; fs.Compressed_file_len = output_length; Program.File_statistics.TryAdd(inputPath, fs); } return (save, nop); } private (bool, bool) ProcessEntry(IArchiveEntry entry, IList 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(); bool isPPM = entry_key_lower.EndsWith(".ppm"); if (!entry_key_lower.EndsWith(".jpg") && !entry_key_lower.EndsWith(".png") && !entry_key_lower.EndsWith(".bmp") && !entry_key_lower.EndsWith(".ppm") && !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 && !isPPM) { 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 { if(isPPM) { Logger.Log("P", LogLevel.Verbose, false, col: Color.PaleGreen); PixelMap ppm = new PixelMap(raw_ms); //bits = ppm.Bitmap; bits = ppm.BitMap; Logger.Log("\b", LogLevel.Verbose, false, col: Color.PaleGreen); } else { 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 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); } } }