Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
f01e858a24 | ||
|
|
8c21b430a9 | ||
|
|
5fbd462a78 | ||
|
|
0797c8456f | ||
|
|
c3f6c5a499 | ||
|
|
fa162972b2 | ||
|
|
aa65bfa4f4 | ||
|
|
6c293adc1f | ||
|
|
6936f697b4 | ||
|
|
83d7a1ea6e | ||
|
|
3d56765415 | ||
|
|
b1fcf530da | ||
|
|
7dc21fc0b4 | ||
|
|
fdfa80875e | ||
|
|
50b0911586 | ||
|
|
8db1690d3c | ||
|
|
91e336f772 | ||
|
|
903a8bbcfc | ||
|
|
e938b140f0 | ||
|
|
0a3f4d6992 | ||
|
|
c5902fb131 | ||
|
|
d6ece90041 | ||
|
|
c34b4d0a98 | ||
|
|
f0e2232e51 | ||
|
|
0ba0337ef8 | ||
|
|
6e97d977ae | ||
|
|
685b6bf153 | ||
|
|
14f2e87525 | ||
|
|
cd2f7501e0 | ||
|
|
69b0b51010 | ||
|
|
c5494af60b | ||
|
|
f447c618de | ||
|
|
54261d8528 | ||
|
|
f92125643f | ||
|
|
7b6c766dcc | ||
|
|
dfea3dc6a3 | ||
|
|
16d01e292e | ||
|
|
6fe10f5bdb | ||
|
|
547a3937a8 | ||
|
|
d47c1c1464 | ||
|
|
5befff07ae | ||
|
|
d4abc99239 | ||
|
|
f1804ce13c | ||
|
|
a74034d669 | ||
|
|
64fb152c36 | ||
|
|
7082e3db90 | ||
|
|
943cd9bb95 | ||
|
|
ec2c321ff3 | ||
|
|
a0ed277971 | ||
|
|
b5b4457088 | ||
|
|
c24d7b7e18 | ||
|
|
8ba065e68b | ||
|
|
9cd5616b48 | ||
|
|
13a3b6e23e | ||
|
|
9a047d1977 | ||
|
|
d6e7248112 | ||
|
|
9eb20db167 | ||
|
|
04d2dd79d5 | ||
|
|
2b2788ed6e | ||
|
|
164e7bfd75 | ||
|
|
27da60ed76 | ||
|
|
0963cb1762 | ||
|
|
443c4e6aaa | ||
|
|
9da5b97f15 | ||
|
|
adbb86f9f7 | ||
|
|
efb8b776fb | ||
|
|
fa954fce1a | ||
|
|
7d80f5238e | ||
|
|
56e07aae23 | ||
|
|
3c3123a50d | ||
|
|
2e03350437 | ||
|
|
1fdc1610f5 | ||
|
|
a5e796d017 | ||
|
|
cffd119eba | ||
|
|
cda03e0122 | ||
|
|
6a73f6f20e | ||
|
|
5df10a58ee | ||
|
|
081736ee04 |
@@ -0,0 +1,23 @@
|
||||
on: [push, pull_request]
|
||||
name: Test
|
||||
jobs:
|
||||
test:
|
||||
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:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
- name: Test
|
||||
run: go test
|
||||
env:
|
||||
CGO_ENABLED: 1
|
||||
@@ -0,0 +1,5 @@
|
||||
cmd/cbconvert-gui/cbconvert-gui
|
||||
cmd/cbconvert/cbconvert
|
||||
|
||||
.idea
|
||||
.vscode
|
||||
@@ -1,184 +1,201 @@
|
||||
CBconvert
|
||||
=========
|
||||
## CBconvert
|
||||
|
||||
Introduction
|
||||
------------
|
||||
### Introduction
|
||||
|
||||
CBconvert is a [Comic Book](http://en.wikipedia.org/wiki/Comic_Book_Archive_file) converter written in [Go language](https://golang.org/).
|
||||
CBconvert is a [Comic Book](http://en.wikipedia.org/wiki/Comic_Book_Archive_file) converter.
|
||||
|
||||
It can convert one comic at a time or bulk 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" />
|
||||
|
||||
Features
|
||||
--------
|
||||
See more [screenshots](https://github.com/gen2brain/cbconvert/blob/master/cmd/cbconvert-gui/screenshots/).
|
||||
|
||||
- reads RAR, ZIP, 7Z, GZ, BZ2, CBR, CBZ, CB7, CBT, PDF, EPUB, XPS and plain directory
|
||||
- always saves processed comic in CBZ (ZIP) archive format
|
||||
- images can be converted to JPEG, PNG, GIF, TIFF or 4-Bit BMP (16 colors) file format
|
||||
- rotate, flip, adjust brightness/contrast, adjust levels (Photoshop like) or grayscale images
|
||||
- choose resize algorithm (NearestNeighbor, Box, Linear, MitchellNetravali, CatmullRom, Gaussian, Lanczos)
|
||||
- export covers from comics
|
||||
- create thumbnails from covers by [freedesktop](http://www.freedesktop.org/wiki/) specification
|
||||
### Features
|
||||
|
||||
Download
|
||||
--------
|
||||
* reads CBR (RAR), CBZ (ZIP), CB7 (7Z), CBT (TAR), PDF, XPS, EPUB, MOBI, DOCX, PPTX and plain directory
|
||||
* saves processed files in ZIP archive format or TAR
|
||||
* images can be converted to JPEG, PNG, TIFF, WEBP, AVIF, JXL, or 4-Bit BMP (16 colors) image format
|
||||
* rotate, adjust brightness/contrast or grayscale images
|
||||
* resize filters (NearestNeighbor, Box, Linear, MitchellNetravali, CatmullRom, Gaussian, Lanczos)
|
||||
* export covers from comics
|
||||
* create thumbnails from covers by [FreeDesktop](http://specifications.freedesktop.org/thumbnail-spec/thumbnail-spec-latest.html) specification
|
||||
|
||||
- [Windows GUI](https://github.com/gen2brain/cbconvert/releases/download/0.5.0/cbconvert-0.5.zip)
|
||||
- [Windows CMD](https://github.com/gen2brain/cbconvert/releases/download/0.5.0/cbconvert-cmd-0.5.zip)
|
||||
### Download
|
||||
|
||||
- [Linux 64bit GUI](https://github.com/gen2brain/cbconvert/releases/download/0.5.0/cbconvert-0.5.tar.gz)
|
||||
- [Linux 64bit CMD](https://github.com/gen2brain/cbconvert/releases/download/0.5.0/cbconvert-cmd-0.5.tar.gz)
|
||||
Download the latest binaries from the [releases](https://github.com/gen2brain/cbconvert/releases).
|
||||
|
||||
Using cbconvert in file managers to generate freedesktop thumbnails
|
||||
-------------------------------------------------------------------
|
||||
Linux Flatpak is available at [Flathub](https://flathub.org/apps/io.github.gen2brain.cbconvert).
|
||||
|
||||
Just copy cbconvert cmd binary to your PATH and create file ~/.local/share/thumbnailers/cbconvert.thumbnailer :
|
||||
|
||||
[Thumbnailer Entry]
|
||||
TryExec=cbconvert
|
||||
Exec=cbconvert thumbnail --quiet --width %s --outfile %o %i
|
||||
MimeType=application/pdf;application/x-pdf;image/pdf;application/x-cbz;application/x-cbr;application/x-cb7;application/x-cbt;application/oxps;application/vnd.ms-xpsdocument;application/epub+zip;
|
||||
### Compile
|
||||
|
||||
This is how it looks like in PCManFM file manager:
|
||||
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
|
||||
|
||||
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:
|
||||
|
||||
<img src="cmd/cbconvert-gui/screenshots/thumbnails.jpg" width="700" alt="thumbnails" />
|
||||
|
||||
|
||||
Using command line app
|
||||
----------------------
|
||||
### Using command line app
|
||||
|
||||
usage: cbconvert [<flags>] <command> [<args> ...]
|
||||
|
||||
Comic Book convert tool.
|
||||
|
||||
Flags:
|
||||
--help Show context-sensitive help (also try --help-long and --help-man).
|
||||
--version Show application version.
|
||||
--outdir="." Output directory
|
||||
--size=0 Process only files larger then size (in MB)
|
||||
--recursive Process subdirectories recursively
|
||||
--quiet Hide console output
|
||||
|
||||
Args:
|
||||
<args> filename or directory
|
||||
|
||||
Commands:
|
||||
help [<command>...]
|
||||
Show help.
|
||||
```
|
||||
Usage: cbconvert <command> [<flags>] [file1 dir1 ... fileOrDirN]
|
||||
|
||||
|
||||
convert [<flags>] <args>...
|
||||
Convert archive or document (default command)
|
||||
Commands:
|
||||
|
||||
--width=0 Image width
|
||||
--height=0 Image height
|
||||
--fit Best fit for required width and height
|
||||
--format="jpeg" Image format, valid values are jpeg, png, gif, tiff, bmp
|
||||
--quality=75 JPEG image quality
|
||||
--filter=2 0=NearestNeighbor, 1=Box, 2=Linear, 3=MitchellNetravali, 4=CatmullRom, 6=Gaussian, 7=Lanczos
|
||||
--cover Convert cover image (use --no-cover if you want to exclude cover)
|
||||
--rgb Convert images that have RGB colorspace (use --no-rgb if you only want to convert grayscaled images)
|
||||
--nonimage Leave non image files in archive (use --no-nonimage to remove non image files from archive)
|
||||
--grayscale Convert images to grayscale (monochromatic)
|
||||
--rotate=0 Rotate images, valid values are 0, 90, 180, 270
|
||||
--flip="none" Flip images, valid values are none, horizontal, vertical
|
||||
--brightness=0 Adjust brightness of the images, must be in range (-100, 100)
|
||||
--contrast=0 Adjust contrast of the images, must be in range (-100, 100)
|
||||
--suffix=SUFFIX Add suffix to file basename
|
||||
--levels-inmin=0 Shadow input value
|
||||
--levels-inmax=255 Highlight input value
|
||||
--levels-gamma=1 Midpoint/Gamma
|
||||
--levels-outmin=0 Shadow output value
|
||||
--levels-outmax=255 Highlight output value
|
||||
convert
|
||||
Convert archive or document
|
||||
|
||||
cover [<flags>] <args>...
|
||||
Extract cover
|
||||
--width
|
||||
Image width (default "0")
|
||||
--height
|
||||
Image height (default "0")
|
||||
--fit
|
||||
Best fit for required width and height (default "false")
|
||||
--format
|
||||
Image format, valid values are jpeg, png, tiff, bmp, webp, avif, jxl (default "jpeg")
|
||||
--archive
|
||||
Archive format, valid values are zip, tar (default "zip")
|
||||
--quality
|
||||
Image quality (default "75")
|
||||
--filter
|
||||
0=NearestNeighbor, 1=Box, 2=Linear, 3=MitchellNetravali, 4=CatmullRom, 6=Gaussian, 7=Lanczos (default "2")
|
||||
--no-cover
|
||||
Do not convert the cover image (default "false")
|
||||
--no-rgb
|
||||
Do not convert images that have RGB colorspace (default "false")
|
||||
--no-nonimage
|
||||
Remove non-image files from the archive (default "false")
|
||||
--no-convert
|
||||
Do not transform or convert images (default "false")
|
||||
--grayscale
|
||||
Convert images to grayscale (monochromatic) (default "false")
|
||||
--rotate
|
||||
Rotate images, valid values are 0, 90, 180, 270 (default "0")
|
||||
--brightness
|
||||
Adjust the brightness of the images, must be in the range (-100, 100) (default "0")
|
||||
--contrast
|
||||
Adjust the contrast of the images, must be in the range (-100, 100) (default "0")
|
||||
--suffix
|
||||
Add suffix to file basename (default "")
|
||||
--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")
|
||||
|
||||
--width=0 Image width
|
||||
--height=0 Image height
|
||||
--fit Best fit for required width and height
|
||||
--quality=75 JPEG image quality
|
||||
--filter=2 0=NearestNeighbor, 1=Box, 2=Linear, 3=MitchellNetravali, 4=CatmullRom, 6=Gaussian, 7=Lanczos
|
||||
cover
|
||||
Extract cover
|
||||
|
||||
thumbnail [<flags>] <args>...
|
||||
Extract cover thumbnail (freedesktop spec.)
|
||||
--width
|
||||
Image width (default "0")
|
||||
--height
|
||||
Image height (default "0")
|
||||
--fit
|
||||
Best fit for required width and height (default "false")
|
||||
--format
|
||||
Image format, valid values are jpeg, png, tiff, bmp, webp, avif (default "jpeg")
|
||||
--quality
|
||||
Image quality (default "75")
|
||||
--filter
|
||||
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")
|
||||
|
||||
--width=0 Image width
|
||||
--height=0 Image height
|
||||
--fit Best fit for required width and height
|
||||
--filter=2 0=NearestNeighbor, 1=Box, 2=Linear, 3=MitchellNetravali, 4=CatmullRom, 6=Gaussian, 7=Lanczos
|
||||
thumbnail
|
||||
Extract cover thumbnail (freedesktop spec.)
|
||||
|
||||
[man page](https://en.wikipedia.org/wiki/Man_page) is also available:
|
||||
--width
|
||||
Image width (default "0")
|
||||
--height
|
||||
Image height (default "0")
|
||||
--fit
|
||||
Best fit for required width and height (default "false")
|
||||
--filter
|
||||
0=NearestNeighbor, 1=Box, 2=Linear, 3=MitchellNetravali, 4=CatmullRom, 6=Gaussian, 7=Lanczos (default "2")
|
||||
--outdir
|
||||
Output directory (default ".")
|
||||
--outfile
|
||||
Output file (default "")
|
||||
--size
|
||||
Process only files larger than size (in MB) (default "0")
|
||||
--recursive
|
||||
Process subdirectories recursively (default "false")
|
||||
--quiet
|
||||
Hide console output (default "false")
|
||||
|
||||
cbconvert --help-man | man /dev/stdin
|
||||
meta
|
||||
CBZ metadata
|
||||
|
||||
Examples
|
||||
--------
|
||||
--cover
|
||||
Print cover name (default "false")
|
||||
--comment
|
||||
Print zip comment (default "false")
|
||||
--comment-body
|
||||
Set zip comment (default "")
|
||||
--file-add
|
||||
Add file to archive (default "")
|
||||
--file-remove
|
||||
Remove file from archive (glob pattern, i.e. *.xml) (default "")
|
||||
|
||||
Rescale images to 1200px for all supported files found in directory with size larger then 60MB:
|
||||
version
|
||||
Print version
|
||||
```
|
||||
|
||||
cbconvert --recursive --width 1200 --size 60 /media/comics/Thorgal/
|
||||
### Examples
|
||||
|
||||
Convert all images in pdf to 4bit BMP image and save result in ~/comics directory:
|
||||
* Rescale images to 1200px for all supported files found in a directory with a size larger than 60MB:
|
||||
|
||||
cbconvert --bmp --outdir ~/comics /media/comics/Garfield/Garfield_01.pdf
|
||||
`cbconvert --recursive --width 1200 --size 60 /media/comics/Thorgal/`
|
||||
|
||||
[BMP](http://en.wikipedia.org/wiki/BMP_file_format) format is very good choice for black&white pages. Archive size can be smaller 2-3x and file will be readable by comic readers.
|
||||
* Convert all images in pdf to 4bit BMP images and save the result in ~/comics directory:
|
||||
|
||||
Generate thumbnails by [freedesktop specification](http://specifications.freedesktop.org/thumbnail-spec/thumbnail-spec-latest.html) in ~/.thumbnails/normal directory with width 512:
|
||||
`cbconvert --format bmp --outdir ~/comics /media/comics/Garfield/Garfield_01.pdf`
|
||||
|
||||
cbconvert thumbnail --width 512 --outdir ~/.thumbnails/normal /media/comics/GrooTheWanderer/
|
||||
[BMP](http://en.wikipedia.org/wiki/BMP_file_format) format is a very good choice for black&white pages. Archive size can be smaller 2-3x and the file will be readable by comic readers.
|
||||
|
||||
Extract covers to ~/covers dir for all supported files found in directory, Lanczos algorithm is used for resizing:
|
||||
* Extract covers to ~/covers dir for all supported files found in the directory, Lanczos algorithm is used for resizing:
|
||||
|
||||
cbconvert cover --outdir ~/covers --filter=7 /media/comics/GrooTheWanderer/
|
||||
`cbconvert cover --outdir ~/covers --filter=7 /media/comics/GrooTheWanderer/`
|
||||
|
||||
Compile
|
||||
-------
|
||||
* Convert all images to AVIF format:
|
||||
|
||||
Install imagemagick dev packages:
|
||||
`cbconvert --format avif --quality 50 --width 1280 --outdir ~/comics /media/comics/Misc/`
|
||||
|
||||
apt-get install libmagickcore-dev libmagickwand-dev
|
||||
### Quality settings
|
||||
|
||||
Compile latest MuPDF:
|
||||
This table maps quality settings for JPEG to the respective AVIF and WEBP quality settings:
|
||||
|
||||
git clone git://git.ghostscript.com/mupdf.git && cd mupdf
|
||||
git submodule update --init --recursive
|
||||
curl -L https://gist.githubusercontent.com/gen2brain/7869ac4c6db5933f670f/raw/1619394dc957ae10bcd73c713760993466b4bfea/mupdf-openssl-curl.patch | patch -p1
|
||||
sed -e "1iHAVE_X11 = no" -e "1iWANT_OPENSSL = no" -e "1iWANT_CURL = no" -i Makerules
|
||||
HAVE_X11=no HAVE_GLFW=no HAVE_GLUT=no WANT_OPENSSL=no WANT_CURL=no HAVE_MUJS=yes HAVE_JSCORE=no HAVE_V8=no make && make install
|
||||
| | | | | |
|
||||
|--------------|----|----|----|----|
|
||||
| JPEG quality | 50 | 60 | 70 | 80 |
|
||||
| AVIF quality | 48 | 51 | 56 | 64 |
|
||||
| WEBP quality | 55 | 64 | 72 | 82 |
|
||||
|
||||
Compile unarr library:
|
||||
|
||||
git clone https://github.com/zeniko/unarr && cd unarr
|
||||
mkdir lzma920 && cd lzma920 && curl -L http://www.7-zip.org/a/lzma920.tar.bz2 | tar -xjvp && cd ..
|
||||
curl -L http://zlib.net/zlib-1.2.8.tar.gz | tar -xzvp
|
||||
curl -L http://www.bzip.org/1.0.6/bzip2-1.0.6.tar.gz | tar -xzvp
|
||||
curl -L https://gist.githubusercontent.com/gen2brain/89fe506863be3fb139e8/raw/8783a7d81e22ad84944d146c5e33beab6dffc641/unarr-makefile.patch | patch -p1
|
||||
CFLAGS="-DHAVE_7Z -DHAVE_ZLIB -DHAVE_BZIP2 -I./lzma920/C -I./zlib-1.2.8 -I./bzip2-1.0.6" make
|
||||
cp build/debug/libunarr.a /usr/lib64/ && cp unarr.h /usr/include
|
||||
|
||||
Install dependencies:
|
||||
|
||||
go get github.com/cheggaaa/pb
|
||||
go get github.com/disintegration/imaging
|
||||
go get github.com/gen2brain/go-fitz
|
||||
go get github.com/gen2brain/go-unarr
|
||||
go get github.com/gographics/imagick/imagick
|
||||
go get github.com/hotei/bmp
|
||||
go get github.com/skarademir/naturalsort
|
||||
go get golang.org/x/image/tiff
|
||||
go get golang.org/x/image/webp
|
||||
go get gopkg.in/alecthomas/kingpin.v2
|
||||
|
||||
For command line app:
|
||||
|
||||
go get github.com/gen2brain/cbconvert
|
||||
go build -o $GOPATH/bin/cbconvert github.com/gen2brain/cbconvert/cmd
|
||||
|
||||
For GUI app:
|
||||
|
||||
go get gopkg.in/qml.v1
|
||||
go get github.com/gen2brain/cbconvert
|
||||
go build -o $GOPATH/bin/cbconvert github.com/gen2brain/cbconvert/gui
|
||||
|
||||
@@ -0,0 +1,517 @@
|
||||
package cbconvert
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"archive/zip"
|
||||
"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 {
|
||||
fDir := strings.Split(filepath.Dir(fileName), string(os.PathSeparator))[1:]
|
||||
err := os.MkdirAll(filepath.Join(c.Opts.OutDir, filepath.Join(fDir...)), 0755)
|
||||
if err != nil {
|
||||
return fmt.Errorf("archiveSaveZip: %w", err)
|
||||
}
|
||||
|
||||
zipName = filepath.Join(c.Opts.OutDir, filepath.Join(fDir...), 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)
|
||||
|
||||
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
|
||||
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 {
|
||||
fDir := strings.Split(filepath.Dir(fileName), string(os.PathSeparator))[1:]
|
||||
err := os.MkdirAll(filepath.Join(c.Opts.OutDir, filepath.Join(fDir...)), 0755)
|
||||
if err != nil {
|
||||
return fmt.Errorf("archiveSaveTar: %w", err)
|
||||
}
|
||||
|
||||
tarName = filepath.Join(c.Opts.OutDir, filepath.Join(fDir...), 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,429 @@
|
||||
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 {
|
||||
var err error
|
||||
|
||||
c.Workdir, err = os.MkdirTemp(os.TempDir(), "cbc")
|
||||
if err != nil {
|
||||
return fmt.Errorf("convertDocument: %w", err)
|
||||
}
|
||||
|
||||
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.Image(n)
|
||||
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 {
|
||||
var err error
|
||||
|
||||
c.Workdir, err = os.MkdirTemp(os.TempDir(), "cbc")
|
||||
if err != nil {
|
||||
return fmt.Errorf("convertArchive: %w", err)
|
||||
}
|
||||
|
||||
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), filepath.Join(c.Workdir, filepath.Base(pathName))); err != nil {
|
||||
return fmt.Errorf("convertArchive: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
if cover == pathName && c.Opts.NoCover {
|
||||
if err = copyFile(bytes.NewReader(data), filepath.Join(c.Workdir, filepath.Base(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), filepath.Join(c.Workdir, filepath.Base(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.Opts.NoNonImage {
|
||||
if err = copyFile(bytes.NewReader(data), filepath.Join(c.Workdir, filepath.Base(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 {
|
||||
var err error
|
||||
|
||||
c.Workdir, err = os.MkdirTemp(os.TempDir(), "cbc")
|
||||
if err != nil {
|
||||
return fmt.Errorf("convertDirectory: %w", err)
|
||||
}
|
||||
|
||||
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())
|
||||
}
|
||||
|
||||
file, err := os.Open(img)
|
||||
if err != nil {
|
||||
return fmt.Errorf("convertDirectory: %w", err)
|
||||
}
|
||||
|
||||
if isNonImage(img) && !c.Opts.NoNonImage {
|
||||
if err = copyFile(file, filepath.Join(c.Workdir, filepath.Base(img))); 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, filepath.Join(c.Workdir, filepath.Base(img))); 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, filepath.Join(c.Workdir, filepath.Base(img))); 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, img)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
err = eg.Wait()
|
||||
if err != nil {
|
||||
return fmt.Errorf("convertDirectory: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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 = filepath.Join(c.Workdir, fmt.Sprintf("%s.%s", baseNoExt(pathName), ext))
|
||||
} else {
|
||||
fileName = filepath.Join(c.Workdir, 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 {
|
||||
if c.Opts.Fit {
|
||||
i = fit(i, c.Opts.Width, c.Opts.Height, filters[c.Opts.Filter])
|
||||
} else {
|
||||
i = resize(i, c.Opts.Width, c.Opts.Height, filters[c.Opts.Filter])
|
||||
}
|
||||
}
|
||||
|
||||
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.Image(0)
|
||||
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,145 @@
|
||||
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))
|
||||
}
|
||||
|
||||
// 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,163 @@
|
||||
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)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
package cbconvert
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestConvert(t *testing.T) {
|
||||
tmpDir, err := os.MkdirTemp(os.TempDir(), "cbc")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
opts := NewOptions()
|
||||
opts.OutDir = tmpDir
|
||||
|
||||
conv := New(opts)
|
||||
|
||||
files, err := conv.Files([]string{"testdata/test", "testdata"})
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
for _, format := range []string{"jpeg", "png", "tiff", "bmp", "webp", "avif", "jxl"} {
|
||||
conv.Opts.Format = format
|
||||
|
||||
for _, file := range files {
|
||||
conv.Opts.Suffix = fmt.Sprintf("_%s%s", format, filepath.Ext(file.Path))
|
||||
|
||||
err = conv.Convert(file.Path, file.Stat)
|
||||
if err != nil {
|
||||
t.Errorf("format %s: file %s: %v", format, file.Name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
err = os.RemoveAll(tmpDir)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCover(t *testing.T) {
|
||||
tmpDir, err := os.MkdirTemp(os.TempDir(), "cbc")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
opts := NewOptions()
|
||||
opts.OutDir = tmpDir
|
||||
|
||||
conv := New(opts)
|
||||
|
||||
files, err := conv.Files([]string{"testdata/test.cbt"})
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
err = conv.Cover(file.Path, file.Stat)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
err = os.RemoveAll(tmpDir)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestThumbnail(t *testing.T) {
|
||||
tmpDir, err := os.MkdirTemp(os.TempDir(), "cbc")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
opts := NewOptions()
|
||||
opts.OutDir = tmpDir
|
||||
|
||||
conv := New(opts)
|
||||
|
||||
files, err := conv.Files([]string{"testdata/test.pdf"})
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
err = conv.Thumbnail(file.Path, file.Stat)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
err = os.RemoveAll(tmpDir)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 5.7 KiB |
@@ -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>
|
||||
|
Before Width: | Height: | Size: 9.4 KiB 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,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,60 @@
|
||||
module github.com/gen2brain/cbconvert/cmd/cbconvert
|
||||
|
||||
go 1.26
|
||||
|
||||
require (
|
||||
github.com/gen2brain/cbconvert v1.0.5-0.20260623161611-a5817c3ba5de
|
||||
github.com/schollz/progressbar/v3 v3.19.0
|
||||
)
|
||||
|
||||
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/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/nwaples/rardecode/v2 v2.2.5 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.27 // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // 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/term v0.44.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,155 @@
|
||||
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/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.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/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/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/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/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/schollz/progressbar/v3 v3.19.0 h1:Ea18xuIRQXLAUidVDox3AbwfUhD0/1IvohyTutOIFoc=
|
||||
github.com/schollz/progressbar/v3 v3.19.0/go.mod h1:IsO3lpbaGuzh8zIMzgY3+J8l4C8GjO0Y9S69eFvNsec=
|
||||
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/term v0.44.0 h1:0rLvDRCtNj0gZkyIXhCyOb2OAzEhLVqc4B+hrsBhrmc=
|
||||
golang.org/x/term v0.44.0/go.mod h1:7ze4MdzUzLXpSAoFP1H0bOI9aXDqveSvatT5vKcFh2Y=
|
||||
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,329 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"runtime/debug"
|
||||
"syscall"
|
||||
|
||||
"github.com/gen2brain/cbconvert"
|
||||
pb "github.com/schollz/progressbar/v3"
|
||||
)
|
||||
|
||||
var appVersion string
|
||||
|
||||
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) > 10 {
|
||||
appVersion = kv.Value[:10]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
opts, args := parseFlags()
|
||||
|
||||
if opts.Version {
|
||||
fmt.Println(filepath.Base(os.Args[0]), appVersion)
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
conv := cbconvert.New(opts)
|
||||
|
||||
c := make(chan os.Signal, 2)
|
||||
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
|
||||
go func() {
|
||||
for range c {
|
||||
if err := os.RemoveAll(conv.Workdir); err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
os.Exit(1)
|
||||
}
|
||||
}()
|
||||
|
||||
if _, err := os.Stat(opts.OutDir); err != nil {
|
||||
if err := os.MkdirAll(opts.OutDir, 0775); err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
files, err := conv.Files(args)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
var bar *pb.ProgressBar
|
||||
if opts.Cover || opts.Thumbnail || opts.Meta {
|
||||
if !opts.Quiet {
|
||||
bar = pb.NewOptions(conv.Nfiles,
|
||||
pb.OptionShowCount(),
|
||||
pb.OptionClearOnFinish(),
|
||||
pb.OptionUseANSICodes(true),
|
||||
pb.OptionSetPredictTime(false),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
conv.OnStart = func() {
|
||||
if !opts.Quiet {
|
||||
bar = pb.NewOptions(conv.Ncontents,
|
||||
pb.OptionShowCount(),
|
||||
pb.OptionClearOnFinish(),
|
||||
pb.OptionUseANSICodes(true),
|
||||
pb.OptionSetDescription(fmt.Sprintf("Converting %d of %d:", conv.CurrFile, conv.Nfiles)),
|
||||
pb.OptionSetPredictTime(false),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
conv.OnProgress = func() {
|
||||
if !opts.Quiet {
|
||||
_ = bar.Add(1)
|
||||
}
|
||||
}
|
||||
|
||||
conv.OnCompress = func() {
|
||||
if !opts.Quiet {
|
||||
fmt.Fprintf(os.Stderr, "Compressing %d of %d...\r", conv.CurrFile, conv.Nfiles)
|
||||
}
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
switch {
|
||||
case opts.Meta:
|
||||
ret, err := conv.Meta(file.Path)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if opts.Cover {
|
||||
fmt.Println(ret)
|
||||
} else if opts.Comment {
|
||||
fmt.Println(ret)
|
||||
}
|
||||
|
||||
continue
|
||||
case opts.Cover:
|
||||
if err := conv.Cover(file.Path, file.Stat); err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
continue
|
||||
case opts.Thumbnail:
|
||||
if err = conv.Thumbnail(file.Path, file.Stat); err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if err := conv.Convert(file.Path, file.Stat); err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Fprintf(os.Stderr, "\r")
|
||||
}
|
||||
|
||||
// parseFlags parses command line flags.
|
||||
func parseFlags() (cbconvert.Options, []string) {
|
||||
opts := cbconvert.Options{}
|
||||
var args []string
|
||||
|
||||
convert := flag.NewFlagSet("convert", flag.ExitOnError)
|
||||
convert.IntVar(&opts.Width, "width", 0, "Image width")
|
||||
convert.IntVar(&opts.Height, "height", 0, "Image 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, jxl")
|
||||
convert.StringVar(&opts.Archive, "archive", "zip", "Archive format, valid values are zip, tar")
|
||||
convert.IntVar(&opts.Quality, "quality", 75, "Image quality")
|
||||
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.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.NoRGB, "no-rgb", false, "Do not convert images that have RGB colorspace")
|
||||
convert.BoolVar(&opts.NoNonImage, "no-nonimage", false, "Remove non-image files from the archive")
|
||||
convert.BoolVar(&opts.NoConvert, "no-convert", false, "Do not transform or convert images")
|
||||
convert.BoolVar(&opts.Grayscale, "grayscale", false, "Convert images to grayscale (monochromatic)")
|
||||
convert.IntVar(&opts.Rotate, "rotate", 0, "Rotate images, valid values are 0, 90, 180, 270")
|
||||
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.StringVar(&opts.Suffix, "suffix", "", "Add suffix to file basename")
|
||||
convert.StringVar(&opts.OutDir, "outdir", ".", "Output directory")
|
||||
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.Quiet, "quiet", false, "Hide console output")
|
||||
|
||||
cover := flag.NewFlagSet("cover", flag.ExitOnError)
|
||||
cover.IntVar(&opts.Width, "width", 0, "Image width")
|
||||
cover.IntVar(&opts.Height, "height", 0, "Image height")
|
||||
cover.BoolVar(&opts.Fit, "fit", false, "Best fit for required width and height")
|
||||
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.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.StringVar(&opts.OutDir, "outdir", ".", "Output directory")
|
||||
cover.IntVar(&opts.Size, "size", 0, "Process only files larger than size (in MB)")
|
||||
cover.BoolVar(&opts.Recursive, "recursive", false, "Process subdirectories recursively")
|
||||
cover.BoolVar(&opts.Quiet, "quiet", false, "Hide console output")
|
||||
|
||||
thumbnail := flag.NewFlagSet("thumbnail", flag.ExitOnError)
|
||||
thumbnail.IntVar(&opts.Width, "width", 0, "Image width")
|
||||
thumbnail.IntVar(&opts.Height, "height", 0, "Image height")
|
||||
thumbnail.BoolVar(&opts.Fit, "fit", false, "Best fit for required width and height")
|
||||
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.OutFile, "outfile", "", "Output file")
|
||||
thumbnail.IntVar(&opts.Size, "size", 0, "Process only files larger than size (in MB)")
|
||||
thumbnail.BoolVar(&opts.Recursive, "recursive", false, "Process subdirectories recursively")
|
||||
thumbnail.BoolVar(&opts.Quiet, "quiet", false, "Hide console output")
|
||||
|
||||
meta := flag.NewFlagSet("meta", flag.ExitOnError)
|
||||
meta.BoolVar(&opts.Cover, "cover", false, "Print cover name")
|
||||
meta.BoolVar(&opts.Comment, "comment", false, "Print zip comment")
|
||||
meta.StringVar(&opts.CommentBody, "comment-body", "", "Set zip comment")
|
||||
meta.StringVar(&opts.FileAdd, "file-add", "", "Add file to archive")
|
||||
meta.StringVar(&opts.FileRemove, "file-remove", "", "Remove file from archive (glob pattern, i.e. *.xml)")
|
||||
|
||||
flag.NewFlagSet("version", flag.ExitOnError)
|
||||
|
||||
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, "\nCommands:\n")
|
||||
fmt.Fprintf(os.Stderr, "\n convert\n \tConvert archive or document\n\n")
|
||||
order := []string{"width", "height", "fit", "format", "archive", "quality", "effort", "lossless", "filter", "no-cover", "no-rgb",
|
||||
"no-nonimage", "no-convert", "grayscale", "rotate", "brightness", "contrast", "suffix", "outdir", "size", "recursive", "quiet"}
|
||||
for _, name := range order {
|
||||
f := convert.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 cover\n \tExtract cover\n\n")
|
||||
order = []string{"width", "height", "fit", "format", "quality", "effort", "lossless", "filter", "outdir", "size", "recursive", "quiet"}
|
||||
for _, name := range order {
|
||||
f := cover.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 thumbnail\n \tExtract cover thumbnail (freedesktop spec.)\n\n")
|
||||
order = []string{"width", "height", "fit", "filter", "outdir", "outfile", "size", "recursive", "quiet"}
|
||||
for _, name := range order {
|
||||
f := thumbnail.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 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 {
|
||||
flag.Usage()
|
||||
fmt.Fprintf(os.Stderr, "no command\n")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
pipe := piped()
|
||||
if pipe {
|
||||
args = lines(os.Stdin)
|
||||
}
|
||||
|
||||
switch os.Args[1] {
|
||||
case "convert":
|
||||
_ = convert.Parse(os.Args[2:])
|
||||
if !pipe {
|
||||
args = convert.Args()
|
||||
}
|
||||
case "cover":
|
||||
opts.Cover = true
|
||||
_ = cover.Parse(os.Args[2:])
|
||||
if !pipe {
|
||||
args = cover.Args()
|
||||
}
|
||||
case "thumbnail":
|
||||
opts.Thumbnail = true
|
||||
_ = thumbnail.Parse(os.Args[2:])
|
||||
if !pipe {
|
||||
args = thumbnail.Args()
|
||||
}
|
||||
case "meta":
|
||||
opts.Meta = true
|
||||
_ = meta.Parse(os.Args[2:])
|
||||
if !pipe {
|
||||
args = meta.Args()
|
||||
}
|
||||
case "version":
|
||||
opts.Version = true
|
||||
}
|
||||
|
||||
if len(args) == 0 && !opts.Version {
|
||||
flag.Usage()
|
||||
_, _ = fmt.Fprintf(os.Stderr, "no arguments\n")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
return opts, args
|
||||
}
|
||||
|
||||
// piped checks if we have piped stdin.
|
||||
func piped() bool {
|
||||
f, err := os.Stdin.Stat()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if f.Mode()&os.ModeNamedPipe == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// lines returns slice of lines from the reader.
|
||||
func lines(r io.Reader) []string {
|
||||
data := make([]string, 0)
|
||||
scanner := bufio.NewScanner(r)
|
||||
|
||||
for scanner.Scan() {
|
||||
text := scanner.Text()
|
||||
data = append(data, text)
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
@@ -1,149 +0,0 @@
|
||||
// Author: Milan Nikolic <gen2brain@gmail.com>
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package main
|
||||
|
||||
//go:generate goversioninfo
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image/jpeg"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strconv"
|
||||
"syscall"
|
||||
|
||||
"github.com/cheggaaa/pb"
|
||||
"github.com/gen2brain/cbconvert"
|
||||
"gopkg.in/alecthomas/kingpin.v2"
|
||||
)
|
||||
|
||||
// Parses command line flags
|
||||
func parseFlags() (cbconvert.Options, []string) {
|
||||
opts := cbconvert.Options{}
|
||||
var args []string
|
||||
|
||||
kingpin.Version("CBconvert 0.5.0")
|
||||
kingpin.CommandLine.Help = "Comic Book convert tool."
|
||||
kingpin.UsageTemplate(kingpin.CompactUsageTemplate)
|
||||
|
||||
kingpin.Flag("outdir", "Output directory").Default(".").StringVar(&opts.Outdir)
|
||||
kingpin.Flag("size", "Process only files larger then size (in MB)").Default(strconv.Itoa(0)).Int64Var(&opts.Size)
|
||||
kingpin.Flag("recursive", "Process subdirectories recursively").BoolVar(&opts.Recursive)
|
||||
kingpin.Flag("quiet", "Hide console output").BoolVar(&opts.Quiet)
|
||||
|
||||
convert := kingpin.Command("convert", "Convert archive or document (default command)").Default()
|
||||
convert.Arg("args", "filename or directory").Required().ExistingFilesOrDirsVar(&args)
|
||||
convert.Flag("width", "Image width").Default(strconv.Itoa(0)).IntVar(&opts.Width)
|
||||
convert.Flag("height", "Image height").Default(strconv.Itoa(0)).IntVar(&opts.Height)
|
||||
convert.Flag("fit", "Best fit for required width and height").BoolVar(&opts.Fit)
|
||||
convert.Flag("format", "Image format, valid values are jpeg, png, gif, tiff, bmp").Default("jpeg").StringVar(&opts.Format)
|
||||
convert.Flag("quality", "JPEG image quality").Default(strconv.Itoa(jpeg.DefaultQuality)).IntVar(&opts.Quality)
|
||||
convert.Flag("filter", "0=NearestNeighbor, 1=Box, 2=Linear, 3=MitchellNetravali, 4=CatmullRom, 6=Gaussian, 7=Lanczos").Default(strconv.Itoa(cbconvert.Linear)).IntVar(&opts.Filter)
|
||||
convert.Flag("cover", "Convert cover image (use --no-cover if you want to exclude cover)").Default("true").BoolVar(&opts.ConvertCover)
|
||||
convert.Flag("rgb", "Convert images that have RGB colorspace (use --no-rgb if you only want to convert grayscaled images)").Default("true").BoolVar(&opts.RGB)
|
||||
convert.Flag("nonimage", "Leave non image files in archive (use --no-nonimage to remove non image files from archive)").Default("true").BoolVar(&opts.NonImage)
|
||||
convert.Flag("grayscale", "Convert images to grayscale (monochromatic)").BoolVar(&opts.Grayscale)
|
||||
convert.Flag("rotate", "Rotate images, valid values are 0, 90, 180, 270").Default(strconv.Itoa(0)).IntVar(&opts.Rotate)
|
||||
convert.Flag("flip", "Flip images, valid values are none, horizontal, vertical").Default("none").StringVar(&opts.Flip)
|
||||
convert.Flag("brightness", "Adjust brightness of the images, must be in range (-100, 100)").Default(strconv.Itoa(0)).Float64Var(&opts.Brightness)
|
||||
convert.Flag("contrast", "Adjust contrast of the images, must be in range (-100, 100)").Default(strconv.Itoa(0)).Float64Var(&opts.Contrast)
|
||||
convert.Flag("suffix", "Add suffix to file basename").StringVar(&opts.Suffix)
|
||||
convert.Flag("levels-inmin", "Shadow input value").Default(strconv.Itoa(0)).Float64Var(&opts.LevelsInMin)
|
||||
convert.Flag("levels-gamma", "Midpoint/Gamma").Default(strconv.Itoa(1.00)).Float64Var(&opts.LevelsGamma)
|
||||
convert.Flag("levels-inmax", "Highlight input value").Default(strconv.Itoa(255)).Float64Var(&opts.LevelsInMax)
|
||||
convert.Flag("levels-outmin", "Shadow output value").Default(strconv.Itoa(0)).Float64Var(&opts.LevelsOutMin)
|
||||
convert.Flag("levels-outmax", "Highlight output value").Default(strconv.Itoa(255)).Float64Var(&opts.LevelsOutMax)
|
||||
|
||||
cover := kingpin.Command("cover", "Extract cover")
|
||||
cover.Arg("args", "filename or directory").Required().ExistingFilesOrDirsVar(&args)
|
||||
cover.Flag("width", "Image width").Default(strconv.Itoa(0)).IntVar(&opts.Width)
|
||||
cover.Flag("height", "Image height").Default(strconv.Itoa(0)).IntVar(&opts.Height)
|
||||
cover.Flag("fit", "Best fit for required width and height").BoolVar(&opts.Fit)
|
||||
cover.Flag("quality", "JPEG image quality").Default(strconv.Itoa(jpeg.DefaultQuality)).IntVar(&opts.Quality)
|
||||
cover.Flag("filter", "0=NearestNeighbor, 1=Box, 2=Linear, 3=MitchellNetravali, 4=CatmullRom, 6=Gaussian, 7=Lanczos").Default(strconv.Itoa(cbconvert.Linear)).IntVar(&opts.Filter)
|
||||
|
||||
thumbnail := kingpin.Command("thumbnail", "Extract cover thumbnail (freedesktop spec.)")
|
||||
thumbnail.Arg("args", "filename or directory").Required().ExistingFilesOrDirsVar(&args)
|
||||
thumbnail.Flag("outfile", "Output file").Default("").StringVar(&opts.Outfile)
|
||||
thumbnail.Flag("width", "Image width").Default(strconv.Itoa(0)).IntVar(&opts.Width)
|
||||
thumbnail.Flag("height", "Image height").Default(strconv.Itoa(0)).IntVar(&opts.Height)
|
||||
thumbnail.Flag("fit", "Best fit for required width and height").BoolVar(&opts.Fit)
|
||||
thumbnail.Flag("filter", "0=NearestNeighbor, 1=Box, 2=Linear, 3=MitchellNetravali, 4=CatmullRom, 6=Gaussian, 7=Lanczos").Default(strconv.Itoa(cbconvert.Linear)).IntVar(&opts.Filter)
|
||||
|
||||
switch kingpin.Parse() {
|
||||
case "cover":
|
||||
opts.Cover = true
|
||||
case "thumbnail":
|
||||
opts.Thumbnail = true
|
||||
}
|
||||
|
||||
return opts, args
|
||||
}
|
||||
|
||||
func main() {
|
||||
opts, args := parseFlags()
|
||||
conv := cbconvert.NewConvertor(opts)
|
||||
|
||||
var bar *pb.ProgressBar
|
||||
|
||||
c := make(chan os.Signal, 3)
|
||||
signal.Notify(c, os.Interrupt, syscall.SIGHUP, syscall.SIGTERM)
|
||||
go func() {
|
||||
for _ = range c {
|
||||
fmt.Fprintf(os.Stderr, "Aborting\n")
|
||||
os.RemoveAll(conv.Workdir)
|
||||
os.Exit(1)
|
||||
}
|
||||
}()
|
||||
|
||||
if _, err := os.Stat(opts.Outdir); err != nil {
|
||||
os.MkdirAll(opts.Outdir, 0777)
|
||||
}
|
||||
|
||||
files := conv.GetFiles(args)
|
||||
|
||||
if opts.Cover || opts.Thumbnail {
|
||||
if !opts.Quiet {
|
||||
bar = pb.New(conv.Nfiles)
|
||||
bar.ShowTimeLeft = false
|
||||
bar.Start()
|
||||
}
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
stat, err := os.Stat(file)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error Stat: %v\n", err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
if opts.Cover {
|
||||
conv.ExtractCover(file, stat)
|
||||
if !opts.Quiet {
|
||||
bar.Increment()
|
||||
}
|
||||
continue
|
||||
} else if opts.Thumbnail {
|
||||
conv.ExtractThumbnail(file, stat)
|
||||
if !opts.Quiet {
|
||||
bar.Increment()
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
conv.ConvertComic(file, stat)
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
CHROOT="/home/milann/chroot"
|
||||
MINGW="/usr/i686-w64-mingw32"
|
||||
|
||||
mkdir -p build
|
||||
rm -f resource.syso
|
||||
|
||||
LIBRARY_PATH="$CHROOT/usr/lib:$CHROOT/lib" \
|
||||
PKG_CONFIG_PATH="$CHROOT/usr/lib/pkgconfig" \
|
||||
PKG_CONFIG_LIBDIR="$CHROOT/usr/lib/pkgconfig" \
|
||||
CGO_LDFLAGS="-L$CHROOT/usr/lib -L$CHROOT/lib" \
|
||||
CC_FOR_TARGET="x86_64-pc-linux-gnu-gcc" CXX_FOR_TARGET="x86_64-pc-linux-gnu-g++" \
|
||||
CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -v -x -o build/cbconvert
|
||||
strip build/cbconvert
|
||||
|
||||
go generate
|
||||
PKG_CONFIG="/usr/bin/i686-w64-mingw32-pkg-config" \
|
||||
PKG_CONFIG_PATH="$MINGW/usr/lib/pkgconfig" \
|
||||
PKG_CONFIG_LIBDIR="$MINGW/usr/lib/pkgconfig" \
|
||||
CGO_LDFLAGS="-L$MINGW/usr/lib" \
|
||||
CGO_CFLAGS="-I$MINGW/usr/include -Wno-poison-system-directories" \
|
||||
CGO_CXXFLAGS="-I$MINGW/usr/include -Wno-poison-system-directories" \
|
||||
CGO_CPPFLAGS="-I$MINGW/usr/include -Wno-poison-system-directories" \
|
||||
CC="i686-w64-mingw32-gcc" CXX="i686-w64-mingw32-g++" \
|
||||
CC_FOR_TARGET="i686-w64-mingw32-gcc" CXX_FOR_TARGET="i686-w64-mingw32-g++" \
|
||||
CGO_ENABLED=1 GOOS=windows GOARCH=386 go build -v -x -o build/cbconvert.exe -ldflags "-linkmode external '-extldflags=-static -Wl,--allow-multiple-definition'"
|
||||
i686-w64-mingw32-strip build/cbconvert.exe
|
||||
@@ -1,44 +0,0 @@
|
||||
{
|
||||
"FixedFileInfo":
|
||||
{
|
||||
"FileVersion": {
|
||||
"Major": 0,
|
||||
"Minor": 6,
|
||||
"Patch": 0,
|
||||
"Build": 0
|
||||
},
|
||||
"ProductVersion": {
|
||||
"Major": 0,
|
||||
"Minor": 6,
|
||||
"Patch": 0,
|
||||
"Build": 0
|
||||
},
|
||||
"FileFlagsMask": "3f",
|
||||
"FileFlags ": "00",
|
||||
"FileOS": "040004",
|
||||
"FileType": "01",
|
||||
"FileSubType": "00"
|
||||
},
|
||||
"StringFileInfo":
|
||||
{
|
||||
"Comments": "Comic Book converter",
|
||||
"CompanyName": "",
|
||||
"FileDescription": "CBconvert CLI",
|
||||
"FileVersion": "0.6.0",
|
||||
"InternalName": "",
|
||||
"LegalCopyright": "",
|
||||
"LegalTrademarks": "",
|
||||
"OriginalFilename": "cbconvert.exe",
|
||||
"PrivateBuild": "",
|
||||
"ProductName": "CBconvert",
|
||||
"ProductVersion": "0.6.0",
|
||||
"SpecialBuild": ""
|
||||
},
|
||||
"VarFileInfo":
|
||||
{
|
||||
"Translation": {
|
||||
"LangID": "0409",
|
||||
"CharsetID": "04B0"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
module github.com/gen2brain/cbconvert
|
||||
|
||||
go 1.26
|
||||
|
||||
require (
|
||||
github.com/anthonynsimon/bild v0.15.0
|
||||
github.com/dsoprea/go-png-image-structure v0.0.0-20210512210324-29b889a6093d
|
||||
github.com/dustin/go-humanize v1.0.1
|
||||
github.com/fvbommel/sortorder v1.1.0
|
||||
github.com/gen2brain/avif v0.5.1
|
||||
github.com/gen2brain/go-fitz v1.24.15
|
||||
github.com/gen2brain/jpegli v0.4.0
|
||||
github.com/gen2brain/jpegxl v0.5.1
|
||||
github.com/gen2brain/webp v0.6.1
|
||||
github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25
|
||||
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
|
||||
)
|
||||
@@ -0,0 +1,141 @@
|
||||
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/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=
|
||||
|
Before Width: | Height: | Size: 1.9 KiB |
@@ -1,965 +0,0 @@
|
||||
import QtQuick 2.5
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQuick.Dialogs 1.2
|
||||
import QtQuick.Layouts 1.2
|
||||
import QtQuick.Window 2.2
|
||||
import Qt.labs.settings 1.0
|
||||
|
||||
ApplicationWindow {
|
||||
id: applicationWindow
|
||||
visible: true
|
||||
width: 800
|
||||
height: 600
|
||||
title: "CBconvert"
|
||||
|
||||
property int margin: 15
|
||||
property int screenWidth: Screen.width
|
||||
property int screenHeight: Screen.height
|
||||
|
||||
function updateImage() {
|
||||
imagePreview.source = ""
|
||||
sizePreview.text = ""
|
||||
if(groupBoxPreview.checked) {
|
||||
if(c.len > 0) {
|
||||
imagePreview.source = "image://cover/" + c.get(listView.currentIndex).path
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: splitView1
|
||||
anchors.rightMargin: margin
|
||||
anchors.leftMargin: margin
|
||||
anchors.bottomMargin: margin
|
||||
anchors.topMargin: margin
|
||||
anchors.fill: parent
|
||||
|
||||
RowLayout {
|
||||
id: rowLayout1
|
||||
anchors.bottom: rowLayout2.top
|
||||
anchors.right: parent.right
|
||||
anchors.left: parent.left
|
||||
anchors.top: parent.top
|
||||
anchors.bottomMargin: 15
|
||||
Layout.minimumHeight: 250
|
||||
|
||||
ScrollView {
|
||||
id: scrollview
|
||||
anchors.right: columnButtons.left
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.top: parent.top
|
||||
anchors.rightMargin: margin
|
||||
frameVisible: true
|
||||
highlightOnFocus: false
|
||||
verticalScrollBarPolicy: 2
|
||||
flickableItem.interactive: true
|
||||
focus: true
|
||||
|
||||
ListView {
|
||||
id: listView
|
||||
anchors.topMargin: 0
|
||||
anchors.fill: parent
|
||||
spacing: 1
|
||||
focus: true
|
||||
|
||||
model: c.len
|
||||
|
||||
header: Rectangle {
|
||||
height: 20
|
||||
width: ListView.view.width
|
||||
color: "#DCDCDC"
|
||||
|
||||
Text {
|
||||
id: nameHeader
|
||||
text: 'Name'
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 5
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
c.byName()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
id: typeHeader
|
||||
text: 'Type'
|
||||
width: 40
|
||||
anchors.right: sizeHeader.left
|
||||
anchors.rightMargin: 40
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
c.byType()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
id: sizeHeader
|
||||
text: 'Size'
|
||||
width: 40
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 15
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
c.bySize()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
delegate: Item {
|
||||
id: item1
|
||||
width: ListView.view.width
|
||||
height: 20
|
||||
|
||||
Text {
|
||||
id: nameItem
|
||||
text: c.get(index).name
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 5
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: parent.width - 170
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
Text {
|
||||
id: typeItem
|
||||
width: 40
|
||||
text: c.get(index).type
|
||||
anchors.right: sizeItem.left
|
||||
anchors.rightMargin: 40
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
|
||||
Text {
|
||||
id: sizeItem
|
||||
width: 40
|
||||
text: c.get(index).sizeHuman
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 15
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
listView.currentIndex = index
|
||||
listView.forceActiveFocus()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
highlight: Rectangle {
|
||||
width: ListView.view ? ListView.view.width : undefined
|
||||
color: "#326686"
|
||||
opacity: 0.2
|
||||
y: listView.currentItem.y
|
||||
Behavior on y {
|
||||
SpringAnimation {
|
||||
spring: 3
|
||||
damping: 0.2
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onCurrentItemChanged: updateImage()
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: columnButtons
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: 0
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 0
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 0
|
||||
|
||||
ColumnLayout {
|
||||
id: columnButtonsFiles
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 0
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 0
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 0
|
||||
spacing: 5
|
||||
|
||||
Button {
|
||||
id: buttonAddFile
|
||||
objectName: "buttonAddFile"
|
||||
text: "Add &Files"
|
||||
onClicked: {
|
||||
fileDialogFile.open()
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
id: buttonAddDir
|
||||
objectName: "buttonAddDir"
|
||||
text: "Add &Dir"
|
||||
onClicked: {
|
||||
fileDialogDir.open()
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
id: buttonRemove
|
||||
objectName: "buttonRemove"
|
||||
text: "Remove"
|
||||
enabled: (c.len !== 0) ? true : false
|
||||
onClicked: {
|
||||
c.remove(listView.currentIndex)
|
||||
updateImage()
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
id: buttonRemoveAll
|
||||
objectName: "buttonRemoveAll"
|
||||
text: "Remove All"
|
||||
enabled: (c.len !== 0) ? true : false
|
||||
onClicked: {
|
||||
c.removeAll()
|
||||
updateImage()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: columnButtonsActions
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 0
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 0
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: 0
|
||||
spacing: 5
|
||||
|
||||
Button {
|
||||
id: buttonThumbnail
|
||||
objectName: "buttonThumbnail"
|
||||
text: "Thumbnail"
|
||||
anchors.bottom: buttonCover.top
|
||||
anchors.bottomMargin: 5
|
||||
tooltip: "Extract Thumbnail (freedesktop spec.)"
|
||||
enabled: (textFieldOutDir.text != "" && c.len !== 0) ? true : false
|
||||
}
|
||||
|
||||
Button {
|
||||
id: buttonCover
|
||||
objectName: "buttonCover"
|
||||
text: "Cover"
|
||||
anchors.bottom: buttonConvert.top
|
||||
anchors.bottomMargin: 15
|
||||
tooltip: "Extract Cover"
|
||||
enabled: (textFieldOutDir.text != "" && c.len !== 0) ? true : false
|
||||
}
|
||||
|
||||
Button {
|
||||
id: buttonConvert
|
||||
objectName: "buttonConvert"
|
||||
text: "&Convert"
|
||||
tooltip: "Convert archives and documents"
|
||||
enabled: (textFieldOutDir.text != "" && c.len !== 0) ? true : false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: rowLayout2
|
||||
spacing: 0
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
|
||||
ColumnLayout {
|
||||
id: columnLeft
|
||||
anchors.right: columnMiddle.left
|
||||
anchors.rightMargin: margin
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 0
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: 0
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 0
|
||||
Layout.minimumWidth: 200
|
||||
|
||||
GroupBox {
|
||||
id: groupBoxPreview
|
||||
checkable: true
|
||||
flat: true
|
||||
anchors.fill: parent
|
||||
title: "Preview"
|
||||
|
||||
Image {
|
||||
id: imagePreview
|
||||
anchors.fill: parent
|
||||
fillMode: Image.PreserveAspectFit
|
||||
asynchronous: true
|
||||
cache: false
|
||||
}
|
||||
|
||||
BusyIndicator {
|
||||
running: imagePreview.status === Image.Loading
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
onCheckedChanged: updateImage()
|
||||
}
|
||||
|
||||
Text {
|
||||
id: sizePreview
|
||||
objectName: "sizePreview"
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: groupBoxPreview.bottom
|
||||
anchors.topMargin: -5
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: columnMiddle
|
||||
anchors.right: columnRight.left
|
||||
anchors.rightMargin: margin
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 0
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: 0
|
||||
Layout.fillHeight: true
|
||||
|
||||
GroupBox {
|
||||
id: groupBoxInput
|
||||
flat: true
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 0
|
||||
title: "Input"
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 0
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 0
|
||||
|
||||
ColumnLayout {
|
||||
id: columnInput
|
||||
anchors.bottomMargin: 0
|
||||
anchors.fill: parent
|
||||
spacing: 5
|
||||
|
||||
CheckBox {
|
||||
id: checkBoxRecursive
|
||||
objectName: "checkBoxRecursive"
|
||||
text: "Recurse SubDirectories"
|
||||
}
|
||||
|
||||
CheckBox {
|
||||
id: checkBoxNoRGB
|
||||
objectName: "checkBoxNoRGB"
|
||||
text: "Only Grayscaled Images"
|
||||
}
|
||||
|
||||
CheckBox {
|
||||
id: checkBoxConvertCover
|
||||
objectName: "checkBoxConvertCover"
|
||||
text: "Exclude Cover"
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: 0
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 0
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 0
|
||||
spacing: 5
|
||||
|
||||
SpinBox {
|
||||
id: spinboxSize
|
||||
objectName: "spinboxSize"
|
||||
stepSize: 10
|
||||
prefix: ""
|
||||
maximumValue: 1000
|
||||
suffix: " MiB"
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Minimum Size"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GroupBox {
|
||||
id: groupBoxTransform
|
||||
flat: true
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 0
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: 0
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 0
|
||||
title: "Transform"
|
||||
|
||||
ColumnLayout {
|
||||
id: columnTransform
|
||||
anchors.fill: parent
|
||||
spacing: 5
|
||||
|
||||
Button {
|
||||
id: buttonLevels
|
||||
text: "Levels..."
|
||||
enabled: (c.len > 0) ? true : false
|
||||
onClicked: {
|
||||
levelsDialog.open()
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Brightness: " + sliderBrightness.value
|
||||
}
|
||||
|
||||
Slider {
|
||||
id: sliderBrightness
|
||||
objectName: "sliderBrightness"
|
||||
value: 0
|
||||
stepSize: 1
|
||||
minimumValue: -100
|
||||
maximumValue: 100
|
||||
activeFocusOnPress: true
|
||||
updateValueWhileDragging: false
|
||||
enabled: (c.len > 0) ? true : false
|
||||
onValueChanged: updateImage()
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Contrast: " + sliderContrast.value
|
||||
}
|
||||
|
||||
Slider {
|
||||
id: sliderContrast
|
||||
objectName: "sliderContrast"
|
||||
value: 0
|
||||
maximumValue: 100
|
||||
stepSize: 1
|
||||
minimumValue: -100
|
||||
activeFocusOnPress: true
|
||||
updateValueWhileDragging: false
|
||||
enabled: (c.len > 0) ? true : false
|
||||
onValueChanged: updateImage()
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Flip:"
|
||||
}
|
||||
|
||||
ComboBox {
|
||||
id: comboBoxFlip
|
||||
objectName: "comboBoxFlip"
|
||||
enabled: (c.len > 0) ? true : false
|
||||
|
||||
model: ListModel {
|
||||
ListElement {
|
||||
text: "None"
|
||||
}
|
||||
|
||||
ListElement {
|
||||
text: "Horizontal"
|
||||
}
|
||||
|
||||
ListElement {
|
||||
text: "Vertical"
|
||||
}
|
||||
}
|
||||
|
||||
onActivated: updateImage()
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Rotate:"
|
||||
}
|
||||
|
||||
ComboBox {
|
||||
id: comboBoxRotate
|
||||
objectName: "comboBoxRotate"
|
||||
enabled: (c.len > 0) ? true : false
|
||||
|
||||
model: ListModel {
|
||||
ListElement {
|
||||
text: "0"
|
||||
}
|
||||
|
||||
ListElement {
|
||||
text: "90"
|
||||
}
|
||||
|
||||
ListElement {
|
||||
text: "180"
|
||||
}
|
||||
|
||||
ListElement {
|
||||
text: "270"
|
||||
}
|
||||
}
|
||||
|
||||
onActivated: updateImage()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: columnRight
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 0
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: 0
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 0
|
||||
Layout.fillHeight: true
|
||||
|
||||
GroupBox {
|
||||
id: groupBoxOutput
|
||||
flat: true
|
||||
title: "Output"
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 0
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 0
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 0
|
||||
|
||||
ColumnLayout {
|
||||
id: columnOutput
|
||||
anchors.bottomMargin: 0
|
||||
anchors.fill: parent
|
||||
spacing: 5
|
||||
|
||||
RowLayout {
|
||||
id: rowLayoutOutput
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 0
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 0
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 0
|
||||
spacing: 5
|
||||
|
||||
TextField {
|
||||
id: textFieldOutDir
|
||||
objectName: "textFieldOutDir"
|
||||
anchors.right: buttonBrowse.left
|
||||
anchors.rightMargin: 5
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 0
|
||||
placeholderText: "Output Directory"
|
||||
|
||||
Settings {
|
||||
id: settingsOutDir
|
||||
property alias text: textFieldOutDir.text
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
id: buttonBrowse
|
||||
text: "..."
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 0
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
onClicked: {
|
||||
fileDialogOutput.open()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TextField {
|
||||
id: textFieldSuffix
|
||||
objectName: "textFieldSuffix"
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 0
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 0
|
||||
placeholderText: "Add Suffix to Output File"
|
||||
}
|
||||
|
||||
CheckBox {
|
||||
id: checkBoxNonImage
|
||||
objectName: "checkBoxNonImage"
|
||||
text: "Remove Non-Image Files"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
GroupBox {
|
||||
id: groupBoxImage
|
||||
flat: true
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 0
|
||||
title: "Image"
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: 0
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 0
|
||||
|
||||
ColumnLayout {
|
||||
id: columnFormat
|
||||
anchors.fill: parent
|
||||
spacing: 5
|
||||
|
||||
Text {
|
||||
text: "Format:"
|
||||
}
|
||||
|
||||
ComboBox {
|
||||
id: comboBoxFormat
|
||||
objectName: "comboBoxFormat"
|
||||
enabled: (c.len > 0) ? true : false
|
||||
|
||||
model: ListModel {
|
||||
ListElement {
|
||||
text: "JPEG"
|
||||
}
|
||||
|
||||
ListElement {
|
||||
text: "PNG"
|
||||
}
|
||||
|
||||
ListElement {
|
||||
text: "GIF"
|
||||
}
|
||||
|
||||
ListElement {
|
||||
text: "BMP"
|
||||
}
|
||||
|
||||
ListElement {
|
||||
text: "TIFF"
|
||||
}
|
||||
}
|
||||
|
||||
onActivated: updateImage()
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Size:"
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: 5
|
||||
|
||||
TextField {
|
||||
id: width
|
||||
objectName: "width"
|
||||
placeholderText: "width"
|
||||
maximumLength: 4
|
||||
implicitWidth: 50
|
||||
onAccepted: updateImage()
|
||||
}
|
||||
|
||||
Text {
|
||||
id: x
|
||||
text: "x"
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
TextField {
|
||||
id: height
|
||||
objectName: "height"
|
||||
placeholderText: "height"
|
||||
maximumLength: 4
|
||||
implicitWidth: 50
|
||||
onAccepted: updateImage()
|
||||
}
|
||||
}
|
||||
|
||||
CheckBox {
|
||||
id: checkBoxFit
|
||||
objectName: "checkBoxFit"
|
||||
text: "Best Fit"
|
||||
enabled: (c.len > 0) ? true : false
|
||||
onClicked: updateImage()
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Resize Algorithm:"
|
||||
}
|
||||
|
||||
ComboBox {
|
||||
id: comboBoxFilter
|
||||
objectName: "comboBoxFilter"
|
||||
currentIndex: 2
|
||||
enabled: (c.len > 0) ? true : false
|
||||
|
||||
model: ListModel {
|
||||
ListElement {
|
||||
text: "NearestNeighbor"
|
||||
}
|
||||
|
||||
ListElement {
|
||||
text: "Box"
|
||||
}
|
||||
|
||||
ListElement {
|
||||
text: "Linear"
|
||||
}
|
||||
|
||||
ListElement {
|
||||
text: "MitchellNetravali"
|
||||
}
|
||||
|
||||
ListElement {
|
||||
text: "CatmullRom"
|
||||
}
|
||||
|
||||
ListElement {
|
||||
text: "Gaussian"
|
||||
}
|
||||
|
||||
ListElement {
|
||||
text: "Lanczos"
|
||||
}
|
||||
}
|
||||
|
||||
onActivated: updateImage()
|
||||
|
||||
Settings {
|
||||
id: settingsFilter
|
||||
property alias currentIndex: comboBoxFilter.currentIndex
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Quality: " + sliderQuality.value
|
||||
}
|
||||
|
||||
Slider {
|
||||
id: sliderQuality
|
||||
objectName: "sliderQuality"
|
||||
stepSize: 1
|
||||
value: 75
|
||||
maximumValue: 100
|
||||
activeFocusOnPress: true
|
||||
updateValueWhileDragging: false
|
||||
enabled: (c.len > 0 && comboBoxFormat.currentText == "JPEG") ? true : false
|
||||
onValueChanged: updateImage()
|
||||
}
|
||||
|
||||
CheckBox {
|
||||
id: checkBoxGrayscale
|
||||
objectName: "checkBoxGrayscale"
|
||||
text: "Convert to Grayscale"
|
||||
enabled: (c.len > 0) ? true : false
|
||||
onClicked: updateImage()
|
||||
}
|
||||
|
||||
Settings {
|
||||
id: settingsQuality
|
||||
property alias value: sliderQuality.value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FileDialog {
|
||||
id: fileDialogFile
|
||||
modality: Qt.WindowModal
|
||||
title: "Add Files"
|
||||
selectFolder: false
|
||||
selectMultiple: true
|
||||
selectExisting: true
|
||||
sidebarVisible: true
|
||||
nameFilters: [ "Comic files (*.rar *.zip *.7z *.gz *.bz2 *.cbr *.cbz *.cb7 *.cbt *.pdf *.epub *.xps)" ]
|
||||
onAccepted: {
|
||||
c.addUrls(decodeURIComponent(fileUrls.join("_CBSEP_")))
|
||||
}
|
||||
}
|
||||
|
||||
FileDialog {
|
||||
id: fileDialogDir
|
||||
modality: Qt.WindowModal
|
||||
title: "Add Directory"
|
||||
selectFolder: true
|
||||
sidebarVisible: true
|
||||
onAccepted: {
|
||||
c.addUrls(decodeURIComponent(fileUrl.toString()))
|
||||
}
|
||||
}
|
||||
|
||||
FileDialog {
|
||||
id: fileDialogOutput
|
||||
modality: Qt.WindowModal
|
||||
title: "Output Directory"
|
||||
selectFolder: true
|
||||
sidebarVisible: true
|
||||
onAccepted: {
|
||||
textFieldOutDir.text = decodeURIComponent(fileUrl.toString().replace("file://", ""))
|
||||
}
|
||||
}
|
||||
|
||||
Dialog {
|
||||
id: levelsDialog
|
||||
objectName: "levelsDialog"
|
||||
title: "Levels"
|
||||
standardButtons: StandardButton.Close
|
||||
width: 230
|
||||
height: 150
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
|
||||
Text {
|
||||
text: "Input Levels:"
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 0
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 0
|
||||
spacing: 5
|
||||
|
||||
SpinBox {
|
||||
id: spinboxLevelsInMin
|
||||
objectName: "spinboxLevelsInMin"
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 0
|
||||
stepSize: 1
|
||||
maximumValue: 255
|
||||
value: 0
|
||||
onEditingFinished: updateImage()
|
||||
Keys.onReturnPressed: {
|
||||
event.accepted = true
|
||||
}
|
||||
}
|
||||
|
||||
SpinBox {
|
||||
id: spinboxLevelsGamma
|
||||
objectName: "spinboxLevelsGamma"
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
decimals: 2
|
||||
stepSize: 0.01
|
||||
maximumValue: 10.00
|
||||
value: 1.00
|
||||
onEditingFinished: updateImage()
|
||||
}
|
||||
|
||||
SpinBox {
|
||||
id: spinboxLevelsInMax
|
||||
objectName: "spinboxLevelsInMax"
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 0
|
||||
stepSize: 1
|
||||
maximumValue: 255
|
||||
value: 255
|
||||
onEditingFinished: updateImage()
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Output Levels:"
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 0
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 0
|
||||
spacing: 5
|
||||
|
||||
SpinBox {
|
||||
id: spinboxLevelsOutMin
|
||||
objectName: "spinboxLevelsOutMin"
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 0
|
||||
stepSize: 1
|
||||
maximumValue: 255
|
||||
value: 0
|
||||
onEditingFinished: updateImage()
|
||||
}
|
||||
|
||||
SpinBox {
|
||||
id: spinboxLevelsOutMax
|
||||
objectName: "spinboxLevelsOutMax"
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 0
|
||||
stepSize: 1
|
||||
maximumValue: 255
|
||||
value: 255
|
||||
onEditingFinished: updateImage()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
statusBar: StatusBar {
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
|
||||
Label {
|
||||
id: labelStatus
|
||||
objectName: "labelStatus"
|
||||
text: "Ready"
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: rectangle1
|
||||
Layout.fillWidth: true
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
Label {
|
||||
id: labelPercent
|
||||
objectName: "labelPercent"
|
||||
font.pointSize: 9
|
||||
anchors.right: progressBar.left
|
||||
anchors.rightMargin: 5
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
ProgressBar {
|
||||
id: progressBar
|
||||
objectName: "progressBar"
|
||||
visible: false
|
||||
value: 0.0
|
||||
minimumValue : 0.0
|
||||
maximumValue : 100.0
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: -10
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: -10
|
||||
anchors.right: labelProgress.left
|
||||
anchors.rightMargin: 5
|
||||
}
|
||||
|
||||
Label {
|
||||
id: labelProgress
|
||||
objectName: "labelProgress"
|
||||
font.pointSize: 9
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 0
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,550 +0,0 @@
|
||||
// Author: Milan Nikolic <gen2brain@gmail.com>
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package main
|
||||
|
||||
//go:generate genqrc assets
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"fmt"
|
||||
"image"
|
||||
"image/gif"
|
||||
"image/jpeg"
|
||||
"image/png"
|
||||
"mime"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/disintegration/imaging"
|
||||
"github.com/dustin/go-humanize"
|
||||
"github.com/gen2brain/cbconvert"
|
||||
"github.com/gographics/imagick/imagick"
|
||||
"github.com/hotei/bmp"
|
||||
"golang.org/x/image/tiff"
|
||||
"gopkg.in/qml.v1"
|
||||
)
|
||||
|
||||
// Model
|
||||
type Comics struct {
|
||||
Root qml.Object
|
||||
Conv *cbconvert.Convertor
|
||||
List []Comic
|
||||
Len int
|
||||
}
|
||||
|
||||
// Comic Element
|
||||
type Comic struct {
|
||||
Name string
|
||||
Path string
|
||||
Type string
|
||||
Size int64
|
||||
SizeHuman string
|
||||
}
|
||||
|
||||
// Sorts by name
|
||||
type ByName []Comic
|
||||
|
||||
func (c ByName) Len() int { return len(c) }
|
||||
func (c ByName) Swap(i, j int) { c[i], c[j] = c[j], c[i] }
|
||||
func (c ByName) Less(i, j int) bool { return c[i].Name < c[j].Name }
|
||||
|
||||
// Sorts by size
|
||||
type BySize []Comic
|
||||
|
||||
func (c BySize) Len() int { return len(c) }
|
||||
func (c BySize) Swap(i, j int) { c[i], c[j] = c[j], c[i] }
|
||||
func (c BySize) Less(i, j int) bool { return c[i].Size < c[j].Size }
|
||||
|
||||
// Sorts by type
|
||||
type ByType []Comic
|
||||
|
||||
func (c ByType) Len() int { return len(c) }
|
||||
func (c ByType) Swap(i, j int) { c[i], c[j] = c[j], c[i] }
|
||||
func (c ByType) Less(i, j int) bool { return c[i].Type < c[j].Type }
|
||||
|
||||
// Adds element to list
|
||||
func (c *Comics) Add(comic Comic) {
|
||||
c.List = append(c.List, comic)
|
||||
c.Len = len(c.List)
|
||||
qml.Changed(c, &c.Len)
|
||||
}
|
||||
|
||||
// Removes element from list
|
||||
func (c *Comics) Remove(i int) {
|
||||
l := c.List
|
||||
l = append(l[:i], l[i+1:]...)
|
||||
c.List = l
|
||||
c.Len = len(c.List)
|
||||
qml.Changed(c, &c.Len)
|
||||
}
|
||||
|
||||
// Removes all elements from list
|
||||
func (c *Comics) RemoveAll() {
|
||||
c.Len = 0
|
||||
c.List = make([]Comic, 0)
|
||||
qml.Changed(c, &c.Len)
|
||||
}
|
||||
|
||||
// Sorts by name
|
||||
func (c *Comics) ByName() {
|
||||
sort.Sort(ByName(c.List))
|
||||
c.Len++
|
||||
qml.Changed(c, &c.Len)
|
||||
c.Len--
|
||||
qml.Changed(c, &c.Len)
|
||||
}
|
||||
|
||||
// Sorts by size
|
||||
func (c *Comics) BySize() {
|
||||
sort.Sort(BySize(c.List))
|
||||
c.Len++
|
||||
qml.Changed(c, &c.Len)
|
||||
c.Len--
|
||||
qml.Changed(c, &c.Len)
|
||||
}
|
||||
|
||||
// Sorts by type
|
||||
func (c *Comics) ByType() {
|
||||
sort.Sort(ByType(c.List))
|
||||
c.Len++
|
||||
qml.Changed(c, &c.Len)
|
||||
c.Len--
|
||||
qml.Changed(c, &c.Len)
|
||||
}
|
||||
|
||||
// Returns element for given index
|
||||
func (c *Comics) Get(i int) Comic {
|
||||
return c.List[i]
|
||||
}
|
||||
|
||||
// Adds elements from fileUrls to list
|
||||
func (c *Comics) AddUrls(u string) {
|
||||
var args []string
|
||||
l := strings.Split(u, "_CBSEP_")
|
||||
re := regexp.MustCompile(`^[a-zA-Z]:`)
|
||||
|
||||
for _, f := range l {
|
||||
f = strings.Replace(f, "file://", "", -1)
|
||||
f = re.ReplaceAllString(f, "")
|
||||
f = re.ReplaceAllString(f, "")
|
||||
args = append(args, f)
|
||||
}
|
||||
|
||||
c.Conv.Opts = c.GetOptions()
|
||||
files := c.Conv.GetFiles(args)
|
||||
|
||||
for _, file := range files {
|
||||
stat, err := os.Stat(file)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error Stat AddUrls: %v\n", err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
m := mime.TypeByExtension(filepath.Ext(file))
|
||||
if m == "" && stat.IsDir() {
|
||||
m = "inode/directory"
|
||||
}
|
||||
|
||||
c.Add(Comic{
|
||||
filepath.Base(file),
|
||||
file,
|
||||
m,
|
||||
stat.Size(),
|
||||
humanize.IBytes(uint64(stat.Size())),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Returns cbconvert options from qml
|
||||
func (c *Comics) GetOptions() cbconvert.Options {
|
||||
var o cbconvert.Options
|
||||
o.Quiet = true
|
||||
|
||||
r := c.Root.ObjectByName("checkBoxRecursive")
|
||||
o.Recursive = r.Bool("checked")
|
||||
|
||||
r = c.Root.ObjectByName("checkBoxNoRGB")
|
||||
o.RGB = !r.Bool("checked")
|
||||
|
||||
r = c.Root.ObjectByName("checkBoxConvertCover")
|
||||
o.ConvertCover = !r.Bool("checked")
|
||||
|
||||
r = c.Root.ObjectByName("spinboxSize")
|
||||
o.Size = r.Int64("value")
|
||||
|
||||
r = c.Root.ObjectByName("sliderBrightness")
|
||||
o.Brightness = r.Float64("value")
|
||||
|
||||
r = c.Root.ObjectByName("sliderContrast")
|
||||
o.Contrast = r.Float64("value")
|
||||
|
||||
r = c.Root.ObjectByName("checkBoxGrayscale")
|
||||
o.Grayscale = r.Bool("checked")
|
||||
|
||||
r = c.Root.ObjectByName("comboBoxFlip")
|
||||
o.Flip = strings.ToLower(r.String("currentText"))
|
||||
|
||||
r = c.Root.ObjectByName("comboBoxRotate")
|
||||
o.Rotate, _ = strconv.Atoi(r.String("currentText"))
|
||||
|
||||
r = c.Root.ObjectByName("textFieldOutDir")
|
||||
o.Outdir = r.String("text")
|
||||
|
||||
r = c.Root.ObjectByName("textFieldSuffix")
|
||||
o.Suffix = r.String("text")
|
||||
|
||||
r = c.Root.ObjectByName("checkBoxNonImage")
|
||||
o.NonImage = !r.Bool("checked")
|
||||
|
||||
r = c.Root.ObjectByName("comboBoxFormat")
|
||||
o.Format = strings.ToLower(r.String("currentText"))
|
||||
|
||||
r = c.Root.ObjectByName("width")
|
||||
o.Width, _ = strconv.Atoi(r.String("text"))
|
||||
|
||||
r = c.Root.ObjectByName("height")
|
||||
o.Height, _ = strconv.Atoi(r.String("text"))
|
||||
|
||||
r = c.Root.ObjectByName("checkBoxFit")
|
||||
o.Fit = r.Bool("checked")
|
||||
|
||||
r = c.Root.ObjectByName("comboBoxFilter")
|
||||
o.Filter = r.Int("currentIndex")
|
||||
|
||||
r = c.Root.ObjectByName("sliderQuality")
|
||||
o.Quality = int(r.Float64("value"))
|
||||
|
||||
r = c.Root.ObjectByName("spinboxLevelsInMin")
|
||||
o.LevelsInMin = r.Float64("value")
|
||||
|
||||
r = c.Root.ObjectByName("spinboxLevelsInMax")
|
||||
o.LevelsInMax = r.Float64("value")
|
||||
|
||||
r = c.Root.ObjectByName("spinboxLevelsGamma")
|
||||
o.LevelsGamma = r.Float64("value")
|
||||
|
||||
r = c.Root.ObjectByName("spinboxLevelsOutMin")
|
||||
o.LevelsOutMin = r.Float64("value")
|
||||
|
||||
r = c.Root.ObjectByName("spinboxLevelsOutMax")
|
||||
o.LevelsOutMax = r.Float64("value")
|
||||
|
||||
return o
|
||||
}
|
||||
|
||||
// Sets "enabled" property
|
||||
func (c *Comics) SetEnabled(b bool) {
|
||||
c.Root.ObjectByName("checkBoxRecursive").Set("enabled", b)
|
||||
c.Root.ObjectByName("checkBoxNoRGB").Set("enabled", b)
|
||||
c.Root.ObjectByName("checkBoxConvertCover").Set("enabled", b)
|
||||
c.Root.ObjectByName("spinboxSize").Set("enabled", b)
|
||||
c.Root.ObjectByName("buttonLevels").Set("enabled", b)
|
||||
c.Root.ObjectByName("sliderBrightness").Set("enabled", b)
|
||||
c.Root.ObjectByName("sliderContrast").Set("enabled", b)
|
||||
c.Root.ObjectByName("checkBoxGrayscale").Set("enabled", b)
|
||||
c.Root.ObjectByName("comboBoxFlip").Set("enabled", b)
|
||||
c.Root.ObjectByName("comboBoxRotate").Set("enabled", b)
|
||||
c.Root.ObjectByName("textFieldOutDir").Set("enabled", b)
|
||||
c.Root.ObjectByName("textFieldSuffix").Set("enabled", b)
|
||||
c.Root.ObjectByName("checkBoxNonImage").Set("enabled", b)
|
||||
c.Root.ObjectByName("comboBoxFormat").Set("enabled", b)
|
||||
c.Root.ObjectByName("width").Set("enabled", b)
|
||||
c.Root.ObjectByName("height").Set("enabled", b)
|
||||
c.Root.ObjectByName("checkBoxFit").Set("enabled", b)
|
||||
c.Root.ObjectByName("comboBoxFilter").Set("enabled", b)
|
||||
c.Root.ObjectByName("sliderQuality").Set("enabled", b)
|
||||
c.Root.ObjectByName("buttonAddFile").Set("enabled", b)
|
||||
c.Root.ObjectByName("buttonAddDir").Set("enabled", b)
|
||||
c.Root.ObjectByName("buttonRemove").Set("enabled", b)
|
||||
c.Root.ObjectByName("buttonRemoveAll").Set("enabled", b)
|
||||
c.Root.ObjectByName("buttonThumbnail").Set("enabled", b)
|
||||
c.Root.ObjectByName("buttonCover").Set("enabled", b)
|
||||
c.Root.ObjectByName("buttonConvert").Set("enabled", b)
|
||||
}
|
||||
|
||||
// Converts comic
|
||||
func (c *Comics) Convert() {
|
||||
c.Conv.Opts = c.GetOptions()
|
||||
c.Conv.Nfiles = c.Len
|
||||
c.Conv.CurrFile = 0
|
||||
|
||||
c.SetEnabled(false)
|
||||
|
||||
go func() {
|
||||
for _, e := range c.List {
|
||||
stat, err := os.Stat(e.Path)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error Stat Convert: %v\n", err.Error())
|
||||
continue
|
||||
}
|
||||
c.Conv.ConvertComic(e.Path, stat)
|
||||
}
|
||||
}()
|
||||
|
||||
go c.showProgress(true, "Converting...")
|
||||
}
|
||||
|
||||
// Extracts cover
|
||||
func (c *Comics) Cover() {
|
||||
c.Conv.Opts = c.GetOptions()
|
||||
c.Conv.Nfiles = c.Len
|
||||
c.Conv.CurrFile = 0
|
||||
|
||||
c.SetEnabled(false)
|
||||
|
||||
go func() {
|
||||
for _, e := range c.List {
|
||||
stat, err := os.Stat(e.Path)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error Stat Cover: %v\n", err.Error())
|
||||
continue
|
||||
}
|
||||
c.Conv.ExtractCover(e.Path, stat)
|
||||
}
|
||||
}()
|
||||
|
||||
go c.showProgress(false, "Extracting...")
|
||||
}
|
||||
|
||||
// Extracts thumbnail
|
||||
func (c *Comics) Thumbnail() {
|
||||
c.Conv.Opts = c.GetOptions()
|
||||
c.Conv.Nfiles = c.Len
|
||||
c.Conv.CurrFile = 0
|
||||
|
||||
c.SetEnabled(false)
|
||||
|
||||
go func() {
|
||||
for _, e := range c.List {
|
||||
stat, err := os.Stat(e.Path)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error Stat Thumbnail: %v\n", err.Error())
|
||||
continue
|
||||
}
|
||||
c.Conv.ExtractThumbnail(e.Path, stat)
|
||||
}
|
||||
}()
|
||||
|
||||
go c.showProgress(false, "Extracting...")
|
||||
}
|
||||
|
||||
// Shows progress
|
||||
func (c *Comics) showProgress(cn bool, text string) {
|
||||
c.Root.ObjectByName("labelStatus").Set("text", text)
|
||||
c.Root.ObjectByName("progressBar").Set("visible", true)
|
||||
|
||||
for {
|
||||
if c.Conv.CurrFile == c.Conv.Nfiles {
|
||||
if c.Conv.CurrContent == c.Conv.Ncontents {
|
||||
c.Root.ObjectByName("progressBar").Set("value", 0)
|
||||
c.Root.ObjectByName("labelProgress").Set("text", "")
|
||||
c.Root.ObjectByName("labelStatus").Set("text", "Ready")
|
||||
c.Root.ObjectByName("labelPercent").Set("text", "")
|
||||
c.Root.ObjectByName("progressBar").Set("visible", false)
|
||||
c.SetEnabled(true)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
var count, current int
|
||||
if cn {
|
||||
count = c.Conv.Ncontents
|
||||
current = c.Conv.CurrContent
|
||||
} else {
|
||||
count = c.Conv.Nfiles
|
||||
current = c.Conv.CurrFile
|
||||
}
|
||||
|
||||
value := float64(current) / float64(count) * 100
|
||||
c.Root.ObjectByName("progressBar").Set("value", float64(value))
|
||||
c.Root.ObjectByName("labelPercent").Set("text",
|
||||
fmt.Sprintf("%d/%d %.0f%%", current, count, float64(value)))
|
||||
c.Root.ObjectByName("labelProgress").Set("text",
|
||||
fmt.Sprintf("File %d of %d", c.Conv.CurrFile, c.Conv.Nfiles))
|
||||
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
|
||||
// Provides image://cover/
|
||||
func (c *Comics) CoverProvider(file string, width int, height int) image.Image {
|
||||
c.Conv.Opts = c.GetOptions()
|
||||
|
||||
stat, err := os.Stat(file)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error Stat CoverProvider: %v\n", err.Error())
|
||||
return image.NewRGBA(image.Rect(0, 0, width, height))
|
||||
}
|
||||
|
||||
cover, err := c.Conv.GetCoverImage(file, stat)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error GetCoverImage: %v\n", err.Error())
|
||||
return image.NewRGBA(image.Rect(0, 0, width, height))
|
||||
}
|
||||
|
||||
cover = c.Conv.TransformImage(cover)
|
||||
|
||||
if c.Conv.Opts.LevelsInMin != 0 || c.Conv.Opts.LevelsInMax != 255 || c.Conv.Opts.LevelsGamma != 1.00 ||
|
||||
c.Conv.Opts.LevelsOutMin != 0 || c.Conv.Opts.LevelsOutMax != 255 {
|
||||
cover = c.Conv.LevelImage(cover)
|
||||
}
|
||||
|
||||
// imaging is used for preview only
|
||||
if c.Conv.Opts.Grayscale {
|
||||
cover = imaging.Grayscale(cover)
|
||||
}
|
||||
|
||||
// size preview
|
||||
s := 0
|
||||
b := new(bytes.Buffer)
|
||||
|
||||
w := 0
|
||||
h := 0
|
||||
|
||||
switch c.Conv.Opts.Format {
|
||||
case "jpeg":
|
||||
jpeg.Encode(b, cover, &jpeg.Options{c.Conv.Opts.Quality})
|
||||
s = len(b.Bytes())
|
||||
cover, _ = jpeg.Decode(bytes.NewReader(b.Bytes()))
|
||||
config, _, _ := image.DecodeConfig(bytes.NewReader(b.Bytes()))
|
||||
w = config.Width
|
||||
h = config.Height
|
||||
case "png":
|
||||
png.Encode(b, cover)
|
||||
s = len(b.Bytes())
|
||||
cover, _ = png.Decode(bytes.NewReader(b.Bytes()))
|
||||
config, _, _ := image.DecodeConfig(bytes.NewReader(b.Bytes()))
|
||||
w = config.Width
|
||||
h = config.Height
|
||||
case "gif":
|
||||
mw := imagick.NewMagickWand()
|
||||
defer mw.Destroy()
|
||||
|
||||
mw.ReadImageBlob(c.Conv.GetImageBytes(cover))
|
||||
mw.SetImageFormat("GIF")
|
||||
blob := mw.GetImageBlob()
|
||||
|
||||
s = len(blob)
|
||||
cover, _ = gif.Decode(bytes.NewReader(blob))
|
||||
config, _, _ := image.DecodeConfig(bytes.NewReader(blob))
|
||||
w = config.Width
|
||||
h = config.Height
|
||||
case "tiff":
|
||||
tiff.Encode(b, cover, &tiff.Options{tiff.Uncompressed, false})
|
||||
|
||||
var buf bytes.Buffer
|
||||
gz := gzip.NewWriter(&buf)
|
||||
gz.Write(b.Bytes())
|
||||
gz.Close()
|
||||
|
||||
s = buf.Len()
|
||||
cover, _ = tiff.Decode(bytes.NewReader(b.Bytes()))
|
||||
config, _, _ := image.DecodeConfig(bytes.NewReader(b.Bytes()))
|
||||
w = config.Width
|
||||
h = config.Height
|
||||
case "bmp":
|
||||
mw := imagick.NewMagickWand()
|
||||
defer mw.Destroy()
|
||||
|
||||
bb := c.Conv.GetImageBytes(cover)
|
||||
mw.ReadImageBlob(bb)
|
||||
|
||||
wand := imagick.NewPixelWand()
|
||||
wand.SetColor("black")
|
||||
defer wand.Destroy()
|
||||
|
||||
mw.SetImageFormat("BMP3")
|
||||
mw.SetImageBackgroundColor(wand)
|
||||
mw.SetImageAlphaChannel(imagick.ALPHA_CHANNEL_REMOVE)
|
||||
mw.SetImageAlphaChannel(imagick.ALPHA_CHANNEL_DEACTIVATE)
|
||||
mw.SetImageMatte(false)
|
||||
mw.SetImageCompression(imagick.COMPRESSION_NO)
|
||||
mw.QuantizeImage(16, mw.GetImageColorspace(), 8, true, true)
|
||||
|
||||
var buf bytes.Buffer
|
||||
blob := mw.GetImageBlob()
|
||||
gz := gzip.NewWriter(&buf)
|
||||
gz.Write(blob)
|
||||
gz.Close()
|
||||
|
||||
s = buf.Len()
|
||||
cover, _ = bmp.Decode(bytes.NewReader(blob))
|
||||
config, _, _ := image.DecodeConfig(bytes.NewReader(bb))
|
||||
w = config.Width
|
||||
h = config.Height
|
||||
}
|
||||
|
||||
if cover == nil {
|
||||
return image.NewRGBA(image.Rect(0, 0, width, height))
|
||||
}
|
||||
|
||||
human := humanize.IBytes(uint64(s))
|
||||
c.Root.ObjectByName("sizePreview").Set("text", fmt.Sprintf("%s (%dx%d)", human, w, h))
|
||||
|
||||
return cover
|
||||
}
|
||||
|
||||
func run() error {
|
||||
qml.SetWindowIcon(":///assets/icon.png")
|
||||
|
||||
engine := qml.NewEngine()
|
||||
|
||||
c := &Comics{}
|
||||
engine.Context().SetVar("c", c)
|
||||
|
||||
engine.AddImportPath("qrc:/assets")
|
||||
engine.AddImageProvider("cover", c.CoverProvider)
|
||||
|
||||
q, err := engine.LoadFile("qrc:///assets/main.qml")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
window := q.CreateWindow(nil)
|
||||
c.Root = window.Root()
|
||||
c.Conv = cbconvert.NewConvertor(c.GetOptions())
|
||||
|
||||
c.Root.On("closing", func(o qml.Object) {
|
||||
os.RemoveAll(c.Conv.Workdir)
|
||||
})
|
||||
|
||||
c.Root.ObjectByName("buttonConvert").On("clicked", c.Convert)
|
||||
c.Root.ObjectByName("buttonCover").On("clicked", c.Cover)
|
||||
c.Root.ObjectByName("buttonThumbnail").On("clicked", c.Thumbnail)
|
||||
|
||||
// center window
|
||||
x := c.Root.Int("screenWidth")/2 - c.Root.Int("width")/2
|
||||
y := c.Root.Int("screenHeight")/2 - c.Root.Int("height")/2
|
||||
window.Set("x", x)
|
||||
window.Set("y", y)
|
||||
|
||||
window.Show()
|
||||
window.Wait()
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
if err := qml.Run(run); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
CHROOT="/home/milann/chroot"
|
||||
MINGW="/usr/i686-w64-mingw32"
|
||||
|
||||
mkdir -p build
|
||||
rm -f resource.syso
|
||||
|
||||
go generate
|
||||
|
||||
LIBRARY_PATH="$CHROOT/usr/lib:$CHROOT/lib" \
|
||||
PKG_CONFIG_PATH="$CHROOT/usr/lib/pkgconfig" \
|
||||
PKG_CONFIG_LIBDIR="$CHROOT/usr/lib/pkgconfig" \
|
||||
CGO_LDFLAGS="-L$CHROOT/usr/lib -L$CHROOT/lib -L$CHROOT/usr/plugins/generic -L$CHROOT/usr/plugins/platforms -L$CHROOT/usr/plugins/qmltooling -L$CHROOT/usr/qml/QtQuick.2 -L$CHROOT/usr/qml/QtQuick/Controls -L$CHROOT/usr/qml/QtQuick/Dialogs -L$CHROOT/usr/qml/QtQuick/Layouts -L$CHROOT/usr/qml/QtQuick/Window.2 -L$CHROOT/usr/qml/Qt/labs/settings -L$CHROOT/usr/qml/QtQuick/PrivateWidgets -L$CHROOT/usr/plugins/xcbglintegrations" \
|
||||
CGO_LDFLAGS="$CGO_LDFLAGS -lqxcb -lqtquick2plugin -lqtquickcontrolsplugin -ldialogplugin -lqquicklayoutsplugin -lwindowplugin -lqmlsettingsplugin -lwidgetsplugin -lqxcb-glx-integration -lqevdevkeyboardplugin -lqevdevmouseplugin" \
|
||||
CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -v -x -o build/cbconvert
|
||||
strip build/cbconvert
|
||||
|
||||
goversioninfo -icon=assets/icon.ico
|
||||
|
||||
PKG_CONFIG="/usr/bin/i686-w64-mingw32-pkg-config" \
|
||||
PKG_CONFIG_PATH="$MINGW/usr/lib/pkgconfig:$MINGW/usr/lib/pkgconfig" \
|
||||
PKG_CONFIG_LIBDIR="$MINGW/usr/lib/pkgconfig:$MINGW/usr/lib/pkgconfig" \
|
||||
CGO_LDFLAGS="-L$MINGW/usr/lib -L$MINGW/lib -L$MINGW/usr/plugins/generic -L$MINGW/usr/plugins/platforms -L$MINGW/usr/qml/Qt/labs/folderlistmodel -L$MINGW/usr/plugins/qmltooling -L$MINGW/usr/qml/QtQuick.2 -L$MINGW/usr/qml/QtQuick/Controls -L$MINGW/usr/qml/QtQuick/Dialogs -L$MINGW/usr/qml/QtQuick/Dialogs/Private -L$MINGW/usr/qml/QtQuick/Layouts -L$MINGW/usr/qml/QtQuick/Window.2 -L$MINGW/usr/qml/Qt/labs/settings -L$MINGW/usr/qml/QtQuick/PrivateWidgets" \
|
||||
CGO_LDFLAGS="$CGO_LDFLAGS -lqwindows -lqtquick2plugin -lqtquickcontrolsplugin -lqmlfolderlistmodelplugin -ldialogplugin -ldialogsprivateplugin -lqquicklayoutsplugin -lwindowplugin -lqmlsettingsplugin -lwidgetsplugin" \
|
||||
CGO_CFLAGS="-I$MINGW/usr/include -Wno-poison-system-directories" \
|
||||
CGO_CXXFLAGS="-I$MINGW/usr/include -Wno-poison-system-directories" \
|
||||
CGO_CPPFLAGS="-I$MINGW/usr/include -Wno-poison-system-directories" \
|
||||
CC="i686-w64-mingw32-gcc" CXX="i686-w64-mingw32-g++" \
|
||||
CC_FOR_TARGET="i686-w64-mingw32-gcc" CXX_FOR_TARGET="i686-w64-mingw32-g++" \
|
||||
CGO_ENABLED=1 GOOS=windows GOARCH=386 go build -v -x -o build/cbconvert.exe -ldflags "-H=windowsgui -linkmode external '-extldflags=-static -Wl,--allow-multiple-definition'"
|
||||
i686-w64-mingw32-strip build/cbconvert.exe
|
||||
@@ -1,44 +0,0 @@
|
||||
{
|
||||
"FixedFileInfo":
|
||||
{
|
||||
"FileVersion": {
|
||||
"Major": 0,
|
||||
"Minor": 6,
|
||||
"Patch": 0,
|
||||
"Build": 0
|
||||
},
|
||||
"ProductVersion": {
|
||||
"Major": 0,
|
||||
"Minor": 6,
|
||||
"Patch": 0,
|
||||
"Build": 0
|
||||
},
|
||||
"FileFlagsMask": "3f",
|
||||
"FileFlags ": "00",
|
||||
"FileOS": "040004",
|
||||
"FileType": "01",
|
||||
"FileSubType": "00"
|
||||
},
|
||||
"StringFileInfo":
|
||||
{
|
||||
"Comments": "Comic Book converter",
|
||||
"CompanyName": "",
|
||||
"FileDescription": "CBconvert GUI",
|
||||
"FileVersion": "0.6.0",
|
||||
"InternalName": "",
|
||||
"LegalCopyright": "",
|
||||
"LegalTrademarks": "",
|
||||
"OriginalFilename": "cbconvert.exe",
|
||||
"PrivateBuild": "",
|
||||
"ProductName": "CBconvert",
|
||||
"ProductVersion": "0.6.0",
|
||||
"SpecialBuild": ""
|
||||
},
|
||||
"VarFileInfo":
|
||||
{
|
||||
"Translation": {
|
||||
"LangID": "0409",
|
||||
"CharsetID": "04B0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 229 KiB |
|
After Width: | Height: | Size: 387 KiB |