Compare commits
5 Commits
4cc0732a8a
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| a39b2bd253 | |||
| 57e3d6ed45 | |||
| 94f3fab41c | |||
| 7661cf506d | |||
| c58f13d77a |
@@ -0,0 +1,75 @@
|
|||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||||
|
|
||||||
|
## Solution Structure
|
||||||
|
|
||||||
|
Three projects in `ComicCompress.sln`, all targeting .NET 8.0:
|
||||||
|
|
||||||
|
- **ComicCompress** (this directory) — Console application entry point. `Program.cs` handles CLI parsing via `McMaster.Extensions.CommandLineUtils` and orchestrates processing.
|
||||||
|
- **iMGcompress-core** (`../iMGcompress-core`) — Class library containing all compression logic. Both the CLI and GUI depend on this.
|
||||||
|
- **iMGcompress** (`../iMGcompress`) — Windows Forms GUI (`net8.0-windows`) wrapping the core library.
|
||||||
|
|
||||||
|
## Build & Run
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
# Development build
|
||||||
|
dotnet build
|
||||||
|
|
||||||
|
# Run CLI (from project root)
|
||||||
|
dotnet run -- --help
|
||||||
|
dotnet run -- -i <input> -o <output> -q 75 -p
|
||||||
|
|
||||||
|
# Publish self-contained Windows executable
|
||||||
|
dotnet publish -r win-x64 -c Release
|
||||||
|
```
|
||||||
|
|
||||||
|
`launchSettings.json` defines named profiles (e.g. `PROD`, `DEV_HOME`, `Compress Image to webp`) for common invocations.
|
||||||
|
|
||||||
|
## CLI Flags
|
||||||
|
|
||||||
|
| Flag | Description |
|
||||||
|
|------|-------------|
|
||||||
|
| `-i` | Input file or folder (required) |
|
||||||
|
| `-o` | Output base path (default: `converted_comics`) |
|
||||||
|
| `-r` | Recurse into subfolders |
|
||||||
|
| `-s` | Skip if output already exists |
|
||||||
|
| `-q` | WebP quality 0–100 (default: 75) |
|
||||||
|
| `-p` | Parallel processing (all CPUs) |
|
||||||
|
| `-c` | Parallel with N CPUs |
|
||||||
|
| `-d` | PDF-only mode |
|
||||||
|
| `-j` | Image-only mode |
|
||||||
|
| `-n` | Zebra PRN mode (generates PNG + WebP Base64 URI) |
|
||||||
|
| `-a` | Copy unprocessed files to output |
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
**Data flow:** Input (`.cbr`/`.cbz` archives or loose images) → extract → convert each image to WebP → repack as `.cbz`.
|
||||||
|
|
||||||
|
### Core library (`iMGcompress-core`)
|
||||||
|
|
||||||
|
- **`Compressor.cs`** — Central engine. Reads RAR/ZIP archives via `SharpCompress`, converts images to WebP via `Imazen.WebP`, and writes output `.cbz`. Handles solid and non-solid archives separately. The first image in an archive (cover) is preserved without conversion.
|
||||||
|
- **`FileProcessor.cs`** — Orchestrates parallel or sequential processing over a file list. Inner class `ProcessorFlags` carries all options. Writes the final compression report.
|
||||||
|
- **`FileParser.cs`** — Recursively enumerates input files.
|
||||||
|
- **`ImageExtractor.cs`** — Implements `IRenderListener` (iText) to extract images from PDF pages and produce a `.cbz`.
|
||||||
|
- **`PixelMap.cs`** — Parses Portable Bitmap formats (PBM/PGM/PPM, variants P1–P6) into `System.Drawing.Bitmap` before WebP encoding.
|
||||||
|
- **`Logger.cs`** — Thread-safe colored console/file logger. `LogLevel`: Error, Warning, Info, Verbose.
|
||||||
|
- **`file_stat.cs`** — Simple DTO tracking per-file uncompressed/compressed sizes for statistics.
|
||||||
|
|
||||||
|
### Console app (`ComicCompress`)
|
||||||
|
|
||||||
|
`Program.cs` parses flags, builds a `ProcessorFlags`, calls `FileParser` to enumerate targets, then hands off to `FileProcessor`. Video (MP4→WebM) conversion uses `Xabe.FFmpeg`. Zebra PRN rendering uses `BinaryKits.Zpl.Viewer`.
|
||||||
|
|
||||||
|
## Key Dependencies
|
||||||
|
|
||||||
|
| Package | Purpose |
|
||||||
|
|---------|---------|
|
||||||
|
| `SharpCompress` | Read/write RAR and ZIP archives |
|
||||||
|
| `Imazen.WebP` + `libwebp.dll` | WebP encoding (requires `libwebp.dll` in output dir) |
|
||||||
|
| `Magick.NET-Q8-x64` | Advanced image format support (TIFF, etc.) |
|
||||||
|
| `iTextSharp` / `itext` | PDF parsing |
|
||||||
|
| `Xabe.FFmpeg` | Video conversion |
|
||||||
|
| `BinaryKits.Zpl.Viewer` | Zebra PRN label rendering |
|
||||||
|
| `McMaster.Extensions.CommandLineUtils` | CLI argument parsing |
|
||||||
|
|
||||||
|
**`libwebp.dll` must be present** alongside the executable at runtime — it is checked into the repo root.
|
||||||
+13
-13
@@ -7,6 +7,7 @@
|
|||||||
<OutputName>ComicCompress</OutputName>
|
<OutputName>ComicCompress</OutputName>
|
||||||
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
||||||
<Configurations>Debug;Release;ReleaseWDbg</Configurations>
|
<Configurations>Debug;Release;ReleaseWDbg</Configurations>
|
||||||
|
<Version>3.1.0.0</Version>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@@ -26,21 +27,20 @@
|
|||||||
|
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="BinaryKits.Zpl.Viewer" Version="1.2.1" />
|
<PackageReference Include="BinaryKits.Zpl.Viewer" Version="1.3.1" />
|
||||||
<PackageReference Include="GroupDocs.Parser" Version="23.8.0" />
|
<PackageReference Include="Imazen.WebP" Version="11.0.0" />
|
||||||
<PackageReference Include="Imazen.WebP" Version="10.0.1" />
|
<PackageReference Include="iTextSharp" Version="5.5.13.5" />
|
||||||
<PackageReference Include="itext7" Version="8.0.1" />
|
|
||||||
<PackageReference Include="iTextSharp" Version="5.5.13.4" />
|
|
||||||
<PackageReference Include="LibHeifSharp" Version="3.2.0" />
|
<PackageReference Include="LibHeifSharp" Version="3.2.0" />
|
||||||
<PackageReference Include="Magick.NET-Q8-x64" Version="13.6.0" />
|
<PackageReference Include="Magick.NET-Q8-x64" Version="14.14.0" />
|
||||||
<PackageReference Include="Magick.NET.SystemDrawing" Version="7.2.2" />
|
<PackageReference Include="Magick.NET.SystemDrawing" Version="8.0.23" />
|
||||||
<PackageReference Include="McMaster.Extensions.CommandLineUtils" Version="4.1.1" />
|
<PackageReference Include="McMaster.Extensions.CommandLineUtils" Version="5.1.0" />
|
||||||
<PackageReference Include="Pastel" Version="6.0.0" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
|
||||||
|
<PackageReference Include="Pastel" Version="7.0.1" />
|
||||||
<PackageReference Include="Pdfbox" Version="1.1.1" />
|
<PackageReference Include="Pdfbox" Version="1.1.1" />
|
||||||
<PackageReference Include="sharpcompress" Version="0.38.0" />
|
<PackageReference Include="sharpcompress" Version="0.49.1" />
|
||||||
<PackageReference Include="System.Drawing.Common" Version="9.0.0" />
|
<PackageReference Include="System.Drawing.Common" Version="10.0.9" />
|
||||||
<PackageReference Include="Xabe.FFmpeg" Version="5.2.6" />
|
<PackageReference Include="Xabe.FFmpeg" Version="6.0.2" />
|
||||||
<PackageReference Include="Xabe.FFmpeg.Downloader" Version="5.2.6" />
|
<PackageReference Include="Xabe.FFmpeg.Downloader" Version="6.0.2" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
Binary file not shown.
+66
@@ -0,0 +1,66 @@
|
|||||||
|
# HANDOFF.md
|
||||||
|
|
||||||
|
Current state of the comic compression toolchain as of 2026-06-28.
|
||||||
|
|
||||||
|
## Repositories
|
||||||
|
|
||||||
|
| Repo | Branch | Latest commit |
|
||||||
|
|------|--------|---------------|
|
||||||
|
| `comic-compress-new` (CLI) | main | `94f3fab` |
|
||||||
|
| `iMGcompress-core` (library) | master | `a03d0e5` |
|
||||||
|
| `iMGcompress` (GUI) | master | `7b7021a` |
|
||||||
|
|
||||||
|
## What was done in this session (v3.1.0 / v2.0.0)
|
||||||
|
|
||||||
|
### NuGet package updates (all three projects)
|
||||||
|
- SharpCompress `0.42` → `0.49.1` (CVE GHSA-6c8g-7p36-r338 fixed)
|
||||||
|
- Magick.NET-Q8-x64 → `14.14.0` (multiple CVEs fixed)
|
||||||
|
- Imazen.WebP → `11.0.0`
|
||||||
|
- Imageflow.AllPlatforms + Imageflow.Net → `0.15.1`
|
||||||
|
- iTextSharp → `5.5.13.5`, itext → `9.6.0`
|
||||||
|
- McMaster.Extensions.CommandLineUtils → `5.1.0`
|
||||||
|
- System.Drawing.Common → `10.0.9`
|
||||||
|
|
||||||
|
### SharpCompress 0.49 breaking API migration (`iMGcompress-core`)
|
||||||
|
- `ReaderFactory.Open` → `OpenReader(stream, new ReaderOptions())`
|
||||||
|
- `RarArchive/ZipArchive.Open` → `OpenArchive(filename, new ReaderOptions())`
|
||||||
|
- `ZipArchive.Create()` → `CreateArchive()`
|
||||||
|
- `SaveTo(path, CompressionType)` → `SaveTo(path, new ZipWriterOptions(CompressionType))`
|
||||||
|
- Return type `ZipArchive` → `IWritableArchive<ZipWriterOptions>`
|
||||||
|
- Removed `using ZstdSharp` (internalized in 0.49)
|
||||||
|
|
||||||
|
### Parallel within-archive compression (`iMGcompress-core`, `-P` flag)
|
||||||
|
- New `ProcessArchivePAR2` + `CompressEntrySync` in `Compressor.cs`
|
||||||
|
- Batched 3-phase design: sequential read → `Parallel.For` (`ProcessorCount/2` threads) → sequential write
|
||||||
|
- Ordering guaranteed by indexed pre-allocated result slots
|
||||||
|
- Measured speedup: **~3.4–5×** vs serial on i7-12700KF (10 batch size)
|
||||||
|
- Memory bounded per batch; logs strictly in original entry order from write phase
|
||||||
|
|
||||||
|
### MemoryStream pre-sizing (`iMGcompress-core`)
|
||||||
|
- All `new MemoryStream()` → `new MemoryStream(entry.Size > 0 ? (int)entry.Size : 4096)`
|
||||||
|
- Eliminates buffer-doubling reallocations on all 9 allocation sites in `Compressor.cs`
|
||||||
|
|
||||||
|
### CLI fixes and new flags (`ComicCompress`)
|
||||||
|
- Fixed missing `OnExecuteAsync` — app was throwing `InvalidOperationException` on launch
|
||||||
|
- Added `-w|--new-webp`: use Imageflow encoder (experimental; benchmarked 8–15% slower than Imazen.WebP)
|
||||||
|
- Added `-P|--par-archive`: enable parallel within-archive compression
|
||||||
|
|
||||||
|
### GUI (`iMGcompress`)
|
||||||
|
- Added `checkBox_parArchive` (flat-button, location `382,231`, TabIndex 14)
|
||||||
|
- Default ON for COMIX and COMIX+PDF modes; OFF for IMAGE and ZEBRA
|
||||||
|
- Wired into `FileProcessor.ProcessorFlags` constructor
|
||||||
|
- Version bumped to `2.0.0 (28/06/2026)`
|
||||||
|
|
||||||
|
### Other
|
||||||
|
- Added `CLAUDE.md` to `comic-compress-new` with build commands and architecture notes
|
||||||
|
|
||||||
|
## Current versions
|
||||||
|
|
||||||
|
- CLI (`ComicCompress`): `3.1.0 (28/06/2026)` — see `Program.cs`
|
||||||
|
- GUI (`iMGcompress`): `2.0.0 (28/06/2026)` — see `MGCompress.cs`
|
||||||
|
|
||||||
|
## Known state / open items
|
||||||
|
|
||||||
|
- **Imageflow encoder** (`-w` flag) is wired and working but consistently slower than Imazen.WebP across all tested archive sizes. Kept as an experimental path only.
|
||||||
|
- **`libwebp.dll`** must be present alongside the executable — it is checked into the repo root of both `comic-compress-new` and `iMGcompress-core`.
|
||||||
|
- The GUI runs single-threaded on the UI thread (`button_start_Click` is synchronous). Long jobs block the window. Not addressed in this session.
|
||||||
+40
-28
@@ -11,9 +11,9 @@ using System.Drawing;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
using GroupDocs.Parser;
|
//using GroupDocs.Parser;
|
||||||
using GroupDocs.Parser.Data;
|
//using GroupDocs.Parser.Data;
|
||||||
using iText.Kernel.Pdf;
|
//using iText.Kernel.Pdf;
|
||||||
|
|
||||||
using Imazen.WebP;
|
using Imazen.WebP;
|
||||||
|
|
||||||
@@ -30,7 +30,6 @@ using ImageMagick;
|
|||||||
using javax.xml.transform;
|
using javax.xml.transform;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using iText.Barcodes.Dmcode;
|
using iText.Barcodes.Dmcode;
|
||||||
using ZstdSharp;
|
|
||||||
using Xabe.FFmpeg.Downloader;
|
using Xabe.FFmpeg.Downloader;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using Microsoft.VisualBasic;
|
using Microsoft.VisualBasic;
|
||||||
@@ -63,7 +62,17 @@ namespace ComicCompressor
|
|||||||
//string progVersion = "Version 3.0.0 - (21/11/2024)";
|
//string progVersion = "Version 3.0.0 - (21/11/2024)";
|
||||||
|
|
||||||
// In core changed color of percent save to white
|
// In core changed color of percent save to white
|
||||||
string progVersion = "Version 3.0.1 - (23/01/2025)";
|
//string progVersion = "Version 3.0.1 - (23/01/2025)";
|
||||||
|
|
||||||
|
// Updated NuGet packages (SharpCompress 0.49, Magick.NET 14.14, System.Drawing.Common 10.0,
|
||||||
|
// itext 9.6, Imazen.WebP 11.0, McMaster 5.1). SharpCompress 0.49 API migration.
|
||||||
|
// Pre-sized MemoryStream allocations with entry.Size (reduced GC pressure).
|
||||||
|
// Fixed missing OnExecuteAsync (CLI was throwing InvalidOperationException on launch).
|
||||||
|
// Added -w|--new-webp flag (Imageflow encoder; benchmarked slower than Imazen.WebP).
|
||||||
|
// Added -x|--par-archive: parallel image compression within archive (~4-5x speedup).
|
||||||
|
// Batched 3-phase: sequential read → Parallel.For (ProcessorCount/2) → sequential write.
|
||||||
|
// GUI: added Par.Archive checkbox (default ON for COMIX/COMIX+PDF modes).
|
||||||
|
string progVersion = "Version 3.1.0 - (27/06/2027)";
|
||||||
public static int Main(string[] args) => CommandLineApplication.Execute<Program>(args);
|
public static int Main(string[] args) => CommandLineApplication.Execute<Program>(args);
|
||||||
|
|
||||||
#region INPUT_FLAGS
|
#region INPUT_FLAGS
|
||||||
@@ -103,6 +112,12 @@ namespace ComicCompressor
|
|||||||
[Option("-a|--copyall", Description = "Copy all unprocessed files")]
|
[Option("-a|--copyall", Description = "Copy all unprocessed files")]
|
||||||
public bool DoCopyAll { get; } = false;
|
public bool DoCopyAll { get; } = false;
|
||||||
|
|
||||||
|
[Option("-w|--new-webp", Description = "Use Imageflow encoder (faster JPEG→WebP transcoding, no full pixel decode)")]
|
||||||
|
public bool NewWebp { get; } = false;
|
||||||
|
|
||||||
|
[Option("-x|--par-archive", Description = "Parallel image compression within each archive (~4-5x faster, uses half the CPUs)")]
|
||||||
|
public bool ParArchive { get; } = false;
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region STATISTICS
|
#region STATISTICS
|
||||||
@@ -141,20 +156,30 @@ namespace ComicCompressor
|
|||||||
// });
|
// });
|
||||||
//}
|
//}
|
||||||
|
|
||||||
private void OnExecute()
|
|
||||||
|
FileProcessor.ProcessorFlags flags = new FileProcessor.ProcessorFlags(null, null,
|
||||||
|
recursive: false,
|
||||||
|
skip: false,
|
||||||
|
parallel: false,
|
||||||
|
pdf_only: false,
|
||||||
|
image_only: false,
|
||||||
|
zebra: false,
|
||||||
|
copy_all: false,
|
||||||
|
new_webp: false
|
||||||
|
);
|
||||||
|
|
||||||
|
private async Task OnExecuteAsync()
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
File_statistics.Clear();
|
File_statistics.Clear();
|
||||||
|
flags.New_webp = NewWebp;
|
||||||
|
flags.Par_archive = ParArchive;
|
||||||
|
|
||||||
myColors = new Color[names.Count()];
|
myColors = new Color[names.Count()];
|
||||||
for(int i = 0; i< myColors.Length; i++)
|
for (int i = 0; i < myColors.Length; i++)
|
||||||
{
|
{
|
||||||
KnownColor randomColorName = names[randomGen.Next(names.Length)];
|
KnownColor randomColorName = names[randomGen.Next(names.Length)];
|
||||||
myColors[i] = Color.FromKnownColor(randomColorName);
|
myColors[i] = Color.FromKnownColor(randomColorName);
|
||||||
}
|
}
|
||||||
//ImageExtractor7.MainBox();
|
|
||||||
|
|
||||||
Stopwatch sw = Stopwatch.StartNew();
|
Stopwatch sw = Stopwatch.StartNew();
|
||||||
|
|
||||||
@@ -164,28 +189,15 @@ namespace ComicCompressor
|
|||||||
|
|
||||||
Logger.Log(progVersion, LogLevel.Verbose, true);
|
Logger.Log(progVersion, LogLevel.Verbose, true);
|
||||||
|
|
||||||
//var progress = new Progress<ProgressInfo>( p => Logger.Log(p.ToString(), LogLevel.Verbose, true) );
|
|
||||||
//FFmpeg.SetExecutablesPath(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location));
|
|
||||||
//FFmpegDownloader.GetLatestVersion(FFmpegVersion.Official,progress);
|
|
||||||
|
|
||||||
|
|
||||||
//==//FFmpeg.SetExecutablesPath (Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "FFmpeg"));
|
|
||||||
|
|
||||||
//Logger.Log(FFmpeg.ExecutablesPath, LogLevel.Verbose, true);
|
|
||||||
|
|
||||||
|
|
||||||
IList<string> files = new FileParser().ListAllFiles(Input, Recursive);
|
IList<string> files = new FileParser().ListAllFiles(Input, Recursive);
|
||||||
|
|
||||||
Process(files);
|
await Process(files);
|
||||||
|
|
||||||
long time_query = sw.ElapsedMilliseconds;
|
long time_query = sw.ElapsedMilliseconds;
|
||||||
Logger.Log($"ElapsedTime {time_query} ms", LogLevel.Verbose, true);
|
Logger.Log($"ElapsedTime {time_query} ms", LogLevel.Verbose, true);
|
||||||
|
|
||||||
Console.ReadKey();
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void Process(IList<string> files)
|
private async Task Process(IList<string> files)
|
||||||
{
|
{
|
||||||
Compressor compressor = new Compressor(Logger);
|
Compressor compressor = new Compressor(Logger);
|
||||||
compressor.Quality = Quality;
|
compressor.Quality = Quality;
|
||||||
@@ -460,7 +472,7 @@ namespace ComicCompressor
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
compressor.Compress(filename, outputPath, file_statistics);
|
compressor.Compress(filename, outputPath, flags:flags);
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
@@ -825,7 +837,7 @@ namespace ComicCompressor
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
image.Dispose();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user