Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0439a2edde | ||
|
|
92c49290ae | ||
|
|
7f025aa9c2 | ||
|
|
a8b2f65ca5 | ||
|
|
8f75ffc43b | ||
|
|
aca062a3f2 | ||
|
|
9de98599a9 | ||
|
|
f65fc4bafa | ||
|
|
26a320ef9d | ||
|
|
c4f236bdf9 | ||
|
|
23ac71ee0c | ||
|
|
3f0ae41456 | ||
|
|
04a047aa2e | ||
|
|
1284b9ded7 | ||
|
|
b8d82e920c | ||
|
|
3c81595e08 | ||
|
|
0f6fbba1d7 | ||
|
|
47a10e9185 | ||
|
|
a5817c3ba5 | ||
|
|
b7c422fe33 | ||
|
|
09630243fb | ||
|
|
59bc2b7a1e | ||
|
|
3a627d92e1 | ||
|
|
f555f09e59 | ||
|
|
ad4f522b1f | ||
|
|
05f08f46ec | ||
|
|
4d845afa43 | ||
|
|
0dee611bf1 | ||
|
|
4bd35fe570 | ||
|
|
f85fe990ff | ||
|
|
31e1c94f70 | ||
|
|
8e14a22a9b | ||
|
|
5d20f5c81e | ||
|
|
a2738b4c08 | ||
|
|
a0b1f65940 | ||
|
|
1e0f5b5956 | ||
|
|
544d824afb | ||
|
|
90ed56d4ad | ||
|
|
eff4730014 | ||
|
|
85059d7df3 | ||
|
|
9e944142d6 | ||
|
|
ee2cf2e14d | ||
|
|
bf95d62242 | ||
|
|
be50bf1e16 | ||
|
|
1a5520c050 | ||
|
|
4f23f228a8 | ||
|
|
5b2ed5233b | ||
|
|
e2c41b3193 | ||
|
|
4c51c152f3 | ||
|
|
c5cbc9fa9b | ||
|
|
a668ae5e3d | ||
|
|
3bd7e672eb | ||
|
|
6b21c36902 | ||
|
|
d54cec94a2 | ||
|
|
0df2f7d39d | ||
|
|
e7b9fd8c2c | ||
|
|
f795c70c33 | ||
|
|
3bb40a87cd | ||
|
|
e38b5b3fb4 | ||
|
|
f57b2598f1 | ||
|
|
fca9f31fcf | ||
|
|
5f5fa2bb80 | ||
|
|
622c82a9df | ||
|
|
f4c149ac31 | ||
|
|
de2f8a2035 | ||
|
|
749ff0b943 | ||
|
|
1f33bec28c | ||
|
|
6a4054455a | ||
|
|
962a2e6dee | ||
|
|
63013998c7 | ||
|
|
c0a077fcd1 | ||
|
|
71d8f9dbb6 | ||
|
|
c794ea6b9d | ||
|
|
74c5de6994 | ||
|
|
1a36ec17b2 | ||
|
|
a9e3281a5f | ||
|
|
962fde8c8d | ||
|
|
a8084ed897 | ||
|
|
183c42613f | ||
|
|
cad75231b1 | ||
|
|
c32019615d | ||
|
|
dd9000cfea | ||
|
|
4fc3209b4e | ||
|
|
000bd20f71 | ||
|
|
f558b109e5 | ||
|
|
5922f102be | ||
|
|
f84d2d4333 | ||
|
|
551ab9e137 | ||
|
|
046cc2c1fd | ||
|
|
a5b1f41ba6 | ||
|
|
9f628741e0 | ||
|
|
ccbb27ff6d | ||
|
|
04473e95bf | ||
|
|
c061297ad0 | ||
|
|
544344d5c2 | ||
|
|
e14aa6baab | ||
|
|
6400ae14d8 | ||
|
|
788a9d3bd5 | ||
|
|
bff17636f6 | ||
|
|
bd19faf18e | ||
|
|
cf1e8a1fec | ||
|
|
63ad96ac3a | ||
|
|
9bd7193124 | ||
|
|
09a27ac9a4 | ||
|
|
4109ae154f |
@@ -1,78 +1,23 @@
|
|||||||
on: [push, pull_request]
|
on: [push, pull_request]
|
||||||
name: Test
|
name: Test
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
test:
|
||||||
runs-on: ubuntu-latest
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
go-version: [1.23.x]
|
||||||
|
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
env:
|
||||||
|
CGO_ENABLED: 0
|
||||||
steps:
|
steps:
|
||||||
|
|
||||||
- name: Setup cmake
|
|
||||||
uses: jwlawson/actions-setup-cmake@v1.13
|
|
||||||
with:
|
|
||||||
cmake-version: '3.27.x'
|
|
||||||
|
|
||||||
- name: Wget libaom
|
|
||||||
uses: wei/wget@v1
|
|
||||||
with:
|
|
||||||
args: https://storage.googleapis.com/aom-releases/libaom-3.7.0.tar.gz
|
|
||||||
- name: Unpack libaom
|
|
||||||
run: |
|
|
||||||
tar -xpf libaom-3.7.0.tar.gz && mkdir build
|
|
||||||
- name: Configure libaom
|
|
||||||
working-directory: build
|
|
||||||
run: |
|
|
||||||
cmake -DCMAKE_INSTALL_PREFIX=/usr -DBUILD_SHARED_LIBS=ON -DCMAKE_BUILD_TYPE=Release -DENABLE_DOCS=OFF \
|
|
||||||
-DENABLE_EXAMPLES=OFF -DENABLE_NASM=OFF -DENABLE_TESTS=OFF -DENABLE_TOOLS=OFF -DENABLE_WERROR=OFF \
|
|
||||||
-DAOM_TARGET_CPU=generic ../libaom-3.7.0
|
|
||||||
- name: Install libaom
|
|
||||||
working-directory: build
|
|
||||||
run: |
|
|
||||||
make -j3 && sudo make install
|
|
||||||
|
|
||||||
- name: Wget libheif
|
|
||||||
uses: wei/wget@v1
|
|
||||||
with:
|
|
||||||
args: https://github.com/strukturag/libheif/releases/download/v1.15.2/libheif-1.15.2.tar.gz
|
|
||||||
- name: Unpack libheif
|
|
||||||
run: |
|
|
||||||
tar -xpf libheif-1.15.2.tar.gz
|
|
||||||
- name: Configure libheif
|
|
||||||
working-directory: libheif-1.15.2
|
|
||||||
run: |
|
|
||||||
./configure --prefix=/usr --libdir=/usr/lib/x86_64-linux-gnu --enable-shared --disable-static --disable-libde265 \
|
|
||||||
--disable-dav1d --disable-go --enable-aom --disable-gdk-pixbuf --disable-rav1e --disable-tests --disable-x265
|
|
||||||
- name: Install libheif
|
|
||||||
working-directory: libheif-1.15.2
|
|
||||||
run: |
|
|
||||||
make -j3 && sudo make install
|
|
||||||
|
|
||||||
- name: Wget ImageMagick
|
|
||||||
uses: wei/wget@v1
|
|
||||||
with:
|
|
||||||
args: -O ImageMagick-7.1.1-15.tar.gz https://github.com/ImageMagick/ImageMagick/archive/refs/tags/7.1.1-15.tar.gz
|
|
||||||
- name: Unpack ImageMagick
|
|
||||||
run: |
|
|
||||||
tar -xpf ImageMagick-7.1.1-15.tar.gz
|
|
||||||
- name: Configure ImageMagick
|
|
||||||
working-directory: ImageMagick-7.1.1-15
|
|
||||||
run: |
|
|
||||||
./configure --prefix=/usr --libdir=/usr/lib/x86_64-linux-gnu --enable-shared --disable-static --enable-zero-configuration \
|
|
||||||
--without-frozenpaths --without-utilities --disable-hdri --disable-opencl --without-modules --without-magick-plus-plus --without-perl \
|
|
||||||
--without-bzlib --without-x --without-zip --with-zlib --without-dps --without-djvu --without-autotrace --without-fftw \
|
|
||||||
--without-fpx --without-fontconfig --without-freetype --without-gslib --without-gvc --without-jbig --without-openjp2 \
|
|
||||||
--without-jxl --without-lcms --without-lqr --without-lzma --without-openexr --without-pango --without-raw \
|
|
||||||
--without-rsvg --without-wmf --without-xml --disable-openmp --with-jpeg --with-heic --with-png --with-tiff --with-webp
|
|
||||||
- name: Install ImageMagick
|
|
||||||
working-directory: ImageMagick-7.1.1-15
|
|
||||||
run: |
|
|
||||||
make -j3 && sudo make install
|
|
||||||
|
|
||||||
- name: Install Go
|
|
||||||
uses: actions/setup-go@v2
|
|
||||||
with:
|
|
||||||
go-version: 1.21
|
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v4
|
||||||
|
- name: Install Go
|
||||||
|
uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: ${{ matrix.go-version }}
|
||||||
- name: Test
|
- name: Test
|
||||||
run: go test
|
run: go test
|
||||||
|
env:
|
||||||
|
CGO_ENABLED: 1
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
cmd/cbconvert-gui/cbconvert-gui
|
||||||
|
cmd/cbconvert/cbconvert
|
||||||
|
|
||||||
|
.idea
|
||||||
|
.vscode
|
||||||
@@ -6,164 +6,193 @@ CBconvert is a [Comic Book](http://en.wikipedia.org/wiki/Comic_Book_Archive_file
|
|||||||
|
|
||||||
It can convert comics to different formats to fit your various devices.
|
It can convert comics to different formats to fit your various devices.
|
||||||
|
|
||||||
|
<img src="cmd/cbconvert-gui/screenshots/linux-01.jpg" width="700" alt="screenshot" />
|
||||||
|
|
||||||
|
See more [screenshots](https://github.com/gen2brain/cbconvert/blob/master/cmd/cbconvert-gui/screenshots/).
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|
||||||
* reads CBR (RAR), CBZ (ZIP), CB7 (7Z), CBT (TAR), PDF, EPUB, MOBI and plain directory
|
* reads CBR (RAR), CBZ (ZIP), CB7 (7Z), CBT (TAR), PDF, XPS, EPUB, MOBI, DOCX, PPTX and plain directory
|
||||||
* saves processed comics in ZIP archive format or TAR
|
* saves processed files in ZIP archive format or TAR
|
||||||
* images can be converted to JPEG, PNG, TIFF, WEBP, AVIF, or 4-Bit BMP (16 colors) file format
|
* images can be converted to JPEG, PNG, TIFF, WEBP, AVIF, JXL, or 4-Bit BMP (16 colors) image format
|
||||||
* rotate, adjust brightness/contrast, adjust levels (Photoshop-like) or grayscale images
|
* rotate, adjust brightness/contrast or grayscale images
|
||||||
* resize filters (NearestNeighbor, Box, Linear, MitchellNetravali, CatmullRom, Gaussian, Lanczos)
|
* resize filters (NearestNeighbor, Box, Linear, MitchellNetravali, CatmullRom, Gaussian, Lanczos)
|
||||||
* export covers from comics
|
* export covers from comics
|
||||||
* create thumbnails from covers by [FreeDesktop](http://specifications.freedesktop.org/thumbnail-spec/thumbnail-spec-latest.html) specification
|
* create thumbnails from covers by [FreeDesktop](http://specifications.freedesktop.org/thumbnail-spec/thumbnail-spec-latest.html) specification
|
||||||
|
|
||||||
### Download
|
### Download
|
||||||
|
|
||||||
* [Windows x86_64](https://github.com/gen2brain/cbconvert/releases/latest/download/cbconvert-0.9.0-windows-x86_64.zip)
|
Download the latest binaries from the [releases](https://github.com/gen2brain/cbconvert/releases).
|
||||||
* [Linux x86_64](https://github.com/gen2brain/cbconvert/releases/latest/download/cbconvert-0.9.0-linux-x86_64.tar.gz)
|
|
||||||
* [Linux aarch64](https://github.com/gen2brain/cbconvert/releases/latest/download/cbconvert-0.9.0-linux-aarch64.tar.gz)
|
Linux Flatpak is available at [Flathub](https://flathub.org/apps/io.github.gen2brain.cbconvert).
|
||||||
* [macOS x86_64](https://github.com/gen2brain/cbconvert/releases/latest/download/cbconvert-0.9.0-darwin-x86_64.zip)
|
|
||||||
* [macOS aarch64](https://github.com/gen2brain/cbconvert/releases/latest/download/cbconvert-0.9.0-darwin-aarch64.zip)
|
### Compile
|
||||||
|
|
||||||
|
You must have `CGO_ENABLED=1`. Note that `Go` will disable cgo when cross-compiling.
|
||||||
|
|
||||||
|
Install to `GOBIN` (you can point `GOBIN` to e.g. `/usr/local/bin` or `~/.local/bin`):
|
||||||
|
|
||||||
|
`go install github.com/gen2brain/cbconvert/cmd/cbconvert@latest`
|
||||||
|
|
||||||
|
For GUI app, check [IUP](https://github.com/gen2brain/iup-go) requirements, and then install:
|
||||||
|
|
||||||
|
`go install github.com/gen2brain/cbconvert/cmd/cbconvert-gui@latest`
|
||||||
|
|
||||||
|
### Build tags
|
||||||
|
|
||||||
|
* `extlib` - use external `libmupdf` and `libunarr` libraries
|
||||||
|
* `pkgconfig` - enable pkg-config (used with `extlib`)
|
||||||
|
|
||||||
### Using cbconvert in file managers to generate FreeDesktop thumbnails
|
### Using cbconvert in file managers to generate FreeDesktop thumbnails
|
||||||
|
|
||||||
Copy cbconvert cli binary to your PATH and create file ~/.local/share/thumbnailers/cbconvert.thumbnailer:
|
Copy/install `cbconvert` cli binary to your `PATH`, create file `~/.local/share/thumbnailers/cbconvert.thumbnailer`
|
||||||
|
and paste contents from [thumbnailer](https://github.com/gen2brain/cbconvert/tree/master/cmd/cbconvert-gui/dist/linux/io.github.gen2brain.cbconvert.thumbnailer).
|
||||||
|
|
||||||
```
|
This is what it looks like in the `PCManFM` file manager:
|
||||||
[Thumbnailer Entry]
|
|
||||||
TryExec=cbconvert
|
|
||||||
Exec=cbconvert thumbnail --quiet --width %s --outfile %o %i
|
|
||||||
MimeType=application/pdf;application/x-cb7;application/x-cbt;application/epub+zip;application/vnd.comicbook-rar;application/vnd.comicbook+zip;application/x-mobipocket-ebook;
|
|
||||||
```
|
|
||||||
|
|
||||||
This is what it looks like in the PCManFM file manager:
|
<img src="cmd/cbconvert-gui/screenshots/thumbnails.jpg" width="700" alt="thumbnails" />
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
|
|
||||||
### Using command line app
|
### Using command line app
|
||||||
|
|
||||||
```
|
```
|
||||||
Usage: cbconvert <command> [<flags>] [file1 dir1 ... fileOrDirN]
|
Usage: cbconvert <command> [<flags>] [file1 dir1 ... fileOrDirN]
|
||||||
|
|
||||||
|
|
||||||
Commands:
|
Commands:
|
||||||
|
|
||||||
convert
|
convert
|
||||||
Convert archive or document
|
Convert archive or document
|
||||||
|
|
||||||
--width
|
--width
|
||||||
Image width (default "0")
|
Image width (default "0")
|
||||||
--height
|
--height
|
||||||
Image height (default "0")
|
Image height (default "0")
|
||||||
--fit
|
--fit
|
||||||
Best fit for required width and height (default "false")
|
Best fit for required width and height (default "false")
|
||||||
--format
|
--no-upscale
|
||||||
Image format, valid values are jpeg, png, tiff, bmp, webp, avif (default "jpeg")
|
Do not upscale images already smaller than the requested width/height (default "false")
|
||||||
--archive
|
--dpi
|
||||||
Archive format, valid values are zip, tar (default "zip")
|
Document rendering resolution in DPI (PDF, EPUB, etc.), 0 uses the default (300) (default "0")
|
||||||
--quality
|
--format
|
||||||
Image quality (default "75")
|
Image format, valid values are jpeg, png, tiff, bmp, webp, avif, jxl (default "jpeg")
|
||||||
--lossless
|
--archive
|
||||||
Lossless compression (avif) (default "false")
|
Archive format, valid values are zip, tar (default "zip")
|
||||||
--filter
|
--zip-level
|
||||||
0=NearestNeighbor, 1=Box, 2=Linear, 3=MitchellNetravali, 4=CatmullRom, 6=Gaussian, 7=Lanczos (default "2")
|
ZIP compression level, 0 disables compression, 1-9 sets deflate level (1 fastest, 9 smallest), -1 uses the default (default "-1")
|
||||||
--no-cover
|
--quality
|
||||||
Do not convert the cover image (default "false")
|
Image quality (default "75")
|
||||||
--no-rgb
|
--effort
|
||||||
Do not convert images that have RGB colorspace (default "false")
|
Encoder speed/effort, format-specific (webp method 0-6, avif speed 0-10, jxl effort 1-10), -1 uses the format default (default "-1")
|
||||||
--no-nonimage
|
--lossless
|
||||||
Remove non-image files from the archive (default "false")
|
Lossless compression (webp, avif, jxl), ignores quality (default "false")
|
||||||
--no-convert
|
--combine
|
||||||
Do not transform or convert images (default "false")
|
Combine all inputs into a single archive (default "false")
|
||||||
--grayscale
|
--outfile
|
||||||
Convert images to grayscale (monochromatic) (default "false")
|
Output file name for --combine (default: first input + -combined) (default "")
|
||||||
--rotate
|
--filter
|
||||||
Rotate images, valid values are 0, 90, 180, 270 (default "0")
|
0=NearestNeighbor, 1=Box, 2=Linear, 3=MitchellNetravali, 4=CatmullRom, 6=Gaussian, 7=Lanczos (default "2")
|
||||||
--brightness
|
--no-cover
|
||||||
Adjust the brightness of the images, must be in the range (-100, 100) (default "0")
|
Do not convert the cover image (default "false")
|
||||||
--contrast
|
--no-rgb
|
||||||
Adjust the contrast of the images, must be in the range (-100, 100) (default "0")
|
Do not convert images that have RGB colorspace (default "false")
|
||||||
--suffix
|
--no-nonimage
|
||||||
Add suffix to file basename (default "")
|
Remove non-image files from the archive (default "false")
|
||||||
--levels-inmin
|
--no-convert
|
||||||
Shadow input value (default "0")
|
Do not transform or convert images (default "false")
|
||||||
--levels-gamma
|
--grayscale
|
||||||
Midpoint/Gamma (default "1")
|
Convert images to grayscale (monochromatic) (default "false")
|
||||||
--levels-inmax
|
--rotate
|
||||||
Highlight input value (default "255")
|
Rotate images, valid values are 0, 90, 180, 270 (default "0")
|
||||||
--levels-outmin
|
--brightness
|
||||||
Shadow output value (default "0")
|
Adjust the brightness of the images, must be in the range (-100, 100) (default "0")
|
||||||
--levels-outmax
|
--contrast
|
||||||
Highlight output value (default "255")
|
Adjust the contrast of the images, must be in the range (-100, 100) (default "0")
|
||||||
--outdir
|
--suffix
|
||||||
Output directory (default ".")
|
Add suffix to file basename (default "")
|
||||||
--size
|
--outdir
|
||||||
Process only files larger than size (in MB) (default "0")
|
Output directory (default ".")
|
||||||
--recursive
|
--size
|
||||||
Process subdirectories recursively (default "false")
|
Process only files larger than size (in MB) (default "0")
|
||||||
--quiet
|
--recursive
|
||||||
Hide console output (default "false")
|
Process subdirectories recursively (default "false")
|
||||||
|
--quiet
|
||||||
|
Hide console output (default "false")
|
||||||
|
|
||||||
cover
|
cover
|
||||||
Extract cover
|
Extract cover
|
||||||
|
|
||||||
--width
|
--width
|
||||||
Image width (default "0")
|
Image width (default "0")
|
||||||
--height
|
--height
|
||||||
Image height (default "0")
|
Image height (default "0")
|
||||||
--fit
|
--fit
|
||||||
Best fit for required width and height (default "false")
|
Best fit for required width and height (default "false")
|
||||||
--format
|
--no-upscale
|
||||||
Image format, valid values are jpeg, png, tiff, bmp, webp, avif (default "jpeg")
|
Do not upscale images already smaller than the requested width/height (default "false")
|
||||||
--quality
|
--dpi
|
||||||
Image quality (default "75")
|
Document rendering resolution in DPI (PDF, EPUB, etc.), 0 uses the default (300) (default "0")
|
||||||
--filter
|
--format
|
||||||
0=NearestNeighbor, 1=Box, 2=Linear, 3=MitchellNetravali, 4=CatmullRom, 6=Gaussian, 7=Lanczos (default "2")
|
Image format, valid values are jpeg, png, tiff, bmp, webp, avif (default "jpeg")
|
||||||
--outdir
|
--quality
|
||||||
Output directory (default ".")
|
Image quality (default "75")
|
||||||
--size
|
--effort
|
||||||
Process only files larger than size (in MB) (default "0")
|
Encoder speed/effort, format-specific (webp method 0-6, avif speed 0-10, jxl effort 1-10), -1 uses the format default (default "-1")
|
||||||
--recursive
|
--lossless
|
||||||
Process subdirectories recursively (default "false")
|
Lossless compression (webp, avif, jxl), ignores quality (default "false")
|
||||||
--quiet
|
--filter
|
||||||
Hide console output (default "false")
|
0=NearestNeighbor, 1=Box, 2=Linear, 3=MitchellNetravali, 4=CatmullRom, 6=Gaussian, 7=Lanczos (default "2")
|
||||||
|
--outdir
|
||||||
|
Output directory (default ".")
|
||||||
|
--size
|
||||||
|
Process only files larger than size (in MB) (default "0")
|
||||||
|
--recursive
|
||||||
|
Process subdirectories recursively (default "false")
|
||||||
|
--quiet
|
||||||
|
Hide console output (default "false")
|
||||||
|
|
||||||
thumbnail
|
thumbnail
|
||||||
Extract cover thumbnail (freedesktop spec.)
|
Extract cover thumbnail (freedesktop spec.)
|
||||||
|
|
||||||
--width
|
--width
|
||||||
Image width (default "0")
|
Image width (default "0")
|
||||||
--height
|
--height
|
||||||
Image height (default "0")
|
Image height (default "0")
|
||||||
--fit
|
--fit
|
||||||
Best fit for required width and height (default "false")
|
Best fit for required width and height (default "false")
|
||||||
--filter
|
--no-upscale
|
||||||
0=NearestNeighbor, 1=Box, 2=Linear, 3=MitchellNetravali, 4=CatmullRom, 6=Gaussian, 7=Lanczos (default "2")
|
Do not upscale images already smaller than the requested width/height (default "false")
|
||||||
--outdir
|
--dpi
|
||||||
Output directory (default ".")
|
Document rendering resolution in DPI (PDF, EPUB, etc.), 0 uses the default (300) (default "0")
|
||||||
--outfile
|
--filter
|
||||||
Output file (default "")
|
0=NearestNeighbor, 1=Box, 2=Linear, 3=MitchellNetravali, 4=CatmullRom, 6=Gaussian, 7=Lanczos (default "2")
|
||||||
--size
|
--outdir
|
||||||
Process only files larger than size (in MB) (default "0")
|
Output directory (default ".")
|
||||||
--recursive
|
--outfile
|
||||||
Process subdirectories recursively (default "false")
|
Output file (default "")
|
||||||
--quiet
|
--size
|
||||||
Hide console output (default "false")
|
Process only files larger than size (in MB) (default "0")
|
||||||
|
--recursive
|
||||||
|
Process subdirectories recursively (default "false")
|
||||||
|
--quiet
|
||||||
|
Hide console output (default "false")
|
||||||
|
|
||||||
meta
|
meta
|
||||||
CBZ metadata
|
CBZ metadata
|
||||||
|
|
||||||
--cover
|
--cover
|
||||||
Print cover name (default "false")
|
Print cover name (default "false")
|
||||||
--comment
|
--comment
|
||||||
Print zip comment (default "false")
|
Print zip comment (default "false")
|
||||||
--comment-body
|
--comment-body
|
||||||
Set zip comment (default "")
|
Set zip comment (default "")
|
||||||
--file-add
|
--file-add
|
||||||
Add file to archive (default "")
|
Add file to archive (default "")
|
||||||
--file-remove
|
--file-remove
|
||||||
Remove file(s) from archive (glob pattern, i.e. *.xml) (default "")
|
Remove file from archive (glob pattern, i.e. *.xml) (default "")
|
||||||
|
|
||||||
|
version
|
||||||
|
Print version
|
||||||
```
|
```
|
||||||
|
|
||||||
### Examples
|
### Examples
|
||||||
@@ -186,6 +215,10 @@ This is what it looks like in the PCManFM file manager:
|
|||||||
|
|
||||||
`cbconvert --format avif --quality 50 --width 1280 --outdir ~/comics /media/comics/Misc/`
|
`cbconvert --format avif --quality 50 --width 1280 --outdir ~/comics /media/comics/Misc/`
|
||||||
|
|
||||||
|
* Combine several issues into a single archive:
|
||||||
|
|
||||||
|
`cbconvert convert --combine --outfile Series.cbz --outdir ~/comics issue1.cbz issue2.cbr issue3.cb7`
|
||||||
|
|
||||||
### Quality settings
|
### Quality settings
|
||||||
|
|
||||||
This table maps quality settings for JPEG to the respective AVIF and WEBP quality settings:
|
This table maps quality settings for JPEG to the respective AVIF and WEBP quality settings:
|
||||||
@@ -196,8 +229,3 @@ This table maps quality settings for JPEG to the respective AVIF and WEBP qualit
|
|||||||
| AVIF quality | 48 | 51 | 56 | 64 |
|
| AVIF quality | 48 | 51 | 56 | 64 |
|
||||||
| WEBP quality | 55 | 64 | 72 | 82 |
|
| WEBP quality | 55 | 64 | 72 | 82 |
|
||||||
|
|
||||||
### Compile
|
|
||||||
|
|
||||||
Install ImageMagick7, MuPDF and libheif (with libaom) libraries and headers and then install to GOBIN:
|
|
||||||
|
|
||||||
`go install -tags extlib github.com/gen2brain/cbconvert/cmd/cbconvert@latest`
|
|
||||||
|
|||||||
@@ -0,0 +1,526 @@
|
|||||||
|
package cbconvert
|
||||||
|
|
||||||
|
import (
|
||||||
|
"archive/tar"
|
||||||
|
"archive/zip"
|
||||||
|
"compress/flate"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/fs"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/mholt/archives"
|
||||||
|
)
|
||||||
|
|
||||||
|
// archiveSave saves workdir to CBZ archive.
|
||||||
|
func (c *Converter) archiveSave(fileName string) error {
|
||||||
|
if c.Opts.Archive == "zip" {
|
||||||
|
return c.archiveSaveZip(fileName)
|
||||||
|
} else if c.Opts.Archive == "tar" {
|
||||||
|
return c.archiveSaveTar(fileName)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// archiveSaveZip saves workdir to CBZ archive.
|
||||||
|
func (c *Converter) archiveSaveZip(fileName string) error {
|
||||||
|
if c.OnCompress != nil {
|
||||||
|
c.OnCompress()
|
||||||
|
}
|
||||||
|
|
||||||
|
var zipName string
|
||||||
|
if c.Opts.Recursive {
|
||||||
|
outDir := c.recursiveDir(fileName)
|
||||||
|
if err := os.MkdirAll(outDir, 0755); err != nil {
|
||||||
|
return fmt.Errorf("archiveSaveZip: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
zipName = filepath.Join(outDir, fmt.Sprintf("%s%s.cbz", baseNoExt(fileName), c.Opts.Suffix))
|
||||||
|
} else {
|
||||||
|
zipName = filepath.Join(c.Opts.OutDir, fmt.Sprintf("%s%s.cbz", baseNoExt(fileName), c.Opts.Suffix))
|
||||||
|
}
|
||||||
|
|
||||||
|
zipFile, err := os.Create(zipName)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("archiveSaveZip: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
z := zip.NewWriter(zipFile)
|
||||||
|
|
||||||
|
if c.Opts.ZipLevel >= 1 {
|
||||||
|
level := c.Opts.ZipLevel
|
||||||
|
z.RegisterCompressor(zip.Deflate, func(out io.Writer) (io.WriteCloser, error) {
|
||||||
|
return flate.NewWriter(out, level)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
files, err := os.ReadDir(c.Workdir)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("archiveSaveZip: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, file := range files {
|
||||||
|
r, err := os.ReadFile(filepath.Join(c.Workdir, file.Name()))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("archiveSaveZip: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
info, err := file.Info()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("archiveSaveZip: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
zipInfo, err := zip.FileInfoHeader(info)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("archiveSaveZip: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
zipInfo.Method = zip.Deflate
|
||||||
|
if c.Opts.ZipLevel == 0 {
|
||||||
|
zipInfo.Method = zip.Store
|
||||||
|
}
|
||||||
|
w, err := z.CreateHeader(zipInfo)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("archiveSaveZip: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = w.Write(r)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("archiveSaveZip: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = z.Close(); err != nil {
|
||||||
|
return fmt.Errorf("archiveSaveZip: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = zipFile.Close(); err != nil {
|
||||||
|
return fmt.Errorf("archiveSaveZip: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = os.RemoveAll(c.Workdir)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("archiveSaveZip: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// archiveSaveTar saves workdir to CBT archive.
|
||||||
|
func (c *Converter) archiveSaveTar(fileName string) error {
|
||||||
|
if c.OnCompress != nil {
|
||||||
|
c.OnCompress()
|
||||||
|
}
|
||||||
|
|
||||||
|
var tarName string
|
||||||
|
if c.Opts.Recursive {
|
||||||
|
outDir := c.recursiveDir(fileName)
|
||||||
|
if err := os.MkdirAll(outDir, 0755); err != nil {
|
||||||
|
return fmt.Errorf("archiveSaveTar: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tarName = filepath.Join(outDir, fmt.Sprintf("%s%s.cbt", baseNoExt(fileName), c.Opts.Suffix))
|
||||||
|
} else {
|
||||||
|
tarName = filepath.Join(c.Opts.OutDir, fmt.Sprintf("%s%s.cbt", baseNoExt(fileName), c.Opts.Suffix))
|
||||||
|
}
|
||||||
|
|
||||||
|
tarFile, err := os.Create(tarName)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("archiveSaveTar: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tw := tar.NewWriter(tarFile)
|
||||||
|
|
||||||
|
files, err := os.ReadDir(c.Workdir)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("archiveSaveTar: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, file := range files {
|
||||||
|
r, err := os.ReadFile(filepath.Join(c.Workdir, file.Name()))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("archiveSaveTar: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
info, err := file.Info()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("archiveSaveTar: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
header, err := tar.FileInfoHeader(info, info.Name())
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("archiveSaveTar: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = tw.WriteHeader(header)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("archiveSaveTar: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = tw.Write(r)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("archiveSaveTar: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = tw.Close(); err != nil {
|
||||||
|
return fmt.Errorf("archiveSaveTar: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = tarFile.Close(); err != nil {
|
||||||
|
return fmt.Errorf("archiveSaveTar: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = os.RemoveAll(c.Workdir)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("archiveSaveTar: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// archiveOpen identifies the archive and returns its extractor and a reader positioned at the start.
|
||||||
|
func archiveOpen(ctx context.Context, fileName string) (io.ReadCloser, archives.Extractor, io.Reader, error) {
|
||||||
|
file, err := os.Open(fileName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
format, input, err := archives.Identify(ctx, fileName, file)
|
||||||
|
if err != nil {
|
||||||
|
file.Close()
|
||||||
|
return nil, nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ex, ok := format.(archives.Extractor)
|
||||||
|
if !ok {
|
||||||
|
file.Close()
|
||||||
|
return nil, nil, nil, fmt.Errorf("%s: unsupported archive format", fileName)
|
||||||
|
}
|
||||||
|
|
||||||
|
return file, ex, input, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FileType returns the detected archive container, document extension, or "DIR".
|
||||||
|
func FileType(path string) string {
|
||||||
|
if isArchive(path) {
|
||||||
|
file, err := os.Open(path)
|
||||||
|
if err == nil {
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
format, _, err := archives.Identify(context.Background(), path, file)
|
||||||
|
if err == nil {
|
||||||
|
return strings.ToUpper(strings.TrimPrefix(format.Extension(), "."))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if info, err := os.Stat(path); err == nil && info.IsDir() {
|
||||||
|
return "DIR"
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.ToUpper(strings.TrimPrefix(filepath.Ext(path), "."))
|
||||||
|
}
|
||||||
|
|
||||||
|
// archiveList lists contents of archive.
|
||||||
|
func (c *Converter) archiveList(fileName string) ([]string, error) {
|
||||||
|
var contents []string
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
file, ex, input, err := archiveOpen(ctx, fileName)
|
||||||
|
if err != nil {
|
||||||
|
return contents, fmt.Errorf("archiveList: %w", err)
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
err = ex.Extract(ctx, input, func(ctx context.Context, f archives.FileInfo) error {
|
||||||
|
if f.IsDir() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
contents = append(contents, f.NameInArchive)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return contents, fmt.Errorf("archiveList: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return contents, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// archiveFile returns the contents of a single named file from the archive.
|
||||||
|
func (c *Converter) archiveFile(fileName, name string) ([]byte, error) {
|
||||||
|
var data []byte
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
file, ex, input, err := archiveOpen(ctx, fileName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("archiveFile: %w", err)
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
err = ex.Extract(ctx, input, func(ctx context.Context, f archives.FileInfo) error {
|
||||||
|
if f.NameInArchive != name {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
rc, err := f.Open()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer rc.Close()
|
||||||
|
|
||||||
|
data, err = io.ReadAll(rc)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return fs.SkipAll
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("archiveFile: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// archiveComment returns ZIP comment.
|
||||||
|
func (c *Converter) archiveComment(fileName string) (string, error) {
|
||||||
|
zr, err := zip.OpenReader(fileName)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("archiveComment: %w", err)
|
||||||
|
}
|
||||||
|
defer zr.Close()
|
||||||
|
|
||||||
|
return zr.Comment, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// archiveSetComment sets ZIP comment.
|
||||||
|
func (c *Converter) archiveSetComment(fileName, commentBody string) error {
|
||||||
|
zr, err := zip.OpenReader(fileName)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("archiveSetComment: %w", err)
|
||||||
|
}
|
||||||
|
defer zr.Close()
|
||||||
|
|
||||||
|
zf, err := os.CreateTemp(os.TempDir(), "cbc")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("archiveSetComment: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpName := zf.Name()
|
||||||
|
defer os.Remove(tmpName)
|
||||||
|
|
||||||
|
zw := zip.NewWriter(zf)
|
||||||
|
err = zw.SetComment(commentBody)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("archiveSetComment: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, item := range zr.File {
|
||||||
|
ir, err := item.OpenRaw()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("archiveSetComment: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
item := item
|
||||||
|
|
||||||
|
it, err := zw.CreateRaw(&item.FileHeader)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("archiveSetComment: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = io.Copy(it, ir)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("archiveSetComment: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = zw.Close()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("archiveSetComment: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = zf.Close()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("archiveSetComment: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := os.ReadFile(tmpName)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("archiveSetComment: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = os.WriteFile(fileName, data, 0644)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("archiveSetComment: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// archiveFileAdd adds a file to the archive.
|
||||||
|
func (c *Converter) archiveFileAdd(fileName, newFileName string) error {
|
||||||
|
zr, err := zip.OpenReader(fileName)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("archiveFileAdd: %w", err)
|
||||||
|
}
|
||||||
|
defer zr.Close()
|
||||||
|
|
||||||
|
zf, err := os.CreateTemp(os.TempDir(), "cbc")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("archiveFileAdd: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpName := zf.Name()
|
||||||
|
defer os.Remove(tmpName)
|
||||||
|
|
||||||
|
zw := zip.NewWriter(zf)
|
||||||
|
|
||||||
|
for _, item := range zr.File {
|
||||||
|
if item.Name == newFileName {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
ir, err := item.OpenRaw()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("archiveFileAdd: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
item := item
|
||||||
|
|
||||||
|
it, err := zw.CreateRaw(&item.FileHeader)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("archiveFileAdd: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = io.Copy(it, ir)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("archiveFileAdd: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
info, err := os.Stat(newFileName)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("archiveFileAdd: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
newData, err := os.ReadFile(newFileName)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("archiveFileAdd: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
zipInfo, err := zip.FileInfoHeader(info)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("archiveFileAdd: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
zipInfo.Method = zip.Deflate
|
||||||
|
w, err := zw.CreateHeader(zipInfo)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("archiveFileAdd: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = w.Write(newData)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("archiveFileAdd: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = zw.Close()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("archiveFileAdd: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = zf.Close()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("archiveFileAdd: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := os.ReadFile(tmpName)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("archiveFileAdd: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = os.WriteFile(fileName, data, 0644)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("archiveFileAdd: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// archiveFileRemove removes files from archive.
|
||||||
|
func (c *Converter) archiveFileRemove(fileName, pattern string) error {
|
||||||
|
zr, err := zip.OpenReader(fileName)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("archiveFileRemove: %w", err)
|
||||||
|
}
|
||||||
|
defer zr.Close()
|
||||||
|
|
||||||
|
zf, err := os.CreateTemp(os.TempDir(), "cbc")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("archiveFileRemove: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpName := zf.Name()
|
||||||
|
defer os.Remove(tmpName)
|
||||||
|
|
||||||
|
zw := zip.NewWriter(zf)
|
||||||
|
|
||||||
|
for _, item := range zr.File {
|
||||||
|
matched, err := filepath.Match(pattern, item.Name)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("archiveFileRemove: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if matched {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
ir, err := item.OpenRaw()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("archiveFileRemove: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
item := item
|
||||||
|
|
||||||
|
it, err := zw.CreateRaw(&item.FileHeader)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("archiveFileRemove: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = io.Copy(it, ir)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("archiveFileRemove: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = zw.Close()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("archiveFileRemove: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = zf.Close()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("archiveFileRemove: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := os.ReadFile(tmpName)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("archiveFileRemove: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = os.WriteFile(fileName, data, 0644)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("archiveFileRemove: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,414 @@
|
|||||||
|
package cbconvert
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"image"
|
||||||
|
"image/png"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
"sync/atomic"
|
||||||
|
|
||||||
|
"github.com/gen2brain/avif"
|
||||||
|
"github.com/gen2brain/go-fitz"
|
||||||
|
"github.com/gen2brain/jpegli"
|
||||||
|
"github.com/gen2brain/jpegxl"
|
||||||
|
"github.com/gen2brain/webp"
|
||||||
|
"github.com/jsummers/gobmp"
|
||||||
|
"github.com/mholt/archives"
|
||||||
|
"golang.org/x/image/tiff"
|
||||||
|
"golang.org/x/sync/errgroup"
|
||||||
|
)
|
||||||
|
|
||||||
|
// convertDocument converts PDF/EPUB document to CBZ.
|
||||||
|
func (c *Converter) convertDocument(ctx context.Context, fileName string) error {
|
||||||
|
doc, err := fitz.New(fileName)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("convertDocument: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer doc.Close()
|
||||||
|
|
||||||
|
c.Ncontents = doc.NumPage()
|
||||||
|
c.CurrContent = 0
|
||||||
|
|
||||||
|
if c.OnStart != nil {
|
||||||
|
c.OnStart()
|
||||||
|
}
|
||||||
|
|
||||||
|
eg, ctx := errgroup.WithContext(ctx)
|
||||||
|
eg.SetLimit(runtime.NumCPU() + 1)
|
||||||
|
|
||||||
|
for n := 0; n < c.Ncontents; n++ {
|
||||||
|
if ctx.Err() != nil {
|
||||||
|
return fmt.Errorf("convertDocument: %w", ctx.Err())
|
||||||
|
}
|
||||||
|
|
||||||
|
img, err := doc.ImageDPI(n, c.renderDPI())
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("convertDocument: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if img != nil {
|
||||||
|
eg.Go(func() error {
|
||||||
|
return c.imageConvert(ctx, img, n, "")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = eg.Wait()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("convertDocument: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// convertArchive converts archive to CBZ.
|
||||||
|
func (c *Converter) convertArchive(ctx context.Context, fileName string) error {
|
||||||
|
contents, err := c.archiveList(fileName)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("convertArchive: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
images := imagesFromSlice(contents)
|
||||||
|
|
||||||
|
c.Ncontents = len(images)
|
||||||
|
c.CurrContent = 0
|
||||||
|
|
||||||
|
if c.OnStart != nil {
|
||||||
|
c.OnStart()
|
||||||
|
}
|
||||||
|
|
||||||
|
cover := c.coverName(images)
|
||||||
|
|
||||||
|
eg, ctx := errgroup.WithContext(ctx)
|
||||||
|
eg.SetLimit(runtime.NumCPU() + 1)
|
||||||
|
|
||||||
|
file, ex, input, err := archiveOpen(ctx, fileName)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("convertArchive: %w", err)
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
err = ex.Extract(ctx, input, func(ctx context.Context, f archives.FileInfo) error {
|
||||||
|
if err := ctx.Err(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.IsDir() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
pathName := f.NameInArchive
|
||||||
|
|
||||||
|
rc, err := f.Open()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("convertArchive: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := io.ReadAll(rc)
|
||||||
|
if err != nil {
|
||||||
|
rc.Close()
|
||||||
|
return fmt.Errorf("convertArchive: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = rc.Close(); err != nil {
|
||||||
|
return fmt.Errorf("convertArchive: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if isImage(pathName) {
|
||||||
|
if c.Opts.NoConvert {
|
||||||
|
if err = copyFile(bytes.NewReader(data), c.workPath(flatName(pathName))); err != nil {
|
||||||
|
return fmt.Errorf("convertArchive: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if cover == pathName && c.Opts.NoCover {
|
||||||
|
if err = copyFile(bytes.NewReader(data), c.workPath(flatName(pathName))); err != nil {
|
||||||
|
return fmt.Errorf("convertArchive: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var img image.Image
|
||||||
|
img, err = c.imageDecode(bytes.NewReader(data))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("convertArchive: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Opts.NoRGB && !isGrayScale(img) {
|
||||||
|
if err = copyFile(bytes.NewReader(data), c.workPath(flatName(pathName))); err != nil {
|
||||||
|
return fmt.Errorf("convertArchive: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if img != nil {
|
||||||
|
eg.Go(func() error {
|
||||||
|
return c.imageConvert(ctx, img, 0, pathName)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if filepath.Ext(pathName) == ".DS_Store" || strings.Contains(pathName, "__MACOSX") {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.prefix == "" && !c.Opts.NoNonImage {
|
||||||
|
if err = copyFile(bytes.NewReader(data), c.workPath(flatName(pathName))); err != nil {
|
||||||
|
return fmt.Errorf("convertArchive: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if werr := eg.Wait(); werr != nil {
|
||||||
|
return fmt.Errorf("convertArchive: %w", werr)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("convertArchive: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// convertDirectory converts directory to CBZ.
|
||||||
|
func (c *Converter) convertDirectory(ctx context.Context, dirPath string) error {
|
||||||
|
contents, err := imagesFromPath(dirPath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("convertDirectory: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
images := imagesFromSlice(contents)
|
||||||
|
c.Ncontents = len(images)
|
||||||
|
c.CurrContent = 0
|
||||||
|
|
||||||
|
if c.OnStart != nil {
|
||||||
|
c.OnStart()
|
||||||
|
}
|
||||||
|
|
||||||
|
eg, ctx := errgroup.WithContext(ctx)
|
||||||
|
eg.SetLimit(runtime.NumCPU() + 1)
|
||||||
|
|
||||||
|
for index, img := range contents {
|
||||||
|
if ctx.Err() != nil {
|
||||||
|
return fmt.Errorf("convertDirectory: %w", ctx.Err())
|
||||||
|
}
|
||||||
|
|
||||||
|
rel, rerr := filepath.Rel(dirPath, img)
|
||||||
|
if rerr != nil {
|
||||||
|
rel = filepath.Base(img)
|
||||||
|
}
|
||||||
|
|
||||||
|
file, err := os.Open(img)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("convertDirectory: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if isNonImage(img) && c.prefix == "" && !c.Opts.NoNonImage {
|
||||||
|
if err = copyFile(file, c.workPath(flatName(rel))); err != nil {
|
||||||
|
return fmt.Errorf("convertDirectory: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = file.Close(); err != nil {
|
||||||
|
return fmt.Errorf("convertDirectory: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
continue
|
||||||
|
} else if isImage(img) {
|
||||||
|
if c.Opts.NoConvert {
|
||||||
|
if err = copyFile(file, c.workPath(flatName(rel))); err != nil {
|
||||||
|
return fmt.Errorf("convertDirectory: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = file.Close(); err != nil {
|
||||||
|
return fmt.Errorf("convertDirectory: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var i image.Image
|
||||||
|
i, err = c.imageDecode(file)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("convertDirectory: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Opts.NoRGB && !isGrayScale(i) {
|
||||||
|
if err = copyFile(file, c.workPath(flatName(rel))); err != nil {
|
||||||
|
return fmt.Errorf("convertDirectory: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = file.Close(); err != nil {
|
||||||
|
return fmt.Errorf("convertDirectory: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = file.Close(); err != nil {
|
||||||
|
return fmt.Errorf("convertDirectory: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if i != nil {
|
||||||
|
eg.Go(func() error {
|
||||||
|
return c.imageConvert(ctx, i, index, rel)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = eg.Wait()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("convertDirectory: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// workPath returns the path of name inside the workdir, with the combine prefix applied.
|
||||||
|
func (c *Converter) workPath(name string) string {
|
||||||
|
return filepath.Join(c.Workdir, c.prefix+name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// imageConvert converts image.Image.
|
||||||
|
func (c *Converter) imageConvert(ctx context.Context, img image.Image, index int, pathName string) error {
|
||||||
|
err := ctx.Err()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("imageConvert: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
atomic.AddInt32(&c.CurrContent, 1)
|
||||||
|
if c.OnProgress != nil {
|
||||||
|
c.OnProgress()
|
||||||
|
}
|
||||||
|
|
||||||
|
ext := c.Opts.Format
|
||||||
|
if ext == "jpeg" {
|
||||||
|
ext = "jpg"
|
||||||
|
}
|
||||||
|
|
||||||
|
var fileName string
|
||||||
|
if pathName != "" {
|
||||||
|
fileName = c.workPath(fmt.Sprintf("%s.%s", flatName(strings.TrimSuffix(pathName, filepath.Ext(pathName))), ext))
|
||||||
|
} else {
|
||||||
|
fileName = c.workPath(fmt.Sprintf("%03d.%s", index, ext))
|
||||||
|
}
|
||||||
|
|
||||||
|
img = c.imageTransform(img)
|
||||||
|
|
||||||
|
w, err := os.Create(fileName)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("imageConvert: %w", err)
|
||||||
|
}
|
||||||
|
defer w.Close()
|
||||||
|
|
||||||
|
if err := c.imageEncode(img, w); err != nil {
|
||||||
|
return fmt.Errorf("imageConvert: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// imageTransform transforms image (resize, rotate, brightness, contrast).
|
||||||
|
func (c *Converter) imageTransform(img image.Image) image.Image {
|
||||||
|
var i = img
|
||||||
|
|
||||||
|
if c.Opts.Width > 0 || c.Opts.Height > 0 {
|
||||||
|
i = c.resizeFit(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Opts.Rotate > 0 {
|
||||||
|
switch c.Opts.Rotate {
|
||||||
|
case 90:
|
||||||
|
i = rotate(i, 90)
|
||||||
|
case 180:
|
||||||
|
i = rotate(i, 180)
|
||||||
|
case 270:
|
||||||
|
i = rotate(i, 270)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Opts.Brightness != 0 {
|
||||||
|
i = brightness(i, float64(c.Opts.Brightness))
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Opts.Contrast != 0 {
|
||||||
|
i = contrast(i, float64(c.Opts.Contrast))
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Opts.Grayscale {
|
||||||
|
i = imageToGray(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
|
// imageDecode decodes image from reader.
|
||||||
|
func (c *Converter) imageDecode(reader io.Reader) (image.Image, error) {
|
||||||
|
img, _, err := image.Decode(reader)
|
||||||
|
if err != nil {
|
||||||
|
return img, fmt.Errorf("imageDecode: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return img, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// imageEncode encodes image to file.
|
||||||
|
func (c *Converter) imageEncode(img image.Image, w io.Writer) error {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
switch c.Opts.Format {
|
||||||
|
case "png":
|
||||||
|
err = png.Encode(w, img)
|
||||||
|
case "tiff":
|
||||||
|
err = tiff.Encode(w, img, &tiff.Options{Compression: tiff.Uncompressed})
|
||||||
|
case "jpeg":
|
||||||
|
opts := &jpegli.EncodingOptions{}
|
||||||
|
opts.Quality = c.Opts.Quality
|
||||||
|
opts.ChromaSubsampling = image.YCbCrSubsampleRatio420
|
||||||
|
opts.ProgressiveLevel = 2
|
||||||
|
opts.AdaptiveQuantization = true
|
||||||
|
opts.DCTMethod = jpegli.DefaultDCTMethod
|
||||||
|
err = jpegli.Encode(w, img, opts)
|
||||||
|
case "webp":
|
||||||
|
method := webp.DefaultMethod
|
||||||
|
if c.Opts.Effort >= 0 {
|
||||||
|
method = min(max(c.Opts.Effort, 0), 6)
|
||||||
|
}
|
||||||
|
err = webp.Encode(w, img, webp.Options{Quality: c.Opts.Quality, Method: method, Lossless: c.Opts.Lossless})
|
||||||
|
case "avif":
|
||||||
|
speed := avif.DefaultSpeed
|
||||||
|
if c.Opts.Effort >= 0 {
|
||||||
|
speed = min(max(c.Opts.Effort, 0), 10)
|
||||||
|
}
|
||||||
|
err = avif.Encode(w, img, avif.Options{Quality: c.Opts.Quality, Speed: speed, Lossless: c.Opts.Lossless})
|
||||||
|
case "jxl":
|
||||||
|
effort := jpegxl.DefaultEffort
|
||||||
|
if c.Opts.Effort >= 0 {
|
||||||
|
effort = min(max(c.Opts.Effort, 1), 10)
|
||||||
|
}
|
||||||
|
err = jpegxl.Encode(w, img, jpegxl.Options{Quality: c.Opts.Quality, Effort: effort, Lossless: c.Opts.Lossless})
|
||||||
|
case "bmp":
|
||||||
|
opts := &gobmp.EncoderOptions{}
|
||||||
|
opts.SupportTransparency(false)
|
||||||
|
err = gobmp.EncodeWithOptions(w, imageToPaletted(img), opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("imageEncode: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,142 @@
|
|||||||
|
package cbconvert
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"image"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/fvbommel/sortorder"
|
||||||
|
"github.com/gen2brain/go-fitz"
|
||||||
|
)
|
||||||
|
|
||||||
|
// coverArchive extracts cover from archive.
|
||||||
|
func (c *Converter) coverArchive(fileName string) (image.Image, error) {
|
||||||
|
var images []string
|
||||||
|
|
||||||
|
contents, err := c.archiveList(fileName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("coverArchive: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ct := range contents {
|
||||||
|
if isImage(ct) {
|
||||||
|
images = append(images, ct)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cover := c.coverName(images)
|
||||||
|
|
||||||
|
data, err := c.archiveFile(fileName, cover)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("coverArchive: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var img image.Image
|
||||||
|
img, err = c.imageDecode(bytes.NewReader(data))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("coverArchive: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return img, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// coverDocument extracts cover from document.
|
||||||
|
func (c *Converter) coverDocument(fileName string) (image.Image, error) {
|
||||||
|
doc, err := fitz.New(fileName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("coverDocument: %w", err)
|
||||||
|
}
|
||||||
|
defer doc.Close()
|
||||||
|
|
||||||
|
img, err := doc.ImageDPI(0, c.renderDPI())
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("coverDocument: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return img, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// coverDirectory extracts cover from directory.
|
||||||
|
func (c *Converter) coverDirectory(dir string) (image.Image, error) {
|
||||||
|
contents, err := imagesFromPath(dir)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("coverDirectory: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
images := imagesFromSlice(contents)
|
||||||
|
cover := c.coverName(images)
|
||||||
|
|
||||||
|
file, err := os.Open(cover)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("coverDirectory: %w", err)
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
var img image.Image
|
||||||
|
img, err = c.imageDecode(file)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("coverDirectory: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return img, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// coverName returns the filename that is the most likely to be the cover.
|
||||||
|
func (c *Converter) coverName(images []string) string {
|
||||||
|
if len(images) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
lower := make([]string, 0)
|
||||||
|
for idx, img := range images {
|
||||||
|
img = strings.ToLower(img)
|
||||||
|
lower = append(lower, img)
|
||||||
|
ext := baseNoExt(img)
|
||||||
|
|
||||||
|
if strings.HasPrefix(img, "cover") || strings.HasPrefix(img, "front") ||
|
||||||
|
strings.HasSuffix(ext, "cover") || strings.HasSuffix(ext, "front") {
|
||||||
|
return filepath.ToSlash(images[idx])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Sort(sortorder.Natural(lower))
|
||||||
|
cover := lower[0]
|
||||||
|
|
||||||
|
for idx, img := range images {
|
||||||
|
img = strings.ToLower(img)
|
||||||
|
if img == cover {
|
||||||
|
return filepath.ToSlash(images[idx])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// coverImage returns cover as image.Image.
|
||||||
|
func (c *Converter) coverImage(fileName string, fileInfo os.FileInfo) (image.Image, error) {
|
||||||
|
var err error
|
||||||
|
var cover image.Image
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case fileInfo.IsDir():
|
||||||
|
cover, err = c.coverDirectory(fileName)
|
||||||
|
case isDocument(fileName):
|
||||||
|
cover, err = c.coverDocument(fileName)
|
||||||
|
case isArchive(fileName):
|
||||||
|
cover, err = c.coverArchive(fileName)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.OnProgress != nil {
|
||||||
|
c.OnProgress()
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("coverImage: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return cover, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,151 @@
|
|||||||
|
package cbconvert
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// imagesFromPath returns list of found image files for given directory.
|
||||||
|
func imagesFromPath(path string) ([]string, error) {
|
||||||
|
var images []string
|
||||||
|
|
||||||
|
walkFiles := func(fp string, f os.FileInfo, err error) error {
|
||||||
|
if !f.IsDir() && f.Mode()&os.ModeType == 0 {
|
||||||
|
if f.Size() > 0 && (isImage(fp)) {
|
||||||
|
images = append(images, fp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := filepath.Abs(path)
|
||||||
|
if err != nil {
|
||||||
|
return images, fmt.Errorf("imagesFromPath: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
stat, err := os.Stat(f)
|
||||||
|
if err != nil {
|
||||||
|
return images, fmt.Errorf("imagesFromPath: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !stat.IsDir() && stat.Mode()&os.ModeType == 0 {
|
||||||
|
if isImage(f) {
|
||||||
|
images = append(images, f)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = filepath.Walk(f, walkFiles)
|
||||||
|
if err != nil {
|
||||||
|
return images, fmt.Errorf("imagesFromPath: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return images, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// imagesFromSlice returns list of found image files for given slice of files.
|
||||||
|
func imagesFromSlice(files []string) []string {
|
||||||
|
var images []string
|
||||||
|
|
||||||
|
for _, f := range files {
|
||||||
|
if isImage(f) {
|
||||||
|
images = append(images, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return images
|
||||||
|
}
|
||||||
|
|
||||||
|
// isArchive checks if a file is archive.
|
||||||
|
func isArchive(f string) bool {
|
||||||
|
var types = []string{".rar", ".zip", ".7z", ".tar", ".cbr", ".cbz", ".cb7", ".cbt"}
|
||||||
|
for _, t := range types {
|
||||||
|
if strings.ToLower(filepath.Ext(f)) == t {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// isDocument checks if file is document.
|
||||||
|
func isDocument(f string) bool {
|
||||||
|
var types = []string{".pdf", ".xps", ".epub", ".mobi", ".docx", ".pptx", ".xlsx"}
|
||||||
|
for _, t := range types {
|
||||||
|
if strings.ToLower(filepath.Ext(f)) == t {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// isImage checks if file is image.
|
||||||
|
func isImage(f string) bool {
|
||||||
|
var types = []string{".jpg", ".jpeg", ".png", ".gif", ".bmp", ".tiff", ".tif", ".webp", ".avif", ".jxl"}
|
||||||
|
for _, t := range types {
|
||||||
|
if strings.ToLower(filepath.Ext(f)) == t {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// isNonImage checks for allowed files in archive.
|
||||||
|
func isNonImage(f string) bool {
|
||||||
|
var types = []string{".nfo", ".xml", ".txt"}
|
||||||
|
for _, t := range types {
|
||||||
|
if strings.ToLower(filepath.Ext(f)) == t {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// isSize checks size of file.
|
||||||
|
func isSize(a, b int64) bool {
|
||||||
|
if a > 0 {
|
||||||
|
if b < int64(a)*(1024*1024) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// baseNoExt returns base name without extension.
|
||||||
|
func baseNoExt(filename string) string {
|
||||||
|
return strings.TrimSuffix(filepath.Base(filename), filepath.Ext(filename))
|
||||||
|
}
|
||||||
|
|
||||||
|
// flatName flattens a path into a single collision-free name by replacing separators.
|
||||||
|
func flatName(name string) string {
|
||||||
|
name = strings.ReplaceAll(name, "\\", "/")
|
||||||
|
return strings.ReplaceAll(name, "/", "_")
|
||||||
|
}
|
||||||
|
|
||||||
|
// copyFile copies reader to file.
|
||||||
|
func copyFile(reader io.Reader, filename string) error {
|
||||||
|
err := os.MkdirAll(filepath.Dir(filename), 0755)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("copyFile: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
file, err := os.Create(filename)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("copyFile: %w", err)
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
_, err = io.Copy(file, reader)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("copyFile: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,183 @@
|
|||||||
|
package cbconvert
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image"
|
||||||
|
"image/color"
|
||||||
|
"image/draw"
|
||||||
|
"math"
|
||||||
|
|
||||||
|
"github.com/anthonynsimon/bild/adjust"
|
||||||
|
"github.com/anthonynsimon/bild/transform"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Resample filters.
|
||||||
|
const (
|
||||||
|
// NearestNeighbor is the fastest resampling filter, no antialiasing.
|
||||||
|
nearestNeighbor int = iota
|
||||||
|
// Box filter (averaging pixels).
|
||||||
|
box
|
||||||
|
// Linear is the bilinear filter, smooth and reasonably fast.
|
||||||
|
linear
|
||||||
|
// MitchellNetravali is a smooth bicubic filter.
|
||||||
|
mitchellNetravali
|
||||||
|
// CatmullRom is a sharp bicubic filter.
|
||||||
|
catmullRom
|
||||||
|
// Gaussian is a blurring filter, which uses gaussian function, useful for noise removal.
|
||||||
|
gaussian
|
||||||
|
// Lanczos is a high-quality resampling filter, it's slower than cubic filters.
|
||||||
|
lanczos
|
||||||
|
)
|
||||||
|
|
||||||
|
var filters = map[int]transform.ResampleFilter{
|
||||||
|
nearestNeighbor: transform.NearestNeighbor,
|
||||||
|
box: transform.Box,
|
||||||
|
linear: transform.Linear,
|
||||||
|
mitchellNetravali: transform.MitchellNetravali,
|
||||||
|
catmullRom: transform.CatmullRom,
|
||||||
|
gaussian: transform.Gaussian,
|
||||||
|
lanczos: transform.Lanczos,
|
||||||
|
}
|
||||||
|
|
||||||
|
func resize(img image.Image, width, height int, filter transform.ResampleFilter) *image.RGBA {
|
||||||
|
dstW, dstH := width, height
|
||||||
|
|
||||||
|
srcW := img.Bounds().Dx()
|
||||||
|
srcH := img.Bounds().Dy()
|
||||||
|
|
||||||
|
if dstW == 0 {
|
||||||
|
tmpW := float64(dstH) * float64(srcW) / float64(srcH)
|
||||||
|
dstW = int(math.Max(1.0, math.Floor(tmpW+0.5)))
|
||||||
|
}
|
||||||
|
if dstH == 0 {
|
||||||
|
tmpH := float64(dstW) * float64(srcH) / float64(srcW)
|
||||||
|
dstH = int(math.Max(1.0, math.Floor(tmpH+0.5)))
|
||||||
|
}
|
||||||
|
|
||||||
|
if srcW == dstW && srcH == dstH {
|
||||||
|
return imageToRGBA(img)
|
||||||
|
}
|
||||||
|
|
||||||
|
return transform.Resize(img, dstW, dstH, filter)
|
||||||
|
}
|
||||||
|
|
||||||
|
func fit(img image.Image, width, height int, filter transform.ResampleFilter) *image.RGBA {
|
||||||
|
maxW, maxH := width, height
|
||||||
|
|
||||||
|
b := img.Bounds()
|
||||||
|
srcW := b.Dx()
|
||||||
|
srcH := b.Dy()
|
||||||
|
|
||||||
|
if srcW <= maxW && srcH <= maxH {
|
||||||
|
return imageToRGBA(img)
|
||||||
|
}
|
||||||
|
|
||||||
|
srcAspectRatio := float64(srcW) / float64(srcH)
|
||||||
|
maxAspectRatio := float64(maxW) / float64(maxH)
|
||||||
|
|
||||||
|
var dstW, dstH int
|
||||||
|
if srcAspectRatio > maxAspectRatio {
|
||||||
|
dstW = maxW
|
||||||
|
dstH = int(float64(dstW) / srcAspectRatio)
|
||||||
|
} else {
|
||||||
|
dstH = maxH
|
||||||
|
dstW = int(float64(dstH) * srcAspectRatio)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resize(img, dstW, dstH, filter)
|
||||||
|
}
|
||||||
|
|
||||||
|
// withinBounds reports whether img already fits within width by height; a zero dimension is unbounded.
|
||||||
|
func withinBounds(img image.Image, width, height int) bool {
|
||||||
|
b := img.Bounds()
|
||||||
|
|
||||||
|
return (width == 0 || b.Dx() <= width) && (height == 0 || b.Dy() <= height)
|
||||||
|
}
|
||||||
|
|
||||||
|
// resizeFit resizes img to the configured width/height, honoring Fit and NoUpscale.
|
||||||
|
func (c *Converter) resizeFit(img image.Image) image.Image {
|
||||||
|
if c.Opts.Fit {
|
||||||
|
return fit(img, c.Opts.Width, c.Opts.Height, filters[c.Opts.Filter])
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Opts.NoUpscale && withinBounds(img, c.Opts.Width, c.Opts.Height) {
|
||||||
|
return img
|
||||||
|
}
|
||||||
|
|
||||||
|
return resize(img, c.Opts.Width, c.Opts.Height, filters[c.Opts.Filter])
|
||||||
|
}
|
||||||
|
|
||||||
|
func rotate(img image.Image, angle float64) *image.RGBA {
|
||||||
|
return transform.Rotate(img, angle, &transform.RotationOptions{ResizeBounds: true, Pivot: &image.Point{}})
|
||||||
|
}
|
||||||
|
|
||||||
|
func brightness(img image.Image, change float64) *image.RGBA {
|
||||||
|
return adjust.Brightness(img, change/100)
|
||||||
|
}
|
||||||
|
|
||||||
|
func contrast(img image.Image, change float64) *image.RGBA {
|
||||||
|
return adjust.Contrast(img, change/100)
|
||||||
|
}
|
||||||
|
|
||||||
|
// imageToRGBA converts an image.Image to *image.RGBA.
|
||||||
|
func imageToRGBA(src image.Image) *image.RGBA {
|
||||||
|
if dst, ok := src.(*image.RGBA); ok {
|
||||||
|
return dst
|
||||||
|
}
|
||||||
|
|
||||||
|
b := src.Bounds()
|
||||||
|
dst := image.NewRGBA(b)
|
||||||
|
draw.Draw(dst, dst.Bounds(), src, b.Min, draw.Src)
|
||||||
|
|
||||||
|
return dst
|
||||||
|
}
|
||||||
|
|
||||||
|
// imageToGray converts an image.Image to *image.Gray.
|
||||||
|
func imageToGray(src image.Image) *image.Gray {
|
||||||
|
if dst, ok := src.(*image.Gray); ok {
|
||||||
|
return dst
|
||||||
|
}
|
||||||
|
|
||||||
|
b := src.Bounds()
|
||||||
|
dst := image.NewGray(b)
|
||||||
|
draw.Draw(dst, dst.Bounds(), src, b.Min, draw.Src)
|
||||||
|
|
||||||
|
return dst
|
||||||
|
}
|
||||||
|
|
||||||
|
// isGrayScale checks if the image is grayscale.
|
||||||
|
func isGrayScale(img image.Image) bool {
|
||||||
|
model := img.ColorModel()
|
||||||
|
if model == color.GrayModel || model == color.Gray16Model {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
var colors16 = []color.Color{
|
||||||
|
color.RGBA{A: 255},
|
||||||
|
color.RGBA{R: 17, G: 17, B: 17, A: 255},
|
||||||
|
color.RGBA{R: 34, G: 34, B: 34, A: 255},
|
||||||
|
color.RGBA{R: 51, G: 51, B: 51, A: 255},
|
||||||
|
color.RGBA{R: 68, G: 68, B: 68, A: 255},
|
||||||
|
color.RGBA{R: 85, G: 85, B: 85, A: 255},
|
||||||
|
color.RGBA{R: 102, G: 102, B: 102, A: 255},
|
||||||
|
color.RGBA{R: 119, G: 119, B: 119, A: 255},
|
||||||
|
color.RGBA{R: 136, G: 136, B: 136, A: 255},
|
||||||
|
color.RGBA{R: 153, G: 153, B: 153, A: 255},
|
||||||
|
color.RGBA{R: 170, G: 170, B: 170, A: 255},
|
||||||
|
color.RGBA{R: 187, G: 187, B: 187, A: 255},
|
||||||
|
color.RGBA{R: 204, G: 204, B: 204, A: 255},
|
||||||
|
color.RGBA{R: 221, G: 221, B: 221, A: 255},
|
||||||
|
color.RGBA{R: 238, G: 238, B: 238, A: 255},
|
||||||
|
color.RGBA{R: 255, G: 255, B: 255, A: 255},
|
||||||
|
}
|
||||||
|
|
||||||
|
// imageToPaletted converts an image.Image to *image.Paletted using 16-color palette.
|
||||||
|
func imageToPaletted(src image.Image) *image.Paletted {
|
||||||
|
b := src.Bounds()
|
||||||
|
dst := image.NewPaletted(b, colors16)
|
||||||
|
draw.FloydSteinberg.Draw(dst, b, imageToGray(src), b.Min)
|
||||||
|
|
||||||
|
return dst
|
||||||
|
}
|
||||||
@@ -1,9 +1,12 @@
|
|||||||
package cbconvert
|
package cbconvert
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"archive/zip"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"image"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -13,29 +16,23 @@ func TestConvert(t *testing.T) {
|
|||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
opts := Options{}
|
opts := NewOptions()
|
||||||
opts.OutDir = tmpDir
|
opts.OutDir = tmpDir
|
||||||
opts.Archive = "zip"
|
|
||||||
opts.Quality = 75
|
|
||||||
opts.Filter = 2
|
|
||||||
|
|
||||||
conv := New(opts)
|
conv := New(opts)
|
||||||
|
|
||||||
conv.Initialize()
|
|
||||||
defer conv.Terminate()
|
|
||||||
|
|
||||||
files, err := conv.Files([]string{"testdata/test", "testdata"})
|
files, err := conv.Files([]string{"testdata/test", "testdata"})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, format := range []string{"jpeg", "png", "tiff", "bmp", "webp", "avif"} {
|
for _, format := range []string{"jpeg", "png", "tiff", "bmp", "webp", "avif", "jxl"} {
|
||||||
conv.Opts.Format = format
|
conv.Opts.Format = format
|
||||||
|
|
||||||
for _, file := range files {
|
for _, file := range files {
|
||||||
conv.Opts.Suffix = fmt.Sprintf("_%s%s", format, filepath.Ext(file.Path))
|
conv.Opts.Suffix = fmt.Sprintf("_%s%s", format, filepath.Ext(file.Path))
|
||||||
|
|
||||||
err = conv.Convert(file.Path, file.Stat)
|
err = conv.Convert(file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("format %s: file %s: %v", format, file.Name, err)
|
t.Errorf("format %s: file %s: %v", format, file.Name, err)
|
||||||
}
|
}
|
||||||
@@ -54,24 +51,18 @@ func TestCover(t *testing.T) {
|
|||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
opts := Options{}
|
opts := NewOptions()
|
||||||
opts.OutDir = tmpDir
|
opts.OutDir = tmpDir
|
||||||
opts.Quality = 75
|
|
||||||
opts.Filter = 2
|
|
||||||
opts.Format = "jpeg"
|
|
||||||
|
|
||||||
conv := New(opts)
|
conv := New(opts)
|
||||||
|
|
||||||
conv.Initialize()
|
|
||||||
defer conv.Terminate()
|
|
||||||
|
|
||||||
files, err := conv.Files([]string{"testdata/test.cbt"})
|
files, err := conv.Files([]string{"testdata/test.cbt"})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, file := range files {
|
for _, file := range files {
|
||||||
err = conv.Cover(file.Path, file.Stat)
|
err = conv.Cover(file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
@@ -89,22 +80,18 @@ func TestThumbnail(t *testing.T) {
|
|||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
opts := Options{}
|
opts := NewOptions()
|
||||||
opts.OutDir = tmpDir
|
opts.OutDir = tmpDir
|
||||||
opts.Filter = 2
|
|
||||||
|
|
||||||
conv := New(opts)
|
conv := New(opts)
|
||||||
|
|
||||||
conv.Initialize()
|
|
||||||
defer conv.Terminate()
|
|
||||||
|
|
||||||
files, err := conv.Files([]string{"testdata/test.pdf"})
|
files, err := conv.Files([]string{"testdata/test.pdf"})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, file := range files {
|
for _, file := range files {
|
||||||
err = conv.Thumbnail(file.Path, file.Stat)
|
err = conv.Thumbnail(file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
@@ -115,3 +102,679 @@ func TestThumbnail(t *testing.T) {
|
|||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestArgs(t *testing.T) {
|
||||||
|
opts := NewOptions()
|
||||||
|
if got := opts.Args(); len(got) != 0 {
|
||||||
|
t.Errorf("defaults should emit no flags, got %v", got)
|
||||||
|
}
|
||||||
|
|
||||||
|
opts.Format = "webp"
|
||||||
|
opts.Quality = 90
|
||||||
|
opts.Effort = 4
|
||||||
|
opts.Lossless = true
|
||||||
|
opts.Width = 1200
|
||||||
|
opts.NoUpscale = true
|
||||||
|
opts.DPI = 150
|
||||||
|
opts.Grayscale = true
|
||||||
|
opts.OutDir = "/out"
|
||||||
|
|
||||||
|
got := strings.Join(opts.Args(), " ")
|
||||||
|
want := "--width 1200 --no-upscale --dpi 150 --format webp --quality 90 --effort 4 --lossless --grayscale --outdir /out"
|
||||||
|
if got != want {
|
||||||
|
t.Errorf("Args() = %q, want %q", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNoUpscale(t *testing.T) {
|
||||||
|
tmpDir, err := os.MkdirTemp(os.TempDir(), "cbc")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(tmpDir)
|
||||||
|
|
||||||
|
width := func(o Options) int {
|
||||||
|
conv := New(o)
|
||||||
|
files, err := conv.Files([]string{"testdata/test.cbz"})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
for _, file := range files {
|
||||||
|
if err := conv.Convert(file); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return firstPage(t, conv, filepath.Join(tmpDir, "test.cbz")).Bounds().Dx()
|
||||||
|
}
|
||||||
|
|
||||||
|
base := NewOptions()
|
||||||
|
base.OutDir = tmpDir
|
||||||
|
orig := width(base)
|
||||||
|
|
||||||
|
up := NewOptions()
|
||||||
|
up.OutDir = tmpDir
|
||||||
|
up.Width = orig * 2
|
||||||
|
up.NoUpscale = true
|
||||||
|
if got := width(up); got != orig {
|
||||||
|
t.Errorf("NoUpscale should keep original width %d, got %d", orig, got)
|
||||||
|
}
|
||||||
|
|
||||||
|
no := NewOptions()
|
||||||
|
no.OutDir = tmpDir
|
||||||
|
no.Width = orig * 2
|
||||||
|
if got := width(no); got != orig*2 {
|
||||||
|
t.Errorf("without NoUpscale should upscale to %d, got %d", orig*2, got)
|
||||||
|
}
|
||||||
|
|
||||||
|
down := NewOptions()
|
||||||
|
down.OutDir = tmpDir
|
||||||
|
down.Width = orig / 2
|
||||||
|
down.NoUpscale = true
|
||||||
|
if got := width(down); got != orig/2 {
|
||||||
|
t.Errorf("NoUpscale should still downscale to %d, got %d", orig/2, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConvertDPI(t *testing.T) {
|
||||||
|
tmpDir, err := os.MkdirTemp(os.TempDir(), "cbc")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(tmpDir)
|
||||||
|
|
||||||
|
dims := func(dpi int) int {
|
||||||
|
opts := NewOptions()
|
||||||
|
opts.OutDir = tmpDir
|
||||||
|
opts.DPI = dpi
|
||||||
|
|
||||||
|
conv := New(opts)
|
||||||
|
|
||||||
|
files, err := conv.Files([]string{"testdata/test.pdf"})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, file := range files {
|
||||||
|
if err := conv.Convert(file); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return firstPage(t, conv, filepath.Join(tmpDir, "test.cbz")).Bounds().Dx()
|
||||||
|
}
|
||||||
|
|
||||||
|
low := dims(150)
|
||||||
|
high := dims(600)
|
||||||
|
if low >= high {
|
||||||
|
t.Errorf("higher DPI should render larger pages: 150dpi=%d, 600dpi=%d", low, high)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConvertResize(t *testing.T) {
|
||||||
|
tmpDir, err := os.MkdirTemp(os.TempDir(), "cbc")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(tmpDir)
|
||||||
|
|
||||||
|
opts := NewOptions()
|
||||||
|
opts.OutDir = tmpDir
|
||||||
|
opts.Width = 100
|
||||||
|
|
||||||
|
conv := New(opts)
|
||||||
|
|
||||||
|
files, err := conv.Files([]string{"testdata/test.cbz"})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, file := range files {
|
||||||
|
if err := conv.Convert(file); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
img := firstPage(t, conv, filepath.Join(tmpDir, "test.cbz"))
|
||||||
|
if got := img.Bounds().Dx(); got != 100 {
|
||||||
|
t.Errorf("resized width: got %d, want 100", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConvertFit(t *testing.T) {
|
||||||
|
tmpDir, err := os.MkdirTemp(os.TempDir(), "cbc")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(tmpDir)
|
||||||
|
|
||||||
|
opts := NewOptions()
|
||||||
|
opts.OutDir = tmpDir
|
||||||
|
opts.Width = 120
|
||||||
|
opts.Height = 120
|
||||||
|
opts.Fit = true
|
||||||
|
|
||||||
|
conv := New(opts)
|
||||||
|
|
||||||
|
files, err := conv.Files([]string{"testdata/test.cbz"})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, file := range files {
|
||||||
|
if err := conv.Convert(file); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
img := firstPage(t, conv, filepath.Join(tmpDir, "test.cbz"))
|
||||||
|
w, h := img.Bounds().Dx(), img.Bounds().Dy()
|
||||||
|
if w > 120 || h > 120 {
|
||||||
|
t.Errorf("fit exceeded bounds: got %dx%d, want <= 120x120", w, h)
|
||||||
|
}
|
||||||
|
if w != 120 && h != 120 {
|
||||||
|
t.Errorf("fit did not touch a bound: got %dx%d, want one side == 120", w, h)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConvertTar(t *testing.T) {
|
||||||
|
tmpDir, err := os.MkdirTemp(os.TempDir(), "cbc")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(tmpDir)
|
||||||
|
|
||||||
|
opts := NewOptions()
|
||||||
|
opts.OutDir = tmpDir
|
||||||
|
opts.Archive = "tar"
|
||||||
|
|
||||||
|
conv := New(opts)
|
||||||
|
|
||||||
|
files, err := conv.Files([]string{"testdata/test.cbz"})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, file := range files {
|
||||||
|
if err := conv.Convert(file); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
out := filepath.Join(tmpDir, "test.cbt")
|
||||||
|
list, err := conv.archiveList(out)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("read tar output: %v", err)
|
||||||
|
}
|
||||||
|
if len(list) != 2 {
|
||||||
|
t.Errorf("expected 2 pages in tar output, got %d: %v", len(list), list)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestZipLevel(t *testing.T) {
|
||||||
|
convertWith := func(level int) *zip.ReadCloser {
|
||||||
|
tmpDir, err := os.MkdirTemp(os.TempDir(), "cbc")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Cleanup(func() { os.RemoveAll(tmpDir) })
|
||||||
|
|
||||||
|
opts := NewOptions()
|
||||||
|
opts.OutDir = tmpDir
|
||||||
|
opts.ZipLevel = level
|
||||||
|
opts.NoConvert = true
|
||||||
|
|
||||||
|
conv := New(opts)
|
||||||
|
|
||||||
|
files, err := conv.Files([]string{"testdata/test.cbz"})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
for _, file := range files {
|
||||||
|
if err := conv.Convert(file); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
zr, err := zip.OpenReader(filepath.Join(tmpDir, "test.cbz"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
t.Cleanup(func() { zr.Close() })
|
||||||
|
|
||||||
|
return zr
|
||||||
|
}
|
||||||
|
|
||||||
|
store := convertWith(0)
|
||||||
|
for _, f := range store.File {
|
||||||
|
if f.Method != zip.Store {
|
||||||
|
t.Errorf("level 0: %s stored with method %d, want Store", f.Name, f.Method)
|
||||||
|
}
|
||||||
|
if f.CompressedSize64 != f.UncompressedSize64 {
|
||||||
|
t.Errorf("level 0: %s is compressed (%d < %d)", f.Name, f.CompressedSize64, f.UncompressedSize64)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deflate := convertWith(9)
|
||||||
|
for _, f := range deflate.File {
|
||||||
|
if f.Method != zip.Deflate {
|
||||||
|
t.Errorf("level 9: %s method %d, want Deflate", f.Name, f.Method)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestImageTransforms(t *testing.T) {
|
||||||
|
conv := New(NewOptions())
|
||||||
|
|
||||||
|
f, err := os.Open("testdata/test/00.jpg")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
src, err := conv.imageDecode(f)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
srcW, srcH := src.Bounds().Dx(), src.Bounds().Dy()
|
||||||
|
|
||||||
|
conv.Opts.Rotate = 90
|
||||||
|
rotated := conv.imageTransform(src)
|
||||||
|
if rotated.Bounds().Dx() != srcH || rotated.Bounds().Dy() != srcW {
|
||||||
|
t.Errorf("rotate 90: got %dx%d, want %dx%d", rotated.Bounds().Dx(), rotated.Bounds().Dy(), srcH, srcW)
|
||||||
|
}
|
||||||
|
|
||||||
|
conv.Opts = NewOptions()
|
||||||
|
conv.Opts.Grayscale = true
|
||||||
|
gray := conv.imageTransform(src)
|
||||||
|
if !isGrayScale(gray) {
|
||||||
|
t.Errorf("grayscale: result is not grayscale")
|
||||||
|
}
|
||||||
|
|
||||||
|
conv.Opts = NewOptions()
|
||||||
|
conv.Opts.Brightness = 20
|
||||||
|
conv.Opts.Contrast = 20
|
||||||
|
adjusted := conv.imageTransform(src)
|
||||||
|
if adjusted.Bounds().Dx() != srcW || adjusted.Bounds().Dy() != srcH {
|
||||||
|
t.Errorf("brightness/contrast changed dimensions: got %dx%d, want %dx%d",
|
||||||
|
adjusted.Bounds().Dx(), adjusted.Bounds().Dy(), srcW, srcH)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCoverName(t *testing.T) {
|
||||||
|
conv := New(NewOptions())
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
images []string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{"empty", nil, ""},
|
||||||
|
{"natural sort", []string{"10.jpg", "2.jpg", "1.jpg"}, "1.jpg"},
|
||||||
|
{"cover prefix wins", []string{"01.jpg", "cover.jpg", "02.jpg"}, "cover.jpg"},
|
||||||
|
{"front prefix wins", []string{"01.jpg", "front.png", "00.jpg"}, "front.png"},
|
||||||
|
{"cover suffix wins", []string{"01.jpg", "page_cover.jpg"}, "page_cover.jpg"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if got := conv.coverName(tt.images); got != tt.want {
|
||||||
|
t.Errorf("coverName(%v) = %q, want %q", tt.images, got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCoverDirectory(t *testing.T) {
|
||||||
|
tmpDir, err := os.MkdirTemp(os.TempDir(), "cbc")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(tmpDir)
|
||||||
|
|
||||||
|
opts := NewOptions()
|
||||||
|
opts.OutDir = tmpDir
|
||||||
|
|
||||||
|
conv := New(opts)
|
||||||
|
|
||||||
|
files, err := conv.Files([]string{"testdata/test"})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(files) != 1 {
|
||||||
|
t.Fatalf("expected 1 directory file, got %d", len(files))
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, file := range files {
|
||||||
|
if err := conv.Cover(file); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := os.Stat(filepath.Join(tmpDir, "test.jpg")); err != nil {
|
||||||
|
t.Errorf("directory cover not written: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFileType(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
path string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{"testdata/test.cbz", "ZIP"},
|
||||||
|
{"testdata/test.cbr", "RAR"},
|
||||||
|
{"testdata/test.cb7", "7Z"},
|
||||||
|
{"testdata/test.cbt", "TAR"},
|
||||||
|
{"testdata/test.pdf", "PDF"},
|
||||||
|
{"testdata/test", "DIR"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.path, func(t *testing.T) {
|
||||||
|
if got := FileType(tt.path); got != tt.want {
|
||||||
|
t.Errorf("FileType(%q) = %q, want %q", tt.path, got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCombine(t *testing.T) {
|
||||||
|
tmpDir, err := os.MkdirTemp(os.TempDir(), "cbc")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(tmpDir)
|
||||||
|
|
||||||
|
opts := NewOptions()
|
||||||
|
opts.OutDir = tmpDir
|
||||||
|
opts.OutFile = "merged"
|
||||||
|
|
||||||
|
conv := New(opts)
|
||||||
|
|
||||||
|
files, err := conv.Files([]string{"testdata/test.cbz", "testdata/test.cbt"})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(files) != 2 {
|
||||||
|
t.Fatalf("expected 2 input files, got %d", len(files))
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := conv.Combine(files); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
zr, err := zip.OpenReader(filepath.Join(tmpDir, "merged.cbz"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("open combined archive: %v", err)
|
||||||
|
}
|
||||||
|
defer zr.Close()
|
||||||
|
|
||||||
|
var names []string
|
||||||
|
for _, f := range zr.File {
|
||||||
|
names = append(names, f.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(names) != 4 {
|
||||||
|
t.Fatalf("expected 4 pages in combined archive, got %d: %v", len(names), names)
|
||||||
|
}
|
||||||
|
|
||||||
|
// each input is prefixed so identically named pages do not collide
|
||||||
|
var first, second int
|
||||||
|
for _, n := range names {
|
||||||
|
switch {
|
||||||
|
case strings.HasPrefix(n, "0001_"):
|
||||||
|
first++
|
||||||
|
case strings.HasPrefix(n, "0002_"):
|
||||||
|
second++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if first != 2 || second != 2 {
|
||||||
|
t.Errorf("expected 2 pages from each input, got 0001_=%d 0002_=%d: %v", first, second, names)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSubfolders(t *testing.T) {
|
||||||
|
page0, err := os.ReadFile("testdata/test/00.jpg")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
page1, err := os.ReadFile("testdata/test/01.jpg")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
inDir, err := os.MkdirTemp(os.TempDir(), "cbc-in")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(inDir)
|
||||||
|
|
||||||
|
src := filepath.Join(inDir, "chapters.cbz")
|
||||||
|
buildZip(t, src, []zipEntry{
|
||||||
|
{"chapter1/00.jpg", page0},
|
||||||
|
{"chapter1/01.jpg", page1},
|
||||||
|
{"chapter2/00.jpg", page0},
|
||||||
|
{"chapter2/01.jpg", page1},
|
||||||
|
})
|
||||||
|
|
||||||
|
tmpDir, err := os.MkdirTemp(os.TempDir(), "cbc-out")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(tmpDir)
|
||||||
|
|
||||||
|
opts := NewOptions()
|
||||||
|
opts.OutDir = tmpDir
|
||||||
|
|
||||||
|
conv := New(opts)
|
||||||
|
|
||||||
|
files, err := conv.Files([]string{src})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, file := range files {
|
||||||
|
if err := conv.Convert(file); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
zr, err := zip.OpenReader(filepath.Join(tmpDir, "chapters.cbz"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("open output archive: %v", err)
|
||||||
|
}
|
||||||
|
defer zr.Close()
|
||||||
|
|
||||||
|
// without subfolder preservation chapter2/00 overwrites chapter1/00 and only 2 pages survive
|
||||||
|
if len(zr.File) != 4 {
|
||||||
|
var names []string
|
||||||
|
for _, f := range zr.File {
|
||||||
|
names = append(names, f.Name)
|
||||||
|
}
|
||||||
|
t.Fatalf("expected 4 pages from numbered subfolders, got %d: %v", len(zr.File), names)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMeta(t *testing.T) {
|
||||||
|
tmpDir, err := os.MkdirTemp(os.TempDir(), "cbc")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(tmpDir)
|
||||||
|
|
||||||
|
// operate on a copy so the fixture stays intact
|
||||||
|
data, err := os.ReadFile("testdata/test.cbz")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
archive := filepath.Join(tmpDir, "meta.cbz")
|
||||||
|
if err := os.WriteFile(archive, data, 0644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
conv := New(NewOptions())
|
||||||
|
|
||||||
|
conv.Opts = NewOptions()
|
||||||
|
conv.Opts.CommentBody = "hello world"
|
||||||
|
if _, err := conv.Meta(archive); err != nil {
|
||||||
|
t.Fatalf("set comment: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
conv.Opts = NewOptions()
|
||||||
|
conv.Opts.Comment = true
|
||||||
|
got, err := conv.Meta(archive)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("get comment: %v", err)
|
||||||
|
}
|
||||||
|
if got != "hello world" {
|
||||||
|
t.Errorf("comment roundtrip: got %q, want %q", got, "hello world")
|
||||||
|
}
|
||||||
|
|
||||||
|
extra := filepath.Join(tmpDir, "ComicInfo.xml")
|
||||||
|
if err := os.WriteFile(extra, []byte("<ComicInfo/>"), 0644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
conv.Opts = NewOptions()
|
||||||
|
conv.Opts.FileAdd = extra
|
||||||
|
if _, err := conv.Meta(archive); err != nil {
|
||||||
|
t.Fatalf("add file: %v", err)
|
||||||
|
}
|
||||||
|
if !archiveHas(t, conv, archive, "ComicInfo.xml") {
|
||||||
|
t.Errorf("added file not found in archive")
|
||||||
|
}
|
||||||
|
|
||||||
|
conv.Opts = NewOptions()
|
||||||
|
conv.Opts.FileRemove = "ComicInfo.xml"
|
||||||
|
if _, err := conv.Meta(archive); err != nil {
|
||||||
|
t.Fatalf("remove file: %v", err)
|
||||||
|
}
|
||||||
|
if archiveHas(t, conv, archive, "ComicInfo.xml") {
|
||||||
|
t.Errorf("removed file still present in archive")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRecursive(t *testing.T) {
|
||||||
|
inDir, err := os.MkdirTemp(os.TempDir(), "cbc-in")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(inDir)
|
||||||
|
|
||||||
|
sub := filepath.Join(inDir, "chapter1")
|
||||||
|
if err := os.MkdirAll(sub, 0755); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
src, err := os.ReadFile("testdata/test.cbz")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := os.WriteFile(filepath.Join(sub, "test.cbz"), src, 0644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
outDir, err := os.MkdirTemp(os.TempDir(), "cbc-out")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(outDir)
|
||||||
|
|
||||||
|
opts := NewOptions()
|
||||||
|
opts.OutDir = outDir
|
||||||
|
opts.Recursive = true
|
||||||
|
|
||||||
|
conv := New(opts)
|
||||||
|
|
||||||
|
files, err := conv.Files([]string{inDir})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(files) != 1 {
|
||||||
|
t.Fatalf("expected 1 file, got %d", len(files))
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, file := range files {
|
||||||
|
if err := conv.Convert(file); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// output must mirror the input subtree relative to the input root, not the absolute path
|
||||||
|
want := filepath.Join(outDir, "chapter1", "test.cbz")
|
||||||
|
if _, err := os.Stat(want); err != nil {
|
||||||
|
t.Errorf("expected output relative to input root at %s: %v", want, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type zipEntry struct {
|
||||||
|
name string
|
||||||
|
data []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildZip(t *testing.T, path string, entries []zipEntry) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
f, err := os.Create(path)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
zw := zip.NewWriter(f)
|
||||||
|
for _, e := range entries {
|
||||||
|
w, err := zw.Create(e.name)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if _, err := w.Write(e.data); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := zw.Close(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func firstPage(t *testing.T, conv *Converter, archive string) image.Image {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
zr, err := zip.OpenReader(archive)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer zr.Close()
|
||||||
|
|
||||||
|
if len(zr.File) == 0 {
|
||||||
|
t.Fatalf("archive %s has no entries", archive)
|
||||||
|
}
|
||||||
|
|
||||||
|
rc, err := zr.File[0].Open()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer rc.Close()
|
||||||
|
|
||||||
|
img, err := conv.imageDecode(rc)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return img
|
||||||
|
}
|
||||||
|
|
||||||
|
func archiveHas(t *testing.T, conv *Converter, archive, name string) bool {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
list, err := conv.archiveList(archive)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
for _, n := range list {
|
||||||
|
if n == name {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|||||||
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 5.7 KiB |
@@ -0,0 +1,172 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gen2brain/cbconvert"
|
||||||
|
"github.com/gen2brain/iup-go/iup"
|
||||||
|
)
|
||||||
|
|
||||||
|
func fileDlg(title string, multiple, directory bool, dirKey string) ([]string, error) {
|
||||||
|
ret := make([]string, 0)
|
||||||
|
|
||||||
|
dlg := iup.FileDlg()
|
||||||
|
defer dlg.Destroy()
|
||||||
|
|
||||||
|
if !directory {
|
||||||
|
mf := "YES"
|
||||||
|
if !multiple {
|
||||||
|
mf = "NO"
|
||||||
|
}
|
||||||
|
|
||||||
|
dlg.SetAttributes(map[string]string{
|
||||||
|
"DIALOGTYPE": "OPEN",
|
||||||
|
"MULTIPLEFILES": mf,
|
||||||
|
"MULTIVALUEPATH": "YES",
|
||||||
|
"EXTFILTER": "Comic Files|*.rar;*.zip;*.7z;*.tar;*.cbr;*.cbz;*.cb7;*.cbt;*.pdf;*.epub;*.mobi;*.docx;*.pptx|",
|
||||||
|
"FILTER": "*.cb*", // for Motif
|
||||||
|
"TITLE": title,
|
||||||
|
"SHOWPREVIEW": "YES",
|
||||||
|
"PREVIEWWIDTH": "240",
|
||||||
|
"PREVIEWHEIGHT": "320",
|
||||||
|
})
|
||||||
|
|
||||||
|
dlg.SetCallback("FILE_CB", iup.FileFunc(previewCover()))
|
||||||
|
} else {
|
||||||
|
dlg.SetAttributes(map[string]string{
|
||||||
|
"DIALOGTYPE": "DIR",
|
||||||
|
"TITLE": title,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
setStartDir(dlg, dirKey)
|
||||||
|
|
||||||
|
iup.Popup(dlg, iup.CENTERPARENT, iup.CENTERPARENT)
|
||||||
|
|
||||||
|
if dlg.GetInt("STATUS") == 0 {
|
||||||
|
switch {
|
||||||
|
case multiple:
|
||||||
|
// MULTIVALUEPATH makes each MULTIVALUE a full path (id 0 is the path), so a folder-spanning selection works.
|
||||||
|
count := dlg.GetInt("MULTIVALUECOUNT")
|
||||||
|
if count > 1 {
|
||||||
|
for i := 1; i < count; i++ {
|
||||||
|
ret = append(ret, iup.GetAttributeId(dlg, "MULTIVALUE", i))
|
||||||
|
}
|
||||||
|
} else if value := dlg.GetAttribute("VALUE"); value != "" {
|
||||||
|
ret = append(ret, value)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
ret = append(ret, dlg.GetAttribute("VALUE"))
|
||||||
|
}
|
||||||
|
|
||||||
|
rememberDir(dlg, dirKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const dlgPreviewName = "_FILEDLGPREVIEW_"
|
||||||
|
|
||||||
|
// previewCover returns a FILE_CB handler that draws the highlighted comic's cover in the dialog preview pane.
|
||||||
|
func previewCover() iup.FileFunc {
|
||||||
|
var image iup.Ihandle
|
||||||
|
var lastFile string
|
||||||
|
|
||||||
|
return func(ih iup.Ihandle, filename, status string) int {
|
||||||
|
switch status {
|
||||||
|
case "PAINT":
|
||||||
|
iup.DrawBegin(ih)
|
||||||
|
cw, ch := iup.DrawGetSize(ih)
|
||||||
|
iup.DrawParentBackground(ih)
|
||||||
|
|
||||||
|
if filename != lastFile {
|
||||||
|
lastFile = filename
|
||||||
|
if image != 0 {
|
||||||
|
image.Destroy()
|
||||||
|
image = 0
|
||||||
|
}
|
||||||
|
if img := loadCover(filename, cw, ch); img != 0 {
|
||||||
|
image = img
|
||||||
|
iup.SetHandle(dlgPreviewName, image)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if image != 0 {
|
||||||
|
iw, iih, _ := iup.DrawGetImageInfo(dlgPreviewName)
|
||||||
|
iup.DrawImage(ih, dlgPreviewName, (cw-iw)/2, (ch-iih)/2, iw, iih)
|
||||||
|
} else {
|
||||||
|
ih.SetAttribute("DRAWCOLOR", "128 128 128")
|
||||||
|
tw, th := iup.DrawGetTextSize(ih, "No preview")
|
||||||
|
iup.DrawText(ih, "No preview", (cw-tw)/2, (ch-th)/2, 0, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
iup.DrawEnd(ih)
|
||||||
|
case "FINISH":
|
||||||
|
if image != 0 {
|
||||||
|
image.Destroy()
|
||||||
|
image = 0
|
||||||
|
}
|
||||||
|
lastFile = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return iup.DEFAULT
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadCover extracts the cover of a comic file and returns it as an IUP image fitted to w by h, or 0.
|
||||||
|
func loadCover(path string, w, h int) iup.Ihandle {
|
||||||
|
if w <= 0 || h <= 0 || !isComic(path) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
fi, err := os.Stat(path)
|
||||||
|
if err != nil || fi.IsDir() {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
opts := cbconvert.NewOptions()
|
||||||
|
opts.DPI = 96
|
||||||
|
|
||||||
|
img, err := cbconvert.New(opts).Preview(path, fi, w, h)
|
||||||
|
if err != nil || img.Image == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return iup.ImageFromImage(img.Image)
|
||||||
|
}
|
||||||
|
|
||||||
|
func isComic(path string) bool {
|
||||||
|
switch strings.ToLower(filepath.Ext(path)) {
|
||||||
|
case ".rar", ".zip", ".7z", ".tar", ".cbr", ".cbz", ".cb7", ".cbt",
|
||||||
|
".pdf", ".xps", ".epub", ".mobi", ".docx", ".pptx", ".xlsx":
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func saveDlg(title, dirKey string) string {
|
||||||
|
dlg := iup.FileDlg()
|
||||||
|
defer dlg.Destroy()
|
||||||
|
|
||||||
|
dlg.SetAttributes(map[string]string{
|
||||||
|
"DIALOGTYPE": "SAVE",
|
||||||
|
"EXTFILTER": "Comic Files|*.cbz;*.cbt|",
|
||||||
|
"FILTER": "*.cb*", // for Motif
|
||||||
|
"TITLE": title,
|
||||||
|
})
|
||||||
|
|
||||||
|
setStartDir(dlg, dirKey)
|
||||||
|
|
||||||
|
iup.Popup(dlg, iup.CENTERPARENT, iup.CENTERPARENT)
|
||||||
|
|
||||||
|
if dlg.GetInt("STATUS") == -1 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
rememberDir(dlg, dirKey)
|
||||||
|
|
||||||
|
return dlg.GetAttribute("VALUE")
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
[Desktop Entry]
|
||||||
|
Name=CBconvert
|
||||||
|
GenericName=A Comic Book converter
|
||||||
|
Comment=A comic converter with support for .cb*, .pdf, .xps, .epub, .mobi and directories.
|
||||||
|
Exec=cbconvert-gui
|
||||||
|
Icon=io.github.gen2brain.cbconvert
|
||||||
|
Terminal=false
|
||||||
|
Type=Application
|
||||||
|
StartupNotify=true
|
||||||
|
Categories=Graphics;Utility;
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<component type="desktop-application">
|
||||||
|
<id>io.github.gen2brain.cbconvert</id>
|
||||||
|
|
||||||
|
<name>CBconvert</name>
|
||||||
|
<summary>A Comic Book converter</summary>
|
||||||
|
|
||||||
|
<metadata_license>MIT</metadata_license>
|
||||||
|
<project_license>GPL-3</project_license>
|
||||||
|
|
||||||
|
<description>
|
||||||
|
<p>
|
||||||
|
A comic book converter with support for .cb*, .pdf, .xps, .epub, .mobi and directories.
|
||||||
|
Images can be converted to JPEG, PNG, TIFF, WEBP, AVIF, JXL or 4-Bit BMP (16 colors) file format.
|
||||||
|
</p>
|
||||||
|
</description>
|
||||||
|
|
||||||
|
<developer id="com.github.gen2brain">
|
||||||
|
<name>Milan Nikolic</name>
|
||||||
|
</developer>
|
||||||
|
|
||||||
|
<launchable type="desktop-id">io.github.gen2brain.cbconvert.desktop</launchable>
|
||||||
|
|
||||||
|
<screenshots>
|
||||||
|
<screenshot type="default">
|
||||||
|
<caption>Options</caption>
|
||||||
|
<image>https://raw.githubusercontent.com/gen2brain/cbconvert/master/cmd/cbconvert-gui/screenshots/linux-01.jpg</image>
|
||||||
|
</screenshot>
|
||||||
|
<screenshot>
|
||||||
|
<caption>Converting</caption>
|
||||||
|
<image>https://raw.githubusercontent.com/gen2brain/cbconvert/master/cmd/cbconvert-gui/screenshots/linux-02.jpg</image>
|
||||||
|
</screenshot>
|
||||||
|
</screenshots>
|
||||||
|
|
||||||
|
<url type="homepage">https://github.com/gen2brain/cbconvert</url>
|
||||||
|
<url type="bugtracker">https://github.com/gen2brain/cbconvert/issues</url>
|
||||||
|
|
||||||
|
<content_rating type="oars-1.1"/>
|
||||||
|
|
||||||
|
<releases>
|
||||||
|
<release version="1.1.0" date="2024-11-06" type="stable">
|
||||||
|
<description>
|
||||||
|
<ul>
|
||||||
|
<li>Use jpegli for smaller files</li>
|
||||||
|
<li>Remove ImageMagick dependency</li>
|
||||||
|
<li>Wayland support</li>
|
||||||
|
<li>Support for DOCX and PPTX</li>
|
||||||
|
</ul>
|
||||||
|
</description>
|
||||||
|
<url type="details">https://github.com/gen2brain/cbconvert/releases/tag/v1.1.0</url>
|
||||||
|
</release>
|
||||||
|
<release version="1.0.4" date="2024-02-08" type="stable">
|
||||||
|
<description>
|
||||||
|
<ul>
|
||||||
|
<li>Update modules and dependencies</li>
|
||||||
|
</ul>
|
||||||
|
</description>
|
||||||
|
<url type="details">https://github.com/gen2brain/cbconvert/releases/tag/v1.0.4</url>
|
||||||
|
</release>
|
||||||
|
<release version="1.0.3" date="2023-09-14" type="stable">
|
||||||
|
<description>
|
||||||
|
<ul>
|
||||||
|
<li>Add content rating</li>
|
||||||
|
</ul>
|
||||||
|
</description>
|
||||||
|
<url type="details">https://github.com/gen2brain/cbconvert/releases/tag/v1.0.3</url>
|
||||||
|
</release>
|
||||||
|
<release version="1.0.2" date="2023-09-14" type="stable">
|
||||||
|
<description>
|
||||||
|
<ul>
|
||||||
|
<li>Change flatpak exec/command name to cbconvert</li>
|
||||||
|
</ul>
|
||||||
|
</description>
|
||||||
|
<url type="details">https://github.com/gen2brain/cbconvert/releases/tag/v1.0.2</url>
|
||||||
|
</release>
|
||||||
|
<release version="1.0.1" date="2023-09-14" type="stable">
|
||||||
|
<description>
|
||||||
|
<ul>
|
||||||
|
<li>Add flatpak manifest</li>
|
||||||
|
</ul>
|
||||||
|
</description>
|
||||||
|
<url type="details">https://github.com/gen2brain/cbconvert/releases/tag/v1.0.1</url>
|
||||||
|
</release>
|
||||||
|
<release version="1.0.0" date="2023-09-14" type="stable">
|
||||||
|
<description>
|
||||||
|
<ul>
|
||||||
|
<li>Add GUI</li>
|
||||||
|
<li>Add support for JPEG XL</li>
|
||||||
|
</ul>
|
||||||
|
</description>
|
||||||
|
<url type="details">https://github.com/gen2brain/cbconvert/releases/tag/v1.0.0</url>
|
||||||
|
</release>
|
||||||
|
</releases>
|
||||||
|
|
||||||
|
</component>
|
||||||
|
After Width: | Height: | Size: 13 KiB |
@@ -0,0 +1,4 @@
|
|||||||
|
[Thumbnailer Entry]
|
||||||
|
TryExec=cbconvert
|
||||||
|
Exec=cbconvert thumbnail --quiet --width %s --outfile %o %i
|
||||||
|
MimeType=application/pdf;application/x-cb7;application/x-cbt;application/epub+zip;application/vnd.comicbook-rar;application/vnd.comicbook+zip;application/x-mobipocket-ebook;application/vnd.ms-xpsdocument;application/vnd.openxmlformats-officedocument.wordprocessingml.document;application/vnd.openxmlformats-officedocument.presentationml.presentation;application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;
|
||||||
@@ -0,0 +1,156 @@
|
|||||||
|
# github.com/anthonynsimon/bild v0.14.0
|
||||||
|
## explicit; go 1.21
|
||||||
|
github.com/anthonynsimon/bild/adjust
|
||||||
|
github.com/anthonynsimon/bild/clone
|
||||||
|
github.com/anthonynsimon/bild/math/f64
|
||||||
|
github.com/anthonynsimon/bild/parallel
|
||||||
|
github.com/anthonynsimon/bild/transform
|
||||||
|
github.com/anthonynsimon/bild/util
|
||||||
|
# github.com/dsoprea/go-exif/v2 v2.0.0-20230826092837-6579e82b732d
|
||||||
|
## explicit; go 1.13
|
||||||
|
github.com/dsoprea/go-exif/v2
|
||||||
|
github.com/dsoprea/go-exif/v2/common
|
||||||
|
github.com/dsoprea/go-exif/v2/undefined
|
||||||
|
# github.com/dsoprea/go-logging v0.0.0-20200710184922-b02d349568dd
|
||||||
|
## explicit; go 1.13
|
||||||
|
github.com/dsoprea/go-logging
|
||||||
|
# github.com/dsoprea/go-png-image-structure v0.0.0-20210512210324-29b889a6093d
|
||||||
|
## explicit; go 1.13
|
||||||
|
github.com/dsoprea/go-png-image-structure
|
||||||
|
# github.com/dsoprea/go-utility v0.0.0-20221003172846-a3e1774ef349
|
||||||
|
## explicit; go 1.12
|
||||||
|
github.com/dsoprea/go-utility/image
|
||||||
|
# github.com/dustin/go-humanize v1.0.1
|
||||||
|
## explicit; go 1.16
|
||||||
|
github.com/dustin/go-humanize
|
||||||
|
# github.com/ebitengine/purego v0.8.1
|
||||||
|
## explicit; go 1.18
|
||||||
|
github.com/ebitengine/purego
|
||||||
|
github.com/ebitengine/purego/internal/cgo
|
||||||
|
github.com/ebitengine/purego/internal/fakecgo
|
||||||
|
github.com/ebitengine/purego/internal/strings
|
||||||
|
# github.com/fvbommel/sortorder v1.1.0
|
||||||
|
## explicit; go 1.13
|
||||||
|
github.com/fvbommel/sortorder
|
||||||
|
# github.com/gen2brain/avif v0.4.1
|
||||||
|
## explicit; go 1.21
|
||||||
|
github.com/gen2brain/avif
|
||||||
|
# github.com/gen2brain/cbconvert v1.0.5-0.20241106181414-0dee611bf1de
|
||||||
|
## explicit; go 1.23
|
||||||
|
github.com/gen2brain/cbconvert
|
||||||
|
# github.com/gen2brain/go-fitz v1.24.14
|
||||||
|
## explicit; go 1.22
|
||||||
|
github.com/gen2brain/go-fitz
|
||||||
|
github.com/gen2brain/go-fitz/include/mupdf
|
||||||
|
github.com/gen2brain/go-fitz/include/mupdf/fitz
|
||||||
|
github.com/gen2brain/go-fitz/libs
|
||||||
|
# github.com/gen2brain/go-unarr v0.2.4
|
||||||
|
## explicit; go 1.21
|
||||||
|
github.com/gen2brain/go-unarr
|
||||||
|
github.com/gen2brain/go-unarr/unarrc
|
||||||
|
github.com/gen2brain/go-unarr/unarrc/external
|
||||||
|
github.com/gen2brain/go-unarr/unarrc/external/bzip2
|
||||||
|
github.com/gen2brain/go-unarr/unarrc/external/unarr
|
||||||
|
github.com/gen2brain/go-unarr/unarrc/external/unarr/_7z
|
||||||
|
github.com/gen2brain/go-unarr/unarrc/external/unarr/common
|
||||||
|
github.com/gen2brain/go-unarr/unarrc/external/unarr/lzmasdk
|
||||||
|
github.com/gen2brain/go-unarr/unarrc/external/unarr/rar
|
||||||
|
github.com/gen2brain/go-unarr/unarrc/external/unarr/tar
|
||||||
|
github.com/gen2brain/go-unarr/unarrc/external/unarr/zip
|
||||||
|
github.com/gen2brain/go-unarr/unarrc/external/zlib
|
||||||
|
# github.com/gen2brain/iup-go/iup v0.0.0-20241106050025-0f971ac33ed4
|
||||||
|
## explicit; go 1.22
|
||||||
|
github.com/gen2brain/iup-go/iup
|
||||||
|
github.com/gen2brain/iup-go/iup/external
|
||||||
|
github.com/gen2brain/iup-go/iup/external/include
|
||||||
|
github.com/gen2brain/iup-go/iup/external/src
|
||||||
|
github.com/gen2brain/iup-go/iup/external/src/cocoa
|
||||||
|
github.com/gen2brain/iup-go/iup/external/src/gtk
|
||||||
|
github.com/gen2brain/iup-go/iup/external/src/mot
|
||||||
|
github.com/gen2brain/iup-go/iup/external/src/win
|
||||||
|
github.com/gen2brain/iup-go/iup/external/src/win/wdl
|
||||||
|
github.com/gen2brain/iup-go/iup/external/src/win/wdl/dummy
|
||||||
|
github.com/gen2brain/iup-go/iup/external/srcgl
|
||||||
|
github.com/gen2brain/iup-go/iup/manifest
|
||||||
|
# github.com/gen2brain/jpegli v0.3.3
|
||||||
|
## explicit; go 1.22
|
||||||
|
github.com/gen2brain/jpegli
|
||||||
|
# github.com/gen2brain/jpegxl v0.4.2
|
||||||
|
## explicit; go 1.22
|
||||||
|
github.com/gen2brain/jpegxl
|
||||||
|
# github.com/gen2brain/webp v0.5.1
|
||||||
|
## explicit; go 1.22
|
||||||
|
github.com/gen2brain/webp
|
||||||
|
# github.com/go-errors/errors v1.5.1
|
||||||
|
## explicit; go 1.14
|
||||||
|
github.com/go-errors/errors
|
||||||
|
# github.com/godbus/dbus/v5 v5.1.0
|
||||||
|
## explicit; go 1.12
|
||||||
|
github.com/godbus/dbus/v5
|
||||||
|
# github.com/golang/geo v0.0.0-20230421003525-6adc56603217
|
||||||
|
## explicit; go 1.18
|
||||||
|
github.com/golang/geo/r1
|
||||||
|
github.com/golang/geo/r2
|
||||||
|
github.com/golang/geo/r3
|
||||||
|
github.com/golang/geo/s1
|
||||||
|
github.com/golang/geo/s2
|
||||||
|
# github.com/google/uuid v1.6.0
|
||||||
|
## explicit
|
||||||
|
github.com/google/uuid
|
||||||
|
# github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25
|
||||||
|
## explicit
|
||||||
|
github.com/jsummers/gobmp
|
||||||
|
# github.com/jupiterrider/ffi v0.2.1
|
||||||
|
## explicit; go 1.18
|
||||||
|
github.com/jupiterrider/ffi
|
||||||
|
# github.com/tetratelabs/wazero v1.8.1
|
||||||
|
## explicit; go 1.21
|
||||||
|
github.com/tetratelabs/wazero
|
||||||
|
github.com/tetratelabs/wazero/api
|
||||||
|
github.com/tetratelabs/wazero/experimental
|
||||||
|
github.com/tetratelabs/wazero/experimental/sys
|
||||||
|
github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1
|
||||||
|
github.com/tetratelabs/wazero/internal/descriptor
|
||||||
|
github.com/tetratelabs/wazero/internal/engine/interpreter
|
||||||
|
github.com/tetratelabs/wazero/internal/engine/wazevo
|
||||||
|
github.com/tetratelabs/wazero/internal/engine/wazevo/backend
|
||||||
|
github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/amd64
|
||||||
|
github.com/tetratelabs/wazero/internal/engine/wazevo/backend/isa/arm64
|
||||||
|
github.com/tetratelabs/wazero/internal/engine/wazevo/backend/regalloc
|
||||||
|
github.com/tetratelabs/wazero/internal/engine/wazevo/frontend
|
||||||
|
github.com/tetratelabs/wazero/internal/engine/wazevo/ssa
|
||||||
|
github.com/tetratelabs/wazero/internal/engine/wazevo/wazevoapi
|
||||||
|
github.com/tetratelabs/wazero/internal/expctxkeys
|
||||||
|
github.com/tetratelabs/wazero/internal/filecache
|
||||||
|
github.com/tetratelabs/wazero/internal/fsapi
|
||||||
|
github.com/tetratelabs/wazero/internal/ieee754
|
||||||
|
github.com/tetratelabs/wazero/internal/internalapi
|
||||||
|
github.com/tetratelabs/wazero/internal/leb128
|
||||||
|
github.com/tetratelabs/wazero/internal/moremath
|
||||||
|
github.com/tetratelabs/wazero/internal/platform
|
||||||
|
github.com/tetratelabs/wazero/internal/sock
|
||||||
|
github.com/tetratelabs/wazero/internal/sys
|
||||||
|
github.com/tetratelabs/wazero/internal/sysfs
|
||||||
|
github.com/tetratelabs/wazero/internal/u32
|
||||||
|
github.com/tetratelabs/wazero/internal/u64
|
||||||
|
github.com/tetratelabs/wazero/internal/version
|
||||||
|
github.com/tetratelabs/wazero/internal/wasip1
|
||||||
|
github.com/tetratelabs/wazero/internal/wasm
|
||||||
|
github.com/tetratelabs/wazero/internal/wasm/binary
|
||||||
|
github.com/tetratelabs/wazero/internal/wasmdebug
|
||||||
|
github.com/tetratelabs/wazero/internal/wasmruntime
|
||||||
|
github.com/tetratelabs/wazero/sys
|
||||||
|
# golang.org/x/image v0.21.0
|
||||||
|
## explicit; go 1.18
|
||||||
|
golang.org/x/image/ccitt
|
||||||
|
golang.org/x/image/tiff
|
||||||
|
golang.org/x/image/tiff/lzw
|
||||||
|
# golang.org/x/net v0.30.0
|
||||||
|
## explicit; go 1.18
|
||||||
|
golang.org/x/net/context
|
||||||
|
# golang.org/x/sync v0.8.0
|
||||||
|
## explicit; go 1.18
|
||||||
|
golang.org/x/sync/errgroup
|
||||||
|
# gopkg.in/yaml.v2 v2.4.0
|
||||||
|
## explicit; go 1.15
|
||||||
|
gopkg.in/yaml.v2
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
|
<string>English</string>
|
||||||
|
|
||||||
|
<key>CFBundleExecutable</key>
|
||||||
|
<string>cbconvert-gui</string>
|
||||||
|
|
||||||
|
<key>CFBundleIconFile</key>
|
||||||
|
<string>icon</string>
|
||||||
|
|
||||||
|
<key>CFBundleIdentifier</key>
|
||||||
|
<string>io.github.gen2brain.cbconvert</string>
|
||||||
|
|
||||||
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
|
<string>6.0</string>
|
||||||
|
|
||||||
|
<key>CFBundleName</key>
|
||||||
|
<string>CBconvert</string>
|
||||||
|
|
||||||
|
<key>CFBundlePackageType</key>
|
||||||
|
<string>APPL</string>
|
||||||
|
|
||||||
|
<key>CFBundleShortVersionString</key>
|
||||||
|
<string>1.0.0</string>
|
||||||
|
|
||||||
|
<key>CFBundleVersion</key>
|
||||||
|
<string>1</string>
|
||||||
|
|
||||||
|
<key>NSHighResolutionCapable</key>
|
||||||
|
<string>True</string>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>BuildMachineOSBuild</key>
|
||||||
|
<string>19H2</string>
|
||||||
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
|
<string>en</string>
|
||||||
|
<key>CFBundleExecutable</key>
|
||||||
|
<string>iup</string>
|
||||||
|
<key>CFBundleIdentifier</key>
|
||||||
|
<string>br.puc-rio.tecgraf.iup</string>
|
||||||
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
|
<string>6.0</string>
|
||||||
|
<key>CFBundleName</key>
|
||||||
|
<string>iup</string>
|
||||||
|
<key>CFBundlePackageType</key>
|
||||||
|
<string>FMWK</string>
|
||||||
|
<key>CFBundleShortVersionString</key>
|
||||||
|
<string>1.0</string>
|
||||||
|
<key>CFBundleSignature</key>
|
||||||
|
<string>????</string>
|
||||||
|
<key>CFBundleSupportedPlatforms</key>
|
||||||
|
<array>
|
||||||
|
<string>MacOSX</string>
|
||||||
|
</array>
|
||||||
|
<key>CFBundleVersion</key>
|
||||||
|
<string>1</string>
|
||||||
|
<key>DTCompiler</key>
|
||||||
|
<string>com.apple.compilers.llvm.clang.1_0</string>
|
||||||
|
<key>DTPlatformBuild</key>
|
||||||
|
<string>12D4e</string>
|
||||||
|
<key>DTPlatformName</key>
|
||||||
|
<string>macosx</string>
|
||||||
|
<key>DTPlatformVersion</key>
|
||||||
|
<string>11.1</string>
|
||||||
|
<key>DTSDKBuild</key>
|
||||||
|
<string>20C63</string>
|
||||||
|
<key>DTSDKName</key>
|
||||||
|
<string>macosx11.1</string>
|
||||||
|
<key>DTXcode</key>
|
||||||
|
<string>1240</string>
|
||||||
|
<key>DTXcodeBuild</key>
|
||||||
|
<string>12D4e</string>
|
||||||
|
<key>LSMinimumSystemVersion</key>
|
||||||
|
<string>10.14</string>
|
||||||
|
<key>NSHumanReadableCopyright</key>
|
||||||
|
<string>Copyright © 2015 Tecgraf, PUC-Rio, Brazil. All rights reserved.</string>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
|
After Width: | Height: | Size: 9.4 KiB |
@@ -0,0 +1,56 @@
|
|||||||
|
module github.com/gen2brain/cbconvert/cmd/cbconvert-gui
|
||||||
|
|
||||||
|
go 1.26
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/gen2brain/cbconvert v1.0.5-0.20260623161611-a5817c3ba5de
|
||||||
|
github.com/gen2brain/iup-go/iup v0.32.1-0.20260604112206-a19da6654449
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/STARRY-S/zip v0.2.3 // indirect
|
||||||
|
github.com/andybalholm/brotli v1.2.1 // indirect
|
||||||
|
github.com/anthonynsimon/bild v0.15.0 // indirect
|
||||||
|
github.com/bodgit/plumbing v1.3.0 // indirect
|
||||||
|
github.com/bodgit/sevenzip v1.6.4 // indirect
|
||||||
|
github.com/bodgit/windows v1.0.1 // indirect
|
||||||
|
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 // indirect
|
||||||
|
github.com/dsoprea/go-exif/v2 v2.0.0-20230826092837-6579e82b732d // indirect
|
||||||
|
github.com/dsoprea/go-logging v0.0.0-20200710184922-b02d349568dd // indirect
|
||||||
|
github.com/dsoprea/go-png-image-structure v0.0.0-20210512210324-29b889a6093d // indirect
|
||||||
|
github.com/dsoprea/go-utility v0.0.0-20221003172846-a3e1774ef349 // indirect
|
||||||
|
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||||
|
github.com/ebitengine/purego v0.10.1 // indirect
|
||||||
|
github.com/fvbommel/sortorder v1.1.0 // indirect
|
||||||
|
github.com/gen2brain/avif v0.5.1 // indirect
|
||||||
|
github.com/gen2brain/go-fitz v1.24.15 // indirect
|
||||||
|
github.com/gen2brain/jpegli v0.4.0 // indirect
|
||||||
|
github.com/gen2brain/jpegxl v0.5.1 // indirect
|
||||||
|
github.com/gen2brain/webp v0.6.1 // indirect
|
||||||
|
github.com/go-errors/errors v1.5.1 // indirect
|
||||||
|
github.com/golang/geo v0.0.0-20260622181633-f80708c60e82 // indirect
|
||||||
|
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
|
||||||
|
github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25 // indirect
|
||||||
|
github.com/jupiterrider/ffi v0.7.0 // indirect
|
||||||
|
github.com/klauspost/compress v1.18.6 // indirect
|
||||||
|
github.com/klauspost/pgzip v1.2.6 // indirect
|
||||||
|
github.com/mholt/archives v0.1.5 // indirect
|
||||||
|
github.com/mikelolasagasti/xz v1.0.1 // indirect
|
||||||
|
github.com/minio/minlz v1.1.1 // indirect
|
||||||
|
github.com/nwaples/rardecode/v2 v2.2.5 // indirect
|
||||||
|
github.com/pierrec/lz4/v4 v4.1.27 // indirect
|
||||||
|
github.com/sorairolake/lzip-go v0.3.8 // indirect
|
||||||
|
github.com/spf13/afero v1.15.0 // indirect
|
||||||
|
github.com/stangelandcl/ppmd v0.1.1 // indirect
|
||||||
|
github.com/tetratelabs/wazero v1.12.0 // indirect
|
||||||
|
github.com/ulikunitz/xz v0.5.15 // indirect
|
||||||
|
go4.org v0.0.0-20260112195520-a5071408f32f // indirect
|
||||||
|
golang.org/x/image v0.43.0 // indirect
|
||||||
|
golang.org/x/net v0.56.0 // indirect
|
||||||
|
golang.org/x/sync v0.21.0 // indirect
|
||||||
|
golang.org/x/sys v0.46.0 // indirect
|
||||||
|
golang.org/x/text v0.38.0 // indirect
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
|
)
|
||||||
|
|
||||||
|
replace github.com/gen2brain/cbconvert => ../..
|
||||||
@@ -0,0 +1,143 @@
|
|||||||
|
github.com/STARRY-S/zip v0.2.3 h1:luE4dMvRPDOWQdeDdUxUoZkzUIpTccdKdhHHsQJ1fm4=
|
||||||
|
github.com/STARRY-S/zip v0.2.3/go.mod h1:lqJ9JdeRipyOQJrYSOtpNAiaesFO6zVDsE8GIGFaoSk=
|
||||||
|
github.com/andybalholm/brotli v1.2.1 h1:R+f5xP285VArJDRgowrfb9DqL18yVK0gKAW/F+eTWro=
|
||||||
|
github.com/andybalholm/brotli v1.2.1/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
|
||||||
|
github.com/anthonynsimon/bild v0.15.0 h1:FzvaNLuNlAPKw1Xz7V2WYOcGIEBMj8Y6ZyAk7CI+HzA=
|
||||||
|
github.com/anthonynsimon/bild v0.15.0/go.mod h1:qIgJ9FldkCn0iy5Ad24fzUkz5R+iJ0WfhiV+6FeCB5A=
|
||||||
|
github.com/bodgit/plumbing v1.3.0 h1:pf9Itz1JOQgn7vEOE7v7nlEfBykYqvUYioC61TwWCFU=
|
||||||
|
github.com/bodgit/plumbing v1.3.0/go.mod h1:JOTb4XiRu5xfnmdnDJo6GmSbSbtSyufrsyZFByMtKEs=
|
||||||
|
github.com/bodgit/sevenzip v1.6.4 h1:iHiVJfxbrB6RF4X+snI2MpVgNBKmVfGaTqZGNlMQIU0=
|
||||||
|
github.com/bodgit/sevenzip v1.6.4/go.mod h1:ZtNi5KNgHXeXg1G7WiF0IWSuFE2eG6lt/cTGlvuirO0=
|
||||||
|
github.com/bodgit/windows v1.0.1 h1:tF7K6KOluPYygXa3Z2594zxlkbKPAOvqr97etrGNIz4=
|
||||||
|
github.com/bodgit/windows v1.0.1/go.mod h1:a6JLwrB4KrTR5hBpp8FI9/9W9jJfeQ2h4XDXU74ZCdM=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 h1:2tV76y6Q9BB+NEBasnqvs7e49aEBFI8ejC89PSnWH+4=
|
||||||
|
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707/go.mod h1:qssHWj60/X5sZFNxpG4HBPDHVqxNm4DfnCKgrbZOT+s=
|
||||||
|
github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=
|
||||||
|
github.com/dsoprea/go-exif/v2 v2.0.0-20200321225314-640175a69fe4/go.mod h1:Lm2lMM2zx8p4a34ZemkaUV95AnMl4ZvLbCUbwOvLC2E=
|
||||||
|
github.com/dsoprea/go-exif/v2 v2.0.0-20200604193436-ca8584a0e1c4/go.mod h1:9EXlPeHfblFFnwu5UOqmP2eoZfJyAZ2Ri/Vki33ajO0=
|
||||||
|
github.com/dsoprea/go-exif/v2 v2.0.0-20230826092837-6579e82b732d h1:yeH8wrJa3+8uKKDAdURHUK1ds2UvKhMqX2MiOdVeKPs=
|
||||||
|
github.com/dsoprea/go-exif/v2 v2.0.0-20230826092837-6579e82b732d/go.mod h1:oKrjk2kb3rAR5NbtSTLUMvMSbc+k8ZosI3MaVH47noc=
|
||||||
|
github.com/dsoprea/go-exif/v3 v3.0.0-20200717053412-08f1b6708903/go.mod h1:0nsO1ce0mh5czxGeLo4+OCZ/C6Eo6ZlMWsz7rH/Gxv8=
|
||||||
|
github.com/dsoprea/go-exif/v3 v3.0.0-20210512043655-120bcdb2a55e/go.mod h1:cg5SNYKHMmzxsr9X6ZeLh/nfBRHHp5PngtEPcujONtk=
|
||||||
|
github.com/dsoprea/go-logging v0.0.0-20190624164917-c4f10aab7696/go.mod h1:Nm/x2ZUNRW6Fe5C3LxdY1PyZY5wmDv/s5dkPJ/VB3iA=
|
||||||
|
github.com/dsoprea/go-logging v0.0.0-20200517223158-a10564966e9d/go.mod h1:7I+3Pe2o/YSU88W0hWlm9S22W7XI1JFNJ86U0zPKMf8=
|
||||||
|
github.com/dsoprea/go-logging v0.0.0-20200710184922-b02d349568dd h1:l+vLbuxptsC6VQyQsfD7NnEC8BZuFpz45PgY+pH8YTg=
|
||||||
|
github.com/dsoprea/go-logging v0.0.0-20200710184922-b02d349568dd/go.mod h1:7I+3Pe2o/YSU88W0hWlm9S22W7XI1JFNJ86U0zPKMf8=
|
||||||
|
github.com/dsoprea/go-png-image-structure v0.0.0-20210512210324-29b889a6093d h1:8+qI8ant/vZkNSsbwSjIR6XJfWcDVTg/qx/3pRUUZNA=
|
||||||
|
github.com/dsoprea/go-png-image-structure v0.0.0-20210512210324-29b889a6093d/go.mod h1:yTR3tKgyk20phAFg6IE9ulMA5NjEDD2wyx+okRFLVtw=
|
||||||
|
github.com/dsoprea/go-utility v0.0.0-20200711062821-fab8125e9bdf/go.mod h1:95+K3z2L0mqsVYd6yveIv1lmtT3tcQQ3dVakPySffW8=
|
||||||
|
github.com/dsoprea/go-utility v0.0.0-20221003172846-a3e1774ef349 h1:/py11NlxDaOxkT9OKN+gXgT+QOH5xj1ZRoyusfRIlo4=
|
||||||
|
github.com/dsoprea/go-utility v0.0.0-20221003172846-a3e1774ef349/go.mod h1:KVK+/Hul09ujXAGq+42UBgCTnXkiJZRnLYdURGjQUwo=
|
||||||
|
github.com/dsoprea/go-utility/v2 v2.0.0-20200717064901-2fccff4aa15e/go.mod h1:uAzdkPTub5Y9yQwXe8W4m2XuP0tK4a9Q/dantD0+uaU=
|
||||||
|
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||||
|
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||||
|
github.com/ebitengine/purego v0.10.1 h1:dewVBCBT2GaMu1SrNTYxQhgQBethzfhiwvZiLGP/qyY=
|
||||||
|
github.com/ebitengine/purego v0.10.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||||
|
github.com/fvbommel/sortorder v1.1.0 h1:fUmoe+HLsBTctBDoaBwpQo5N+nrCp8g/BjKb/6ZQmYw=
|
||||||
|
github.com/fvbommel/sortorder v1.1.0/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0=
|
||||||
|
github.com/gen2brain/avif v0.5.1 h1:LQzLsJpWyGlsa4wuZ3D57qEbCiICIK7Yidz5ZPEwzTk=
|
||||||
|
github.com/gen2brain/avif v0.5.1/go.mod h1:QgrYqdVE9y40PCfArK9VakcMIpYeDYpZmCSLkW6C1n8=
|
||||||
|
github.com/gen2brain/go-fitz v1.24.15 h1:sJNB1MOWkqnzzENPHggFpgxTwW0+S5WF/rM5wUBpJWo=
|
||||||
|
github.com/gen2brain/go-fitz v1.24.15/go.mod h1:SftkiVbTHqF141DuiLwBBM65zP7ig6AVDQpf2WlHamo=
|
||||||
|
github.com/gen2brain/iup-go/iup v0.32.1-0.20260604112206-a19da6654449 h1:HPqLUcspf4aPnyQIgeFKlLmphFVpFP34fzr1ZdnNyfc=
|
||||||
|
github.com/gen2brain/iup-go/iup v0.32.1-0.20260604112206-a19da6654449/go.mod h1:V4f7tHOJAeHtjQ+ju795QKv6DGdLEb4L5cmWB1sjSzU=
|
||||||
|
github.com/gen2brain/jpegli v0.4.0 h1:TnmAO8EcekS/XCboyLPjP6cqz025tkhdaFkiYNd3wH8=
|
||||||
|
github.com/gen2brain/jpegli v0.4.0/go.mod h1:zJ++s4symmKCN1CLkrY0dGXTY3s0NWbd94Rz9KLdCzk=
|
||||||
|
github.com/gen2brain/jpegxl v0.5.1 h1:UuBUIkZ35DErImU3bTA6rltfV5zSgVNOA/K5a6JibfE=
|
||||||
|
github.com/gen2brain/jpegxl v0.5.1/go.mod h1:Wlc6lqx03RJfhiQRyHa2e+8VQwT4/qv7zSRsNv9T+yE=
|
||||||
|
github.com/gen2brain/webp v0.6.1 h1:ei7Y1SWpQcdqz3YNDNyn4y2nQanxs9WLzwW5/2DKS64=
|
||||||
|
github.com/gen2brain/webp v0.6.1/go.mod h1:iGWMaCSw7t3I/Cv9llzEKmpnR36S8lS8VL/ZVjxU0JE=
|
||||||
|
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
|
||||||
|
github.com/go-errors/errors v1.0.2/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWEp4lssFEs=
|
||||||
|
github.com/go-errors/errors v1.1.1/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWEp4lssFEs=
|
||||||
|
github.com/go-errors/errors v1.5.1 h1:ZwEMSLRCapFLflTpT7NKaAc7ukJ8ZPEjzlxt8rPN8bk=
|
||||||
|
github.com/go-errors/errors v1.5.1/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
|
||||||
|
github.com/golang/geo v0.0.0-20190916061304-5b978397cfec/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
|
||||||
|
github.com/golang/geo v0.0.0-20200319012246-673a6f80352d/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
|
||||||
|
github.com/golang/geo v0.0.0-20260622181633-f80708c60e82 h1:q100v18oYIaE4p5myLFqDyfXTOxivzwGyLjGCEfv8x4=
|
||||||
|
github.com/golang/geo v0.0.0-20260622181633-f80708c60e82/go.mod h1:Mymr9kRGDc64JPr03TSZmuIBODZ3KyswLzm1xL0HFA8=
|
||||||
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
|
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||||
|
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
|
||||||
|
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
|
||||||
|
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||||
|
github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25 h1:YLvr1eE6cdCqjOe972w/cYF+FjW34v27+9Vo5106B4M=
|
||||||
|
github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25/go.mod h1:kLgvv7o6UM+0QSf0QjAse3wReFDsb9qbZJdfexWlrQw=
|
||||||
|
github.com/jupiterrider/ffi v0.7.0 h1:RKsl6Ascal+3kyAqR5Qcbp83LceQMLc1VZbPfHWoNzs=
|
||||||
|
github.com/jupiterrider/ffi v0.7.0/go.mod h1:9dauhpOfNqrqk28fxuu0kkdeFtT9Qr4vbfigiuIXN7c=
|
||||||
|
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
||||||
|
github.com/klauspost/compress v1.18.6 h1:2jupLlAwFm95+YDR+NwD2MEfFO9d4z4Prjl1XXDjuao=
|
||||||
|
github.com/klauspost/compress v1.18.6/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
|
||||||
|
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||||
|
github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=
|
||||||
|
github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
|
||||||
|
github.com/mholt/archives v0.1.5 h1:Fh2hl1j7VEhc6DZs2DLMgiBNChUux154a1G+2esNvzQ=
|
||||||
|
github.com/mholt/archives v0.1.5/go.mod h1:3TPMmBLPsgszL+1As5zECTuKwKvIfj6YcwWPpeTAXF4=
|
||||||
|
github.com/mikelolasagasti/xz v1.0.1 h1:Q2F2jX0RYJUG3+WsM+FJknv+6eVjsjXNDV0KJXZzkD0=
|
||||||
|
github.com/mikelolasagasti/xz v1.0.1/go.mod h1:muAirjiOUxPRXwm9HdDtB3uoRPrGnL85XHtokL9Hcgc=
|
||||||
|
github.com/minio/minlz v1.1.1 h1:OGmft1V6AnI/Wme332U6bhG54nxEan+VFgkD7lat4KM=
|
||||||
|
github.com/minio/minlz v1.1.1/go.mod h1:qT0aEB35q79LLornSzeDH75LBf3aH1MV+jB5w9Wasec=
|
||||||
|
github.com/nwaples/rardecode/v2 v2.2.5 h1:L5doqgGfQwI7qADJMqnkrSB86rpPsqQDrHeO0HWa5JY=
|
||||||
|
github.com/nwaples/rardecode/v2 v2.2.5/go.mod h1:7uz379lSxPe6j9nvzxUZ+n7mnJNgjsRNb6IbvGVHRmw=
|
||||||
|
github.com/pierrec/lz4/v4 v4.1.27 h1:+PhzhWDrjRj89TH2sw43nE3+4+W8lSxIuQadEHZyjUk=
|
||||||
|
github.com/pierrec/lz4/v4 v4.1.27/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/sorairolake/lzip-go v0.3.8 h1:j5Q2313INdTA80ureWYRhX+1K78mUXfMoPZCw/ivWik=
|
||||||
|
github.com/sorairolake/lzip-go v0.3.8/go.mod h1:JcBqGMV0frlxwrsE9sMWXDjqn3EeVf0/54YPsw66qkU=
|
||||||
|
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
|
||||||
|
github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
|
||||||
|
github.com/stangelandcl/ppmd v0.1.1 h1:c25QazhlWUn5nmR1QOzafKhQxBicAr7GGCKER2aJ8H8=
|
||||||
|
github.com/stangelandcl/ppmd v0.1.1/go.mod h1:Rrv7M+/2P5jYr/GMLhBl7Ug3uJ1bUiVzr5LbbaV6xgY=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
|
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||||
|
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||||
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
|
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||||
|
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
|
github.com/tetratelabs/wazero v1.12.0 h1:DuWcpNu/FzgEXgGBDp8J1Spc+CWOvvtvVyjKlaZopYU=
|
||||||
|
github.com/tetratelabs/wazero v1.12.0/go.mod h1:LvKtzl2RqO4gyF27BiXU+nKAjcV8f38U+kP/q2vgxh0=
|
||||||
|
github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||||
|
github.com/ulikunitz/xz v0.5.15 h1:9DNdB5s+SgV3bQ2ApL10xRc35ck0DuIX/isZvIk+ubY=
|
||||||
|
github.com/ulikunitz/xz v0.5.15/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||||
|
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
|
||||||
|
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
|
||||||
|
go4.org v0.0.0-20260112195520-a5071408f32f h1:ziUVAjmTPwQMBmYR1tbdRFJPtTcQUI12fH9QQjfb0Sw=
|
||||||
|
go4.org v0.0.0-20260112195520-a5071408f32f/go.mod h1:ZRJnO5ZI4zAwMFp+dS1+V6J6MSyAowhRqAE+DPa1Xp0=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/image v0.43.0 h1:FLxcP4ec2350nTfOC8ysKtqYSIFbk/QGjw1ZHNP4tsY=
|
||||||
|
golang.org/x/image v0.43.0/go.mod h1:rrpelvGFt+kLPAjPM4HeWPgrl0FtafueU//e5N0qk/Q=
|
||||||
|
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20200320220750-118fecf932d8/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
|
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
|
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
|
golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o=
|
||||||
|
golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec=
|
||||||
|
golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM=
|
||||||
|
golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw=
|
||||||
|
golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE=
|
||||||
|
golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
@@ -0,0 +1,442 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"slices"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gen2brain/cbconvert"
|
||||||
|
"github.com/gen2brain/iup-go/iup"
|
||||||
|
)
|
||||||
|
|
||||||
|
// selectRow focuses and selects the given 0-based row in the table.
|
||||||
|
func selectRow(i int) {
|
||||||
|
if i < 0 || i >= len(files) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
index = i
|
||||||
|
iup.GetHandle("Table").SetAttribute("FOCUSCELL", fmt.Sprintf("%d:1", i+1))
|
||||||
|
}
|
||||||
|
|
||||||
|
// onSort re-syncs the files slice to the table's displayed order after a sort, so rows keep mapping to the right file.
|
||||||
|
func onSort(ih iup.Ihandle, col int) int {
|
||||||
|
n := len(files)
|
||||||
|
if n < 2 {
|
||||||
|
return iup.DEFAULT
|
||||||
|
}
|
||||||
|
|
||||||
|
rowKey := func(name, size string) string {
|
||||||
|
return name + "\x00" + size
|
||||||
|
}
|
||||||
|
|
||||||
|
buckets := make(map[string][]int, n)
|
||||||
|
for i, f := range files {
|
||||||
|
size := strconv.FormatFloat(float64(f.Stat.Size())/(1024*1024), 'f', 2, 64)
|
||||||
|
k := rowKey(f.Name, size)
|
||||||
|
buckets[k] = append(buckets[k], i)
|
||||||
|
}
|
||||||
|
|
||||||
|
var selPath string
|
||||||
|
if index >= 0 && index < len(files) {
|
||||||
|
selPath = files[index].Path
|
||||||
|
}
|
||||||
|
|
||||||
|
reordered := make([]cbconvert.File, 0, n)
|
||||||
|
for lin := 1; lin <= n; lin++ {
|
||||||
|
k := rowKey(iup.GetAttributeId2(ih, "", lin, 1), iup.GetAttributeId2(ih, "", lin, 3))
|
||||||
|
idxs := buckets[k]
|
||||||
|
if len(idxs) == 0 {
|
||||||
|
return iup.DEFAULT
|
||||||
|
}
|
||||||
|
reordered = append(reordered, files[idxs[0]])
|
||||||
|
buckets[k] = idxs[1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
files = reordered
|
||||||
|
|
||||||
|
index = -1
|
||||||
|
if selPath != "" {
|
||||||
|
for i, f := range files {
|
||||||
|
if f.Path == selPath {
|
||||||
|
selectRow(i)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return iup.DEFAULT
|
||||||
|
}
|
||||||
|
|
||||||
|
// appendFile adds a file as a new row to the table and the files slice.
|
||||||
|
func appendFile(file cbconvert.File) {
|
||||||
|
files = append(files, file)
|
||||||
|
|
||||||
|
t := iup.GetHandle("Table")
|
||||||
|
lin := len(files)
|
||||||
|
t.SetAttribute("NUMLIN", strconv.Itoa(lin))
|
||||||
|
iup.SetAttributeId2(t, "", lin, 1, file.Name)
|
||||||
|
iup.SetAttributeId2(t, "", lin, 2, cbconvert.FileType(file.Path))
|
||||||
|
iup.SetAttributeId2(t, "", lin, 3, strconv.FormatFloat(float64(file.Stat.Size())/(1024*1024), 'f', 2, 64))
|
||||||
|
}
|
||||||
|
|
||||||
|
func previewPost() {
|
||||||
|
if index == -1 || len(files) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
width, height := previewSize()
|
||||||
|
iup.GetHandle("Loading").SetAttributes("VISIBLE=YES, START=YES")
|
||||||
|
if strings.ToLower(iup.GetGlobal("DRIVER")) == "motif" {
|
||||||
|
iup.GetHandle("Preview").SetAttribute("IMAGE", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
opts := options()
|
||||||
|
|
||||||
|
go func(opts cbconvert.Options) {
|
||||||
|
conv := cbconvert.New(opts)
|
||||||
|
|
||||||
|
var s string
|
||||||
|
file := files[index]
|
||||||
|
|
||||||
|
img, err := conv.Preview(file.Path, file.Stat, width, height)
|
||||||
|
if err != nil {
|
||||||
|
s = err.Error()
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
iup.PostMessage(iup.GetHandle("Preview"), s, 0, img)
|
||||||
|
}(opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
func onAddFiles(ih iup.Ihandle) int {
|
||||||
|
args, err := fileDlg("Add Files", true, false, inputDirKey)
|
||||||
|
if err != nil {
|
||||||
|
iup.PostMessage(iup.GetHandle("dlg"), err.Error(), 0, 0)
|
||||||
|
fmt.Println(err)
|
||||||
|
|
||||||
|
return iup.DEFAULT
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(args) > 0 {
|
||||||
|
conv := cbconvert.New(options())
|
||||||
|
|
||||||
|
fs, err := conv.Files(args)
|
||||||
|
if err != nil {
|
||||||
|
iup.PostMessage(iup.GetHandle("dlg"), err.Error(), 0, 0)
|
||||||
|
fmt.Println(err)
|
||||||
|
|
||||||
|
return iup.DEFAULT
|
||||||
|
}
|
||||||
|
|
||||||
|
wasEmpty := len(files) == 0
|
||||||
|
|
||||||
|
for _, file := range fs {
|
||||||
|
appendFile(file)
|
||||||
|
}
|
||||||
|
|
||||||
|
if wasEmpty && len(files) > 0 {
|
||||||
|
selectRow(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
setActive()
|
||||||
|
|
||||||
|
if wasEmpty {
|
||||||
|
previewPost()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return iup.DEFAULT
|
||||||
|
}
|
||||||
|
|
||||||
|
func onAddDir(ih iup.Ihandle) int {
|
||||||
|
args, err := fileDlg("Add Directory", false, true, inputDirKey)
|
||||||
|
if err != nil {
|
||||||
|
iup.PostMessage(iup.GetHandle("dlg"), err.Error(), 0, 0)
|
||||||
|
fmt.Println(err)
|
||||||
|
|
||||||
|
return iup.DEFAULT
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(args) > 0 {
|
||||||
|
conv := cbconvert.New(options())
|
||||||
|
|
||||||
|
fs, err := conv.Files(args)
|
||||||
|
if err != nil {
|
||||||
|
iup.PostMessage(iup.GetHandle("dlg"), err.Error(), 0, 0)
|
||||||
|
fmt.Println(err)
|
||||||
|
|
||||||
|
return iup.DEFAULT
|
||||||
|
}
|
||||||
|
|
||||||
|
wasEmpty := len(files) == 0
|
||||||
|
|
||||||
|
for _, file := range fs {
|
||||||
|
appendFile(file)
|
||||||
|
}
|
||||||
|
|
||||||
|
if wasEmpty && len(files) > 0 {
|
||||||
|
selectRow(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
setActive()
|
||||||
|
|
||||||
|
if wasEmpty {
|
||||||
|
previewPost()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return iup.DEFAULT
|
||||||
|
}
|
||||||
|
|
||||||
|
func onRemove(ih iup.Ihandle) int {
|
||||||
|
if index < 0 || index >= len(files) {
|
||||||
|
return iup.IGNORE
|
||||||
|
}
|
||||||
|
|
||||||
|
iup.GetHandle("Table").SetAttribute("DELLIN", strconv.Itoa(index+1))
|
||||||
|
files = slices.Delete(files, index, index+1)
|
||||||
|
|
||||||
|
if index >= len(files) {
|
||||||
|
index = len(files) - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
setActive()
|
||||||
|
previewPost()
|
||||||
|
|
||||||
|
return iup.DEFAULT
|
||||||
|
}
|
||||||
|
|
||||||
|
func onRemoveAll(ih iup.Ihandle) int {
|
||||||
|
index = -1
|
||||||
|
files = make([]cbconvert.File, 0)
|
||||||
|
|
||||||
|
iup.GetHandle("Table").SetAttribute("NUMLIN", "0")
|
||||||
|
setActive()
|
||||||
|
|
||||||
|
return iup.DEFAULT
|
||||||
|
}
|
||||||
|
|
||||||
|
func onThumbnail(ih iup.Ihandle) int {
|
||||||
|
conv := cbconvert.New(options())
|
||||||
|
conv.Nfiles = len(files)
|
||||||
|
activeConv = conv
|
||||||
|
setBusy(true)
|
||||||
|
|
||||||
|
conv.OnProgress = func() {
|
||||||
|
iup.PostMessage(iup.GetHandle("ProgressBar"), "progress2", 0, conv)
|
||||||
|
}
|
||||||
|
|
||||||
|
var canceled = false
|
||||||
|
conv.OnCancel = func() {
|
||||||
|
canceled = true
|
||||||
|
}
|
||||||
|
|
||||||
|
iup.GetHandle("dlg").SetCallback("K_ANY", iup.KAnyFunc(func(ih iup.Ihandle, c int) int {
|
||||||
|
if c == iup.K_ESC {
|
||||||
|
conv.Cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
return iup.DEFAULT
|
||||||
|
}))
|
||||||
|
|
||||||
|
iup.PostMessage(iup.GetHandle("ProgressBar"), "start", 0, conv)
|
||||||
|
|
||||||
|
go func(c *cbconvert.Converter) {
|
||||||
|
for _, file := range files {
|
||||||
|
if canceled {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.Thumbnail(file); err != nil {
|
||||||
|
iup.PostMessage(iup.GetHandle("dlg"), err.Error(), 0, 0)
|
||||||
|
fmt.Println(err)
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
iup.PostMessage(iup.GetHandle("ProgressBar"), "finish", 0, 0)
|
||||||
|
}(conv)
|
||||||
|
|
||||||
|
return iup.DEFAULT
|
||||||
|
}
|
||||||
|
|
||||||
|
func onCover(ih iup.Ihandle) int {
|
||||||
|
conv := cbconvert.New(options())
|
||||||
|
conv.Nfiles = len(files)
|
||||||
|
activeConv = conv
|
||||||
|
setBusy(true)
|
||||||
|
|
||||||
|
conv.OnProgress = func() {
|
||||||
|
iup.PostMessage(iup.GetHandle("ProgressBar"), "progress2", 0, conv)
|
||||||
|
}
|
||||||
|
|
||||||
|
var canceled = false
|
||||||
|
conv.OnCancel = func() {
|
||||||
|
canceled = true
|
||||||
|
}
|
||||||
|
|
||||||
|
iup.GetHandle("dlg").SetCallback("K_ANY", iup.KAnyFunc(func(ih iup.Ihandle, c int) int {
|
||||||
|
if c == iup.K_ESC {
|
||||||
|
conv.Cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
return iup.DEFAULT
|
||||||
|
}))
|
||||||
|
|
||||||
|
iup.PostMessage(iup.GetHandle("ProgressBar"), "start", 0, conv)
|
||||||
|
|
||||||
|
go func(c *cbconvert.Converter) {
|
||||||
|
for _, file := range files {
|
||||||
|
if canceled {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.Cover(file); err != nil {
|
||||||
|
iup.PostMessage(iup.GetHandle("dlg"), err.Error(), 0, 0)
|
||||||
|
fmt.Println(err)
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
iup.PostMessage(iup.GetHandle("ProgressBar"), "finish", 0, 0)
|
||||||
|
}(conv)
|
||||||
|
|
||||||
|
return iup.DEFAULT
|
||||||
|
}
|
||||||
|
|
||||||
|
func onConvert(ih iup.Ihandle) int {
|
||||||
|
if busy {
|
||||||
|
if activeConv != nil {
|
||||||
|
activeConv.Cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
return iup.DEFAULT
|
||||||
|
}
|
||||||
|
|
||||||
|
conv := cbconvert.New(options())
|
||||||
|
conv.Nfiles = len(files)
|
||||||
|
activeConv = conv
|
||||||
|
setBusy(true)
|
||||||
|
|
||||||
|
conv.OnStart = func() {
|
||||||
|
iup.PostMessage(iup.GetHandle("ProgressBar"), "convert", 0, conv)
|
||||||
|
}
|
||||||
|
|
||||||
|
conv.OnProgress = func() {
|
||||||
|
iup.PostMessage(iup.GetHandle("ProgressBar"), "progress", 0, conv)
|
||||||
|
}
|
||||||
|
|
||||||
|
iup.GetHandle("dlg").SetCallback("K_ANY", iup.KAnyFunc(func(ih iup.Ihandle, c int) int {
|
||||||
|
if c == iup.K_ESC {
|
||||||
|
conv.Cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
return iup.DEFAULT
|
||||||
|
})).SetCallback("CLOSE_CB", iup.CloseFunc(func(ih iup.Ihandle) int {
|
||||||
|
if err := os.RemoveAll(conv.Workdir); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return iup.DEFAULT
|
||||||
|
}))
|
||||||
|
|
||||||
|
convertErr := func(err error) {
|
||||||
|
if errors.Is(err, context.Canceled) {
|
||||||
|
if err := os.RemoveAll(conv.Workdir); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
iup.PostMessage(iup.GetHandle("dlg"), err.Error(), 0, 0)
|
||||||
|
fmt.Println(err)
|
||||||
|
|
||||||
|
if err := os.RemoveAll(conv.Workdir); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
go func(c *cbconvert.Converter) {
|
||||||
|
if c.Opts.Combine {
|
||||||
|
if err := c.Combine(files); err != nil {
|
||||||
|
convertErr(err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for _, file := range files {
|
||||||
|
if err := c.Convert(file); err != nil {
|
||||||
|
convertErr(err)
|
||||||
|
if errors.Is(err, context.Canceled) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
iup.PostMessage(iup.GetHandle("ProgressBar"), "finish", 0, 0)
|
||||||
|
}(conv)
|
||||||
|
|
||||||
|
return iup.DEFAULT
|
||||||
|
}
|
||||||
|
|
||||||
|
func onOutputDirectory(ih iup.Ihandle) int {
|
||||||
|
args, err := fileDlg("Output Directory", false, true, outputDirKey)
|
||||||
|
if err != nil {
|
||||||
|
iup.PostMessage(iup.GetHandle("dlg"), err.Error(), 0, 0)
|
||||||
|
fmt.Println(err)
|
||||||
|
|
||||||
|
return iup.DEFAULT
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(args) == 1 {
|
||||||
|
iup.GetHandle("OutDir").SetAttribute("VALUE", args[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
setActive()
|
||||||
|
|
||||||
|
return iup.DEFAULT
|
||||||
|
}
|
||||||
|
|
||||||
|
func onOutputFile(ih iup.Ihandle) int {
|
||||||
|
name := saveDlg("Output File", outputDirKey)
|
||||||
|
if name != "" {
|
||||||
|
iup.GetHandle("OutFile").SetAttribute("VALUE", filepath.Base(name))
|
||||||
|
iup.GetHandle("OutDir").SetAttribute("VALUE", filepath.Dir(name))
|
||||||
|
setActive()
|
||||||
|
}
|
||||||
|
|
||||||
|
return iup.DEFAULT
|
||||||
|
}
|
||||||
|
|
||||||
|
func onFilterChanged(ih iup.Ihandle) int {
|
||||||
|
switch ih.GetInt("VALUE") {
|
||||||
|
case 1:
|
||||||
|
ih.SetAttribute("TIP", "NearestNeighbor is the fastest resampling filter, no antialiasing")
|
||||||
|
case 2:
|
||||||
|
ih.SetAttribute("TIP", "Box filter (averaging pixels)")
|
||||||
|
case 3:
|
||||||
|
ih.SetAttribute("TIP", "Linear is the bilinear filter, smooth and reasonably fast")
|
||||||
|
case 4:
|
||||||
|
ih.SetAttribute("TIP", "MitchellNetravali is a smooth bicubic filter")
|
||||||
|
case 5:
|
||||||
|
ih.SetAttribute("TIP", "CatmullRom is a sharp bicubic filter")
|
||||||
|
case 6:
|
||||||
|
ih.SetAttribute("TIP", "Gaussian is a blurring filter that uses gaussian function, useful for noise removal")
|
||||||
|
case 7:
|
||||||
|
ih.SetAttribute("TIP", "Lanczos is a high-quality resampling filter, it's slower than cubic filters")
|
||||||
|
}
|
||||||
|
|
||||||
|
previewPost()
|
||||||
|
|
||||||
|
return iup.DEFAULT
|
||||||
|
}
|
||||||
@@ -0,0 +1,153 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
_ "embed"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"image/png"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime/debug"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gen2brain/cbconvert"
|
||||||
|
"github.com/gen2brain/iup-go/iup"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:generate rsrc --ico dist/windows/icon.ico --arch amd64 -o main_windows_amd64.syso
|
||||||
|
|
||||||
|
//go:embed assets/logo.png
|
||||||
|
var appLogo []byte
|
||||||
|
|
||||||
|
//go:embed assets/loading.gif
|
||||||
|
var appLoading []byte
|
||||||
|
|
||||||
|
var appVersion string
|
||||||
|
|
||||||
|
var (
|
||||||
|
index = -1
|
||||||
|
files []cbconvert.File
|
||||||
|
|
||||||
|
config iup.Ihandle
|
||||||
|
|
||||||
|
activeConv *cbconvert.Converter
|
||||||
|
busy bool
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
if appVersion != "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
info, ok := debug.ReadBuildInfo()
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if info.Main.Version != "" {
|
||||||
|
appVersion = info.Main.Version
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, kv := range info.Settings {
|
||||||
|
if kv.Value == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if kv.Key == "vcs.revision" {
|
||||||
|
appVersion = kv.Value
|
||||||
|
if len(appVersion) > 7 {
|
||||||
|
appVersion = kv.Value[:7]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
parseFlags()
|
||||||
|
|
||||||
|
iup.Open()
|
||||||
|
defer iup.Close()
|
||||||
|
|
||||||
|
iup.SetGlobal("APPNAME", "cbconvert")
|
||||||
|
iup.SetGlobal("APPID", "io.github.gen2brain.cbconvert")
|
||||||
|
iup.SetGlobal("AUTODARKMODE", "YES")
|
||||||
|
|
||||||
|
config = iup.Config()
|
||||||
|
iup.ConfigLoad(config)
|
||||||
|
|
||||||
|
img, _ := png.Decode(bytes.NewReader(appLogo))
|
||||||
|
iup.ImageFromImage(img).SetHandle("logo")
|
||||||
|
|
||||||
|
dlg := iup.Dialog(layout()).SetAttributes(fmt.Sprintf(`TITLE="CBconvert %s", ICON=logo, SHRINK=YES`, appVersion)).SetHandle("dlg")
|
||||||
|
|
||||||
|
dlg.SetCallback("POSTMESSAGE_CB", iup.PostMessageFunc(func(ih iup.Ihandle, s string, i int, p any) int {
|
||||||
|
sp := strings.Split(s, ": ")
|
||||||
|
if len(sp) > 1 {
|
||||||
|
iup.MessageError(ih, fmt.Sprintf("%s\n\n%s", sp[0], strings.Join(sp[1:], ": ")))
|
||||||
|
}
|
||||||
|
|
||||||
|
return iup.DEFAULT
|
||||||
|
}))
|
||||||
|
|
||||||
|
dlg.SetCallback("RESIZE_CB", iup.ResizeFunc(func(ih iup.Ihandle, width, height int) int {
|
||||||
|
iup.GetHandle("Preview").SetAttribute("IMAGE", "logo")
|
||||||
|
iup.Refresh(ih)
|
||||||
|
|
||||||
|
previewPost()
|
||||||
|
|
||||||
|
return iup.DEFAULT
|
||||||
|
}))
|
||||||
|
|
||||||
|
dlg.SetCallback("THEMECHANGED_CB", iup.ThemeChangedFunc(func(ih iup.Ihandle, darkMode int) int {
|
||||||
|
t := iup.GetHandle("Table")
|
||||||
|
tableRowColors(t, darkMode == 1)
|
||||||
|
t.SetAttribute("REDRAW", "YES")
|
||||||
|
|
||||||
|
return iup.DEFAULT
|
||||||
|
}))
|
||||||
|
|
||||||
|
iup.Map(dlg)
|
||||||
|
profilesInit()
|
||||||
|
|
||||||
|
iup.ShowXY(dlg, iup.CENTER, iup.CENTER)
|
||||||
|
iup.MainLoop()
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseFlags() {
|
||||||
|
flag.Usage = func() {
|
||||||
|
fmt.Fprintf(os.Stderr, "Usage: %s <command> [<flags>]\n\n", filepath.Base(os.Args[0]))
|
||||||
|
fmt.Fprintf(os.Stderr, "\nCommands:\n")
|
||||||
|
fmt.Fprintf(os.Stderr, "\n version\n \tPrint version\n\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
flag.NewFlagSet("version", flag.ExitOnError)
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
if flag.NArg() >= 1 {
|
||||||
|
if flag.Arg(0) == "version" {
|
||||||
|
fmt.Println(filepath.Base(os.Args[0]), appVersion)
|
||||||
|
os.Exit(0)
|
||||||
|
} else {
|
||||||
|
flag.Usage()
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func layout() iup.Ihandle {
|
||||||
|
return iup.Vbox(
|
||||||
|
iup.Hbox(
|
||||||
|
preview(),
|
||||||
|
iup.Hbox(
|
||||||
|
iup.Vbox(
|
||||||
|
list(),
|
||||||
|
tabs(),
|
||||||
|
).SetAttributes("NGAP=5"),
|
||||||
|
).SetAttributes("NGAP=5"),
|
||||||
|
buttons(),
|
||||||
|
).SetAttributes("NGAP=5, NMARGIN=5x5"),
|
||||||
|
|
||||||
|
iup.Label("").SetAttributes("SEPARATOR=HORIZONTAL"),
|
||||||
|
status(),
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gen2brain/cbconvert"
|
||||||
|
"github.com/gen2brain/iup-go/iup"
|
||||||
|
)
|
||||||
|
|
||||||
|
func options() cbconvert.Options {
|
||||||
|
var opts cbconvert.Options
|
||||||
|
opts.Recursive = iup.GetHandle("Recursive").GetAttribute("VALUE") == "ON"
|
||||||
|
opts.NoRGB = iup.GetHandle("NoRGB").GetAttribute("VALUE") == "ON"
|
||||||
|
opts.NoCover = iup.GetHandle("NoCover").GetAttribute("VALUE") == "ON"
|
||||||
|
opts.Size = iup.GetHandle("Size").GetInt("VALUE")
|
||||||
|
opts.OutDir = iup.GetHandle("OutDir").GetAttribute("VALUE")
|
||||||
|
opts.Suffix = iup.GetHandle("Suffix").GetAttribute("VALUE")
|
||||||
|
opts.NoConvert = iup.GetHandle("NoConvert").GetAttribute("VALUE") == "ON"
|
||||||
|
opts.NoNonImage = iup.GetHandle("NoNonImage").GetAttribute("VALUE") == "ON"
|
||||||
|
opts.Archive = strings.ToLower(iup.GetHandle("Archive").GetAttribute("VALUESTRING"))
|
||||||
|
opts.ZipLevel = zipLevel(iup.GetHandle("ZipLevel").GetAttribute("VALUESTRING"))
|
||||||
|
opts.Format = strings.ToLower(iup.GetHandle("Format").GetAttribute("VALUESTRING"))
|
||||||
|
opts.Width = iup.GetHandle("Width").GetInt("VALUE")
|
||||||
|
opts.Height = iup.GetHandle("Height").GetInt("VALUE")
|
||||||
|
opts.DPI = dpiValue(iup.GetHandle("DPI").GetAttribute("VALUE"))
|
||||||
|
opts.Fit = iup.GetHandle("Fit").GetAttribute("VALUE") == "ON"
|
||||||
|
opts.NoUpscale = iup.GetHandle("NoUpscale").GetAttribute("VALUE") == "ON"
|
||||||
|
opts.Filter = iup.GetHandle("Filter").GetInt("VALUE") - 1
|
||||||
|
opts.Quality = iup.GetHandle("Quality").GetInt("VALUE")
|
||||||
|
switch opts.Format {
|
||||||
|
case "webp", "avif", "jxl":
|
||||||
|
opts.Effort = iup.GetHandle("Effort").GetInt("VALUE")
|
||||||
|
default:
|
||||||
|
opts.Effort = -1
|
||||||
|
}
|
||||||
|
opts.Lossless = iup.GetHandle("Lossless").GetAttribute("VALUE") == "ON"
|
||||||
|
opts.Combine = iup.GetHandle("Combine").GetAttribute("VALUE") == "ON"
|
||||||
|
if opts.Combine {
|
||||||
|
opts.OutFile = iup.GetHandle("OutFile").GetAttribute("VALUE")
|
||||||
|
}
|
||||||
|
opts.Grayscale = iup.GetHandle("Grayscale").GetAttribute("VALUE") == "ON"
|
||||||
|
opts.Brightness = iup.GetHandle("Brightness").GetInt("VALUE")
|
||||||
|
opts.Contrast = iup.GetHandle("Contrast").GetInt("VALUE")
|
||||||
|
opts.Rotate = iup.GetHandle("Rotate").GetInt("VALUESTRING")
|
||||||
|
|
||||||
|
return opts
|
||||||
|
}
|
||||||
|
|
||||||
|
// shellArg quotes a command-line argument that contains whitespace.
|
||||||
|
func shellArg(s string) string {
|
||||||
|
if strings.ContainsAny(s, " \t") {
|
||||||
|
return `"` + strings.ReplaceAll(s, `"`, `\"`) + `"`
|
||||||
|
}
|
||||||
|
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func commandLine() string {
|
||||||
|
parts := append([]string{"cbconvert", "convert"}, options().Args()...)
|
||||||
|
for _, file := range files {
|
||||||
|
parts = append(parts, file.Path)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, p := range parts {
|
||||||
|
parts[i] = shellArg(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.Join(parts, " ")
|
||||||
|
}
|
||||||
|
|
||||||
|
func onCommand(iup.Ihandle) int {
|
||||||
|
iup.GetText("Command Line", commandLine(), -1)
|
||||||
|
|
||||||
|
return iup.DEFAULT
|
||||||
|
}
|
||||||
|
|
||||||
|
// zipLevel maps the compression dropdown selection to Options.ZipLevel.
|
||||||
|
func zipLevel(value string) int {
|
||||||
|
switch value {
|
||||||
|
case "Default":
|
||||||
|
return -1
|
||||||
|
case "Store (none)":
|
||||||
|
return 0
|
||||||
|
default:
|
||||||
|
level, err := strconv.Atoi(value)
|
||||||
|
if err != nil {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
return level
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func dpiValue(value string) int {
|
||||||
|
dpi, err := strconv.Atoi(strings.TrimSpace(value))
|
||||||
|
if err != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return dpi
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
## Screenshots
|
||||||
|
|
||||||
|
Windows
|
||||||
|
<img src="windows-01.jpg" width="700" title="Windows" alt="Windows" />
|
||||||
|
|
||||||
|
Linux
|
||||||
|
<img src="linux-01.jpg" width="700" title="Linux" alt="Linux" />
|
||||||
|
|
||||||
|
macOS
|
||||||
|
<img src="macos-01.jpg" width="700" title="macOS" alt="macOS" />
|
||||||
|
After Width: | Height: | Size: 210 KiB |
|
After Width: | Height: | Size: 251 KiB |
|
After Width: | Height: | Size: 300 KiB |
|
After Width: | Height: | Size: 280 KiB |
|
After Width: | Height: | Size: 206 KiB |
@@ -0,0 +1,249 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"slices"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gen2brain/iup-go/iup"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
pathsGroup = "Paths"
|
||||||
|
profilesGroup = "Profiles"
|
||||||
|
|
||||||
|
inputDirKey = "InputDir"
|
||||||
|
outputDirKey = "OutputDir"
|
||||||
|
)
|
||||||
|
|
||||||
|
type settingKind int
|
||||||
|
|
||||||
|
const (
|
||||||
|
kindBool settingKind = iota
|
||||||
|
kindInt
|
||||||
|
kindStr
|
||||||
|
)
|
||||||
|
|
||||||
|
type setting struct {
|
||||||
|
handle string
|
||||||
|
kind settingKind
|
||||||
|
def string
|
||||||
|
}
|
||||||
|
|
||||||
|
var settings = []setting{
|
||||||
|
{"Recursive", kindBool, "OFF"},
|
||||||
|
{"NoRGB", kindBool, "OFF"},
|
||||||
|
{"NoCover", kindBool, "OFF"},
|
||||||
|
{"NoConvert", kindBool, "OFF"},
|
||||||
|
{"NoNonImage", kindBool, "OFF"},
|
||||||
|
{"Combine", kindBool, "OFF"},
|
||||||
|
{"Fit", kindBool, "OFF"},
|
||||||
|
{"NoUpscale", kindBool, "OFF"},
|
||||||
|
{"Lossless", kindBool, "OFF"},
|
||||||
|
{"Grayscale", kindBool, "OFF"},
|
||||||
|
{"OutDir", kindStr, ""},
|
||||||
|
{"Suffix", kindStr, ""},
|
||||||
|
{"Width", kindStr, ""},
|
||||||
|
{"Height", kindStr, ""},
|
||||||
|
{"DPI", kindStr, "Default"},
|
||||||
|
{"Size", kindInt, "0"},
|
||||||
|
{"Quality", kindInt, "75"},
|
||||||
|
{"Effort", kindInt, "0"},
|
||||||
|
{"Brightness", kindInt, "0"},
|
||||||
|
{"Contrast", kindInt, "0"},
|
||||||
|
{"Format", kindInt, "1"},
|
||||||
|
{"Archive", kindInt, "1"},
|
||||||
|
{"ZipLevel", kindInt, "1"},
|
||||||
|
{"Filter", kindInt, "3"},
|
||||||
|
{"Rotate", kindInt, "1"},
|
||||||
|
}
|
||||||
|
|
||||||
|
func profileGroup(name string) string {
|
||||||
|
return "Profile:" + name
|
||||||
|
}
|
||||||
|
|
||||||
|
func profileNames() []string {
|
||||||
|
s := iup.ConfigGetVariableStr(config, profilesGroup, "Names")
|
||||||
|
if s == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.Split(s, ";")
|
||||||
|
}
|
||||||
|
|
||||||
|
func currentProfile() string {
|
||||||
|
return iup.ConfigGetVariableStrDef(config, profilesGroup, "Current", "Default")
|
||||||
|
}
|
||||||
|
|
||||||
|
func setStartDir(dlg iup.Ihandle, key string) {
|
||||||
|
if dir := iup.ConfigGetVariableStr(config, pathsGroup, key); dir != "" {
|
||||||
|
dlg.SetAttribute("DIRECTORY", dir)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func rememberDir(dlg iup.Ihandle, key string) {
|
||||||
|
dir := dlg.GetAttribute("DIRECTORY")
|
||||||
|
if dir == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
iup.ConfigSetVariableStr(config, pathsGroup, key, dir)
|
||||||
|
iup.ConfigSave(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
func settingsSave(group string) {
|
||||||
|
for _, s := range settings {
|
||||||
|
h := iup.GetHandle(s.handle)
|
||||||
|
switch s.kind {
|
||||||
|
case kindBool:
|
||||||
|
v := 0
|
||||||
|
if h.GetAttribute("VALUE") == "ON" {
|
||||||
|
v = 1
|
||||||
|
}
|
||||||
|
iup.ConfigSetVariableInt(config, group, s.handle, v)
|
||||||
|
case kindInt:
|
||||||
|
iup.ConfigSetVariableInt(config, group, s.handle, h.GetInt("VALUE"))
|
||||||
|
case kindStr:
|
||||||
|
iup.ConfigSetVariableStr(config, group, s.handle, h.GetAttribute("VALUE"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
iup.ConfigSave(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
// settingsApply sets every control from the given profile group, or from defaults when a group is empty.
|
||||||
|
func settingsApply(group string) {
|
||||||
|
for _, s := range settings {
|
||||||
|
h := iup.GetHandle(s.handle)
|
||||||
|
switch s.kind {
|
||||||
|
case kindBool:
|
||||||
|
def := 0
|
||||||
|
if s.def == "ON" {
|
||||||
|
def = 1
|
||||||
|
}
|
||||||
|
v := def
|
||||||
|
if group != "" {
|
||||||
|
v = iup.ConfigGetVariableIntDef(config, group, s.handle, def)
|
||||||
|
}
|
||||||
|
if v != 0 {
|
||||||
|
h.SetAttribute("VALUE", "ON")
|
||||||
|
} else {
|
||||||
|
h.SetAttribute("VALUE", "OFF")
|
||||||
|
}
|
||||||
|
case kindInt:
|
||||||
|
def, _ := strconv.Atoi(s.def)
|
||||||
|
v := def
|
||||||
|
if group != "" {
|
||||||
|
v = iup.ConfigGetVariableIntDef(config, group, s.handle, def)
|
||||||
|
}
|
||||||
|
h.SetAttribute("VALUE", strconv.Itoa(v))
|
||||||
|
case kindStr:
|
||||||
|
v := s.def
|
||||||
|
if group != "" {
|
||||||
|
v = iup.ConfigGetVariableStrDef(config, group, s.handle, s.def)
|
||||||
|
}
|
||||||
|
h.SetAttribute("VALUE", v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
syncLabels()
|
||||||
|
setActive()
|
||||||
|
previewPost()
|
||||||
|
}
|
||||||
|
|
||||||
|
// syncLabels mirrors slider values into their value labels and retunes the effort slider for the current format.
|
||||||
|
func syncLabels() {
|
||||||
|
iup.GetHandle("LabelQuality").SetAttribute("TITLE", iup.GetHandle("Quality").GetInt("VALUE"))
|
||||||
|
iup.GetHandle("LabelBrightness").SetAttribute("TITLE", iup.GetHandle("Brightness").GetInt("VALUE"))
|
||||||
|
iup.GetHandle("LabelContrast").SetAttribute("TITLE", iup.GetHandle("Contrast").GetInt("VALUE"))
|
||||||
|
|
||||||
|
format := strings.ToLower(iup.GetHandle("Format").GetAttribute("VALUESTRING"))
|
||||||
|
eff := iup.GetHandle("Effort").GetInt("VALUE")
|
||||||
|
setEffort(format)
|
||||||
|
switch format {
|
||||||
|
case "webp", "avif", "jxl":
|
||||||
|
val := iup.GetHandle("Effort")
|
||||||
|
val.SetAttribute("VALUE", strconv.Itoa(eff))
|
||||||
|
iup.GetHandle("LabelEffort").SetAttribute("TITLE", fmt.Sprintf("%s: %d", val.GetAttribute("EFFORTNAME"), eff))
|
||||||
|
}
|
||||||
|
|
||||||
|
iup.Refresh(iup.GetHandle("Tabs"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func fillProfileList() {
|
||||||
|
list := iup.GetHandle("Profile")
|
||||||
|
list.SetAttribute("REMOVEITEM", "ALL")
|
||||||
|
|
||||||
|
cur := currentProfile()
|
||||||
|
sel := 1
|
||||||
|
for i, n := range profileNames() {
|
||||||
|
list.SetAttribute(strconv.Itoa(i+1), n)
|
||||||
|
if n == cur {
|
||||||
|
sel = i + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
list.SetAttribute("VALUE", strconv.Itoa(sel))
|
||||||
|
}
|
||||||
|
|
||||||
|
// profilesInit loads the current profile on startup, creating a default one on the first run.
|
||||||
|
func profilesInit() {
|
||||||
|
if len(profileNames()) == 0 {
|
||||||
|
iup.ConfigSetVariableStr(config, profilesGroup, "Names", "Default")
|
||||||
|
iup.ConfigSetVariableStr(config, profilesGroup, "Current", "Default")
|
||||||
|
settingsSave(profileGroup("Default"))
|
||||||
|
}
|
||||||
|
|
||||||
|
fillProfileList()
|
||||||
|
settingsApply(profileGroup(currentProfile()))
|
||||||
|
}
|
||||||
|
|
||||||
|
func onProfileSelect(ih iup.Ihandle) int {
|
||||||
|
name := ih.GetAttribute("VALUESTRING")
|
||||||
|
if name == "" {
|
||||||
|
return iup.DEFAULT
|
||||||
|
}
|
||||||
|
|
||||||
|
iup.ConfigSetVariableStr(config, profilesGroup, "Current", name)
|
||||||
|
iup.ConfigSave(config)
|
||||||
|
|
||||||
|
settingsApply(profileGroup(name))
|
||||||
|
|
||||||
|
return iup.DEFAULT
|
||||||
|
}
|
||||||
|
|
||||||
|
func onSave(iup.Ihandle) int {
|
||||||
|
name := currentProfile()
|
||||||
|
if iup.GetParam("Save Profile", nil, "Name: %s\n", &name) != 1 {
|
||||||
|
return iup.DEFAULT
|
||||||
|
}
|
||||||
|
|
||||||
|
name = strings.TrimSpace(name)
|
||||||
|
if name == "" || strings.ContainsAny(name, ".;") {
|
||||||
|
iup.Message("Invalid Name", "Profile name must not be empty or contain '.' or ';'.")
|
||||||
|
|
||||||
|
return iup.DEFAULT
|
||||||
|
}
|
||||||
|
|
||||||
|
settingsSave(profileGroup(name))
|
||||||
|
|
||||||
|
names := profileNames()
|
||||||
|
if !slices.Contains(names, name) {
|
||||||
|
names = append(names, name)
|
||||||
|
iup.ConfigSetVariableStr(config, profilesGroup, "Names", strings.Join(names, ";"))
|
||||||
|
}
|
||||||
|
|
||||||
|
iup.ConfigSetVariableStr(config, profilesGroup, "Current", name)
|
||||||
|
iup.ConfigSave(config)
|
||||||
|
|
||||||
|
fillProfileList()
|
||||||
|
|
||||||
|
return iup.DEFAULT
|
||||||
|
}
|
||||||
|
|
||||||
|
func onReset(iup.Ihandle) int {
|
||||||
|
settingsApply("")
|
||||||
|
|
||||||
|
return iup.DEFAULT
|
||||||
|
}
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import "github.com/gen2brain/iup-go/iup"
|
||||||
|
|
||||||
|
func setActive() {
|
||||||
|
if busy {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
opts := options()
|
||||||
|
count := iup.GetHandle("Table").GetInt("NUMLIN")
|
||||||
|
|
||||||
|
if count == 0 {
|
||||||
|
iup.GetHandle("Remove").SetAttribute("ACTIVE", "NO")
|
||||||
|
iup.GetHandle("RemoveAll").SetAttribute("ACTIVE", "NO")
|
||||||
|
|
||||||
|
iup.GetHandle("Preview").SetAttribute("IMAGE", "logo")
|
||||||
|
iup.GetHandle("PreviewInfo").SetAttribute("TITLE", "")
|
||||||
|
} else {
|
||||||
|
if index != -1 {
|
||||||
|
iup.GetHandle("Remove").SetAttribute("ACTIVE", "YES")
|
||||||
|
}
|
||||||
|
iup.GetHandle("RemoveAll").SetAttribute("ACTIVE", "YES")
|
||||||
|
}
|
||||||
|
|
||||||
|
active := "YES"
|
||||||
|
var tip string
|
||||||
|
switch {
|
||||||
|
case count == 0 && opts.OutDir == "":
|
||||||
|
active, tip = "NO", "Add files and set output directory"
|
||||||
|
case count == 0:
|
||||||
|
active, tip = "NO", "Add files"
|
||||||
|
case opts.OutDir == "":
|
||||||
|
active, tip = "NO", "Set output directory"
|
||||||
|
}
|
||||||
|
|
||||||
|
enabledTip := map[string]string{
|
||||||
|
"Thumbnail": "Extract cover thumbnails",
|
||||||
|
"Cover": "Extract covers",
|
||||||
|
"Convert": "Convert files to the selected format",
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, h := range []string{"Thumbnail", "Cover", "Convert"} {
|
||||||
|
b := iup.GetHandle(h)
|
||||||
|
b.SetAttribute("ACTIVE", active)
|
||||||
|
if active == "YES" {
|
||||||
|
b.SetAttribute("TIP", enabledTip[h])
|
||||||
|
} else {
|
||||||
|
b.SetAttribute("TIP", tip)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.NoConvert {
|
||||||
|
iup.GetHandle("VboxImage").SetAttribute("ACTIVE", "NO")
|
||||||
|
iup.GetHandle("VboxTransform").SetAttribute("ACTIVE", "NO")
|
||||||
|
} else {
|
||||||
|
iup.GetHandle("VboxImage").SetAttribute("ACTIVE", "YES")
|
||||||
|
iup.GetHandle("VboxTransform").SetAttribute("ACTIVE", "YES")
|
||||||
|
}
|
||||||
|
|
||||||
|
canLossless := opts.Format == "webp" || opts.Format == "avif" || opts.Format == "jxl"
|
||||||
|
losslessOn := canLossless && opts.Lossless
|
||||||
|
|
||||||
|
if (opts.Format == "jpeg" || canLossless) && !opts.NoConvert && !losslessOn {
|
||||||
|
iup.GetHandle("VboxQuality").SetAttribute("ACTIVE", "YES")
|
||||||
|
} else {
|
||||||
|
iup.GetHandle("VboxQuality").SetAttribute("ACTIVE", "NO")
|
||||||
|
}
|
||||||
|
|
||||||
|
if canLossless && !opts.NoConvert {
|
||||||
|
iup.GetHandle("VboxEffort").SetAttribute("ACTIVE", "YES")
|
||||||
|
iup.GetHandle("Lossless").SetAttribute("ACTIVE", "YES")
|
||||||
|
} else {
|
||||||
|
iup.GetHandle("VboxEffort").SetAttribute("ACTIVE", "NO")
|
||||||
|
iup.GetHandle("Lossless").SetAttribute("ACTIVE", "NO")
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.Width != 0 && opts.Height != 0 && !opts.NoConvert {
|
||||||
|
iup.GetHandle("Fit").SetAttribute("ACTIVE", "YES")
|
||||||
|
} else {
|
||||||
|
iup.GetHandle("Fit").SetAttribute("ACTIVE", "NO")
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.Combine {
|
||||||
|
iup.GetHandle("VboxOutFile").SetAttribute("ACTIVE", "YES")
|
||||||
|
} else {
|
||||||
|
iup.GetHandle("VboxOutFile").SetAttribute("ACTIVE", "NO")
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.Archive == "zip" {
|
||||||
|
iup.GetHandle("VboxZipLevel").SetAttribute("ACTIVE", "YES")
|
||||||
|
} else {
|
||||||
|
iup.GetHandle("VboxZipLevel").SetAttribute("ACTIVE", "NO")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// setBusy locks the UI while an operation runs and turns Convert into a Cancel button.
|
||||||
|
func setBusy(on bool) {
|
||||||
|
busy = on
|
||||||
|
|
||||||
|
// Controls not governed by setActive; setActive owns the rest.
|
||||||
|
always := "YES"
|
||||||
|
if on {
|
||||||
|
always = "NO"
|
||||||
|
}
|
||||||
|
for _, h := range []string{"AddFiles", "AddDir", "Profile", "Reset", "Save", "Command", "Tabs", "Table"} {
|
||||||
|
iup.GetHandle(h).SetAttribute("ACTIVE", always)
|
||||||
|
}
|
||||||
|
|
||||||
|
convert := iup.GetHandle("Convert")
|
||||||
|
if on {
|
||||||
|
for _, h := range []string{"Remove", "RemoveAll", "Thumbnail", "Cover"} {
|
||||||
|
iup.GetHandle(h).SetAttribute("ACTIVE", "NO")
|
||||||
|
}
|
||||||
|
convert.SetAttribute("ACTIVE", "YES")
|
||||||
|
convert.SetAttribute("TITLE", "Cancel")
|
||||||
|
convert.SetAttribute("TIP", "Cancel the running operation (or press Esc)")
|
||||||
|
} else {
|
||||||
|
activeConv = nil
|
||||||
|
convert.SetAttribute("TITLE", "&Convert")
|
||||||
|
convert.SetAttribute("TIP", "Convert files to the selected format")
|
||||||
|
setActive() // restores the conditional buttons and option boxes
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,655 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"image/gif"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gen2brain/cbconvert"
|
||||||
|
"github.com/gen2brain/iup-go/iup"
|
||||||
|
)
|
||||||
|
|
||||||
|
func setEffort(format string) {
|
||||||
|
val := iup.GetHandle("Effort")
|
||||||
|
|
||||||
|
var name string
|
||||||
|
|
||||||
|
switch format {
|
||||||
|
case "webp":
|
||||||
|
val.SetAttributes("MIN=0, MAX=6, SHOWTICKS=7, VALUE=4")
|
||||||
|
val.SetAttribute("TIP", "WEBP method, higher is better/slower (0-6, default 4)")
|
||||||
|
name = "Method"
|
||||||
|
case "avif":
|
||||||
|
val.SetAttributes("MIN=0, MAX=10, SHOWTICKS=11, VALUE=10")
|
||||||
|
val.SetAttribute("TIP", "AVIF speed, higher is faster/worse (0-10, default 10)")
|
||||||
|
name = "Speed"
|
||||||
|
case "jxl":
|
||||||
|
val.SetAttributes("MIN=1, MAX=10, SHOWTICKS=10, VALUE=7")
|
||||||
|
val.SetAttribute("TIP", "JXL effort, higher is better/slower (1-10, default 7)")
|
||||||
|
name = "Effort"
|
||||||
|
default:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val.SetAttribute("EFFORTNAME", name)
|
||||||
|
iup.GetHandle("LabelEffort").SetAttribute("TITLE", fmt.Sprintf("%s: %d", name, val.GetInt("VALUE")))
|
||||||
|
iup.Refresh(iup.GetHandle("LabelEffort"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// tableRowColors sets the alternating row colors for dark or light mode.
|
||||||
|
func tableRowColors(t iup.Ihandle, dark bool) {
|
||||||
|
even, odd := "#F0F0F0", "#FFFFFF"
|
||||||
|
if dark {
|
||||||
|
even, odd = "#3A3A3A", "#2D2D2D"
|
||||||
|
}
|
||||||
|
t.SetAttribute("EVENROWCOLOR", even)
|
||||||
|
t.SetAttribute("ODDROWCOLOR", odd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func list() iup.Ihandle {
|
||||||
|
t := iup.Table().SetHandle("Table")
|
||||||
|
t.SetAttributes(map[string]string{
|
||||||
|
"EXPAND": "YES",
|
||||||
|
"NUMCOL": "3",
|
||||||
|
"NUMLIN": "0",
|
||||||
|
"TITLE1": "Title",
|
||||||
|
"TITLE2": "Type",
|
||||||
|
"TITLE3": "Size (MiB)",
|
||||||
|
"WIDTH1": "150",
|
||||||
|
"WIDTH2": "50",
|
||||||
|
"WIDTH3": "100",
|
||||||
|
"ALIGNMENT2": "ACENTER",
|
||||||
|
"ALIGNMENT3": "ARIGHT",
|
||||||
|
"SELECTIONMODE": "SINGLE",
|
||||||
|
"USERRESIZE": "YES",
|
||||||
|
"STRETCHLAST": "NO",
|
||||||
|
"FOCUSRECT": "NO",
|
||||||
|
"SORTABLE": "YES",
|
||||||
|
"ALTERNATECOLOR": "YES",
|
||||||
|
})
|
||||||
|
|
||||||
|
tableRowColors(t, iup.GetGlobal("DARKMODE") == "YES" && iup.GetGlobal("AUTODARKMODE") == "YES")
|
||||||
|
|
||||||
|
t.SetCallback("ENTERITEM_CB", iup.EnterItemFunc(func(ih iup.Ihandle, lin, col int) int {
|
||||||
|
index = lin - 1
|
||||||
|
setActive()
|
||||||
|
previewPost()
|
||||||
|
|
||||||
|
return iup.DEFAULT
|
||||||
|
}))
|
||||||
|
|
||||||
|
t.SetCallback("SORT_CB", iup.TableSortFunc(onSort))
|
||||||
|
|
||||||
|
t.SetCallback("DROPFILES_CB", iup.DropFilesFunc(func(ih iup.Ihandle, fileName string, num, x, y int) int {
|
||||||
|
dec, err := url.QueryUnescape(fileName)
|
||||||
|
if err != nil {
|
||||||
|
iup.PostMessage(iup.GetHandle("dlg"), err.Error(), 0, 0)
|
||||||
|
fmt.Println(err)
|
||||||
|
|
||||||
|
return iup.DEFAULT
|
||||||
|
}
|
||||||
|
|
||||||
|
conv := cbconvert.New(options())
|
||||||
|
|
||||||
|
fs, err := conv.Files([]string{dec})
|
||||||
|
if err != nil {
|
||||||
|
iup.PostMessage(iup.GetHandle("dlg"), err.Error(), 0, 0)
|
||||||
|
fmt.Println(err)
|
||||||
|
|
||||||
|
return iup.DEFAULT
|
||||||
|
}
|
||||||
|
|
||||||
|
wasEmpty := len(files) == 0
|
||||||
|
|
||||||
|
for _, file := range fs {
|
||||||
|
appendFile(file)
|
||||||
|
}
|
||||||
|
|
||||||
|
if wasEmpty && len(files) > 0 {
|
||||||
|
selectRow(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
setActive()
|
||||||
|
|
||||||
|
if wasEmpty {
|
||||||
|
previewPost()
|
||||||
|
}
|
||||||
|
|
||||||
|
return iup.DEFAULT
|
||||||
|
}))
|
||||||
|
|
||||||
|
return iup.Vbox(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func previewSize() (int, int) {
|
||||||
|
var width, height int
|
||||||
|
sp := strings.Split(iup.GetHandle("Preview").GetAttribute("RASTERSIZE"), "x")
|
||||||
|
if len(sp) == 2 {
|
||||||
|
width, _ = strconv.Atoi(sp[0])
|
||||||
|
height, _ = strconv.Atoi(sp[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
return width, height
|
||||||
|
}
|
||||||
|
|
||||||
|
func preview() iup.Ihandle {
|
||||||
|
return iup.Frame(
|
||||||
|
iup.Vbox(
|
||||||
|
iup.Label("").SetAttributes("EXPAND=YES, ALIGNMENT=ACENTER, MINSIZE=400x, IMAGE=cover").SetHandle("Preview").
|
||||||
|
SetCallback("POSTMESSAGE_CB", iup.PostMessageFunc(func(ih iup.Ihandle, s string, i int, p any) int {
|
||||||
|
img := p.(cbconvert.Image)
|
||||||
|
|
||||||
|
iup.GetHandle("Loading").SetAttributes("VISIBLE=NO, STOP=YES")
|
||||||
|
|
||||||
|
if img.Image != nil && len(s) == 0 {
|
||||||
|
iup.Destroy(iup.GetHandle("cover"))
|
||||||
|
iup.ImageFromImage(img.Image).SetHandle("cover")
|
||||||
|
|
||||||
|
ih.SetAttribute("IMAGE", "cover")
|
||||||
|
iup.GetHandle("PreviewInfo").SetAttribute("TITLE", fmt.Sprintf("%s (%dx%d)", img.SizeHuman, img.Width, img.Height))
|
||||||
|
} else {
|
||||||
|
ih.SetAttribute("IMAGE", "logo")
|
||||||
|
iup.GetHandle("PreviewInfo").SetAttribute("TITLE", "")
|
||||||
|
|
||||||
|
sp := strings.Split(s, ": ")
|
||||||
|
if len(sp) > 1 {
|
||||||
|
iup.MessageError(ih, fmt.Sprintf("%s\n\n%s", sp[0], strings.Join(sp[1:], ": ")))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return iup.DEFAULT
|
||||||
|
})),
|
||||||
|
iup.Label("").SetAttributes("EXPAND=HORIZONTAL, ALIGNMENT=ACENTER").SetHandle("PreviewInfo"),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func tabInput() iup.Ihandle {
|
||||||
|
return iup.Hbox(
|
||||||
|
iup.Vbox(
|
||||||
|
iup.Toggle(" Recurse SubDirectories").SetHandle("Recursive").
|
||||||
|
SetAttributes(`TIP="Process subdirectories recursively"`),
|
||||||
|
iup.Toggle(" Only Grayscale Images").SetHandle("NoRGB").
|
||||||
|
SetAttributes(`TIP="Do not convert images that have RGB colorspace"`),
|
||||||
|
iup.Toggle(" Exclude Cover").SetHandle("NoCover").
|
||||||
|
SetAttributes(`TIP="Do not convert the cover image"`),
|
||||||
|
iup.Toggle(" Remove Non-Image Files from the Archive").SetHandle("NoNonImage").
|
||||||
|
SetAttribute("TIP", "Remove .nfo, .xml, .txt files from the archive"),
|
||||||
|
iup.Toggle(" Do not Transform or Convert Images").SetHandle("NoConvert").
|
||||||
|
SetAttributes(`TIP="Copy images from archive or directory without modifications"`).
|
||||||
|
SetCallback("VALUECHANGED_CB", iup.ValueChangedFunc(func(ih iup.Ihandle) int {
|
||||||
|
setActive()
|
||||||
|
|
||||||
|
return iup.DEFAULT
|
||||||
|
})),
|
||||||
|
).SetAttributes("NGAP=10"),
|
||||||
|
iup.Space().SetAttribute("SIZE", "15"),
|
||||||
|
iup.Vbox(
|
||||||
|
iup.Vbox(
|
||||||
|
iup.Label("Minimum Size (MiB):"),
|
||||||
|
iup.Text().SetAttributes(`SPIN=YES, SPINMAX=2048, VISIBLECOLUMNS=4, MASK="/d*"`).SetHandle("Size").
|
||||||
|
SetAttributes(`TIP="Process only files larger than minimum size"`),
|
||||||
|
),
|
||||||
|
iup.Vbox(
|
||||||
|
iup.Label("Document DPI:"),
|
||||||
|
iup.List().SetAttributes(map[string]string{
|
||||||
|
"DROPDOWN": "YES",
|
||||||
|
"EDITBOX": "YES",
|
||||||
|
"VISIBLECOLUMNS": "6",
|
||||||
|
"VALUE": "Default",
|
||||||
|
"1": "Default",
|
||||||
|
"2": "150",
|
||||||
|
"3": "300",
|
||||||
|
"4": "600",
|
||||||
|
"5": "1200",
|
||||||
|
}).SetHandle("DPI").
|
||||||
|
SetAttribute("TIP", "Resolution for rendering documents (PDF, EPUB, etc.); Default is 300"),
|
||||||
|
),
|
||||||
|
).SetAttributes("NGAP=10"),
|
||||||
|
).SetHandle("VboxInput")
|
||||||
|
}
|
||||||
|
|
||||||
|
func tabOutput() iup.Ihandle {
|
||||||
|
return iup.Hbox(
|
||||||
|
iup.Vbox(
|
||||||
|
iup.Vbox(
|
||||||
|
iup.Label("Output Directory:"),
|
||||||
|
iup.Text().SetAttributes("VISIBLECOLUMNS=16, MINSIZE=100x").SetHandle("OutDir").
|
||||||
|
SetAttribute("TIP", "Directory where converted files are written (required)").
|
||||||
|
SetCallback("VALUECHANGED_CB", iup.ValueChangedFunc(func(ih iup.Ihandle) int {
|
||||||
|
setActive()
|
||||||
|
|
||||||
|
return iup.DEFAULT
|
||||||
|
})),
|
||||||
|
iup.Space().SetAttribute("SIZE", "5x0"),
|
||||||
|
iup.Button("Browse...").SetAttributes("PADDING=DEFAULTBUTTONPADDING").
|
||||||
|
SetCallback("ACTION", iup.ActionFunc(onOutputDirectory)),
|
||||||
|
),
|
||||||
|
iup.Vbox(
|
||||||
|
iup.Label("Add Suffix to Output File:"),
|
||||||
|
iup.Text().SetAttributes("VISIBLECOLUMNS=16, MINSIZE=100x").SetHandle("Suffix").
|
||||||
|
SetAttribute("TIP", "Add suffix to filename, i.e. filename_suffix.cbz"),
|
||||||
|
),
|
||||||
|
iup.Vbox(
|
||||||
|
iup.Label("Archive Format:"),
|
||||||
|
iup.List().SetAttributes(map[string]string{
|
||||||
|
"DROPDOWN": "YES",
|
||||||
|
"VALUE": "1",
|
||||||
|
"1": "ZIP",
|
||||||
|
"2": "TAR",
|
||||||
|
}).SetHandle("Archive").
|
||||||
|
SetAttribute("TIP", "Output container: ZIP (.cbz) or uncompressed TAR (.cbt)").
|
||||||
|
SetCallback("VALUECHANGED_CB", iup.ValueChangedFunc(func(ih iup.Ihandle) int {
|
||||||
|
setActive()
|
||||||
|
|
||||||
|
return iup.DEFAULT
|
||||||
|
})),
|
||||||
|
),
|
||||||
|
iup.Vbox(
|
||||||
|
iup.Label("Compression:"),
|
||||||
|
iup.List().SetAttributes(map[string]string{
|
||||||
|
"DROPDOWN": "YES",
|
||||||
|
"VALUE": "1",
|
||||||
|
"1": "Default",
|
||||||
|
"2": "Store (none)",
|
||||||
|
"3": "1",
|
||||||
|
"4": "2",
|
||||||
|
"5": "3",
|
||||||
|
"6": "4",
|
||||||
|
"7": "5",
|
||||||
|
"8": "6",
|
||||||
|
"9": "7",
|
||||||
|
"10": "8",
|
||||||
|
"11": "9",
|
||||||
|
}).SetHandle("ZipLevel").
|
||||||
|
SetAttribute("TIP", "ZIP compression: Store disables it, 1 is fastest, 9 is smallest"),
|
||||||
|
).SetHandle("VboxZipLevel"),
|
||||||
|
).SetAttributes("NGAP=10"),
|
||||||
|
iup.Space().SetAttribute("SIZE", "15"),
|
||||||
|
iup.Vbox(
|
||||||
|
iup.Vbox(
|
||||||
|
iup.Toggle(" Combine into single file").SetHandle("Combine").
|
||||||
|
SetAttributes(`TIP="Merge all listed files into one archive"`).
|
||||||
|
SetCallback("VALUECHANGED_CB", iup.ValueChangedFunc(func(ih iup.Ihandle) int {
|
||||||
|
setActive()
|
||||||
|
|
||||||
|
return iup.DEFAULT
|
||||||
|
})),
|
||||||
|
),
|
||||||
|
iup.Vbox(
|
||||||
|
iup.Label("Output File:"),
|
||||||
|
iup.Text().SetAttributes("VISIBLECOLUMNS=16, MINSIZE=100x").SetHandle("OutFile").
|
||||||
|
SetAttribute("TIP", "Combined file name (default: first input + -combined)"),
|
||||||
|
iup.Space().SetAttribute("SIZE", "5x0"),
|
||||||
|
iup.Button("Browse...").SetAttributes("PADDING=DEFAULTBUTTONPADDING").
|
||||||
|
SetCallback("ACTION", iup.ActionFunc(onOutputFile)),
|
||||||
|
).SetHandle("VboxOutFile"),
|
||||||
|
).SetAttributes("NGAP=10"),
|
||||||
|
).SetHandle("VboxOutput")
|
||||||
|
}
|
||||||
|
|
||||||
|
func tabImage() iup.Ihandle {
|
||||||
|
return iup.Hbox(
|
||||||
|
iup.Vbox(
|
||||||
|
iup.Vbox(
|
||||||
|
iup.Label("Format:"),
|
||||||
|
iup.List().SetAttributes(map[string]string{
|
||||||
|
"DROPDOWN": "YES",
|
||||||
|
"VALUE": "1",
|
||||||
|
"1": "JPEG",
|
||||||
|
"2": "PNG",
|
||||||
|
"3": "TIFF",
|
||||||
|
"4": "BMP",
|
||||||
|
"5": "WEBP",
|
||||||
|
"6": "AVIF",
|
||||||
|
"7": "JXL",
|
||||||
|
}).SetHandle("Format").
|
||||||
|
SetAttribute("TIP", "Output image format for the converted pages").
|
||||||
|
SetCallback("VALUECHANGED_CB", iup.ValueChangedFunc(func(ih iup.Ihandle) int {
|
||||||
|
setEffort(strings.ToLower(ih.GetAttribute("VALUESTRING")))
|
||||||
|
setActive()
|
||||||
|
previewPost()
|
||||||
|
|
||||||
|
return iup.DEFAULT
|
||||||
|
})),
|
||||||
|
),
|
||||||
|
iup.Vbox(
|
||||||
|
iup.Label("Size:"),
|
||||||
|
iup.Hbox(
|
||||||
|
iup.Text().SetAttributes(`CUEBANNER="width", VISIBLECOLUMNS=6, MASK="/d*"`).SetHandle("Width").
|
||||||
|
SetAttribute("TIP", "If one of, width or height is not set, the image aspect ratio is preserved").
|
||||||
|
SetCallback("VALUECHANGED_CB", iup.ValueChangedFunc(func(ih iup.Ihandle) int {
|
||||||
|
setActive()
|
||||||
|
ih.SetAttribute("MYVALUE", ih.GetInt("VALUE"))
|
||||||
|
|
||||||
|
return iup.DEFAULT
|
||||||
|
})).
|
||||||
|
SetCallback("KILLFOCUS_CB", iup.KillFocusFunc(func(ih iup.Ihandle) int {
|
||||||
|
if ih.GetAttribute("MYVALUE") != "" {
|
||||||
|
previewPost()
|
||||||
|
}
|
||||||
|
ih.SetAttribute("MYVALUE", "")
|
||||||
|
|
||||||
|
return iup.DEFAULT
|
||||||
|
})),
|
||||||
|
iup.Space().SetAttribute("SIZE", "2"),
|
||||||
|
iup.Label("x"),
|
||||||
|
iup.Space().SetAttribute("SIZE", "2"),
|
||||||
|
iup.Text().SetAttributes(`CUEBANNER="height", VISIBLECOLUMNS=6, MASK="/d*"`).SetHandle("Height").
|
||||||
|
SetAttribute("TIP", "If one of, width or height is not set, the image aspect ratio is preserved").
|
||||||
|
SetCallback("VALUECHANGED_CB", iup.ValueChangedFunc(func(ih iup.Ihandle) int {
|
||||||
|
setActive()
|
||||||
|
ih.SetAttribute("MYVALUE", ih.GetInt("VALUE"))
|
||||||
|
|
||||||
|
return iup.DEFAULT
|
||||||
|
})).
|
||||||
|
SetCallback("KILLFOCUS_CB", iup.KillFocusFunc(func(ih iup.Ihandle) int {
|
||||||
|
if ih.GetAttribute("MYVALUE") != "" {
|
||||||
|
previewPost()
|
||||||
|
}
|
||||||
|
ih.SetAttribute("MYVALUE", "")
|
||||||
|
|
||||||
|
return iup.DEFAULT
|
||||||
|
})),
|
||||||
|
).SetAttributes("ALIGNMENT=ACENTER, NMARGIN=0"),
|
||||||
|
),
|
||||||
|
iup.Vbox(
|
||||||
|
iup.Toggle(" Best Fit").SetHandle("Fit").
|
||||||
|
SetAttributes(`TIP="Best fit for required width and height"`),
|
||||||
|
iup.Toggle(" No Upscale").SetHandle("NoUpscale").
|
||||||
|
SetAttribute("TIP", "Do not enlarge images already smaller than the requested size"),
|
||||||
|
),
|
||||||
|
iup.Vbox(
|
||||||
|
iup.Label("Resize Filter:"),
|
||||||
|
iup.List().SetAttributes(map[string]string{
|
||||||
|
"DROPDOWN": "YES",
|
||||||
|
"VALUE": "3",
|
||||||
|
"TIP": "Linear is the bilinear filter, smooth and reasonably fast",
|
||||||
|
"1": "NearestNeighbor",
|
||||||
|
"2": "Box",
|
||||||
|
"3": "Linear",
|
||||||
|
"4": "MitchellNetravali",
|
||||||
|
"5": "CatmullRom",
|
||||||
|
"6": "Gaussian",
|
||||||
|
"7": "Lanczos",
|
||||||
|
}).SetHandle("Filter").SetCallback("VALUECHANGED_CB", iup.ValueChangedFunc(onFilterChanged)),
|
||||||
|
),
|
||||||
|
).SetAttributes("NGAP=10"),
|
||||||
|
iup.Space().SetAttribute("SIZE", "15"),
|
||||||
|
iup.Vbox(
|
||||||
|
iup.Vbox(
|
||||||
|
iup.Hbox(
|
||||||
|
iup.Label("Quality: "),
|
||||||
|
iup.Label("75").SetHandle("LabelQuality"),
|
||||||
|
).SetAttributes("NMARGIN=0"),
|
||||||
|
iup.Val("").SetAttributes(`MIN=0, MAX=100, VALUE=75, SHOWTICKS=10`).SetHandle("Quality").
|
||||||
|
SetAttribute("TIP", "Quality affects JPEG, WEBP, AVIF and JXL").
|
||||||
|
SetCallback("VALUECHANGED_CB", iup.ValueChangedFunc(func(ih iup.Ihandle) int {
|
||||||
|
iup.GetHandle("LabelQuality").SetAttribute("TITLE", ih.GetInt("VALUE"))
|
||||||
|
iup.Refresh(iup.GetHandle("LabelQuality"))
|
||||||
|
ih.SetAttribute("MYVALUE", ih.GetInt("VALUE"))
|
||||||
|
|
||||||
|
return iup.DEFAULT
|
||||||
|
})).
|
||||||
|
SetCallback("KILLFOCUS_CB", iup.KillFocusFunc(func(ih iup.Ihandle) int {
|
||||||
|
if ih.GetAttribute("MYVALUE") != "" {
|
||||||
|
previewPost()
|
||||||
|
}
|
||||||
|
ih.SetAttribute("MYVALUE", "")
|
||||||
|
|
||||||
|
return iup.DEFAULT
|
||||||
|
})),
|
||||||
|
).SetHandle("VboxQuality"),
|
||||||
|
iup.Vbox(
|
||||||
|
iup.Label("Effort:").SetHandle("LabelEffort"),
|
||||||
|
iup.Val("").SetAttributes(`MIN=0, MAX=10, VALUE=0, SHOWTICKS=11`).SetHandle("Effort").
|
||||||
|
SetAttribute("TIP", "Encoder speed/effort (WEBP, AVIF, JXL)").
|
||||||
|
SetCallback("VALUECHANGED_CB", iup.ValueChangedFunc(func(ih iup.Ihandle) int {
|
||||||
|
iup.GetHandle("LabelEffort").SetAttribute("TITLE", fmt.Sprintf("%s: %d", ih.GetAttribute("EFFORTNAME"), ih.GetInt("VALUE")))
|
||||||
|
iup.Refresh(iup.GetHandle("LabelEffort"))
|
||||||
|
ih.SetAttribute("MYVALUE", ih.GetInt("VALUE"))
|
||||||
|
|
||||||
|
return iup.DEFAULT
|
||||||
|
})).
|
||||||
|
SetCallback("KILLFOCUS_CB", iup.KillFocusFunc(func(ih iup.Ihandle) int {
|
||||||
|
if ih.GetAttribute("MYVALUE") != "" {
|
||||||
|
previewPost()
|
||||||
|
}
|
||||||
|
ih.SetAttribute("MYVALUE", "")
|
||||||
|
|
||||||
|
return iup.DEFAULT
|
||||||
|
})),
|
||||||
|
).SetHandle("VboxEffort"),
|
||||||
|
iup.Vbox(
|
||||||
|
iup.Toggle(" Lossless").SetHandle("Lossless").
|
||||||
|
SetAttributes(`TIP="Lossless compression (WEBP, AVIF, JXL), ignores quality"`).
|
||||||
|
SetCallback("VALUECHANGED_CB", iup.ValueChangedFunc(func(ih iup.Ihandle) int {
|
||||||
|
setActive()
|
||||||
|
previewPost()
|
||||||
|
|
||||||
|
return iup.DEFAULT
|
||||||
|
})),
|
||||||
|
),
|
||||||
|
iup.Vbox(
|
||||||
|
iup.Toggle(" Grayscale").SetHandle("Grayscale").
|
||||||
|
SetAttributes(`TIP="Convert images to grayscale (monochromatic)"`).
|
||||||
|
SetCallback("VALUECHANGED_CB", iup.ValueChangedFunc(func(ih iup.Ihandle) int {
|
||||||
|
previewPost()
|
||||||
|
|
||||||
|
return iup.DEFAULT
|
||||||
|
})),
|
||||||
|
),
|
||||||
|
).SetAttributes("NGAP=10"),
|
||||||
|
).SetHandle("VboxImage")
|
||||||
|
}
|
||||||
|
|
||||||
|
func tabTransform() iup.Ihandle {
|
||||||
|
return iup.Vbox(
|
||||||
|
iup.Vbox(
|
||||||
|
iup.Hbox(
|
||||||
|
iup.Label("Brightness: "),
|
||||||
|
iup.Label("0").SetHandle("LabelBrightness"),
|
||||||
|
).SetAttributes("ALIGNMENT=ACENTER, NMARGIN=0"),
|
||||||
|
iup.Val("").SetAttributes(`MIN=-100, MAX=100, VALUE=0, SHOWTICKS=10`).SetHandle("Brightness").
|
||||||
|
SetAttributes(`TIP="Adjust the brightness of the images"`).
|
||||||
|
SetCallback("VALUECHANGED_CB", iup.ValueChangedFunc(func(ih iup.Ihandle) int {
|
||||||
|
iup.GetHandle("LabelBrightness").SetAttribute("TITLE", iup.GetHandle("Brightness").GetInt("VALUE"))
|
||||||
|
iup.Refresh(iup.GetHandle("LabelBrightness"))
|
||||||
|
ih.SetAttribute("MYVALUE", ih.GetInt("VALUE"))
|
||||||
|
|
||||||
|
return iup.DEFAULT
|
||||||
|
})).
|
||||||
|
SetCallback("KILLFOCUS_CB", iup.KillFocusFunc(func(ih iup.Ihandle) int {
|
||||||
|
if ih.GetAttribute("MYVALUE") != "" {
|
||||||
|
previewPost()
|
||||||
|
}
|
||||||
|
ih.SetAttribute("MYVALUE", "")
|
||||||
|
|
||||||
|
return iup.DEFAULT
|
||||||
|
})),
|
||||||
|
),
|
||||||
|
iup.Vbox(
|
||||||
|
iup.Hbox(
|
||||||
|
iup.Label("Contrast: "),
|
||||||
|
iup.Label("0").SetHandle("LabelContrast"),
|
||||||
|
).SetAttributes("ALIGNMENT=ACENTER, NMARGIN=0"),
|
||||||
|
iup.Val("").SetAttributes(`MIN=-100, MAX=100, VALUE=0, SHOWTICKS=10`).SetHandle("Contrast").
|
||||||
|
SetAttributes(`TIP="Adjust the contrast of the images"`).
|
||||||
|
SetCallback("VALUECHANGED_CB", iup.ValueChangedFunc(func(ih iup.Ihandle) int {
|
||||||
|
iup.GetHandle("LabelContrast").SetAttribute("TITLE", iup.GetHandle("Contrast").GetInt("VALUE"))
|
||||||
|
iup.Refresh(iup.GetHandle("LabelContrast"))
|
||||||
|
ih.SetAttribute("MYVALUE", ih.GetInt("VALUE"))
|
||||||
|
|
||||||
|
return iup.DEFAULT
|
||||||
|
})).
|
||||||
|
SetCallback("KILLFOCUS_CB", iup.KillFocusFunc(func(ih iup.Ihandle) int {
|
||||||
|
if ih.GetAttribute("MYVALUE") != "" {
|
||||||
|
previewPost()
|
||||||
|
}
|
||||||
|
ih.SetAttribute("MYVALUE", "")
|
||||||
|
|
||||||
|
return iup.DEFAULT
|
||||||
|
})),
|
||||||
|
),
|
||||||
|
iup.Vbox(
|
||||||
|
iup.Label("Rotate:"),
|
||||||
|
iup.List().SetAttributes(map[string]string{
|
||||||
|
"DROPDOWN": "YES",
|
||||||
|
"VALUE": "1",
|
||||||
|
"1": "0",
|
||||||
|
"2": "90",
|
||||||
|
"3": "180",
|
||||||
|
"4": "270",
|
||||||
|
}).SetHandle("Rotate").
|
||||||
|
SetAttribute("TIP", "Rotate every page clockwise by the given angle in degrees").
|
||||||
|
SetCallback("VALUECHANGED_CB", iup.ValueChangedFunc(func(ih iup.Ihandle) int {
|
||||||
|
previewPost()
|
||||||
|
|
||||||
|
return iup.DEFAULT
|
||||||
|
})),
|
||||||
|
),
|
||||||
|
).SetHandle("VboxTransform").SetAttributes("NGAP=10")
|
||||||
|
}
|
||||||
|
|
||||||
|
func tabs() iup.Ihandle {
|
||||||
|
return iup.Tabs(
|
||||||
|
tabInput().SetAttributes("TABTITLE=Input, NMARGIN=10x10"),
|
||||||
|
tabOutput().SetAttributes("TABTITLE=Output, NMARGIN=10x10"),
|
||||||
|
tabImage().SetAttributes("TABTITLE=Image, NMARGIN=10x10"),
|
||||||
|
tabTransform().SetAttributes("TABTITLE=Transform, NMARGIN=10x10"),
|
||||||
|
).SetHandle("Tabs")
|
||||||
|
}
|
||||||
|
|
||||||
|
func buttons() iup.Ihandle {
|
||||||
|
addFiles := iup.Button("Add &Files...").SetHandle("AddFiles").SetAttributes("PADDING=DEFAULTBUTTONPADDING").
|
||||||
|
SetCallback("ACTION", iup.ActionFunc(onAddFiles))
|
||||||
|
addDir := iup.Button("Add &Dir...").SetHandle("AddDir").SetAttributes("PADDING=DEFAULTBUTTONPADDING").
|
||||||
|
SetCallback("ACTION", iup.ActionFunc(onAddDir))
|
||||||
|
remove := iup.Button("Remove").SetHandle("Remove").SetAttributes("PADDING=DEFAULTBUTTONPADDING").
|
||||||
|
SetCallback("ACTION", iup.ActionFunc(onRemove))
|
||||||
|
removeAll := iup.Button("Remove All").SetHandle("RemoveAll").SetAttributes("PADDING=DEFAULTBUTTONPADDING").
|
||||||
|
SetCallback("ACTION", iup.ActionFunc(onRemoveAll))
|
||||||
|
thumbnail := iup.Button("Thumbnail").SetHandle("Thumbnail").SetAttributes("PADDING=DEFAULTBUTTONPADDING").
|
||||||
|
SetCallback("ACTION", iup.ActionFunc(onThumbnail))
|
||||||
|
cover := iup.Button("Cover").SetHandle("Cover").SetAttributes("PADDING=DEFAULTBUTTONPADDING").
|
||||||
|
SetCallback("ACTION", iup.ActionFunc(onCover))
|
||||||
|
convert := iup.Button("&Convert").SetHandle("Convert").SetAttributes("PADDING=DEFAULTBUTTONPADDING").
|
||||||
|
SetCallback("ACTION", iup.ActionFunc(onConvert))
|
||||||
|
reset := iup.Button("Reset").SetHandle("Reset").SetAttributes("PADDING=DEFAULTBUTTONPADDING").
|
||||||
|
SetAttribute("TIP", "Restore all settings to their defaults").
|
||||||
|
SetCallback("ACTION", iup.ActionFunc(onReset))
|
||||||
|
save := iup.Button("Save").SetHandle("Save").SetAttributes("PADDING=DEFAULTBUTTONPADDING").
|
||||||
|
SetAttribute("TIP", "Save current settings to a profile").
|
||||||
|
SetCallback("ACTION", iup.ActionFunc(onSave))
|
||||||
|
|
||||||
|
command := iup.Button("Command").SetHandle("Command").SetAttributes("PADDING=DEFAULTBUTTONPADDING").
|
||||||
|
SetAttribute("TIP", "Show the equivalent command line").
|
||||||
|
SetCallback("ACTION", iup.ActionFunc(onCommand))
|
||||||
|
|
||||||
|
profile := iup.List().SetAttributes("DROPDOWN=YES").SetHandle("Profile").
|
||||||
|
SetAttribute("TIP", "Select a settings profile").
|
||||||
|
SetCallback("VALUECHANGED_CB", iup.ValueChangedFunc(onProfileSelect))
|
||||||
|
|
||||||
|
iup.Normalizer(addFiles, addDir, remove, removeAll, thumbnail, cover, convert, reset, save, command).SetAttribute("NORMALIZE", "BOTH")
|
||||||
|
iup.Normalizer(addFiles, addDir, remove, removeAll, thumbnail, cover, convert, reset, save, command, profile).SetAttribute("NORMALIZE", "HORIZONTAL")
|
||||||
|
|
||||||
|
return iup.Vbox(
|
||||||
|
iup.Vbox(
|
||||||
|
addFiles,
|
||||||
|
addDir,
|
||||||
|
remove,
|
||||||
|
removeAll,
|
||||||
|
).SetAttribute("NGAP", "2"),
|
||||||
|
iup.Space().SetAttribute("SIZE", "x5"),
|
||||||
|
iup.Vbox(
|
||||||
|
thumbnail,
|
||||||
|
cover,
|
||||||
|
).SetAttribute("NGAP", "2"),
|
||||||
|
iup.Space().SetAttribute("SIZE", "x5"),
|
||||||
|
iup.Vbox(
|
||||||
|
convert,
|
||||||
|
),
|
||||||
|
iup.Fill(),
|
||||||
|
iup.Vbox(
|
||||||
|
iup.Label("Profile:"),
|
||||||
|
profile,
|
||||||
|
reset,
|
||||||
|
save,
|
||||||
|
command,
|
||||||
|
).SetAttribute("NGAP", "2"),
|
||||||
|
).SetHandle("Buttons").SetAttributes("ALIGNMENT=ACENTER")
|
||||||
|
}
|
||||||
|
|
||||||
|
func status() iup.Ihandle {
|
||||||
|
return iup.Hbox(
|
||||||
|
loading(),
|
||||||
|
iup.Fill(),
|
||||||
|
iup.Label("File 1 of 1").SetHandle("LabelStatus1").SetAttributes("VISIBLE=NO"),
|
||||||
|
iup.Space().SetAttribute("SIZE", "5"),
|
||||||
|
iup.Label("(000/000)").SetHandle("LabelStatus2").SetAttributes("VISIBLE=NO"),
|
||||||
|
iup.Space().SetAttribute("SIZE", "5"),
|
||||||
|
iup.ProgressBar().SetAttributes("VISIBLE=NO").SetHandle("ProgressBar").
|
||||||
|
SetCallback("POSTMESSAGE_CB", iup.PostMessageFunc(func(ih iup.Ihandle, s string, i int, p any) int {
|
||||||
|
switch s {
|
||||||
|
case "convert":
|
||||||
|
conv := p.(*cbconvert.Converter)
|
||||||
|
ih.SetAttributes("VALUE=0, VISIBLE=YES")
|
||||||
|
ih.SetAttribute("MAX", conv.Ncontents)
|
||||||
|
|
||||||
|
iup.GetHandle("LabelStatus1").SetAttribute("TITLE", fmt.Sprintf("File %d of %d", conv.CurrFile, conv.Nfiles))
|
||||||
|
iup.GetHandle("LabelStatus1").SetAttributes("VISIBLE=YES")
|
||||||
|
iup.GetHandle("LabelStatus2").SetAttributes("VISIBLE=YES")
|
||||||
|
|
||||||
|
iup.Refresh(iup.GetHandle("StatusBar"))
|
||||||
|
case "start":
|
||||||
|
conv := p.(*cbconvert.Converter)
|
||||||
|
ih.SetAttributes("VALUE=0, VISIBLE=YES")
|
||||||
|
ih.SetAttribute("MAX", conv.Nfiles)
|
||||||
|
|
||||||
|
iup.GetHandle("LabelStatus2").SetAttributes("VISIBLE=YES")
|
||||||
|
case "progress":
|
||||||
|
conv := p.(*cbconvert.Converter)
|
||||||
|
ih.SetAttribute("VALUE", conv.CurrContent)
|
||||||
|
iup.GetHandle("LabelStatus2").SetAttribute("TITLE", fmt.Sprintf("(%03d/%03d)", conv.CurrContent, conv.Ncontents))
|
||||||
|
|
||||||
|
iup.Refresh(iup.GetHandle("StatusBar"))
|
||||||
|
case "progress2":
|
||||||
|
conv := p.(*cbconvert.Converter)
|
||||||
|
ih.SetAttribute("VALUE", conv.CurrFile)
|
||||||
|
iup.GetHandle("LabelStatus2").SetAttribute("TITLE", fmt.Sprintf("(%03d/%03d)", conv.CurrFile, conv.Nfiles))
|
||||||
|
|
||||||
|
iup.Refresh(iup.GetHandle("StatusBar"))
|
||||||
|
case "finish":
|
||||||
|
setBusy(false)
|
||||||
|
|
||||||
|
iup.GetHandle("LabelStatus1").SetAttributes(`TITLE="", VISIBLE=NO`)
|
||||||
|
iup.GetHandle("LabelStatus2").SetAttributes(`TITLE="", VISIBLE=NO`)
|
||||||
|
ih.SetAttributes("VALUE=0, VISIBLE=NO")
|
||||||
|
|
||||||
|
iup.Refresh(iup.GetHandle("StatusBar"))
|
||||||
|
|
||||||
|
iup.GetHandle("dlg").SetCallback("K_ANY", nil)
|
||||||
|
iup.GetHandle("dlg").SetCallback("CLOSE_CB", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
return iup.DEFAULT
|
||||||
|
})),
|
||||||
|
iup.Space().SetAttribute("SIZE", "5x0"),
|
||||||
|
).SetAttributes("ALIGNMENT=ACENTER, NMARGIN=5x5").SetHandle("StatusBar")
|
||||||
|
}
|
||||||
|
|
||||||
|
func loading() iup.Ihandle {
|
||||||
|
img, _ := gif.DecodeAll(bytes.NewReader(appLoading))
|
||||||
|
animation := iup.User()
|
||||||
|
|
||||||
|
for idx, i := range img.Image {
|
||||||
|
name := fmt.Sprintf("Loading%d", idx)
|
||||||
|
iup.ImageFromImage(i).SetHandle(name)
|
||||||
|
iup.Append(animation, iup.GetHandle(name))
|
||||||
|
}
|
||||||
|
|
||||||
|
return iup.AnimatedLabel(animation).SetAttributes("VISIBLE=NO").SetHandle("Loading")
|
||||||
|
}
|
||||||
@@ -1,30 +1,60 @@
|
|||||||
module github.com/gen2brain/cbconvert/cmd/cbconvert
|
module github.com/gen2brain/cbconvert/cmd/cbconvert
|
||||||
|
|
||||||
go 1.21
|
go 1.26
|
||||||
|
|
||||||
toolchain go1.21.0
|
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/gen2brain/cbconvert v0.0.0-20230902080208-6936f697b466
|
github.com/gen2brain/cbconvert v1.0.5-0.20260623161611-a5817c3ba5de
|
||||||
github.com/schollz/progressbar/v3 v3.10.0
|
github.com/schollz/progressbar/v3 v3.19.0
|
||||||
github.com/spf13/pflag v1.0.5
|
golang.org/x/term v0.44.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/chai2010/webp v1.1.1 // indirect
|
github.com/STARRY-S/zip v0.2.3 // indirect
|
||||||
github.com/disintegration/imaging v1.6.2 // indirect
|
github.com/andybalholm/brotli v1.2.1 // indirect
|
||||||
|
github.com/anthonynsimon/bild v0.15.0 // indirect
|
||||||
|
github.com/bodgit/plumbing v1.3.0 // indirect
|
||||||
|
github.com/bodgit/sevenzip v1.6.4 // indirect
|
||||||
|
github.com/bodgit/windows v1.0.1 // indirect
|
||||||
|
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 // indirect
|
||||||
|
github.com/dsoprea/go-exif/v2 v2.0.0-20230826092837-6579e82b732d // indirect
|
||||||
|
github.com/dsoprea/go-logging v0.0.0-20200710184922-b02d349568dd // indirect
|
||||||
|
github.com/dsoprea/go-png-image-structure v0.0.0-20210512210324-29b889a6093d // indirect
|
||||||
|
github.com/dsoprea/go-utility v0.0.0-20221003172846-a3e1774ef349 // indirect
|
||||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||||
|
github.com/ebitengine/purego v0.10.1 // indirect
|
||||||
github.com/fvbommel/sortorder v1.1.0 // indirect
|
github.com/fvbommel/sortorder v1.1.0 // indirect
|
||||||
github.com/gen2brain/go-fitz v1.23.1 // indirect
|
github.com/gen2brain/avif v0.5.1 // indirect
|
||||||
github.com/gen2brain/go-unarr v0.1.7 // indirect
|
github.com/gen2brain/go-fitz v1.24.15 // indirect
|
||||||
github.com/hotei/bmp v0.0.0-20150430041436-f620cebab0c7 // indirect
|
github.com/gen2brain/jpegli v0.4.0 // indirect
|
||||||
github.com/mattn/go-runewidth v0.0.13 // indirect
|
github.com/gen2brain/jpegxl v0.5.1 // indirect
|
||||||
|
github.com/gen2brain/webp v0.6.1 // indirect
|
||||||
|
github.com/go-errors/errors v1.5.1 // indirect
|
||||||
|
github.com/golang/geo v0.0.0-20260622181633-f80708c60e82 // indirect
|
||||||
|
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
|
||||||
|
github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25 // indirect
|
||||||
|
github.com/jupiterrider/ffi v0.7.0 // indirect
|
||||||
|
github.com/klauspost/compress v1.18.6 // indirect
|
||||||
|
github.com/klauspost/pgzip v1.2.6 // indirect
|
||||||
|
github.com/mattn/go-runewidth v0.0.24 // indirect
|
||||||
|
github.com/mholt/archives v0.1.5 // indirect
|
||||||
|
github.com/mikelolasagasti/xz v1.0.1 // indirect
|
||||||
|
github.com/minio/minlz v1.1.1 // indirect
|
||||||
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
|
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
|
||||||
github.com/rivo/uniseg v0.3.4 // indirect
|
github.com/nwaples/rardecode/v2 v2.2.5 // indirect
|
||||||
github.com/strukturag/libheif v1.15.2 // indirect
|
github.com/pierrec/lz4/v4 v4.1.27 // indirect
|
||||||
golang.org/x/image v0.11.0 // indirect
|
github.com/rivo/uniseg v0.4.7 // indirect
|
||||||
golang.org/x/sync v0.3.0 // indirect
|
github.com/sorairolake/lzip-go v0.3.8 // indirect
|
||||||
golang.org/x/sys v0.5.0 // indirect
|
github.com/spf13/afero v1.15.0 // indirect
|
||||||
golang.org/x/term v0.5.0 // indirect
|
github.com/stangelandcl/ppmd v0.1.1 // indirect
|
||||||
gopkg.in/gographics/imagick.v3 v3.4.3 // indirect
|
github.com/tetratelabs/wazero v1.12.0 // indirect
|
||||||
|
github.com/ulikunitz/xz v0.5.15 // indirect
|
||||||
|
go4.org v0.0.0-20260112195520-a5071408f32f // indirect
|
||||||
|
golang.org/x/image v0.43.0 // indirect
|
||||||
|
golang.org/x/net v0.56.0 // indirect
|
||||||
|
golang.org/x/sync v0.21.0 // indirect
|
||||||
|
golang.org/x/sys v0.46.0 // indirect
|
||||||
|
golang.org/x/text v0.38.0 // indirect
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
|
replace github.com/gen2brain/cbconvert => ../..
|
||||||
|
|||||||
@@ -1,82 +1,155 @@
|
|||||||
github.com/chai2010/webp v1.1.1 h1:jTRmEccAJ4MGrhFOrPMpNGIJ/eybIgwKpcACsrTEapk=
|
github.com/STARRY-S/zip v0.2.3 h1:luE4dMvRPDOWQdeDdUxUoZkzUIpTccdKdhHHsQJ1fm4=
|
||||||
github.com/chai2010/webp v1.1.1/go.mod h1:0XVwvZWdjjdxpUEIf7b9g9VkHFnInUSYujwqTLEuldU=
|
github.com/STARRY-S/zip v0.2.3/go.mod h1:lqJ9JdeRipyOQJrYSOtpNAiaesFO6zVDsE8GIGFaoSk=
|
||||||
|
github.com/andybalholm/brotli v1.2.1 h1:R+f5xP285VArJDRgowrfb9DqL18yVK0gKAW/F+eTWro=
|
||||||
|
github.com/andybalholm/brotli v1.2.1/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
|
||||||
|
github.com/anthonynsimon/bild v0.15.0 h1:FzvaNLuNlAPKw1Xz7V2WYOcGIEBMj8Y6ZyAk7CI+HzA=
|
||||||
|
github.com/anthonynsimon/bild v0.15.0/go.mod h1:qIgJ9FldkCn0iy5Ad24fzUkz5R+iJ0WfhiV+6FeCB5A=
|
||||||
|
github.com/bodgit/plumbing v1.3.0 h1:pf9Itz1JOQgn7vEOE7v7nlEfBykYqvUYioC61TwWCFU=
|
||||||
|
github.com/bodgit/plumbing v1.3.0/go.mod h1:JOTb4XiRu5xfnmdnDJo6GmSbSbtSyufrsyZFByMtKEs=
|
||||||
|
github.com/bodgit/sevenzip v1.6.4 h1:iHiVJfxbrB6RF4X+snI2MpVgNBKmVfGaTqZGNlMQIU0=
|
||||||
|
github.com/bodgit/sevenzip v1.6.4/go.mod h1:ZtNi5KNgHXeXg1G7WiF0IWSuFE2eG6lt/cTGlvuirO0=
|
||||||
|
github.com/bodgit/windows v1.0.1 h1:tF7K6KOluPYygXa3Z2594zxlkbKPAOvqr97etrGNIz4=
|
||||||
|
github.com/bodgit/windows v1.0.1/go.mod h1:a6JLwrB4KrTR5hBpp8FI9/9W9jJfeQ2h4XDXU74ZCdM=
|
||||||
|
github.com/chengxilo/virtualterm v1.0.4 h1:Z6IpERbRVlfB8WkOmtbHiDbBANU7cimRIof7mk9/PwM=
|
||||||
|
github.com/chengxilo/virtualterm v1.0.4/go.mod h1:DyxxBZz/x1iqJjFxTFcr6/x+jSpqN0iwWCOK1q10rlY=
|
||||||
|
github.com/clipperhouse/uax29/v2 v2.2.0 h1:ChwIKnQN3kcZteTXMgb1wztSgaU+ZemkgWdohwgs8tY=
|
||||||
|
github.com/clipperhouse/uax29/v2 v2.2.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
|
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 h1:2tV76y6Q9BB+NEBasnqvs7e49aEBFI8ejC89PSnWH+4=
|
||||||
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
|
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707/go.mod h1:qssHWj60/X5sZFNxpG4HBPDHVqxNm4DfnCKgrbZOT+s=
|
||||||
|
github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=
|
||||||
|
github.com/dsoprea/go-exif/v2 v2.0.0-20200321225314-640175a69fe4/go.mod h1:Lm2lMM2zx8p4a34ZemkaUV95AnMl4ZvLbCUbwOvLC2E=
|
||||||
|
github.com/dsoprea/go-exif/v2 v2.0.0-20200604193436-ca8584a0e1c4/go.mod h1:9EXlPeHfblFFnwu5UOqmP2eoZfJyAZ2Ri/Vki33ajO0=
|
||||||
|
github.com/dsoprea/go-exif/v2 v2.0.0-20230826092837-6579e82b732d h1:yeH8wrJa3+8uKKDAdURHUK1ds2UvKhMqX2MiOdVeKPs=
|
||||||
|
github.com/dsoprea/go-exif/v2 v2.0.0-20230826092837-6579e82b732d/go.mod h1:oKrjk2kb3rAR5NbtSTLUMvMSbc+k8ZosI3MaVH47noc=
|
||||||
|
github.com/dsoprea/go-exif/v3 v3.0.0-20200717053412-08f1b6708903/go.mod h1:0nsO1ce0mh5czxGeLo4+OCZ/C6Eo6ZlMWsz7rH/Gxv8=
|
||||||
|
github.com/dsoprea/go-exif/v3 v3.0.0-20210512043655-120bcdb2a55e/go.mod h1:cg5SNYKHMmzxsr9X6ZeLh/nfBRHHp5PngtEPcujONtk=
|
||||||
|
github.com/dsoprea/go-logging v0.0.0-20190624164917-c4f10aab7696/go.mod h1:Nm/x2ZUNRW6Fe5C3LxdY1PyZY5wmDv/s5dkPJ/VB3iA=
|
||||||
|
github.com/dsoprea/go-logging v0.0.0-20200517223158-a10564966e9d/go.mod h1:7I+3Pe2o/YSU88W0hWlm9S22W7XI1JFNJ86U0zPKMf8=
|
||||||
|
github.com/dsoprea/go-logging v0.0.0-20200710184922-b02d349568dd h1:l+vLbuxptsC6VQyQsfD7NnEC8BZuFpz45PgY+pH8YTg=
|
||||||
|
github.com/dsoprea/go-logging v0.0.0-20200710184922-b02d349568dd/go.mod h1:7I+3Pe2o/YSU88W0hWlm9S22W7XI1JFNJ86U0zPKMf8=
|
||||||
|
github.com/dsoprea/go-png-image-structure v0.0.0-20210512210324-29b889a6093d h1:8+qI8ant/vZkNSsbwSjIR6XJfWcDVTg/qx/3pRUUZNA=
|
||||||
|
github.com/dsoprea/go-png-image-structure v0.0.0-20210512210324-29b889a6093d/go.mod h1:yTR3tKgyk20phAFg6IE9ulMA5NjEDD2wyx+okRFLVtw=
|
||||||
|
github.com/dsoprea/go-utility v0.0.0-20200711062821-fab8125e9bdf/go.mod h1:95+K3z2L0mqsVYd6yveIv1lmtT3tcQQ3dVakPySffW8=
|
||||||
|
github.com/dsoprea/go-utility v0.0.0-20221003172846-a3e1774ef349 h1:/py11NlxDaOxkT9OKN+gXgT+QOH5xj1ZRoyusfRIlo4=
|
||||||
|
github.com/dsoprea/go-utility v0.0.0-20221003172846-a3e1774ef349/go.mod h1:KVK+/Hul09ujXAGq+42UBgCTnXkiJZRnLYdURGjQUwo=
|
||||||
|
github.com/dsoprea/go-utility/v2 v2.0.0-20200717064901-2fccff4aa15e/go.mod h1:uAzdkPTub5Y9yQwXe8W4m2XuP0tK4a9Q/dantD0+uaU=
|
||||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||||
|
github.com/ebitengine/purego v0.10.1 h1:dewVBCBT2GaMu1SrNTYxQhgQBethzfhiwvZiLGP/qyY=
|
||||||
|
github.com/ebitengine/purego v0.10.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||||
github.com/fvbommel/sortorder v1.1.0 h1:fUmoe+HLsBTctBDoaBwpQo5N+nrCp8g/BjKb/6ZQmYw=
|
github.com/fvbommel/sortorder v1.1.0 h1:fUmoe+HLsBTctBDoaBwpQo5N+nrCp8g/BjKb/6ZQmYw=
|
||||||
github.com/fvbommel/sortorder v1.1.0/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0=
|
github.com/fvbommel/sortorder v1.1.0/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0=
|
||||||
github.com/gen2brain/cbconvert v0.0.0-20230902080208-6936f697b466 h1:xrc1+L/Wc9BHokp+smaBt/A2R2+nfK0Ws0cvyzTHE7I=
|
github.com/gen2brain/avif v0.5.1 h1:LQzLsJpWyGlsa4wuZ3D57qEbCiICIK7Yidz5ZPEwzTk=
|
||||||
github.com/gen2brain/cbconvert v0.0.0-20230902080208-6936f697b466/go.mod h1:5CIPgEHKNZtfH7FR0zzGMAzkFwagUyH7FcjIUDEx7Ps=
|
github.com/gen2brain/avif v0.5.1/go.mod h1:QgrYqdVE9y40PCfArK9VakcMIpYeDYpZmCSLkW6C1n8=
|
||||||
github.com/gen2brain/go-fitz v1.23.1 h1:x69/szWZXpI3jZ57mMqCg7WqqvtYnQG0lXts3L6M1Fc=
|
github.com/gen2brain/go-fitz v1.24.15 h1:sJNB1MOWkqnzzENPHggFpgxTwW0+S5WF/rM5wUBpJWo=
|
||||||
github.com/gen2brain/go-fitz v1.23.1/go.mod h1:HU04vc+RisUh/kvEd2pB0LAxmK1oyXdN4ftyshUr9rQ=
|
github.com/gen2brain/go-fitz v1.24.15/go.mod h1:SftkiVbTHqF141DuiLwBBM65zP7ig6AVDQpf2WlHamo=
|
||||||
github.com/gen2brain/go-unarr v0.1.7 h1:mEE7bPShJIsmAX67t6BW2ibpEUO7j5WK152KgNM9NbQ=
|
github.com/gen2brain/jpegli v0.4.0 h1:TnmAO8EcekS/XCboyLPjP6cqz025tkhdaFkiYNd3wH8=
|
||||||
github.com/gen2brain/go-unarr v0.1.7/go.mod h1:MK9a3hddpaIxjEtrE1f/LA5yJ7gA34cS7Oyr325sY9s=
|
github.com/gen2brain/jpegli v0.4.0/go.mod h1:zJ++s4symmKCN1CLkrY0dGXTY3s0NWbd94Rz9KLdCzk=
|
||||||
github.com/hotei/bmp v0.0.0-20150430041436-f620cebab0c7 h1:NlUATi3cllRJhpM4mfR9BxiLRXT83bcSLcOa+S8lrME=
|
github.com/gen2brain/jpegxl v0.5.1 h1:UuBUIkZ35DErImU3bTA6rltfV5zSgVNOA/K5a6JibfE=
|
||||||
github.com/hotei/bmp v0.0.0-20150430041436-f620cebab0c7/go.mod h1:Hku3FQ2laCEwSv7Z8YkC0er38jLaUycUCbsFkWMr+z4=
|
github.com/gen2brain/jpegxl v0.5.1/go.mod h1:Wlc6lqx03RJfhiQRyHa2e+8VQwT4/qv7zSRsNv9T+yE=
|
||||||
github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213/go.mod h1:vNUNkEQ1e29fT/6vq2aBdFsgNPmy8qMdSay1npru+Sw=
|
github.com/gen2brain/webp v0.6.1 h1:ei7Y1SWpQcdqz3YNDNyn4y2nQanxs9WLzwW5/2DKS64=
|
||||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
github.com/gen2brain/webp v0.6.1/go.mod h1:iGWMaCSw7t3I/Cv9llzEKmpnR36S8lS8VL/ZVjxU0JE=
|
||||||
github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
|
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
|
||||||
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
github.com/go-errors/errors v1.0.2/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWEp4lssFEs=
|
||||||
|
github.com/go-errors/errors v1.1.1/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWEp4lssFEs=
|
||||||
|
github.com/go-errors/errors v1.5.1 h1:ZwEMSLRCapFLflTpT7NKaAc7ukJ8ZPEjzlxt8rPN8bk=
|
||||||
|
github.com/go-errors/errors v1.5.1/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
|
||||||
|
github.com/golang/geo v0.0.0-20190916061304-5b978397cfec/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
|
||||||
|
github.com/golang/geo v0.0.0-20200319012246-673a6f80352d/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
|
||||||
|
github.com/golang/geo v0.0.0-20260622181633-f80708c60e82 h1:q100v18oYIaE4p5myLFqDyfXTOxivzwGyLjGCEfv8x4=
|
||||||
|
github.com/golang/geo v0.0.0-20260622181633-f80708c60e82/go.mod h1:Mymr9kRGDc64JPr03TSZmuIBODZ3KyswLzm1xL0HFA8=
|
||||||
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
|
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||||
|
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
|
||||||
|
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
|
||||||
|
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||||
|
github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25 h1:YLvr1eE6cdCqjOe972w/cYF+FjW34v27+9Vo5106B4M=
|
||||||
|
github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25/go.mod h1:kLgvv7o6UM+0QSf0QjAse3wReFDsb9qbZJdfexWlrQw=
|
||||||
|
github.com/jupiterrider/ffi v0.7.0 h1:RKsl6Ascal+3kyAqR5Qcbp83LceQMLc1VZbPfHWoNzs=
|
||||||
|
github.com/jupiterrider/ffi v0.7.0/go.mod h1:9dauhpOfNqrqk28fxuu0kkdeFtT9Qr4vbfigiuIXN7c=
|
||||||
|
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
||||||
|
github.com/klauspost/compress v1.18.6 h1:2jupLlAwFm95+YDR+NwD2MEfFO9d4z4Prjl1XXDjuao=
|
||||||
|
github.com/klauspost/compress v1.18.6/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
|
||||||
|
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||||
|
github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=
|
||||||
|
github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
|
||||||
|
github.com/mattn/go-runewidth v0.0.24 h1:cpokDiIn0MGnhdHwuWnJBITySJ20QyNGnY2kR/ay2DU=
|
||||||
|
github.com/mattn/go-runewidth v0.0.24/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
|
||||||
|
github.com/mholt/archives v0.1.5 h1:Fh2hl1j7VEhc6DZs2DLMgiBNChUux154a1G+2esNvzQ=
|
||||||
|
github.com/mholt/archives v0.1.5/go.mod h1:3TPMmBLPsgszL+1As5zECTuKwKvIfj6YcwWPpeTAXF4=
|
||||||
|
github.com/mikelolasagasti/xz v1.0.1 h1:Q2F2jX0RYJUG3+WsM+FJknv+6eVjsjXNDV0KJXZzkD0=
|
||||||
|
github.com/mikelolasagasti/xz v1.0.1/go.mod h1:muAirjiOUxPRXwm9HdDtB3uoRPrGnL85XHtokL9Hcgc=
|
||||||
|
github.com/minio/minlz v1.1.1 h1:OGmft1V6AnI/Wme332U6bhG54nxEan+VFgkD7lat4KM=
|
||||||
|
github.com/minio/minlz v1.1.1/go.mod h1:qT0aEB35q79LLornSzeDH75LBf3aH1MV+jB5w9Wasec=
|
||||||
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ=
|
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ=
|
||||||
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw=
|
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw=
|
||||||
|
github.com/nwaples/rardecode/v2 v2.2.5 h1:L5doqgGfQwI7qADJMqnkrSB86rpPsqQDrHeO0HWa5JY=
|
||||||
|
github.com/nwaples/rardecode/v2 v2.2.5/go.mod h1:7uz379lSxPe6j9nvzxUZ+n7mnJNgjsRNb6IbvGVHRmw=
|
||||||
|
github.com/pierrec/lz4/v4 v4.1.27 h1:+PhzhWDrjRj89TH2sw43nE3+4+W8lSxIuQadEHZyjUk=
|
||||||
|
github.com/pierrec/lz4/v4 v4.1.27/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||||
github.com/rivo/uniseg v0.3.4 h1:3Z3Eu6FGHZWSfNKJTOUiPatWwfc7DzJRU04jFUqJODw=
|
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||||
github.com/rivo/uniseg v0.3.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
github.com/schollz/progressbar/v3 v3.19.0 h1:Ea18xuIRQXLAUidVDox3AbwfUhD0/1IvohyTutOIFoc=
|
||||||
github.com/schollz/progressbar/v3 v3.10.0 h1:pOab0roS2jf6zkEBKAe9EyEdmRKJvhbbuxqVp9/Qjyw=
|
github.com/schollz/progressbar/v3 v3.19.0/go.mod h1:IsO3lpbaGuzh8zIMzgY3+J8l4C8GjO0Y9S69eFvNsec=
|
||||||
github.com/schollz/progressbar/v3 v3.10.0/go.mod h1:0N6zRwbDVLFCFy5chxuukVlRkoHWYFFLzlxQrw/sf3M=
|
github.com/sorairolake/lzip-go v0.3.8 h1:j5Q2313INdTA80ureWYRhX+1K78mUXfMoPZCw/ivWik=
|
||||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
github.com/sorairolake/lzip-go v0.3.8/go.mod h1:JcBqGMV0frlxwrsE9sMWXDjqn3EeVf0/54YPsw66qkU=
|
||||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
|
||||||
|
github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
|
||||||
|
github.com/stangelandcl/ppmd v0.1.1 h1:c25QazhlWUn5nmR1QOzafKhQxBicAr7GGCKER2aJ8H8=
|
||||||
|
github.com/stangelandcl/ppmd v0.1.1/go.mod h1:Rrv7M+/2P5jYr/GMLhBl7Ug3uJ1bUiVzr5LbbaV6xgY=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
github.com/strukturag/libheif v1.15.2 h1:pgdcpDHqtLKRXL9ETSTeht0CsJODB3BojpTsb3S/3Wg=
|
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||||
github.com/strukturag/libheif v1.15.2/go.mod h1:E/PNRlmVtrtj9j2AvBZlrO4dsBDu6KfwDZn7X1Ce8Ks=
|
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
|
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||||
|
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
|
github.com/tetratelabs/wazero v1.12.0 h1:DuWcpNu/FzgEXgGBDp8J1Spc+CWOvvtvVyjKlaZopYU=
|
||||||
|
github.com/tetratelabs/wazero v1.12.0/go.mod h1:LvKtzl2RqO4gyF27BiXU+nKAjcV8f38U+kP/q2vgxh0=
|
||||||
|
github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||||
|
github.com/ulikunitz/xz v0.5.15 h1:9DNdB5s+SgV3bQ2ApL10xRc35ck0DuIX/isZvIk+ubY=
|
||||||
|
github.com/ulikunitz/xz v0.5.15/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||||
|
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
|
||||||
|
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
|
||||||
|
go4.org v0.0.0-20260112195520-a5071408f32f h1:ziUVAjmTPwQMBmYR1tbdRFJPtTcQUI12fH9QQjfb0Sw=
|
||||||
|
go4.org v0.0.0-20260112195520-a5071408f32f/go.mod h1:ZRJnO5ZI4zAwMFp+dS1+V6J6MSyAowhRqAE+DPa1Xp0=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/image v0.43.0 h1:FLxcP4ec2350nTfOC8ysKtqYSIFbk/QGjw1ZHNP4tsY=
|
||||||
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
golang.org/x/image v0.43.0/go.mod h1:rrpelvGFt+kLPAjPM4HeWPgrl0FtafueU//e5N0qk/Q=
|
||||||
golang.org/x/image v0.11.0 h1:ds2RoQvBvYTiJkwpSFDwCcDFNX7DqjL2WsUgTNk0Ooo=
|
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/image v0.11.0/go.mod h1:bglhjqbqVuEb9e9+eNR45Jfu7D+T4Qan+NhQk8Ck2P8=
|
golang.org/x/net v0.0.0-20200320220750-118fecf932d8/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o=
|
||||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec=
|
||||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
|
|
||||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw=
|
||||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/term v0.44.0 h1:0rLvDRCtNj0gZkyIXhCyOb2OAzEhLVqc4B+hrsBhrmc=
|
||||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/term v0.44.0/go.mod h1:7ze4MdzUzLXpSAoFP1H0bOI9aXDqveSvatT5vKcFh2Y=
|
||||||
golang.org/x/sys v0.0.0-20220829200755-d48e67d00261/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
|
|
||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
|
||||||
golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
|
||||||
golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY=
|
|
||||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4=
|
||||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
gopkg.in/gographics/imagick.v3 v3.4.3 h1:9plKFE/Us913jBN6KohtLG9FNW8LPvfpjiGAORIiEHg=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/gographics/imagick.v3 v3.4.3/go.mod h1:+Q9nyA2xRZXrDyTtJ/eko+8V/5E7bWYs08ndkZp8UmA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
@@ -12,7 +13,7 @@ import (
|
|||||||
|
|
||||||
"github.com/gen2brain/cbconvert"
|
"github.com/gen2brain/cbconvert"
|
||||||
pb "github.com/schollz/progressbar/v3"
|
pb "github.com/schollz/progressbar/v3"
|
||||||
flag "github.com/spf13/pflag"
|
"golang.org/x/term"
|
||||||
)
|
)
|
||||||
|
|
||||||
var appVersion string
|
var appVersion string
|
||||||
@@ -58,7 +59,6 @@ func main() {
|
|||||||
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
|
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
|
||||||
go func() {
|
go func() {
|
||||||
for range c {
|
for range c {
|
||||||
fmt.Println("\naborting")
|
|
||||||
if err := os.RemoveAll(conv.Workdir); err != nil {
|
if err := os.RemoveAll(conv.Workdir); err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
}
|
}
|
||||||
@@ -69,55 +69,79 @@ func main() {
|
|||||||
if _, err := os.Stat(opts.OutDir); err != nil {
|
if _, err := os.Stat(opts.OutDir); err != nil {
|
||||||
if err := os.MkdirAll(opts.OutDir, 0775); err != nil {
|
if err := os.MkdirAll(opts.OutDir, 0775); err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
conv.Initialize()
|
|
||||||
defer conv.Terminate()
|
|
||||||
|
|
||||||
files, err := conv.Files(args)
|
files, err := conv.Files(args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interactive := !opts.Quiet && term.IsTerminal(int(os.Stderr.Fd()))
|
||||||
|
|
||||||
var bar *pb.ProgressBar
|
var bar *pb.ProgressBar
|
||||||
if opts.Cover || opts.Thumbnail || opts.Meta {
|
newBar := func(max int, description string) {
|
||||||
if !opts.Quiet {
|
bar = pb.NewOptions(max,
|
||||||
bar = pb.NewOptions(conv.Nfiles,
|
pb.OptionShowCount(),
|
||||||
pb.OptionShowCount(),
|
pb.OptionClearOnFinish(),
|
||||||
pb.OptionClearOnFinish(),
|
pb.OptionUseANSICodes(true),
|
||||||
pb.OptionUseANSICodes(true),
|
pb.OptionSetPredictTime(false),
|
||||||
pb.OptionSetPredictTime(false),
|
pb.OptionSetDescription(description),
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
clearLine := func() {
|
||||||
|
fmt.Fprint(os.Stderr, "\033[2K\r")
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup := func() {
|
||||||
|
if interactive {
|
||||||
|
if bar != nil {
|
||||||
|
_ = bar.Finish()
|
||||||
|
}
|
||||||
|
clearLine()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if interactive && (opts.Cover || opts.Thumbnail || opts.Meta) {
|
||||||
|
newBar(conv.Nfiles, "")
|
||||||
|
}
|
||||||
|
|
||||||
conv.OnStart = func() {
|
conv.OnStart = func() {
|
||||||
if !opts.Quiet {
|
if interactive {
|
||||||
bar = pb.NewOptions(conv.Ncontents,
|
clearLine()
|
||||||
pb.OptionShowCount(),
|
newBar(conv.Ncontents, fmt.Sprintf("Converting %d of %d:", conv.CurrFile, conv.Nfiles))
|
||||||
pb.OptionClearOnFinish(),
|
|
||||||
pb.OptionUseANSICodes(true),
|
|
||||||
pb.OptionSetDescription(fmt.Sprintf("Converting %d of %d:", conv.CurrFile, conv.Nfiles)),
|
|
||||||
pb.OptionSetPredictTime(false),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
conv.OnProgress = func() {
|
conv.OnProgress = func() {
|
||||||
if !opts.Quiet {
|
if bar != nil {
|
||||||
_ = bar.Add(1)
|
_ = bar.Add(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
conv.OnCompress = func() {
|
conv.OnCompress = func() {
|
||||||
if !opts.Quiet {
|
if interactive {
|
||||||
_, _ = fmt.Fprintf(os.Stderr, "Compressing %d of %d...\r", conv.CurrFile, conv.Nfiles)
|
if bar != nil {
|
||||||
|
_ = bar.Finish()
|
||||||
|
}
|
||||||
|
fmt.Fprintf(os.Stderr, "Compressing %d of %d...", conv.CurrFile, conv.Nfiles)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if opts.Combine {
|
||||||
|
if err := conv.Combine(files); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup()
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
for _, file := range files {
|
for _, file := range files {
|
||||||
switch {
|
switch {
|
||||||
case opts.Meta:
|
case opts.Meta:
|
||||||
@@ -135,14 +159,14 @@ func main() {
|
|||||||
|
|
||||||
continue
|
continue
|
||||||
case opts.Cover:
|
case opts.Cover:
|
||||||
if err := conv.Cover(file.Path, file.Stat); err != nil {
|
if err := conv.Cover(file); err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
continue
|
continue
|
||||||
case opts.Thumbnail:
|
case opts.Thumbnail:
|
||||||
if err = conv.Thumbnail(file.Path, file.Stat); err != nil {
|
if err = conv.Thumbnail(file); err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
@@ -150,13 +174,13 @@ func main() {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := conv.Convert(file.Path, file.Stat); err != nil {
|
if err := conv.Convert(file); err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _ = fmt.Fprintf(os.Stderr, "\r")
|
cleanup()
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseFlags parses command line flags.
|
// parseFlags parses command line flags.
|
||||||
@@ -165,14 +189,19 @@ func parseFlags() (cbconvert.Options, []string) {
|
|||||||
var args []string
|
var args []string
|
||||||
|
|
||||||
convert := flag.NewFlagSet("convert", flag.ExitOnError)
|
convert := flag.NewFlagSet("convert", flag.ExitOnError)
|
||||||
convert.SortFlags = false
|
|
||||||
convert.IntVar(&opts.Width, "width", 0, "Image width")
|
convert.IntVar(&opts.Width, "width", 0, "Image width")
|
||||||
convert.IntVar(&opts.Height, "height", 0, "Image height")
|
convert.IntVar(&opts.Height, "height", 0, "Image height")
|
||||||
convert.BoolVar(&opts.Fit, "fit", false, "Best fit for required width and height")
|
convert.BoolVar(&opts.Fit, "fit", false, "Best fit for required width and height")
|
||||||
convert.StringVar(&opts.Format, "format", "jpeg", "Image format, valid values are jpeg, png, tiff, bmp, webp, avif")
|
convert.BoolVar(&opts.NoUpscale, "no-upscale", false, "Do not upscale images already smaller than the requested width/height")
|
||||||
|
convert.IntVar(&opts.DPI, "dpi", 0, "Document rendering resolution in DPI (PDF, EPUB, etc.), 0 uses the default (300)")
|
||||||
|
convert.StringVar(&opts.Format, "format", "jpeg", "Image format, valid values are jpeg, png, tiff, bmp, webp, avif, jxl")
|
||||||
convert.StringVar(&opts.Archive, "archive", "zip", "Archive format, valid values are zip, tar")
|
convert.StringVar(&opts.Archive, "archive", "zip", "Archive format, valid values are zip, tar")
|
||||||
|
convert.IntVar(&opts.ZipLevel, "zip-level", -1, "ZIP compression level, 0 disables compression, 1-9 sets deflate level (1 fastest, 9 smallest), -1 uses the default")
|
||||||
convert.IntVar(&opts.Quality, "quality", 75, "Image quality")
|
convert.IntVar(&opts.Quality, "quality", 75, "Image quality")
|
||||||
convert.BoolVar(&opts.Lossless, "lossless", false, "Lossless compression (avif)")
|
convert.IntVar(&opts.Effort, "effort", -1, "Encoder speed/effort, format-specific (webp method 0-6, avif speed 0-10, jxl effort 1-10), -1 uses the format default")
|
||||||
|
convert.BoolVar(&opts.Lossless, "lossless", false, "Lossless compression (webp, avif, jxl), ignores quality")
|
||||||
|
convert.BoolVar(&opts.Combine, "combine", false, "Combine all inputs into a single archive")
|
||||||
|
convert.StringVar(&opts.OutFile, "outfile", "", "Output file name for --combine (default: first input + -combined)")
|
||||||
convert.IntVar(&opts.Filter, "filter", 2, "0=NearestNeighbor, 1=Box, 2=Linear, 3=MitchellNetravali, 4=CatmullRom, 6=Gaussian, 7=Lanczos")
|
convert.IntVar(&opts.Filter, "filter", 2, "0=NearestNeighbor, 1=Box, 2=Linear, 3=MitchellNetravali, 4=CatmullRom, 6=Gaussian, 7=Lanczos")
|
||||||
convert.BoolVar(&opts.NoCover, "no-cover", false, "Do not convert the cover image")
|
convert.BoolVar(&opts.NoCover, "no-cover", false, "Do not convert the cover image")
|
||||||
convert.BoolVar(&opts.NoRGB, "no-rgb", false, "Do not convert images that have RGB colorspace")
|
convert.BoolVar(&opts.NoRGB, "no-rgb", false, "Do not convert images that have RGB colorspace")
|
||||||
@@ -183,23 +212,21 @@ func parseFlags() (cbconvert.Options, []string) {
|
|||||||
convert.IntVar(&opts.Brightness, "brightness", 0, "Adjust the brightness of the images, must be in the range (-100, 100)")
|
convert.IntVar(&opts.Brightness, "brightness", 0, "Adjust the brightness of the images, must be in the range (-100, 100)")
|
||||||
convert.IntVar(&opts.Contrast, "contrast", 0, "Adjust the contrast of the images, must be in the range (-100, 100)")
|
convert.IntVar(&opts.Contrast, "contrast", 0, "Adjust the contrast of the images, must be in the range (-100, 100)")
|
||||||
convert.StringVar(&opts.Suffix, "suffix", "", "Add suffix to file basename")
|
convert.StringVar(&opts.Suffix, "suffix", "", "Add suffix to file basename")
|
||||||
convert.IntVar(&opts.LevelsInMin, "levels-inmin", 0, "Shadow input value")
|
|
||||||
convert.Float64Var(&opts.LevelsGamma, "levels-gamma", 1.0, "Midpoint/Gamma")
|
|
||||||
convert.IntVar(&opts.LevelsInMax, "levels-inmax", 255, "Highlight input value")
|
|
||||||
convert.IntVar(&opts.LevelsOutMin, "levels-outmin", 0, "Shadow output value")
|
|
||||||
convert.IntVar(&opts.LevelsOutMax, "levels-outmax", 255, "Highlight output value")
|
|
||||||
convert.StringVar(&opts.OutDir, "outdir", ".", "Output directory")
|
convert.StringVar(&opts.OutDir, "outdir", ".", "Output directory")
|
||||||
convert.IntVar(&opts.Size, "size", 0, "Process only files larger than size (in MB)")
|
convert.IntVar(&opts.Size, "size", 0, "Process only files larger than size (in MB)")
|
||||||
convert.BoolVar(&opts.Recursive, "recursive", false, "Process subdirectories recursively")
|
convert.BoolVar(&opts.Recursive, "recursive", false, "Process subdirectories recursively")
|
||||||
convert.BoolVar(&opts.Quiet, "quiet", false, "Hide console output")
|
convert.BoolVar(&opts.Quiet, "quiet", false, "Hide console output")
|
||||||
|
|
||||||
cover := flag.NewFlagSet("cover", flag.ExitOnError)
|
cover := flag.NewFlagSet("cover", flag.ExitOnError)
|
||||||
cover.SortFlags = false
|
|
||||||
cover.IntVar(&opts.Width, "width", 0, "Image width")
|
cover.IntVar(&opts.Width, "width", 0, "Image width")
|
||||||
cover.IntVar(&opts.Height, "height", 0, "Image height")
|
cover.IntVar(&opts.Height, "height", 0, "Image height")
|
||||||
cover.BoolVar(&opts.Fit, "fit", false, "Best fit for required width and height")
|
cover.BoolVar(&opts.Fit, "fit", false, "Best fit for required width and height")
|
||||||
|
cover.BoolVar(&opts.NoUpscale, "no-upscale", false, "Do not upscale images already smaller than the requested width/height")
|
||||||
|
cover.IntVar(&opts.DPI, "dpi", 0, "Document rendering resolution in DPI (PDF, EPUB, etc.), 0 uses the default (300)")
|
||||||
cover.StringVar(&opts.Format, "format", "jpeg", "Image format, valid values are jpeg, png, tiff, bmp, webp, avif")
|
cover.StringVar(&opts.Format, "format", "jpeg", "Image format, valid values are jpeg, png, tiff, bmp, webp, avif")
|
||||||
cover.IntVar(&opts.Quality, "quality", 75, "Image quality")
|
cover.IntVar(&opts.Quality, "quality", 75, "Image quality")
|
||||||
|
cover.IntVar(&opts.Effort, "effort", -1, "Encoder speed/effort, format-specific (webp method 0-6, avif speed 0-10, jxl effort 1-10), -1 uses the format default")
|
||||||
|
cover.BoolVar(&opts.Lossless, "lossless", false, "Lossless compression (webp, avif, jxl), ignores quality")
|
||||||
cover.IntVar(&opts.Filter, "filter", 2, "0=NearestNeighbor, 1=Box, 2=Linear, 3=MitchellNetravali, 4=CatmullRom, 6=Gaussian, 7=Lanczos")
|
cover.IntVar(&opts.Filter, "filter", 2, "0=NearestNeighbor, 1=Box, 2=Linear, 3=MitchellNetravali, 4=CatmullRom, 6=Gaussian, 7=Lanczos")
|
||||||
cover.StringVar(&opts.OutDir, "outdir", ".", "Output directory")
|
cover.StringVar(&opts.OutDir, "outdir", ".", "Output directory")
|
||||||
cover.IntVar(&opts.Size, "size", 0, "Process only files larger than size (in MB)")
|
cover.IntVar(&opts.Size, "size", 0, "Process only files larger than size (in MB)")
|
||||||
@@ -207,10 +234,11 @@ func parseFlags() (cbconvert.Options, []string) {
|
|||||||
cover.BoolVar(&opts.Quiet, "quiet", false, "Hide console output")
|
cover.BoolVar(&opts.Quiet, "quiet", false, "Hide console output")
|
||||||
|
|
||||||
thumbnail := flag.NewFlagSet("thumbnail", flag.ExitOnError)
|
thumbnail := flag.NewFlagSet("thumbnail", flag.ExitOnError)
|
||||||
thumbnail.SortFlags = false
|
|
||||||
thumbnail.IntVar(&opts.Width, "width", 0, "Image width")
|
thumbnail.IntVar(&opts.Width, "width", 0, "Image width")
|
||||||
thumbnail.IntVar(&opts.Height, "height", 0, "Image height")
|
thumbnail.IntVar(&opts.Height, "height", 0, "Image height")
|
||||||
thumbnail.BoolVar(&opts.Fit, "fit", false, "Best fit for required width and height")
|
thumbnail.BoolVar(&opts.Fit, "fit", false, "Best fit for required width and height")
|
||||||
|
thumbnail.BoolVar(&opts.NoUpscale, "no-upscale", false, "Do not upscale images already smaller than the requested width/height")
|
||||||
|
thumbnail.IntVar(&opts.DPI, "dpi", 0, "Document rendering resolution in DPI (PDF, EPUB, etc.), 0 uses the default (300)")
|
||||||
thumbnail.IntVar(&opts.Filter, "filter", 2, "0=NearestNeighbor, 1=Box, 2=Linear, 3=MitchellNetravali, 4=CatmullRom, 6=Gaussian, 7=Lanczos")
|
thumbnail.IntVar(&opts.Filter, "filter", 2, "0=NearestNeighbor, 1=Box, 2=Linear, 3=MitchellNetravali, 4=CatmullRom, 6=Gaussian, 7=Lanczos")
|
||||||
thumbnail.StringVar(&opts.OutDir, "outdir", ".", "Output directory")
|
thumbnail.StringVar(&opts.OutDir, "outdir", ".", "Output directory")
|
||||||
thumbnail.StringVar(&opts.OutFile, "outfile", "", "Output file")
|
thumbnail.StringVar(&opts.OutFile, "outfile", "", "Output file")
|
||||||
@@ -219,7 +247,6 @@ func parseFlags() (cbconvert.Options, []string) {
|
|||||||
thumbnail.BoolVar(&opts.Quiet, "quiet", false, "Hide console output")
|
thumbnail.BoolVar(&opts.Quiet, "quiet", false, "Hide console output")
|
||||||
|
|
||||||
meta := flag.NewFlagSet("meta", flag.ExitOnError)
|
meta := flag.NewFlagSet("meta", flag.ExitOnError)
|
||||||
meta.SortFlags = false
|
|
||||||
meta.BoolVar(&opts.Cover, "cover", false, "Print cover name")
|
meta.BoolVar(&opts.Cover, "cover", false, "Print cover name")
|
||||||
meta.BoolVar(&opts.Comment, "comment", false, "Print zip comment")
|
meta.BoolVar(&opts.Comment, "comment", false, "Print zip comment")
|
||||||
meta.StringVar(&opts.CommentBody, "comment-body", "", "Set zip comment")
|
meta.StringVar(&opts.CommentBody, "comment-body", "", "Set zip comment")
|
||||||
@@ -229,34 +256,43 @@ func parseFlags() (cbconvert.Options, []string) {
|
|||||||
flag.NewFlagSet("version", flag.ExitOnError)
|
flag.NewFlagSet("version", flag.ExitOnError)
|
||||||
|
|
||||||
flag.Usage = func() {
|
flag.Usage = func() {
|
||||||
_, _ = fmt.Fprintf(os.Stderr, "Usage: %s <command> [<flags>] [file1 dir1 ... fileOrDirN]\n\n", filepath.Base(os.Args[0]))
|
fmt.Fprintf(os.Stderr, "Usage: %s <command> [<flags>] [file1 dir1 ... fileOrDirN]\n\n", filepath.Base(os.Args[0]))
|
||||||
_, _ = fmt.Fprintf(os.Stderr, "\nCommands:\n")
|
fmt.Fprintf(os.Stderr, "\nCommands:\n")
|
||||||
_, _ = fmt.Fprintf(os.Stderr, "\n convert\n \tConvert archive or document\n\n")
|
fmt.Fprintf(os.Stderr, "\n convert\n \tConvert archive or document\n\n")
|
||||||
convert.VisitAll(func(f *flag.Flag) {
|
order := []string{"width", "height", "fit", "no-upscale", "dpi", "format", "archive", "zip-level", "quality", "effort", "lossless", "combine", "outfile", "filter", "no-cover", "no-rgb",
|
||||||
_, _ = fmt.Fprintf(os.Stderr, " --%s\n \t", f.Name)
|
"no-nonimage", "no-convert", "grayscale", "rotate", "brightness", "contrast", "suffix", "outdir", "size", "recursive", "quiet"}
|
||||||
_, _ = fmt.Fprintf(os.Stderr, "%v (default %q)\n", f.Usage, f.DefValue)
|
for _, name := range order {
|
||||||
})
|
f := convert.Lookup(name)
|
||||||
_, _ = fmt.Fprintf(os.Stderr, "\n cover\n \tExtract cover\n\n")
|
fmt.Fprintf(os.Stderr, " --%s\n \t", f.Name)
|
||||||
cover.VisitAll(func(f *flag.Flag) {
|
fmt.Fprintf(os.Stderr, "%v (default %q)\n", f.Usage, f.DefValue)
|
||||||
_, _ = fmt.Fprintf(os.Stderr, " --%s\n \t", f.Name)
|
}
|
||||||
_, _ = fmt.Fprintf(os.Stderr, "%v (default %q)\n", f.Usage, f.DefValue)
|
fmt.Fprintf(os.Stderr, "\n cover\n \tExtract cover\n\n")
|
||||||
})
|
order = []string{"width", "height", "fit", "no-upscale", "dpi", "format", "quality", "effort", "lossless", "filter", "outdir", "size", "recursive", "quiet"}
|
||||||
_, _ = fmt.Fprintf(os.Stderr, "\n thumbnail\n \tExtract cover thumbnail (freedesktop spec.)\n\n")
|
for _, name := range order {
|
||||||
thumbnail.VisitAll(func(f *flag.Flag) {
|
f := cover.Lookup(name)
|
||||||
_, _ = fmt.Fprintf(os.Stderr, " --%s\n \t", f.Name)
|
fmt.Fprintf(os.Stderr, " --%s\n \t", f.Name)
|
||||||
_, _ = fmt.Fprintf(os.Stderr, "%v (default %q)\n", f.Usage, f.DefValue)
|
fmt.Fprintf(os.Stderr, "%v (default %q)\n", f.Usage, f.DefValue)
|
||||||
})
|
}
|
||||||
_, _ = fmt.Fprintf(os.Stderr, "\n meta\n \tCBZ metadata\n\n")
|
fmt.Fprintf(os.Stderr, "\n thumbnail\n \tExtract cover thumbnail (freedesktop spec.)\n\n")
|
||||||
meta.VisitAll(func(f *flag.Flag) {
|
order = []string{"width", "height", "fit", "no-upscale", "dpi", "filter", "outdir", "outfile", "size", "recursive", "quiet"}
|
||||||
_, _ = fmt.Fprintf(os.Stderr, " --%s\n \t", f.Name)
|
for _, name := range order {
|
||||||
_, _ = fmt.Fprintf(os.Stderr, "%v (default %q)\n", f.Usage, f.DefValue)
|
f := thumbnail.Lookup(name)
|
||||||
})
|
fmt.Fprintf(os.Stderr, " --%s\n \t", f.Name)
|
||||||
_, _ = fmt.Fprintf(os.Stderr, "\n version\n \tPrint version\n\n")
|
fmt.Fprintf(os.Stderr, "%v (default %q)\n", f.Usage, f.DefValue)
|
||||||
|
}
|
||||||
|
fmt.Fprintf(os.Stderr, "\n meta\n \tCBZ metadata\n\n")
|
||||||
|
order = []string{"cover", "comment", "comment-body", "file-add", "file-remove"}
|
||||||
|
for _, name := range order {
|
||||||
|
f := meta.Lookup(name)
|
||||||
|
fmt.Fprintf(os.Stderr, " --%s\n \t", f.Name)
|
||||||
|
fmt.Fprintf(os.Stderr, "%v (default %q)\n", f.Usage, f.DefValue)
|
||||||
|
}
|
||||||
|
fmt.Fprintf(os.Stderr, "\n version\n \tPrint version\n\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(os.Args) < 2 {
|
if len(os.Args) < 2 {
|
||||||
convert.Usage()
|
flag.Usage()
|
||||||
_, _ = fmt.Fprintf(os.Stderr, "no command\n")
|
fmt.Fprintf(os.Stderr, "no command\n")
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -267,27 +303,27 @@ func parseFlags() (cbconvert.Options, []string) {
|
|||||||
|
|
||||||
switch os.Args[1] {
|
switch os.Args[1] {
|
||||||
case "convert":
|
case "convert":
|
||||||
_ = convert.Parse(os.Args[2:])
|
operands := parseArgs(convert, os.Args[2:])
|
||||||
if !pipe {
|
if !pipe {
|
||||||
args = convert.Args()
|
args = operands
|
||||||
}
|
}
|
||||||
case "cover":
|
case "cover":
|
||||||
opts.Cover = true
|
opts.Cover = true
|
||||||
_ = cover.Parse(os.Args[2:])
|
operands := parseArgs(cover, os.Args[2:])
|
||||||
if !pipe {
|
if !pipe {
|
||||||
args = cover.Args()
|
args = operands
|
||||||
}
|
}
|
||||||
case "thumbnail":
|
case "thumbnail":
|
||||||
opts.Thumbnail = true
|
opts.Thumbnail = true
|
||||||
_ = thumbnail.Parse(os.Args[2:])
|
operands := parseArgs(thumbnail, os.Args[2:])
|
||||||
if !pipe {
|
if !pipe {
|
||||||
args = thumbnail.Args()
|
args = operands
|
||||||
}
|
}
|
||||||
case "meta":
|
case "meta":
|
||||||
opts.Meta = true
|
opts.Meta = true
|
||||||
_ = meta.Parse(os.Args[2:])
|
operands := parseArgs(meta, os.Args[2:])
|
||||||
if !pipe {
|
if !pipe {
|
||||||
args = meta.Args()
|
args = operands
|
||||||
}
|
}
|
||||||
case "version":
|
case "version":
|
||||||
opts.Version = true
|
opts.Version = true
|
||||||
@@ -302,7 +338,20 @@ func parseFlags() (cbconvert.Options, []string) {
|
|||||||
return opts, args
|
return opts, args
|
||||||
}
|
}
|
||||||
|
|
||||||
// piped checks if we have a piped stdin.
|
// parseArgs parses flags interspersed with file/dir operands.
|
||||||
|
func parseArgs(fs *flag.FlagSet, args []string) []string {
|
||||||
|
var operands []string
|
||||||
|
|
||||||
|
_ = fs.Parse(args)
|
||||||
|
for fs.NArg() > 0 {
|
||||||
|
operands = append(operands, fs.Arg(0))
|
||||||
|
_ = fs.Parse(fs.Args()[1:])
|
||||||
|
}
|
||||||
|
|
||||||
|
return operands
|
||||||
|
}
|
||||||
|
|
||||||
|
// piped checks if we have piped stdin.
|
||||||
func piped() bool {
|
func piped() bool {
|
||||||
f, err := os.Stdin.Stat()
|
f, err := os.Stdin.Stat()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -316,7 +365,7 @@ func piped() bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// lines returns slice of lines from reader.
|
// lines returns slice of lines from the reader.
|
||||||
func lines(r io.Reader) []string {
|
func lines(r io.Reader) []string {
|
||||||
data := make([]string, 0)
|
data := make([]string, 0)
|
||||||
scanner := bufio.NewScanner(r)
|
scanner := bufio.NewScanner(r)
|
||||||
|
|||||||
@@ -1,73 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
GLIBC_x86_64="/usr/x86_64-pc-linux-gnu-static"
|
|
||||||
MUSL_aarch64="/usr/aarch64-pc-linux-musl"
|
|
||||||
MINGW_x86_64="/usr/x86_64-w64-mingw32"
|
|
||||||
MACOS_x86_64="/usr/x86_64-apple-darwin"
|
|
||||||
MACOS_aarch64="/usr/aarch64-apple-darwin"
|
|
||||||
|
|
||||||
VERSION="`git --git-dir ../../.git describe --tags --abbrev=0 2>/dev/null || echo '0.0.0'`"
|
|
||||||
|
|
||||||
BUILDDIR="cbconvert-${VERSION}"; mkdir -p ${BUILDDIR}
|
|
||||||
CC=x86_64-pc-linux-gnu-gcc \
|
|
||||||
PKG_CONFIG="x86_64-pc-linux-gnu-pkg-config" \
|
|
||||||
PKG_CONFIG_PATH="$GLIBC_x86_64/usr/lib64/pkgconfig" \
|
|
||||||
PKG_CONFIG_LIBDIR="$GLIBC_x86_64/usr/lib64/pkgconfig" \
|
|
||||||
CGO_CFLAGS="-I$GLIBC_x86_64/usr/include" \
|
|
||||||
CGO_LDFLAGS="-L$GLIBC_x86_64/usr/lib64" \
|
|
||||||
CGO_ENABLED=1 GOOS=linux GOARCH=amd64 \
|
|
||||||
go build -trimpath -tags 'extlib pkgconfig' -v -o ${BUILDDIR}/cbconvert -ldflags "-linkmode external -s -w -X main.appVersion=${VERSION} '-extldflags=-static'" && \
|
|
||||||
cp ../../README.md ../../AUTHORS ../../COPYING ${BUILDDIR} && tar -czf "${BUILDDIR}-linux-x86_64.tar.gz" ${BUILDDIR}
|
|
||||||
rm -rf ${BUILDDIR}
|
|
||||||
|
|
||||||
BUILDDIR="cbconvert-${VERSION}"; mkdir -p ${BUILDDIR}
|
|
||||||
CC=aarch64-pc-linux-musl-gcc \
|
|
||||||
PKG_CONFIG="aarch64-pc-linux-musl-pkg-config" \
|
|
||||||
PKG_CONFIG_PATH="$MUSL_aarch64/usr/lib/pkgconfig" \
|
|
||||||
PKG_CONFIG_LIBDIR="$MUSL_aarch64/usr/lib/pkgconfig" \
|
|
||||||
CGO_CFLAGS="-I$MUSL_aarch64/usr/include" \
|
|
||||||
CGO_LDFLAGS="-L$MUSL_aarch64/usr/lib" \
|
|
||||||
CGO_ENABLED=1 GOOS=linux GOARCH=arm64 \
|
|
||||||
go build -trimpath -tags 'extlib pkgconfig' -v -o ${BUILDDIR}/cbconvert -ldflags "-linkmode external -s -w -X main.appVersion=${VERSION} '-extldflags=-static'" && \
|
|
||||||
cp ../../README.md ../../AUTHORS ../../COPYING ${BUILDDIR} && tar -czf "${BUILDDIR}-linux-aarch64.tar.gz" ${BUILDDIR}
|
|
||||||
rm -rf ${BUILDDIR}
|
|
||||||
|
|
||||||
BUILDDIR="cbconvert-${VERSION}"; mkdir -p ${BUILDDIR}
|
|
||||||
CC=x86_64-w64-mingw32-gcc \
|
|
||||||
PKG_CONFIG="/usr/bin/x86_64-w64-mingw32-pkg-config" \
|
|
||||||
PKG_CONFIG_PATH="$MINGW_x86_64/usr/lib/pkgconfig" \
|
|
||||||
PKG_CONFIG_LIBDIR="$MINGW_x86_64/usr/lib/pkgconfig" \
|
|
||||||
CGO_CFLAGS="-I$MINGW_x86_64/usr/include" \
|
|
||||||
CGO_LDFLAGS="-L$MINGW_x86_64/usr/lib" \
|
|
||||||
CGO_ENABLED=1 GOOS=windows GOARCH=amd64 \
|
|
||||||
go build -trimpath -tags 'extlib pkgconfig' -v -o ${BUILDDIR}/cbconvert.exe -ldflags "-s -w -X main.appVersion=${VERSION} '-extldflags=-static -Wl,--allow-multiple-definition'" && \
|
|
||||||
cp ../../README.md ../../AUTHORS ../../COPYING ${BUILDDIR} && zip -rq "${BUILDDIR}-windows-x86_64.zip" ${BUILDDIR}
|
|
||||||
rm -rf ${BUILDDIR}
|
|
||||||
|
|
||||||
export OSXCROSS_PKG_CONFIG_USE_NATIVE_VARIABLES=1
|
|
||||||
BUILDDIR="cbconvert-${VERSION}"; mkdir -p ${BUILDDIR}
|
|
||||||
PATH=${PATH}:${MACOS_x86_64}/bin \
|
|
||||||
CC=x86_64-apple-darwin21.1-clang \
|
|
||||||
PKG_CONFIG="x86_64-apple-darwin21.1-pkg-config" \
|
|
||||||
PKG_CONFIG_PATH="$MACOS_x86_64/SDK/MacOSX12.1.sdk/usr/lib/pkgconfig" \
|
|
||||||
PKG_CONFIG_LIBDIR="$MACOS_x86_64/SDK/MacOSX12.1.sdk/usr/lib/pkgconfig" \
|
|
||||||
CGO_CFLAGS="-I$MACOS_x86_64/usr/include -I$MACOS_x86_64/macports/pkgs/opt/local/include" \
|
|
||||||
CGO_LDFLAGS="-L$MACOS_x86_64/SDK/MacOSX12.1.sdk/usr/lib -L$MACOS_x86_64/macports/pkgs/opt/local/lib -mmacosx-version-min=10.13" \
|
|
||||||
CGO_ENABLED=1 GOOS=darwin GOARCH=amd64 \
|
|
||||||
go build -trimpath -tags 'extlib pkgconfig' -v -o ${BUILDDIR}/cbconvert -ldflags "-linkmode external -s -w -X main.appVersion=${VERSION}" && \
|
|
||||||
cp ../../README.md ../../AUTHORS ../../COPYING ${BUILDDIR} && zip -rq "${BUILDDIR}-darwin-x86_64.zip" ${BUILDDIR}
|
|
||||||
rm -rf ${BUILDDIR}
|
|
||||||
|
|
||||||
export OSXCROSS_PKG_CONFIG_USE_NATIVE_VARIABLES=1
|
|
||||||
BUILDDIR="cbconvert-${VERSION}"; mkdir -p ${BUILDDIR}
|
|
||||||
PATH=${PATH}:${MACOS_aarch64}/bin \
|
|
||||||
CC=aarch64-apple-darwin21.1-clang \
|
|
||||||
PKG_CONFIG="aarch64-apple-darwin21.1-pkg-config" \
|
|
||||||
PKG_CONFIG_PATH="$MACOS_aarch64/SDK/MacOSX12.1.sdk/usr/lib/pkgconfig" \
|
|
||||||
PKG_CONFIG_LIBDIR="$MACOS_aarch64/SDK/MacOSX12.1.sdk/usr/lib/pkgconfig" \
|
|
||||||
CGO_CFLAGS="-I$MACOS_aarch64/usr/include -I$MACOS_aarch64/macports/pkgs/opt/local/include" \
|
|
||||||
CGO_LDFLAGS="-L$MACOS_aarch64/SDK/MacOSX12.1.sdk/usr/lib -L$MACOS_aarch64/macports/pkgs/opt/local/lib -mmacosx-version-min=10.13" \
|
|
||||||
CGO_ENABLED=1 GOOS=darwin GOARCH=arm64 \
|
|
||||||
go build -trimpath -tags 'extlib pkgconfig' -v -o ${BUILDDIR}/cbconvert -ldflags "-linkmode external -s -w -X main.appVersion=${VERSION}" && \
|
|
||||||
cp ../../README.md ../../AUTHORS ../../COPYING ${BUILDDIR} && zip -rq "${BUILDDIR}-darwin-aarch64.zip" ${BUILDDIR}
|
|
||||||
rm -rf ${BUILDDIR}
|
|
||||||
@@ -1,17 +1,52 @@
|
|||||||
module github.com/gen2brain/cbconvert
|
module github.com/gen2brain/cbconvert
|
||||||
|
|
||||||
go 1.21
|
go 1.26
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/chai2010/webp v1.1.1
|
github.com/anthonynsimon/bild v0.15.0
|
||||||
github.com/disintegration/imaging v1.6.2
|
github.com/dsoprea/go-png-image-structure v0.0.0-20210512210324-29b889a6093d
|
||||||
github.com/dustin/go-humanize v1.0.1
|
github.com/dustin/go-humanize v1.0.1
|
||||||
github.com/fvbommel/sortorder v1.1.0
|
github.com/fvbommel/sortorder v1.1.0
|
||||||
github.com/gen2brain/go-fitz v1.23.1
|
github.com/gen2brain/avif v0.5.1
|
||||||
github.com/gen2brain/go-unarr v0.1.7
|
github.com/gen2brain/go-fitz v1.24.15
|
||||||
github.com/hotei/bmp v0.0.0-20150430041436-f620cebab0c7
|
github.com/gen2brain/jpegli v0.4.0
|
||||||
github.com/strukturag/libheif v1.15.2
|
github.com/gen2brain/jpegxl v0.5.1
|
||||||
golang.org/x/image v0.11.0
|
github.com/gen2brain/webp v0.6.1
|
||||||
golang.org/x/sync v0.3.0
|
github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25
|
||||||
gopkg.in/gographics/imagick.v3 v3.4.3
|
github.com/mholt/archives v0.1.5
|
||||||
|
golang.org/x/image v0.43.0
|
||||||
|
golang.org/x/sync v0.21.0
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/STARRY-S/zip v0.2.3 // indirect
|
||||||
|
github.com/andybalholm/brotli v1.2.1 // indirect
|
||||||
|
github.com/bodgit/plumbing v1.3.0 // indirect
|
||||||
|
github.com/bodgit/sevenzip v1.6.4 // indirect
|
||||||
|
github.com/bodgit/windows v1.0.1 // indirect
|
||||||
|
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 // indirect
|
||||||
|
github.com/dsoprea/go-exif/v2 v2.0.0-20230826092837-6579e82b732d // indirect
|
||||||
|
github.com/dsoprea/go-logging v0.0.0-20200710184922-b02d349568dd // indirect
|
||||||
|
github.com/dsoprea/go-utility v0.0.0-20221003172846-a3e1774ef349 // indirect
|
||||||
|
github.com/ebitengine/purego v0.10.1 // indirect
|
||||||
|
github.com/go-errors/errors v1.5.1 // indirect
|
||||||
|
github.com/golang/geo v0.0.0-20260622181633-f80708c60e82 // indirect
|
||||||
|
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
|
||||||
|
github.com/jupiterrider/ffi v0.7.0 // indirect
|
||||||
|
github.com/klauspost/compress v1.18.6 // indirect
|
||||||
|
github.com/klauspost/pgzip v1.2.6 // indirect
|
||||||
|
github.com/mikelolasagasti/xz v1.0.1 // indirect
|
||||||
|
github.com/minio/minlz v1.1.1 // indirect
|
||||||
|
github.com/nwaples/rardecode/v2 v2.2.5 // indirect
|
||||||
|
github.com/pierrec/lz4/v4 v4.1.27 // indirect
|
||||||
|
github.com/sorairolake/lzip-go v0.3.8 // indirect
|
||||||
|
github.com/spf13/afero v1.15.0 // indirect
|
||||||
|
github.com/stangelandcl/ppmd v0.1.1 // indirect
|
||||||
|
github.com/tetratelabs/wazero v1.12.0 // indirect
|
||||||
|
github.com/ulikunitz/xz v0.5.15 // indirect
|
||||||
|
go4.org v0.0.0-20260112195520-a5071408f32f // indirect
|
||||||
|
golang.org/x/net v0.56.0 // indirect
|
||||||
|
golang.org/x/sys v0.46.0 // indirect
|
||||||
|
golang.org/x/text v0.38.0 // indirect
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,54 +1,141 @@
|
|||||||
github.com/chai2010/webp v1.1.1 h1:jTRmEccAJ4MGrhFOrPMpNGIJ/eybIgwKpcACsrTEapk=
|
github.com/STARRY-S/zip v0.2.3 h1:luE4dMvRPDOWQdeDdUxUoZkzUIpTccdKdhHHsQJ1fm4=
|
||||||
github.com/chai2010/webp v1.1.1/go.mod h1:0XVwvZWdjjdxpUEIf7b9g9VkHFnInUSYujwqTLEuldU=
|
github.com/STARRY-S/zip v0.2.3/go.mod h1:lqJ9JdeRipyOQJrYSOtpNAiaesFO6zVDsE8GIGFaoSk=
|
||||||
github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
|
github.com/andybalholm/brotli v1.2.1 h1:R+f5xP285VArJDRgowrfb9DqL18yVK0gKAW/F+eTWro=
|
||||||
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
|
github.com/andybalholm/brotli v1.2.1/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
|
||||||
|
github.com/anthonynsimon/bild v0.15.0 h1:FzvaNLuNlAPKw1Xz7V2WYOcGIEBMj8Y6ZyAk7CI+HzA=
|
||||||
|
github.com/anthonynsimon/bild v0.15.0/go.mod h1:qIgJ9FldkCn0iy5Ad24fzUkz5R+iJ0WfhiV+6FeCB5A=
|
||||||
|
github.com/bodgit/plumbing v1.3.0 h1:pf9Itz1JOQgn7vEOE7v7nlEfBykYqvUYioC61TwWCFU=
|
||||||
|
github.com/bodgit/plumbing v1.3.0/go.mod h1:JOTb4XiRu5xfnmdnDJo6GmSbSbtSyufrsyZFByMtKEs=
|
||||||
|
github.com/bodgit/sevenzip v1.6.4 h1:iHiVJfxbrB6RF4X+snI2MpVgNBKmVfGaTqZGNlMQIU0=
|
||||||
|
github.com/bodgit/sevenzip v1.6.4/go.mod h1:ZtNi5KNgHXeXg1G7WiF0IWSuFE2eG6lt/cTGlvuirO0=
|
||||||
|
github.com/bodgit/windows v1.0.1 h1:tF7K6KOluPYygXa3Z2594zxlkbKPAOvqr97etrGNIz4=
|
||||||
|
github.com/bodgit/windows v1.0.1/go.mod h1:a6JLwrB4KrTR5hBpp8FI9/9W9jJfeQ2h4XDXU74ZCdM=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 h1:2tV76y6Q9BB+NEBasnqvs7e49aEBFI8ejC89PSnWH+4=
|
||||||
|
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707/go.mod h1:qssHWj60/X5sZFNxpG4HBPDHVqxNm4DfnCKgrbZOT+s=
|
||||||
|
github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=
|
||||||
|
github.com/dsoprea/go-exif/v2 v2.0.0-20200321225314-640175a69fe4/go.mod h1:Lm2lMM2zx8p4a34ZemkaUV95AnMl4ZvLbCUbwOvLC2E=
|
||||||
|
github.com/dsoprea/go-exif/v2 v2.0.0-20200604193436-ca8584a0e1c4/go.mod h1:9EXlPeHfblFFnwu5UOqmP2eoZfJyAZ2Ri/Vki33ajO0=
|
||||||
|
github.com/dsoprea/go-exif/v2 v2.0.0-20230826092837-6579e82b732d h1:yeH8wrJa3+8uKKDAdURHUK1ds2UvKhMqX2MiOdVeKPs=
|
||||||
|
github.com/dsoprea/go-exif/v2 v2.0.0-20230826092837-6579e82b732d/go.mod h1:oKrjk2kb3rAR5NbtSTLUMvMSbc+k8ZosI3MaVH47noc=
|
||||||
|
github.com/dsoprea/go-exif/v3 v3.0.0-20200717053412-08f1b6708903/go.mod h1:0nsO1ce0mh5czxGeLo4+OCZ/C6Eo6ZlMWsz7rH/Gxv8=
|
||||||
|
github.com/dsoprea/go-exif/v3 v3.0.0-20210512043655-120bcdb2a55e/go.mod h1:cg5SNYKHMmzxsr9X6ZeLh/nfBRHHp5PngtEPcujONtk=
|
||||||
|
github.com/dsoprea/go-logging v0.0.0-20190624164917-c4f10aab7696/go.mod h1:Nm/x2ZUNRW6Fe5C3LxdY1PyZY5wmDv/s5dkPJ/VB3iA=
|
||||||
|
github.com/dsoprea/go-logging v0.0.0-20200517223158-a10564966e9d/go.mod h1:7I+3Pe2o/YSU88W0hWlm9S22W7XI1JFNJ86U0zPKMf8=
|
||||||
|
github.com/dsoprea/go-logging v0.0.0-20200710184922-b02d349568dd h1:l+vLbuxptsC6VQyQsfD7NnEC8BZuFpz45PgY+pH8YTg=
|
||||||
|
github.com/dsoprea/go-logging v0.0.0-20200710184922-b02d349568dd/go.mod h1:7I+3Pe2o/YSU88W0hWlm9S22W7XI1JFNJ86U0zPKMf8=
|
||||||
|
github.com/dsoprea/go-png-image-structure v0.0.0-20210512210324-29b889a6093d h1:8+qI8ant/vZkNSsbwSjIR6XJfWcDVTg/qx/3pRUUZNA=
|
||||||
|
github.com/dsoprea/go-png-image-structure v0.0.0-20210512210324-29b889a6093d/go.mod h1:yTR3tKgyk20phAFg6IE9ulMA5NjEDD2wyx+okRFLVtw=
|
||||||
|
github.com/dsoprea/go-utility v0.0.0-20200711062821-fab8125e9bdf/go.mod h1:95+K3z2L0mqsVYd6yveIv1lmtT3tcQQ3dVakPySffW8=
|
||||||
|
github.com/dsoprea/go-utility v0.0.0-20221003172846-a3e1774ef349 h1:/py11NlxDaOxkT9OKN+gXgT+QOH5xj1ZRoyusfRIlo4=
|
||||||
|
github.com/dsoprea/go-utility v0.0.0-20221003172846-a3e1774ef349/go.mod h1:KVK+/Hul09ujXAGq+42UBgCTnXkiJZRnLYdURGjQUwo=
|
||||||
|
github.com/dsoprea/go-utility/v2 v2.0.0-20200717064901-2fccff4aa15e/go.mod h1:uAzdkPTub5Y9yQwXe8W4m2XuP0tK4a9Q/dantD0+uaU=
|
||||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||||
|
github.com/ebitengine/purego v0.10.1 h1:dewVBCBT2GaMu1SrNTYxQhgQBethzfhiwvZiLGP/qyY=
|
||||||
|
github.com/ebitengine/purego v0.10.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||||
github.com/fvbommel/sortorder v1.1.0 h1:fUmoe+HLsBTctBDoaBwpQo5N+nrCp8g/BjKb/6ZQmYw=
|
github.com/fvbommel/sortorder v1.1.0 h1:fUmoe+HLsBTctBDoaBwpQo5N+nrCp8g/BjKb/6ZQmYw=
|
||||||
github.com/fvbommel/sortorder v1.1.0/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0=
|
github.com/fvbommel/sortorder v1.1.0/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0=
|
||||||
github.com/gen2brain/go-fitz v1.23.1 h1:x69/szWZXpI3jZ57mMqCg7WqqvtYnQG0lXts3L6M1Fc=
|
github.com/gen2brain/avif v0.5.1 h1:LQzLsJpWyGlsa4wuZ3D57qEbCiICIK7Yidz5ZPEwzTk=
|
||||||
github.com/gen2brain/go-fitz v1.23.1/go.mod h1:HU04vc+RisUh/kvEd2pB0LAxmK1oyXdN4ftyshUr9rQ=
|
github.com/gen2brain/avif v0.5.1/go.mod h1:QgrYqdVE9y40PCfArK9VakcMIpYeDYpZmCSLkW6C1n8=
|
||||||
github.com/gen2brain/go-unarr v0.1.7 h1:mEE7bPShJIsmAX67t6BW2ibpEUO7j5WK152KgNM9NbQ=
|
github.com/gen2brain/go-fitz v1.24.15 h1:sJNB1MOWkqnzzENPHggFpgxTwW0+S5WF/rM5wUBpJWo=
|
||||||
github.com/gen2brain/go-unarr v0.1.7/go.mod h1:MK9a3hddpaIxjEtrE1f/LA5yJ7gA34cS7Oyr325sY9s=
|
github.com/gen2brain/go-fitz v1.24.15/go.mod h1:SftkiVbTHqF141DuiLwBBM65zP7ig6AVDQpf2WlHamo=
|
||||||
github.com/hotei/bmp v0.0.0-20150430041436-f620cebab0c7 h1:NlUATi3cllRJhpM4mfR9BxiLRXT83bcSLcOa+S8lrME=
|
github.com/gen2brain/jpegli v0.4.0 h1:TnmAO8EcekS/XCboyLPjP6cqz025tkhdaFkiYNd3wH8=
|
||||||
github.com/hotei/bmp v0.0.0-20150430041436-f620cebab0c7/go.mod h1:Hku3FQ2laCEwSv7Z8YkC0er38jLaUycUCbsFkWMr+z4=
|
github.com/gen2brain/jpegli v0.4.0/go.mod h1:zJ++s4symmKCN1CLkrY0dGXTY3s0NWbd94Rz9KLdCzk=
|
||||||
github.com/strukturag/libheif v1.15.2 h1:pgdcpDHqtLKRXL9ETSTeht0CsJODB3BojpTsb3S/3Wg=
|
github.com/gen2brain/jpegxl v0.5.1 h1:UuBUIkZ35DErImU3bTA6rltfV5zSgVNOA/K5a6JibfE=
|
||||||
github.com/strukturag/libheif v1.15.2/go.mod h1:E/PNRlmVtrtj9j2AvBZlrO4dsBDu6KfwDZn7X1Ce8Ks=
|
github.com/gen2brain/jpegxl v0.5.1/go.mod h1:Wlc6lqx03RJfhiQRyHa2e+8VQwT4/qv7zSRsNv9T+yE=
|
||||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
github.com/gen2brain/webp v0.6.1 h1:ei7Y1SWpQcdqz3YNDNyn4y2nQanxs9WLzwW5/2DKS64=
|
||||||
|
github.com/gen2brain/webp v0.6.1/go.mod h1:iGWMaCSw7t3I/Cv9llzEKmpnR36S8lS8VL/ZVjxU0JE=
|
||||||
|
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
|
||||||
|
github.com/go-errors/errors v1.0.2/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWEp4lssFEs=
|
||||||
|
github.com/go-errors/errors v1.1.1/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWEp4lssFEs=
|
||||||
|
github.com/go-errors/errors v1.5.1 h1:ZwEMSLRCapFLflTpT7NKaAc7ukJ8ZPEjzlxt8rPN8bk=
|
||||||
|
github.com/go-errors/errors v1.5.1/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
|
||||||
|
github.com/golang/geo v0.0.0-20190916061304-5b978397cfec/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
|
||||||
|
github.com/golang/geo v0.0.0-20200319012246-673a6f80352d/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
|
||||||
|
github.com/golang/geo v0.0.0-20260622181633-f80708c60e82 h1:q100v18oYIaE4p5myLFqDyfXTOxivzwGyLjGCEfv8x4=
|
||||||
|
github.com/golang/geo v0.0.0-20260622181633-f80708c60e82/go.mod h1:Mymr9kRGDc64JPr03TSZmuIBODZ3KyswLzm1xL0HFA8=
|
||||||
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
|
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||||
|
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
|
||||||
|
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
|
||||||
|
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||||
|
github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25 h1:YLvr1eE6cdCqjOe972w/cYF+FjW34v27+9Vo5106B4M=
|
||||||
|
github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25/go.mod h1:kLgvv7o6UM+0QSf0QjAse3wReFDsb9qbZJdfexWlrQw=
|
||||||
|
github.com/jupiterrider/ffi v0.7.0 h1:RKsl6Ascal+3kyAqR5Qcbp83LceQMLc1VZbPfHWoNzs=
|
||||||
|
github.com/jupiterrider/ffi v0.7.0/go.mod h1:9dauhpOfNqrqk28fxuu0kkdeFtT9Qr4vbfigiuIXN7c=
|
||||||
|
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
||||||
|
github.com/klauspost/compress v1.18.6 h1:2jupLlAwFm95+YDR+NwD2MEfFO9d4z4Prjl1XXDjuao=
|
||||||
|
github.com/klauspost/compress v1.18.6/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
|
||||||
|
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||||
|
github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=
|
||||||
|
github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
|
||||||
|
github.com/mholt/archives v0.1.5 h1:Fh2hl1j7VEhc6DZs2DLMgiBNChUux154a1G+2esNvzQ=
|
||||||
|
github.com/mholt/archives v0.1.5/go.mod h1:3TPMmBLPsgszL+1As5zECTuKwKvIfj6YcwWPpeTAXF4=
|
||||||
|
github.com/mikelolasagasti/xz v1.0.1 h1:Q2F2jX0RYJUG3+WsM+FJknv+6eVjsjXNDV0KJXZzkD0=
|
||||||
|
github.com/mikelolasagasti/xz v1.0.1/go.mod h1:muAirjiOUxPRXwm9HdDtB3uoRPrGnL85XHtokL9Hcgc=
|
||||||
|
github.com/minio/minlz v1.1.1 h1:OGmft1V6AnI/Wme332U6bhG54nxEan+VFgkD7lat4KM=
|
||||||
|
github.com/minio/minlz v1.1.1/go.mod h1:qT0aEB35q79LLornSzeDH75LBf3aH1MV+jB5w9Wasec=
|
||||||
|
github.com/nwaples/rardecode/v2 v2.2.5 h1:L5doqgGfQwI7qADJMqnkrSB86rpPsqQDrHeO0HWa5JY=
|
||||||
|
github.com/nwaples/rardecode/v2 v2.2.5/go.mod h1:7uz379lSxPe6j9nvzxUZ+n7mnJNgjsRNb6IbvGVHRmw=
|
||||||
|
github.com/pierrec/lz4/v4 v4.1.27 h1:+PhzhWDrjRj89TH2sw43nE3+4+W8lSxIuQadEHZyjUk=
|
||||||
|
github.com/pierrec/lz4/v4 v4.1.27/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/sorairolake/lzip-go v0.3.8 h1:j5Q2313INdTA80ureWYRhX+1K78mUXfMoPZCw/ivWik=
|
||||||
|
github.com/sorairolake/lzip-go v0.3.8/go.mod h1:JcBqGMV0frlxwrsE9sMWXDjqn3EeVf0/54YPsw66qkU=
|
||||||
|
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
|
||||||
|
github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
|
||||||
|
github.com/stangelandcl/ppmd v0.1.1 h1:c25QazhlWUn5nmR1QOzafKhQxBicAr7GGCKER2aJ8H8=
|
||||||
|
github.com/stangelandcl/ppmd v0.1.1/go.mod h1:Rrv7M+/2P5jYr/GMLhBl7Ug3uJ1bUiVzr5LbbaV6xgY=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
|
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||||
|
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||||
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
|
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||||
|
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
|
github.com/tetratelabs/wazero v1.12.0 h1:DuWcpNu/FzgEXgGBDp8J1Spc+CWOvvtvVyjKlaZopYU=
|
||||||
|
github.com/tetratelabs/wazero v1.12.0/go.mod h1:LvKtzl2RqO4gyF27BiXU+nKAjcV8f38U+kP/q2vgxh0=
|
||||||
|
github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||||
|
github.com/ulikunitz/xz v0.5.15 h1:9DNdB5s+SgV3bQ2ApL10xRc35ck0DuIX/isZvIk+ubY=
|
||||||
|
github.com/ulikunitz/xz v0.5.15/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||||
|
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
|
||||||
|
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
|
||||||
|
go4.org v0.0.0-20260112195520-a5071408f32f h1:ziUVAjmTPwQMBmYR1tbdRFJPtTcQUI12fH9QQjfb0Sw=
|
||||||
|
go4.org v0.0.0-20260112195520-a5071408f32f/go.mod h1:ZRJnO5ZI4zAwMFp+dS1+V6J6MSyAowhRqAE+DPa1Xp0=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/image v0.43.0 h1:FLxcP4ec2350nTfOC8ysKtqYSIFbk/QGjw1ZHNP4tsY=
|
||||||
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
golang.org/x/image v0.43.0/go.mod h1:rrpelvGFt+kLPAjPM4HeWPgrl0FtafueU//e5N0qk/Q=
|
||||||
golang.org/x/image v0.11.0 h1:ds2RoQvBvYTiJkwpSFDwCcDFNX7DqjL2WsUgTNk0Ooo=
|
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/image v0.11.0/go.mod h1:bglhjqbqVuEb9e9+eNR45Jfu7D+T4Qan+NhQk8Ck2P8=
|
golang.org/x/net v0.0.0-20200320220750-118fecf932d8/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o=
|
||||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec=
|
||||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
|
|
||||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw=
|
||||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
|
||||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4=
|
||||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
gopkg.in/gographics/imagick.v3 v3.4.3 h1:9plKFE/Us913jBN6KohtLG9FNW8LPvfpjiGAORIiEHg=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/gographics/imagick.v3 v3.4.3/go.mod h1:+Q9nyA2xRZXrDyTtJ/eko+8V/5E7bWYs08ndkZp8UmA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
|||||||