diff --git a/ComicCompress.csproj.user b/ComicCompress.csproj.user index 80919ff..a835e16 100644 --- a/ComicCompress.csproj.user +++ b/ComicCompress.csproj.user @@ -4,7 +4,7 @@ ProjectDebugger - ComicCompress PDF + ComicCompress ProjectDebugger diff --git a/Compressor.cs b/Compressor.cs index a32bf9b..5bf7d75 100644 --- a/Compressor.cs +++ b/Compressor.cs @@ -88,7 +88,7 @@ namespace ComicCompressor } } else - (save, num_pages) = ProcessArchive(archive, outputFile); + (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); @@ -124,6 +124,7 @@ namespace ComicCompressor 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")) { @@ -149,7 +150,6 @@ namespace ComicCompressor } ProcessReaderEntry(reader, entryStream, imageStreams, output, encoder); - } } } @@ -175,7 +175,7 @@ namespace ComicCompressor { return ((value % (place * 10)) - (value % place)) / place; } - private (double, int) ProcessArchive(IArchive archive, string outputPath) + 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); @@ -222,6 +222,17 @@ namespace ComicCompressor 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); } @@ -244,11 +255,13 @@ namespace ComicCompressor { 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")) { @@ -260,7 +273,7 @@ namespace ComicCompressor return (false, true); } - if (cover) + if (cover && !isPPM) { var ms = new MemoryStream(); imageStreams.Add(ms); @@ -276,7 +289,18 @@ namespace ComicCompressor stream.CopyTo(raw_ms); try { - bits = new Bitmap(raw_ms); + 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) { diff --git a/PixelMap.cs b/PixelMap.cs new file mode 100644 index 0000000..c475c4d --- /dev/null +++ b/PixelMap.cs @@ -0,0 +1,536 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Imaging; +using System.IO; +using System.Runtime.InteropServices; +using System.Text; + +//using Microsoft.Xna.Framework; +//using Microsoft.Xna.Framework.Graphics; + +//using XnaColor = Microsoft.Xna.Framework.Graphics.Color; +//using XnaRectangle = Microsoft.Xna.Framework.Rectangle; +using SysColor = System.Drawing.Color; +using SysRectangle = System.Drawing.Rectangle; + +namespace ComicCompressor +{ + /// + /// This class provides a managed C# abstraction for Portable Bit Map (.pbm), Portable Grey Map (.pgm) and + /// Portable Pixel Map (.ppm)images. The class is able to handle P1 (ASCII .pbm), P2 (ASCII .pgm) P3 (ASCII .ppm), + /// P5 (binary .pgm), and P6 (binary .ppm) files and streams. P4 (binary .pbm) is not supported. The class can + /// process normal images, which have widths that are multiples of 4 pixels wide, and can also handle "off-size" + /// images using a less efficient algorithm. It presents .pbm and .pgm images using the 8bppIndexed pixel format, + /// and presents .ppm images using the 24bppRGB pixel format. It can convert these images into a visually pleasing + /// greyscale bitmap image (in a less efficient 24bppRGB pixel format), which is accessible using the GreyMap + /// property. + /// + /// To use this class: + /// 1. Copy the PixelMap.cs file into your own project. + /// 2. Change the namespace of the class (above this summary) from "PixelMap" to the namespace of your project. + /// 3. Compile. + /// + /// This class is used by the PixelMapViewer application. + /// + public class PixelMap + { + private PixelMapHeader header; + /// + /// The header portion of the PixelMap. + /// + public PixelMapHeader Header + { + get { return header; } + set { header = value; } + } + + private byte[] imageData; + /// + /// The data portion of the PixelMap. + /// + public byte[] ImageData + { + get { return imageData; } + set { imageData = value; } + } + + private PixelFormat pixelFormat; + /// + /// The pixel format used by the BitMap generated by this PixelMap. + /// + public PixelFormat PixelFormat + { + get { return pixelFormat; } + } + + private int bytesPerPixel; + /// + /// The number of bytes per pixel. + /// + public int BytesPerPixel + { + get { return bytesPerPixel; } + } + + private int stride; + /// + /// The stride of the scan across the image. Typically this is width * bytesPerPixel, and is a multiple of 4. + /// + public int Stride + { + get { return stride; } + set { stride = value; } + } + + private Bitmap bitmap; + /// + /// The Bitmap created from the PixelMap. + /// + public Bitmap BitMap + { + get { return bitmap; } + } + + /// + /// A 24bpp Grey map (really only 8bpp of information, since R,G,B bytes are identical) created from the PixelMap. + /// + public Bitmap GreyMap + { + get { return CreateGreyMap(); } + } + + /// + /// File-based constructor. + /// + /// The name of the .pbm, .pbm, or .ppm file. + public PixelMap(string filename) + { + if (File.Exists(filename)) + { + FileStream stream = new FileStream(filename, FileMode.Open); + this.FromStream(stream); + stream.Close(); + } + else + { + throw new FileNotFoundException("The file " + filename + " does not exist", filename); + } + } + + /// + /// Stream-based constructor. Typically, the stream will be a FileStream, but it may also be a MemoryStream + /// or other object derived from the Stream class. + /// + /// A stream containing the header and data portions of the .pbm, .pgm, or .ppm image. + public PixelMap(Stream stream) + { + this.FromStream(stream); + } + + private void FromStream(Stream stream) + { + int index; + this.header = new PixelMapHeader(); + int headerItemCount = 0; + stream.Position = 0; + BinaryReader binReader = new BinaryReader(stream, Encoding.ASCII); + try + { + //1. Read the Header. + while (headerItemCount < 4) + { + char nextChar = (char)binReader.PeekChar(); + if (nextChar == '#') // comment + { + while (binReader.ReadChar() != '\n') ; // ignore the rest of the line. + } + else if (Char.IsWhiteSpace(nextChar)) // whitespace + { + binReader.ReadChar(); // ignore whitespace + } + else + { + switch (headerItemCount) + { + case 0: // next item is Magic Number + // Read the first 2 characters and determine the type of pixelmap. + char[] chars = binReader.ReadChars(2); + this.header.MagicNumber = chars[0].ToString() + chars[1].ToString(); + headerItemCount++; + break; + case 1: // next item is the width. + this.header.Width = ReadValue(binReader); + headerItemCount++; + break; + case 2: // next item is the height. + this.header.Height = ReadValue(binReader); + headerItemCount++; + break; + case 3: // next item is the depth. + if (this.header.MagicNumber == "P1" | this.header.MagicNumber == "P4") + { + // no depth value for PBM type. + headerItemCount++; + } + else + { + this.header.Depth = ReadValue(binReader); + headerItemCount++; + } + break; + default: + throw new Exception("Error parsing the file header."); + } + } + } + + // 2. Read the image data. + // 2.1 Size the imageData array to hold the image bytes. + switch (this.header.MagicNumber) + { + case "P1": // 1 byte per pixel + this.pixelFormat = PixelFormat.Format8bppIndexed; + this.bytesPerPixel = 1; + break; + case "P2": // 1 byte per pixel + this.pixelFormat = PixelFormat.Format8bppIndexed; + this.bytesPerPixel = 1; + break; + case "P3": // 3 bytes per pixel + this.pixelFormat = PixelFormat.Format24bppRgb; + this.bytesPerPixel = 3; + break; + case "P4": + throw new Exception("Binary .pbm (Magic Number P4) is not supported at this time."); + case "P5": // 1 byte per pixel + this.pixelFormat = PixelFormat.Format8bppIndexed; + this.bytesPerPixel = 1; + break; + case "P6": // 3 bytes per pixel + this.pixelFormat = PixelFormat.Format24bppRgb; + this.bytesPerPixel = 3; + break; + default: + throw new Exception("Unknown Magic Number: " + this.header.MagicNumber); + } + this.imageData = new byte[this.header.Width * this.header.Height * this.bytesPerPixel]; + this.stride = this.header.Width * this.bytesPerPixel; + //while ((this.stride % 4) != 0) + //{ + // this.stride++; + //} + if (this.header.MagicNumber == "P1" | this.header.MagicNumber == "P2" | this.header.MagicNumber == "P3") // ASCII Encoding + { + int charsLeft = (int)(binReader.BaseStream.Length - binReader.BaseStream.Position); + char[] charData = binReader.ReadChars(charsLeft); // read all the data into an array in one go, for efficiency. + string valueString = string.Empty; + index = 0; + for (int i = 0; i < charData.Length; i++) + { + if (Char.IsWhiteSpace(charData[i])) // value is ignored if empty, or converted to byte and added to array otherwise. + { + if (valueString != string.Empty) + { + this.imageData[index] = (byte)int.Parse(valueString); + valueString = string.Empty; + index++; + } + } + else // add the character to the end of the valueString. + { + valueString += charData[i]; + } + } + } + else // binary encoding. + { + int bytesLeft = (int)(binReader.BaseStream.Length - binReader.BaseStream.Position); + this.imageData = binReader.ReadBytes(bytesLeft); + } + + // 3. We need to change the byte order of this.imageData from BGR to RGB. + ReorderBGRtoRGB(); + + // 4. Create the BitMap + if (this.stride % 4 == 0) + { + this.bitmap = CreateBitMap(); + } + else + { + this.bitmap = CreateBitmapOffSize(); + } + + // 5. Rotate the BitMap by 180 degrees so it is in the same orientation as the original image. + this.bitmap.RotateFlip(RotateFlipType.Rotate180FlipNone); + } + + // If the end of the stream is reached before reading all of the expected values raise an exception. + catch (EndOfStreamException e) + { + Console.WriteLine(e.Message); + throw new Exception("Error reading the stream! ", e); + } + catch (Exception ex) + { + Console.WriteLine(ex.Message); + throw new Exception("Error reading the stream! ", ex); + } + finally + { + binReader.Close(); + } + } + + /// + /// As it stands, the native byte order in .pbm, .pgm, and .ppm images is BGR. We need to reverse the order + /// into RGB as this is what is needed for 24bppRGB bitmap pixel format on Windows. + /// + private void ReorderBGRtoRGB() + { + byte[] tempData = new byte[this.imageData.Length]; + for (int i = 0; i < this.imageData.Length; i++) + { + tempData[i] = this.imageData[this.imageData.Length - 1 - i]; + } + this.imageData = tempData; + } + + private int ReadValue(BinaryReader binReader) + { + string value = string.Empty; + while (!Char.IsWhiteSpace((char)binReader.PeekChar())) + { + value += binReader.ReadChar().ToString(); + } + binReader.ReadByte(); // get rid of the whitespace. + return int.Parse(value); + } + + private Bitmap CreateBitMap() + { + IntPtr pImageData = Marshal.AllocHGlobal(this.imageData.Length); + Marshal.Copy(this.imageData, 0, pImageData, this.imageData.Length); + Bitmap bitmap = new Bitmap(this.header.Width, this.header.Height, this.stride, this.pixelFormat, pImageData); + return bitmap; + } + + private Bitmap CreateGreyMap() + { + byte[] greyData = new byte[this.header.Width * this.header.Height * 3]; + int stride = this.header.Width * 3; + //while (stride % 4 != 0) + //{ + // stride++; + //} + if (stride % 4 != 0) + { + return CreateGreyMapOffSize(greyData); + } + byte grey; + switch (this.pixelFormat) + { + case PixelFormat.Format24bppRgb: + int red, green, blue; + for (int i = 0; i < this.imageData.Length; i = i + 3) + { + red = (int)this.imageData[i]; + green = (int)this.imageData[i + 1]; + blue = (int)this.imageData[i + 2]; + grey = (byte)((red + green + blue) / 3); + greyData[i] = grey; + greyData[i + 1] = grey; + greyData[i + 2] = grey; + } + break; + case PixelFormat.Format8bppIndexed: + int index = 0; + for (int i = 0; i < this.imageData.Length; i++) + { + index = 3 * i; + grey = this.imageData[i]; + if (this.header.MagicNumber == "P1") + { + if ((int)grey == 1) + { + grey = (byte)255; + } + } + greyData[index] = grey; + greyData[index + 1] = grey; + greyData[index + 2] = grey; + } + break; + default: + throw new Exception("Unsupported Pixel Format: " + this.pixelFormat.ToString()); + } + IntPtr pGreyData = Marshal.AllocHGlobal(greyData.Length); + Marshal.Copy(greyData, 0, pGreyData, greyData.Length); + Bitmap bitmap = new Bitmap(this.header.Width, this.header.Height, stride, PixelFormat.Format24bppRgb, pGreyData); + bitmap.RotateFlip(RotateFlipType.Rotate180FlipNone); + return bitmap; + } + + /// + /// This method is able to handle the process of creating Bitmaps that are not sized in widths that are multiples + /// of 4 pixels (which is needed for the rapid drawing using the Marshal class.) Unfortunately, this is a very slow + /// method, since it uses SetPixel(). + /// + /// + private Bitmap CreateBitmapOffSize() + { + Bitmap bitmap = new Bitmap(this.header.Width, this.header.Height, PixelFormat.Format24bppRgb); + SysColor sysColor = new SysColor(); + int red, green, blue, grey; + int index; + + for (int x = 0; x < this.header.Width; x++) + { + for (int y = 0; y < this.header.Height; y++) + { + index = x + y * this.header.Width; + + switch (this.header.MagicNumber) + { + case "P1": + grey = (int)this.imageData[index]; + if (grey == 1) + { + grey = 255; + } + sysColor = SysColor.FromArgb(grey, grey, grey); + break; + case "P2": + grey = (int)this.imageData[index]; + sysColor = SysColor.FromArgb(grey, grey, grey); + break; + case "P3": + index = 3 * index; + blue = (int)this.imageData[index]; + green = (int)this.imageData[index + 1]; + red = (int)this.imageData[index + 2]; + sysColor = SysColor.FromArgb(red, green, blue); + break; + case "P4": + break; + case "P5": + grey = (int)this.imageData[index]; + sysColor = SysColor.FromArgb(grey, grey, grey); + break; + case "P6": + index = 3 * index; + blue = (int)this.imageData[index]; + green = (int)this.imageData[index + 1]; + red = (int)this.imageData[index + 2]; + sysColor = SysColor.FromArgb(red, green, blue); + break; + default: + break; + } + bitmap.SetPixel(x, y, sysColor); + } + } + return bitmap; + } + + private Bitmap CreateGreyMapOffSize(byte[] greyData) + { + Bitmap bitmap = new Bitmap(this.header.Width, this.header.Height, PixelFormat.Format24bppRgb); + SysColor sysColor = new SysColor(); + int index = 0; + int x = 0; + int y = -1; + switch (this.pixelFormat) + { + case PixelFormat.Format24bppRgb: + int red, green, blue, grey; + for (int i = 0; i < this.imageData.Length; i = i + 3) + { + index = i / 3; // the current pixel + if (index%this.header.Width == 0) y++; // the current row = y; + x = index - y * this.header.Width; // the current column = x; + red = (int)this.imageData[i]; + green = (int)this.imageData[i + 1]; + blue = (int)this.imageData[i + 2]; + grey = (byte)((red + green + blue) / 3); + sysColor = SysColor.FromArgb(grey, grey, grey); + bitmap.SetPixel(x, y, sysColor); + } + break; + case PixelFormat.Format8bppIndexed: + for (int i = 0; i < this.imageData.Length; i++) + { + index = i; // the current pixel + if (index % this.header.Width == 0) y++; // the current row = y; + x = index - y * this.header.Width; // the current column = x; + + grey = this.imageData[i]; + if (this.header.MagicNumber == "P1") + { + if ((int)grey == 1) + { + grey = (byte)255; + } + } + sysColor = SysColor.FromArgb(grey, grey, grey); + bitmap.SetPixel(x, y, sysColor); + } + break; + default: + throw new Exception("Unsupported Pixel Format: " + this.pixelFormat.ToString()); + } + bitmap.RotateFlip(RotateFlipType.Rotate180FlipNone); + return bitmap; + } + + /// + /// This struct contains the objects that are found in the header of .pbm, .pgm, and .ppm files. + /// + [Serializable] + public struct PixelMapHeader + { + private string magicNumber; + /// + /// The "Magic Number" that identifies the type of Pixelmap. P1 = PBM (ASCII); P2 = PGM (ASCII); P3 = PPM (ASCII); P4 is not used; + /// P5 = PGM (Binary); P6 = PPM (Binary). + /// + public string MagicNumber + { + get { return magicNumber; } + set { magicNumber = value; } + } + + private int width; + /// + /// The width of the image. + /// + public int Width + { + get { return width; } + set { width = value; } + } + + private int height; + /// + /// The height of the image. + /// + public int Height + { + get { return height; } + set { height = value; } + } + + private int depth; + /// + /// The depth (maximum color value in each channel) of the image. This allows the format to represent + /// more than a single byte (0-255) for each color channel. + /// + public int Depth + { + get { return depth; } + set { depth = value; } + } + } + } +} diff --git a/Program.cs b/Program.cs index d46533c..6c5960b 100644 --- a/Program.cs +++ b/Program.cs @@ -25,6 +25,10 @@ using org.omg.PortableInterceptor; using org.apache.pdfbox.util.@operator; using java.nio; using ImageMagick; +using javax.xml.transform; +using System.Collections.Concurrent; +using iText.Barcodes.Dmcode; +using ZstdSharp; namespace ComicCompressor { @@ -32,6 +36,7 @@ namespace ComicCompressor { public static int Main(string[] args) => CommandLineApplication.Execute(args); + #region INPUT_FLAGS [FileOrDirectoryExists] [Option("-i|--input ", Description = "The file or folder to convert")] [Required] @@ -63,15 +68,50 @@ namespace ComicCompressor [Option("-j|--image", Description = "process images only")] public bool DoImage { get; } = false; + #endregion + + #region STATISTICS + + public class file_stat + { + public string Filename { get; set; } + public file_stat(string filename) { + Filename = filename; + } + public long Uncompressed_file_len { get; set; } + public long Compressed_file_len { get; set; } + + + } + + static Dictionary file_statistics = new Dictionary(); + public static Dictionary File_statistics { get => file_statistics; set => file_statistics = value; } + + #endregion private Logger Logger { get; set; } SimpleEncoder encoder = new SimpleEncoder(); private List imageStreams = null; public ZipArchive output = null; + + static Random randomGen = new Random(); + static KnownColor[] names = (KnownColor[])Enum.GetValues(typeof(KnownColor)); +// static KnownColor randomColorName = names[randomGen.Next(names.Length)]; +// static Color randomColor = Color.FromKnownColor(randomColorName); + + public Color[] myColors = null; private void OnExecute() { + File_statistics.Clear(); + + myColors = new Color[names.Count()]; + for(int i = 0; i< myColors.Length; i++) + { + KnownColor randomColorName = names[randomGen.Next(names.Length)]; + myColors[i] = Color.FromKnownColor(randomColorName); + } //ImageExtractor7.MainBox(); Stopwatch sw = Stopwatch.StartNew(); @@ -99,6 +139,7 @@ namespace ComicCompressor foreach (var file in files) { + File_statistics.Add(file, new file_stat(file)); if(file.Length > filename_len) filename_len = file.Length; } @@ -115,6 +156,25 @@ namespace ComicCompressor n++; } + long tot_uncomp = 0; + long tot_comp = 0; + + foreach (var file in file_statistics) + { + long uncomp = file.Value.Uncompressed_file_len; + long comp = file.Value.Compressed_file_len; + if (uncomp != 0 && comp != 0) + { + Logger.Log($"{file.Value.Filename} U[{uncomp}] C[{comp}]", LogLevel.Verbose, true, Color.Pink); + tot_uncomp += uncomp; + tot_comp += comp; + } + } + if (tot_uncomp != 0) + { + double save = 100 * (double)tot_comp / (double)tot_uncomp; + Logger.Log($"TU[{tot_uncomp}] TC[{tot_comp}] R%[{save,5:##.00}] S[{(tot_uncomp-tot_comp)/1024.0/1024.0,5:##.00} MB]", LogLevel.Verbose, true, Color.Pink); + } return; } @@ -122,37 +182,107 @@ namespace ComicCompressor { MaxDegreeOfParallelism = (NumOfCpu > Environment.ProcessorCount) ? Environment.ProcessorCount : NumOfCpu }; +#if true + ParallelQuery source = Partitioner.Create(files) + .AsParallel() + .AsOrdered(); - Parallel.ForEach(files, options, file => CompressTask(compressor, file, filename_len, true)); + Parallel.ForEach(source, options, (file, state, index) => CompressTask(compressor, file, filename_len, true, state, index, files.Count)); +#else + Parallel.ForEach(files, options, (file, state, index) => CompressTask(compressor, file, filename_len, true, state, index+1, files.Count)); + +#endif + + + foreach (var file in file_statistics) + { + Logger.Log($"{file.Value.Filename} U[{file.Value.Uncompressed_file_len}] C[{file.Value.Compressed_file_len}]", LogLevel.Verbose, true, Color.Pink); + } } - private void CompressTask(Compressor compressor, string filename, int max_name_len = -1, Boolean isPar = false) + private void CompressTask(Compressor compressor, string filename, int max_name_len = -1, Boolean isPar = false, ParallelLoopState state = null, long index = 0, long total = 0) { 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")); + int digits = 1+(int)Math.Floor(Math.Log((Double)total) / Math.Log(10.0)); + + string print_line_header = ""; + switch (digits) + { + case 1: + print_line_header = $"{index,1}/{total,1} - "; + break; + case 2: + print_line_header = $"{index,2}/{total,2} - "; + break; + case 3: + print_line_header = $"{index,3}/{total,3} - "; + break; + case 4: + print_line_header = $"{index,4}/{total,4} - "; + break; + case 5: + print_line_header = $"{index,5}/{total,5} - "; + break; + } if (File.Exists(outputPath) && Skip) { + long lengthin = new System.IO.FileInfo(filename).Length; + long lengthout = new System.IO.FileInfo(outputPath).Length; + Program.file_stat fs = null; + Program.File_statistics.TryGetValue(filename, out fs); + if (fs != null) + { + fs.Uncompressed_file_len = lengthin; + fs.Compressed_file_len = lengthout; + Program.File_statistics.TryAdd(filename, fs); + } + + Logger.Log(outputPath, LogLevel.Verbose, true, Color.Yellow); return; } if (DoImage && Skip && File.Exists(outputPath_webp)) { + long lengthin = new System.IO.FileInfo(filename).Length; + long lengthout = new System.IO.FileInfo(outputPath).Length; + Program.file_stat fs = null; + Program.File_statistics.TryGetValue(filename, out fs); + if (fs != null) + { + fs.Uncompressed_file_len = lengthin; + fs.Compressed_file_len = lengthout; + Program.File_statistics.TryAdd(filename, fs); + } + Logger.Log(outputPath_webp, LogLevel.Verbose, true, Color.Yellow); return; } if (DoPdf && filename.ToLower().EndsWith(".pdf")) { - ExtractImageFromPDF(filename, outputPath); + + ExtractImageFromPDF(compressor,filename, outputPath); + + long lengthin = new System.IO.FileInfo(filename).Length; + long lengthout = new System.IO.FileInfo(outputPath).Length; + Program.file_stat fs = null; + Program.File_statistics.TryGetValue(filename, out fs); + if (fs != null) + { + fs.Uncompressed_file_len = lengthin; + fs.Compressed_file_len = lengthout; + Program.File_statistics.TryAdd(filename, fs); + } //compressor.CompressPdf(filename, outputPath); return; } - if (DoPdf) - return; + //if (DoPdf) + // return; if (DoImage && filename.ToLower().EndsWith("jpg") || @@ -161,7 +291,18 @@ namespace ComicCompressor filename.ToLower().EndsWith("png")) { outputPath = Path.Join(OutputFolder, Path.ChangeExtension(relativePath, "webp")); - CompressImage(filename, outputPath, max_name_len,isPar); + CompressImage(filename, outputPath, max_name_len,isPar, print_line_header); + + long lengthin = new System.IO.FileInfo(filename).Length; + long lengthout = new System.IO.FileInfo(outputPath).Length; + Program.file_stat fs = null; + Program.File_statistics.TryGetValue(filename, out fs); + if (fs != null) + { + fs.Uncompressed_file_len = lengthin; + fs.Compressed_file_len = lengthout; + Program.File_statistics.TryAdd(filename, fs); + } return; } @@ -288,7 +429,7 @@ namespace ComicCompressor } return ""; } -#else +#elif false public int GetPlace(int value, int place) { return ((value % (place * 10)) - (value % place)) / place; @@ -437,18 +578,64 @@ namespace ComicCompressor //Logger.Log(sw.ElapsedMilliseconds.ToString(), LogLevel.Verbose); } +#else + public void ExtractImageFromPDF(Compressor compressor, string sourcePdf, string outputFolder) + { + + //pdfimages.exe -j "Dragon Ball Full Color [Volume 1].pdf" "Dragon Ball Full Color [Volume 1]\img" + //7za a -r -mx0 -tzip "Dragon Ball Full Color [Volume 1]" "Dragon Ball Full Color [Volume 1]\*" + + + string subfolder = Path.GetFileNameWithoutExtension(sourcePdf); + string folder = Path.GetDirectoryName(sourcePdf); + string tempfolder = folder + "\\" + subfolder; + Directory.CreateDirectory(tempfolder); + + Logger.Log(sourcePdf, LogLevel.Verbose, true, Color.Green); + + ProcessStartInfo startInfo_pdfimages = new ProcessStartInfo("pdfimages.exe"); + startInfo_pdfimages.Arguments = $@" -j ""{sourcePdf}"" ""{tempfolder}\img"""; + startInfo_pdfimages.UseShellExecute = false; + Process pdfimages = System.Diagnostics.Process.Start(startInfo_pdfimages); + pdfimages.WaitForExit(); + + string zipFile = tempfolder + ".zip"; + ProcessStartInfo startInfo_7za = new ProcessStartInfo("7za.exe"); + startInfo_7za.Arguments = $@" a -r -mx0 -tzip -sdel ""{zipFile}"" ""{tempfolder}\*"" > $null 2>&1"; + startInfo_7za.UseShellExecute = false; + startInfo_7za.RedirectStandardOutput = true; + Process zip7za = System.Diagnostics.Process.Start(startInfo_7za); + zip7za.WaitForExit(); + Directory.Delete(tempfolder); + + if (File.Exists(outputFolder) && Skip) + { + Logger.Log(outputFolder, LogLevel.Verbose, true, Color.Yellow); + return; + } + + try + { + compressor.Compress(zipFile, outputFolder); + } + catch (Exception e) + { + Logger.Log("Error processing file: " + zipFile, LogLevel.Error, Color.Red); + Logger.LogDebug(e, LogLevel.Error, col: Color.Red); + } + } #endif -#endregion + #endregion #region COMPRESS_IMAGE - public void CompressImage(string imagefile, string outputFolder, int max_name_len = -1, Boolean isPar = false) + public void CompressImage(string imagefile, string outputFolder, int max_name_len = -1, Boolean isPar = false, string print_line_header = "") { 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); + Logger.Log(print_line_header+name_print, LogLevel.Verbose, false, col:Color.LightBlue); try { @@ -482,13 +669,34 @@ namespace ComicCompressor 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); + { +#if false + Logger.Log($"{print_line_header}[{Environment.CurrentManagedThreadId}]{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 + lock (Logger) + { + Logger.Log($"{print_line_header}[{Environment.CurrentManagedThreadId,2}] ", LogLevel.Verbose, false, col: myColors[Environment.CurrentManagedThreadId]); + 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); + } +#endif + } 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); + 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); + lock (Logger) + { + if (isPar) + { + Logger.Log($"{print_line_header}[{Environment.CurrentManagedThreadId}]{name_print} ERROR", LogLevel.Verbose, true, col: Color.Red); + } + else + { + Logger.Log($" ERROR", LogLevel.Verbose, true, col: Color.Red); + } + Logger.LogError(e, col: Color.Red); + } } } @@ -548,6 +756,6 @@ namespace ComicCompressor } } - #endregion +#endregion } } diff --git a/Properties/launchSettings.json b/Properties/launchSettings.json index 017566b..d1a7267 100644 --- a/Properties/launchSettings.json +++ b/Properties/launchSettings.json @@ -6,7 +6,7 @@ }, "ComicCompress PDF": { "commandName": "Project", - "commandLineArgs": "-i _PDF_ -o _NEW_PDF_ -r -s " + "commandLineArgs": "-i _PDF_ -o _NEW_PDF_ -r -s -d" }, "Compress Image to webp": { "commandName": "Project", @@ -15,6 +15,10 @@ "Compress Image to WEBP PAR": { "commandName": "Project", "commandLineArgs": "-i _IMG_ -o _IMG_WEBP_ -r -s -j -p -c 4" + }, + "ComicCompress": { + "commandName": "Project", + "commandLineArgs": "-i _IMG_ -o _IMG_WEBP_ -r -s " } } } \ No newline at end of file