mirror of
https://github.com/gen2brain/cbconvert
synced 2026-06-30 09:11:54 +02:00
Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| efb8b776fb | |||
| fa954fce1a | |||
| 7d80f5238e | |||
| 56e07aae23 | |||
| 3c3123a50d | |||
| 2e03350437 | |||
| 1fdc1610f5 | |||
| a5e796d017 | |||
| cffd119eba | |||
| cda03e0122 | |||
| 6a73f6f20e | |||
| 5df10a58ee | |||
| 081736ee04 |
@@ -1,184 +1,171 @@
|
|||||||
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.
|
||||||
|
|
||||||

|
### Features
|
||||||
|
|
||||||
Features
|
* reads RAR, ZIP, 7Z, CBR, CBZ, CB7, CBT, PDF, EPUB, and plain directory
|
||||||
--------
|
* always saves processed comics in CBZ (ZIP) archive format
|
||||||
|
* images can be converted to JPEG, PNG, TIFF, WEBP, or 4-Bit BMP (16 colors) file format
|
||||||
|
* rotate, flip, adjust brightness/contrast, adjust levels (Photoshop-like) or grayscale images
|
||||||
|
* resize algorithms (NearestNeighbor, Box, Linear, MitchellNetravali, CatmullRom, Gaussian, Lanczos)
|
||||||
|
* export covers from comics
|
||||||
|
* create thumbnails from covers by [FreeDesktop](http://www.freedesktop.org/wiki/) specification
|
||||||
|
|
||||||
- reads RAR, ZIP, 7Z, GZ, BZ2, CBR, CBZ, CB7, CBT, PDF, EPUB, XPS and plain directory
|
### Download
|
||||||
- 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
|
|
||||||
|
|
||||||
Download
|
* [Windows](https://github.com/gen2brain/cbconvert/releases/download/0.7.0/cbconvert-0.7.0-windows-i686.zip)
|
||||||
--------
|
* [Linux](https://github.com/gen2brain/cbconvert/releases/download/0.7.0/cbconvert-0.7.0-linux-x86_64.tar.gz)
|
||||||
|
|
||||||
- [Windows GUI](https://github.com/gen2brain/cbconvert/releases/download/0.5.0/cbconvert-0.5.zip)
|
### Using cbconvert in file managers to generate FreeDesktop thumbnails
|
||||||
- [Windows CMD](https://github.com/gen2brain/cbconvert/releases/download/0.5.0/cbconvert-cmd-0.5.zip)
|
|
||||||
|
|
||||||
- [Linux 64bit GUI](https://github.com/gen2brain/cbconvert/releases/download/0.5.0/cbconvert-0.5.tar.gz)
|
Copy cbconvert cli binary to your PATH and create file ~/.local/share/thumbnailers/cbconvert.thumbnailer:
|
||||||
- [Linux 64bit CMD](https://github.com/gen2brain/cbconvert/releases/download/0.5.0/cbconvert-cmd-0.5.tar.gz)
|
|
||||||
|
|
||||||
Using cbconvert in file managers to generate freedesktop thumbnails
|
```
|
||||||
-------------------------------------------------------------------
|
[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/epub+zip;
|
||||||
|
```
|
||||||
|
|
||||||
Just copy cbconvert cmd binary to your PATH and create file ~/.local/share/thumbnailers/cbconvert.thumbnailer :
|
This is what it looks like in the PCManFM file manager:
|
||||||
|
|
||||||
[Thumbnailer Entry]
|

|
||||||
TryExec=cbconvert
|
|
||||||
Exec=cbconvert thumbnail --quiet --width %s --outfile %o %i
|
|
||||||
MimeType=application/pdf;application/x-pdf;image/pdf;application/x-cbz;application/x-cbr;application/x-cb7;application/x-cbt;application/oxps;application/vnd.ms-xpsdocument;application/epub+zip;
|
|
||||||
|
|
||||||
This is how it looks like in PCManFM file manager:
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
|
|
||||||
Using command line app
|
### Using command line app
|
||||||
----------------------
|
|
||||||
|
|
||||||
usage: cbconvert [<flags>] <command> [<args> ...]
|
```
|
||||||
|
Usage: cbconvert <command> [<flags>] [file1 dir1 ... fileOrDirN]
|
||||||
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.
|
|
||||||
|
|
||||||
|
|
||||||
convert [<flags>] <args>...
|
Commands:
|
||||||
Convert archive or document (default command)
|
|
||||||
|
|
||||||
--width=0 Image width
|
convert*
|
||||||
--height=0 Image height
|
Convert archive or document (default command)
|
||||||
--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
|
|
||||||
|
|
||||||
cover [<flags>] <args>...
|
--width
|
||||||
Extract cover
|
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 (default "jpeg")
|
||||||
|
--quality
|
||||||
|
JPEG 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")
|
||||||
|
--flip
|
||||||
|
Flip images, valid values are none, horizontal, vertical (default "none")
|
||||||
|
--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 "")
|
||||||
|
--levels-inmin
|
||||||
|
Shadow input value (default "0")
|
||||||
|
--levels-gamma
|
||||||
|
Midpoint/Gamma (default "1")
|
||||||
|
--levels-inmax
|
||||||
|
Highlight input value (default "255")
|
||||||
|
--levels-outmin
|
||||||
|
Shadow output value (default "0")
|
||||||
|
--levels-outmax
|
||||||
|
Highlight output value (default "255")
|
||||||
|
--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
|
cover
|
||||||
--height=0 Image height
|
Extract cover
|
||||||
--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
|
|
||||||
|
|
||||||
thumbnail [<flags>] <args>...
|
--width
|
||||||
Extract cover thumbnail (freedesktop spec.)
|
Image width (default "0")
|
||||||
|
--height
|
||||||
|
Image height (default "0")
|
||||||
|
--fit
|
||||||
|
Best fit for required width and height (default "false")
|
||||||
|
--quality
|
||||||
|
JPEG 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
|
thumbnail
|
||||||
--height=0 Image height
|
Extract cover thumbnail (freedesktop spec.)
|
||||||
--fit Best fit for required width and height
|
|
||||||
--filter=2 0=NearestNeighbor, 1=Box, 2=Linear, 3=MitchellNetravali, 4=CatmullRom, 6=Gaussian, 7=Lanczos
|
|
||||||
|
|
||||||
[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
|
### Examples
|
||||||
|
|
||||||
Examples
|
Rescale images to 1200px for all supported files found in a directory with a size larger than 60MB:
|
||||||
--------
|
|
||||||
|
|
||||||
Rescale images to 1200px for all supported files found in directory with size larger then 60MB:
|
`cbconvert --recursive --width 1200 --size 60 /media/comics/Thorgal/`
|
||||||
|
|
||||||
cbconvert --recursive --width 1200 --size 60 /media/comics/Thorgal/
|
Convert all images in pdf to 4bit BMP images and save the result in ~/comics directory:
|
||||||
|
|
||||||
Convert all images in pdf to 4bit BMP image and save result in ~/comics directory:
|
`cbconvert --bmp --outdir ~/comics /media/comics/Garfield/Garfield_01.pdf`
|
||||||
|
|
||||||
cbconvert --bmp --outdir ~/comics /media/comics/Garfield/Garfield_01.pdf
|
[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.
|
||||||
|
|
||||||
[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.
|
Generate thumbnails by [freedesktop specification](http://specifications.freedesktop.org/thumbnail-spec/thumbnail-spec-latest.html) in ~/.cache/thumbnails/normal directory with width 512:
|
||||||
|
|
||||||
Generate thumbnails by [freedesktop specification](http://specifications.freedesktop.org/thumbnail-spec/thumbnail-spec-latest.html) in ~/.thumbnails/normal directory with width 512:
|
`cbconvert thumbnail --width 512 --outdir ~/.cache/thumbnails/normal /media/comics/GrooTheWanderer/`
|
||||||
|
|
||||||
cbconvert thumbnail --width 512 --outdir ~/.thumbnails/normal /media/comics/GrooTheWanderer/
|
Extract covers to ~/covers dir for all supported files found in the directory, Lanczos algorithm is used for resizing:
|
||||||
|
|
||||||
Extract covers to ~/covers dir for all supported files found in 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
|
||||||
|
|
||||||
Compile
|
Install ImageMagick 7 libraries and headers and then install to GOBIN:
|
||||||
-------
|
|
||||||
|
|
||||||
Install imagemagick dev packages:
|
`go install github.com/gen2brain/cbconvert/cmd/cbconvert@latest`
|
||||||
|
|
||||||
apt-get install libmagickcore-dev libmagickwand-dev
|
|
||||||
|
|
||||||
Compile latest MuPDF:
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|||||||
+747
-636
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,28 @@
|
|||||||
|
module cbconvert
|
||||||
|
|
||||||
|
go 1.19
|
||||||
|
|
||||||
|
replace github.com/gen2brain/cbconvert => ../../
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/gen2brain/cbconvert v0.0.0-20170124143008-5df10a58ee74
|
||||||
|
github.com/schollz/progressbar/v3 v3.10.0
|
||||||
|
github.com/spf13/pflag v1.0.5
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/chai2010/webp v1.1.1 // indirect
|
||||||
|
github.com/disintegration/imaging v1.6.2 // indirect
|
||||||
|
github.com/fvbommel/sortorder v1.0.2 // indirect
|
||||||
|
github.com/gen2brain/go-fitz v1.20.1 // indirect
|
||||||
|
github.com/gen2brain/go-unarr v0.1.6 // indirect
|
||||||
|
github.com/hotei/bmp v0.0.0-20150430041436-f620cebab0c7 // indirect
|
||||||
|
github.com/mattn/go-runewidth v0.0.13 // indirect
|
||||||
|
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
|
||||||
|
github.com/rivo/uniseg v0.3.4 // indirect
|
||||||
|
golang.org/x/image v0.0.0-20220902085622-e7cb96979f69 // indirect
|
||||||
|
golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde // indirect
|
||||||
|
golang.org/x/sys v0.0.0-20220829200755-d48e67d00261 // indirect
|
||||||
|
golang.org/x/term v0.0.0-20220722155259-a9ba230a4035 // indirect
|
||||||
|
gopkg.in/gographics/imagick.v3 v3.4.1 // indirect
|
||||||
|
)
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
github.com/chai2010/webp v1.1.1 h1:jTRmEccAJ4MGrhFOrPMpNGIJ/eybIgwKpcACsrTEapk=
|
||||||
|
github.com/chai2010/webp v1.1.1/go.mod h1:0XVwvZWdjjdxpUEIf7b9g9VkHFnInUSYujwqTLEuldU=
|
||||||
|
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/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
|
||||||
|
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
|
||||||
|
github.com/fvbommel/sortorder v1.0.2 h1:mV4o8B2hKboCdkJm+a7uX/SIpZob4JzUpc5GGnM45eo=
|
||||||
|
github.com/fvbommel/sortorder v1.0.2/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0=
|
||||||
|
github.com/gen2brain/go-fitz v1.20.1 h1:i5GPe/58q/gbNqa2mO+ZcTymwowbJEsDOXp7D0JwxgU=
|
||||||
|
github.com/gen2brain/go-fitz v1.20.1/go.mod h1:UZAxMETTDK4UPpuh80HaRpPzgkSibUihXVzwj2ip5oQ=
|
||||||
|
github.com/gen2brain/go-unarr v0.1.6 h1:2TtfIQ2dGuCkgEYa+vPE1ydcpkB3CtBbdYMfRSGLdA8=
|
||||||
|
github.com/gen2brain/go-unarr v0.1.6/go.mod h1:P05CsEe8jVEXhxqXqp9mFKUKFV0BKpFmtgNWf8Mcoos=
|
||||||
|
github.com/hotei/bmp v0.0.0-20150430041436-f620cebab0c7 h1:NlUATi3cllRJhpM4mfR9BxiLRXT83bcSLcOa+S8lrME=
|
||||||
|
github.com/hotei/bmp v0.0.0-20150430041436-f620cebab0c7/go.mod h1:Hku3FQ2laCEwSv7Z8YkC0er38jLaUycUCbsFkWMr+z4=
|
||||||
|
github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213/go.mod h1:vNUNkEQ1e29fT/6vq2aBdFsgNPmy8qMdSay1npru+Sw=
|
||||||
|
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||||
|
github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
|
||||||
|
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||||
|
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/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.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
|
github.com/rivo/uniseg v0.3.4 h1:3Z3Eu6FGHZWSfNKJTOUiPatWwfc7DzJRU04jFUqJODw=
|
||||||
|
github.com/rivo/uniseg v0.3.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||||
|
github.com/schollz/progressbar/v3 v3.10.0 h1:pOab0roS2jf6zkEBKAe9EyEdmRKJvhbbuxqVp9/Qjyw=
|
||||||
|
github.com/schollz/progressbar/v3 v3.10.0/go.mod h1:0N6zRwbDVLFCFy5chxuukVlRkoHWYFFLzlxQrw/sf3M=
|
||||||
|
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||||
|
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||||
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||||
|
golang.org/x/image v0.0.0-20220902085622-e7cb96979f69 h1:Lj6HJGCSn5AjxRAH2+r35Mir4icalbqku+CLUtjnvXY=
|
||||||
|
golang.org/x/image v0.0.0-20220902085622-e7cb96979f69/go.mod h1:doUCurBvlfPMKfmIpRIywoHmhN3VyhnoFDbvIEWF4hY=
|
||||||
|
golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde h1:ejfdSekXMDxDLbRrJMwUk6KnSLZ2McaUCVcIKM+N6jc=
|
||||||
|
golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220829200755-d48e67d00261 h1:v6hYoSR9T5oet+pMXwUWkbiVqx/63mlHjefrHmxwfeY=
|
||||||
|
golang.org/x/sys v0.0.0-20220829200755-d48e67d00261/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/term v0.0.0-20220722155259-a9ba230a4035 h1:Q5284mrmYTpACcm+eAKjKJH48BBwSyfJqmmGDTtT8Vc=
|
||||||
|
golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
gopkg.in/gographics/imagick.v3 v3.4.1 h1:CClNBnd1UGxH9KAl4Vuwx+jgNRkyKN+cHlbuFPyt+KU=
|
||||||
|
gopkg.in/gographics/imagick.v3 v3.4.1/go.mod h1:+Q9nyA2xRZXrDyTtJ/eko+8V/5E7bWYs08ndkZp8UmA=
|
||||||
@@ -0,0 +1,224 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"path/filepath"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/gen2brain/cbconvert"
|
||||||
|
"github.com/schollz/progressbar/v3"
|
||||||
|
flag "github.com/spf13/pflag"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
opts, args := parseFlags()
|
||||||
|
conv := cbconvert.New(opts)
|
||||||
|
|
||||||
|
c := make(chan os.Signal, 2)
|
||||||
|
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
|
||||||
|
go func() {
|
||||||
|
for range c {
|
||||||
|
fmt.Println("\naborting")
|
||||||
|
err := os.RemoveAll(conv.Workdir)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if _, err := os.Stat(opts.Outdir); err != nil {
|
||||||
|
err = os.MkdirAll(opts.Outdir, 0775)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
files, err := conv.Files(args)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
var bar *progressbar.ProgressBar
|
||||||
|
if opts.Cover || opts.Thumbnail {
|
||||||
|
if !opts.Quiet {
|
||||||
|
bar = progressbar.NewOptions(conv.Nfiles,
|
||||||
|
progressbar.OptionShowCount(),
|
||||||
|
progressbar.OptionClearOnFinish(),
|
||||||
|
progressbar.OptionUseANSICodes(true),
|
||||||
|
progressbar.OptionSetPredictTime(false),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
conv.OnStart = func() {
|
||||||
|
if !opts.Quiet {
|
||||||
|
bar = progressbar.NewOptions(conv.Ncontents,
|
||||||
|
progressbar.OptionShowCount(),
|
||||||
|
progressbar.OptionClearOnFinish(),
|
||||||
|
progressbar.OptionUseANSICodes(true),
|
||||||
|
progressbar.OptionSetDescription(fmt.Sprintf("Converting %d of %d:", conv.CurrFile, conv.Nfiles)),
|
||||||
|
progressbar.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 {
|
||||||
|
stat, err := os.Stat(file)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.Cover {
|
||||||
|
err = conv.ExtractCover(file, stat)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
} else if opts.Thumbnail {
|
||||||
|
err = conv.ExtractThumbnail(file, stat)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
err = conv.Convert(file, stat)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseFlags parses command line flags
|
||||||
|
func parseFlags() (cbconvert.Options, []string) {
|
||||||
|
opts := cbconvert.Options{}
|
||||||
|
var args []string
|
||||||
|
|
||||||
|
convert := flag.NewFlagSet("convert", flag.ExitOnError)
|
||||||
|
convert.SortFlags = false
|
||||||
|
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")
|
||||||
|
convert.IntVar(&opts.Quality, "quality", 75, "Image 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.StringVar(&opts.Flip, "flip", "none", "Flip images, valid values are none, horizontal, vertical")
|
||||||
|
convert.Float64Var(&opts.Brightness, "brightness", 0, "Adjust the brightness of the images, must be in the range (-100, 100)")
|
||||||
|
convert.Float64Var(&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.Float64Var(&opts.LevelsInMin, "levels-inmin", 0, "Shadow input value")
|
||||||
|
convert.Float64Var(&opts.LevelsGamma, "levels-gamma", 1.0, "Midpoint/Gamma")
|
||||||
|
convert.Float64Var(&opts.LevelsInMax, "levels-inmax", 255, "Highlight input value")
|
||||||
|
convert.Float64Var(&opts.LevelsOutMin, "levels-outmin", 0, "Shadow output value")
|
||||||
|
convert.Float64Var(&opts.LevelsOutMax, "levels-outmax", 255, "Highlight output value")
|
||||||
|
convert.StringVar(&opts.Outdir, "outdir", ".", "Output directory")
|
||||||
|
convert.Int64Var(&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.SortFlags = false
|
||||||
|
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.IntVar(&opts.Quality, "quality", 75, "Image 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.Int64Var(&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.SortFlags = false
|
||||||
|
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.Int64Var(&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")
|
||||||
|
|
||||||
|
convert.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 (default command)\n\n")
|
||||||
|
convert.VisitAll(func(f *flag.Flag) {
|
||||||
|
_, _ = fmt.Fprintf(os.Stderr, " --%s", f.Name)
|
||||||
|
_, _ = fmt.Fprintf(os.Stderr, "\n \t")
|
||||||
|
_, _ = fmt.Fprintf(os.Stderr, "%v (default %q)\n", f.Usage, f.DefValue)
|
||||||
|
})
|
||||||
|
_, _ = fmt.Fprintf(os.Stderr, "\n cover\n \tExtract cover\n\n")
|
||||||
|
cover.VisitAll(func(f *flag.Flag) {
|
||||||
|
_, _ = fmt.Fprintf(os.Stderr, " --%s", f.Name)
|
||||||
|
_, _ = fmt.Fprintf(os.Stderr, "\n \t")
|
||||||
|
_, _ = 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")
|
||||||
|
thumbnail.VisitAll(func(f *flag.Flag) {
|
||||||
|
_, _ = fmt.Fprintf(os.Stderr, " --%s", f.Name)
|
||||||
|
_, _ = fmt.Fprintf(os.Stderr, "\n \t")
|
||||||
|
_, _ = fmt.Fprintf(os.Stderr, "%v (default %q)\n", f.Usage, f.DefValue)
|
||||||
|
})
|
||||||
|
_, _ = fmt.Fprintf(os.Stderr, "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(os.Args) < 2 {
|
||||||
|
convert.Usage()
|
||||||
|
_, _ = fmt.Fprintf(os.Stderr, "no arguments\n")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch os.Args[1] {
|
||||||
|
case "convert":
|
||||||
|
_ = convert.Parse(os.Args[2:])
|
||||||
|
args = convert.Args()
|
||||||
|
case "cover":
|
||||||
|
opts.Cover = true
|
||||||
|
_ = cover.Parse(os.Args[2:])
|
||||||
|
args = cover.Args()
|
||||||
|
case "thumbnail":
|
||||||
|
opts.Thumbnail = true
|
||||||
|
_ = thumbnail.Parse(os.Args[2:])
|
||||||
|
args = thumbnail.Args()
|
||||||
|
default:
|
||||||
|
_ = convert.Parse(os.Args[1:])
|
||||||
|
args = convert.Args()
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(args) == 0 {
|
||||||
|
convert.Usage()
|
||||||
|
_, _ = fmt.Fprintf(os.Stderr, "no arguments\n")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return opts, args
|
||||||
|
}
|
||||||
Executable
+37
@@ -0,0 +1,37 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
MUSL="/usr/x86_64-pc-linux-musl"
|
||||||
|
MINGW="/usr/i686-w64-mingw32"
|
||||||
|
|
||||||
|
VERSION="`git --git-dir ../../.git describe --tags --abbrev=0 2>/dev/null || echo '0.0.0'`"
|
||||||
|
|
||||||
|
BUILDDIR="cbconvert-${VERSION}"
|
||||||
|
mkdir -p ${BUILDDIR}
|
||||||
|
|
||||||
|
CC=x86_64-pc-linux-musl-gcc \
|
||||||
|
PKG_CONFIG="x86_64-pc-linux-musl-pkg-config" \
|
||||||
|
PKG_CONFIG_PATH="$MUSL/usr/lib/pkgconfig" \
|
||||||
|
PKG_CONFIG_LIBDIR="$MUSL/usr/lib/pkgconfig" \
|
||||||
|
CGO_CFLAGS="-I$MUSL/usr/include" \
|
||||||
|
CGO_LDFLAGS="-L$MUSL/usr/lib" \
|
||||||
|
CGO_ENABLED=1 GOOS=linux GOARCH=amd64 \
|
||||||
|
go build -tags 'extlib static' -v -o ${BUILDDIR}/cbconvert -ldflags "-linkmode external -s -w '-extldflags=-static'"
|
||||||
|
|
||||||
|
cp ../../README.md ../../AUTHORS ../../COPYING ${BUILDDIR} && tar -czf "${BUILDDIR}-linux-x86_64.tar.gz" ${BUILDDIR}
|
||||||
|
rm -rf ${BUILDDIR}
|
||||||
|
|
||||||
|
|
||||||
|
BUILDDIR="cbconvert-${VERSION}"
|
||||||
|
mkdir -p ${BUILDDIR}
|
||||||
|
|
||||||
|
CC=i686-w64-mingw32-gcc \
|
||||||
|
PKG_CONFIG="/usr/bin/i686-w64-mingw32-pkg-config" \
|
||||||
|
PKG_CONFIG_PATH="$MINGW/usr/lib/pkgconfig" \
|
||||||
|
PKG_CONFIG_LIBDIR="$MINGW/usr/lib/pkgconfig" \
|
||||||
|
CGO_CFLAGS="-I$MINGW/usr/include" \
|
||||||
|
CGO_LDFLAGS="-L$MINGW/usr/lib" \
|
||||||
|
CGO_ENABLED=1 GOOS=windows GOARCH=386 \
|
||||||
|
go build -tags 'extlib static' -v -o ${BUILDDIR}/cbconvert.exe -ldflags "-s -w '-extldflags=-static -Wl,--allow-multiple-definition'"
|
||||||
|
|
||||||
|
cp ../../README.md ../../AUTHORS ../../COPYING ${BUILDDIR} && zip -rq "${BUILDDIR}-windows-i686.zip" ${BUILDDIR}
|
||||||
|
rm -rf ${BUILDDIR}
|
||||||
-149
@@ -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,15 @@
|
|||||||
|
module github.com/gen2brain/cbconvert
|
||||||
|
|
||||||
|
go 1.19
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/chai2010/webp v1.1.1
|
||||||
|
github.com/disintegration/imaging v1.6.2
|
||||||
|
github.com/fvbommel/sortorder v1.0.2
|
||||||
|
github.com/gen2brain/go-fitz v1.20.1
|
||||||
|
github.com/gen2brain/go-unarr v0.1.6
|
||||||
|
github.com/hotei/bmp v0.0.0-20150430041436-f620cebab0c7
|
||||||
|
golang.org/x/image v0.0.0-20220902085622-e7cb96979f69
|
||||||
|
golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde
|
||||||
|
gopkg.in/gographics/imagick.v3 v3.4.1
|
||||||
|
)
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
github.com/chai2010/webp v1.1.1 h1:jTRmEccAJ4MGrhFOrPMpNGIJ/eybIgwKpcACsrTEapk=
|
||||||
|
github.com/chai2010/webp v1.1.1/go.mod h1:0XVwvZWdjjdxpUEIf7b9g9VkHFnInUSYujwqTLEuldU=
|
||||||
|
github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
|
||||||
|
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
|
||||||
|
github.com/fvbommel/sortorder v1.0.2 h1:mV4o8B2hKboCdkJm+a7uX/SIpZob4JzUpc5GGnM45eo=
|
||||||
|
github.com/fvbommel/sortorder v1.0.2/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0=
|
||||||
|
github.com/gen2brain/go-fitz v1.20.1 h1:i5GPe/58q/gbNqa2mO+ZcTymwowbJEsDOXp7D0JwxgU=
|
||||||
|
github.com/gen2brain/go-fitz v1.20.1/go.mod h1:UZAxMETTDK4UPpuh80HaRpPzgkSibUihXVzwj2ip5oQ=
|
||||||
|
github.com/gen2brain/go-unarr v0.1.6 h1:2TtfIQ2dGuCkgEYa+vPE1ydcpkB3CtBbdYMfRSGLdA8=
|
||||||
|
github.com/gen2brain/go-unarr v0.1.6/go.mod h1:P05CsEe8jVEXhxqXqp9mFKUKFV0BKpFmtgNWf8Mcoos=
|
||||||
|
github.com/hotei/bmp v0.0.0-20150430041436-f620cebab0c7 h1:NlUATi3cllRJhpM4mfR9BxiLRXT83bcSLcOa+S8lrME=
|
||||||
|
github.com/hotei/bmp v0.0.0-20150430041436-f620cebab0c7/go.mod h1:Hku3FQ2laCEwSv7Z8YkC0er38jLaUycUCbsFkWMr+z4=
|
||||||
|
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||||
|
golang.org/x/image v0.0.0-20220902085622-e7cb96979f69 h1:Lj6HJGCSn5AjxRAH2+r35Mir4icalbqku+CLUtjnvXY=
|
||||||
|
golang.org/x/image v0.0.0-20220902085622-e7cb96979f69/go.mod h1:doUCurBvlfPMKfmIpRIywoHmhN3VyhnoFDbvIEWF4hY=
|
||||||
|
golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde h1:ejfdSekXMDxDLbRrJMwUk6KnSLZ2McaUCVcIKM+N6jc=
|
||||||
|
golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
gopkg.in/gographics/imagick.v3 v3.4.1 h1:CClNBnd1UGxH9KAl4Vuwx+jgNRkyKN+cHlbuFPyt+KU=
|
||||||
|
gopkg.in/gographics/imagick.v3 v3.4.1/go.mod h1:+Q9nyA2xRZXrDyTtJ/eko+8V/5E7bWYs08ndkZp8UmA=
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 9.4 KiB |
Binary file not shown.
|
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
-550
@@ -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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user