61 Commits

Author SHA1 Message Date
Milan Nikolic 903a8bbcfc Update modules 2023-08-24 11:46:48 +02:00
Milan Nikolic e938b140f0 Update README.md 2023-08-24 11:43:14 +02:00
Milan Nikolic 0a3f4d6992 Add support for MOBI 2023-08-24 11:42:19 +02:00
Milan Nikolic c5902fb131 Update modules 2023-08-24 09:05:37 +02:00
Milan Nikolic d6ece90041 Add support for adding/removing files 2023-08-24 09:00:52 +02:00
Milan Nikolic c34b4d0a98 Allow recursive for plain directories 2023-08-23 13:16:30 +02:00
Milan Nikolic f0e2232e51 Update modules 2023-08-23 12:43:05 +02:00
Milan Nikolic 0ba0337ef8 Preserve output directory structure when recursive is enabled 2023-08-23 12:17:32 +02:00
Milan Nikolic 6e97d977ae Update modules 2023-08-22 14:39:07 +02:00
Milan Nikolic 685b6bf153 Update modules 2023-08-22 14:37:05 +02:00
Milan Nikolic 14f2e87525 Lint 2023-08-22 14:36:03 +02:00
Milan Nikolic cd2f7501e0 Update modules 2023-08-22 11:30:50 +02:00
Milan Nikolic 69b0b51010 Allow for piped input 2023-08-22 11:29:33 +02:00
Milan Nikolic c5494af60b Add support for CBT 2023-08-22 10:27:48 +02:00
Milan Nikolic f447c618de Update README.md 2023-08-21 20:12:03 +02:00
Milan Nikolic 54261d8528 Set comment 2023-08-21 20:04:05 +02:00
Milan Nikolic f92125643f Update modules 2023-08-21 17:54:10 +02:00
Milan Nikolic 7b6c766dcc Refactor 2023-08-21 17:52:57 +02:00
Milan Nikolic dfea3dc6a3 Update dependencies 2023-06-02 10:55:02 +02:00
Milan Nikolic 16d01e292e Update dependencies 2023-06-02 10:52:08 +02:00
Milan Nikolic 6fe10f5bdb Update README.md 2023-06-02 10:46:38 +02:00
Milan Nikolic 547a3937a8 Update dependencies 2023-06-02 10:43:36 +02:00
Milan Nikolic d47c1c1464 Use ToSlash for cover, issue #19 2023-06-02 10:32:09 +02:00
Milan Nikolic 5befff07ae Handle all errors 2023-03-08 06:21:57 +01:00
Milan Nikolic d4abc99239 Add meta command 2023-03-08 05:42:49 +01:00
Milan Nikolic f1804ce13c Refactor 2023-03-04 11:30:11 +01:00
Milan Nikolic a74034d669 Update modules 2023-03-04 10:35:41 +01:00
Milan Nikolic 64fb152c36 Cover format, issue #18 2023-03-04 10:27:01 +01:00
Milan Nikolic 7082e3db90 Initialize IM 2023-01-14 10:52:04 +01:00
Milan Nikolic 943cd9bb95 Close file 2023-01-14 10:39:20 +01:00
Milan Nikolic ec2c321ff3 Expose IM functions 2023-01-14 10:37:17 +01:00
Milan Nikolic a0ed277971 Update README.md 2023-01-13 18:37:40 +01:00
Milan Nikolic b5b4457088 Update build 2023-01-13 18:33:57 +01:00
Milan Nikolic c24d7b7e18 Update README.md 2023-01-12 13:36:20 +01:00
Milan Nikolic 8ba065e68b Use pkgconfig 2023-01-12 13:18:42 +01:00
Milan Nikolic 9cd5616b48 Build linux/arm64 binary 2023-01-12 13:08:21 +01:00
Milan Nikolic 13a3b6e23e Update README.md 2023-01-12 10:06:09 +01:00
Milan Nikolic 9a047d1977 Fallback to IM decoding for broken images, issue #15 2023-01-12 10:03:45 +01:00
Milan Nikolic d6e7248112 Case-insensitive for coverName, issue #13 2023-01-12 09:10:24 +01:00
Milan Nikolic 9eb20db167 Handle errors 2023-01-12 08:45:32 +01:00
Milan Nikolic 04d2dd79d5 Handle no-convert for directories, issue #9 2023-01-12 08:44:00 +01:00
Milan Nikolic 2b2788ed6e Update modules 2022-12-08 12:38:38 +01:00
Milan Nikolic 164e7bfd75 Update modules 2022-12-08 12:35:53 +01:00
Milan Nikolic 27da60ed76 Update modules 2022-12-08 12:28:08 +01:00
Milan Nikolic 0963cb1762 Update README.md 2022-09-09 10:01:14 +02:00
Milan Nikolic 443c4e6aaa Update README.md 2022-09-08 23:44:56 +02:00
Milan Nikolic 9da5b97f15 Add support for AVIF 2022-09-08 23:38:58 +02:00
Milan Nikolic adbb86f9f7 Add more architectures 2022-09-08 23:36:47 +02:00
Milan Nikolic efb8b776fb Fixes 2022-09-07 19:55:59 +02:00
Milan Nikolic fa954fce1a Fixes 2022-09-07 12:16:50 +02:00
Milan Nikolic 7d80f5238e Update README.md 2022-09-05 18:45:43 +02:00
Milan Nikolic 56e07aae23 Update version 2022-09-05 18:45:30 +02:00
Milan Nikolic 3c3123a50d Update README.md 2022-09-05 18:32:46 +02:00
Milan Nikolic 2e03350437 Update README.md 2022-09-05 18:16:27 +02:00
Milan Nikolic 1fdc1610f5 Update README.md 2022-09-05 18:13:03 +02:00
Milan Nikolic a5e796d017 Remove actions 2022-09-05 18:12:48 +02:00
Milan Nikolic cffd119eba Add actions 2022-09-05 18:04:18 +02:00
Milan Nikolic cda03e0122 Big update 2022-09-05 18:03:58 +02:00
Milan Nikolic 6a73f6f20e Remove defunct GUI 2022-09-05 18:01:45 +02:00
Milan Nikolic 5df10a58ee Update README.md 2017-01-24 15:30:08 +01:00
Milan Nikolic 081736ee04 new release 2016-01-22 17:28:02 +01:00
17 changed files with 2138 additions and 2652 deletions
+151 -144
View File
@@ -1,184 +1,191 @@
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.
![screenshot](http://cbconvert.com/screenshot.png)
### Features
Features
--------
* reads RAR, ZIP, 7Z, CBR, CBZ, CB7, CBT, PDF, EPUB, MOBI and plain directory
* saves processed comics in CBZ (ZIP) archive format or CBT (TAR)
* images can be converted to JPEG, PNG, TIFF, WEBP, AVIF, 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://specifications.freedesktop.org/thumbnail-spec/thumbnail-spec-latest.html) specification
- reads RAR, ZIP, 7Z, GZ, BZ2, CBR, CBZ, CB7, CBT, PDF, EPUB, XPS and plain directory
- always saves processed comic in CBZ (ZIP) archive format
- images can be converted to JPEG, PNG, GIF, TIFF or 4-Bit BMP (16 colors) file format
- rotate, flip, adjust brightness/contrast, adjust levels (Photoshop like) or grayscale images
- choose resize algorithm (NearestNeighbor, Box, Linear, MitchellNetravali, CatmullRom, Gaussian, Lanczos)
- export covers from comics
- create thumbnails from covers by [freedesktop](http://www.freedesktop.org/wiki/) specification
### Download
Download
--------
* [Windows x86_64](https://github.com/gen2brain/cbconvert/releases/latest/download/cbconvert-0.8.3-windows-x86_64.zip)
* [Linux x86_64](https://github.com/gen2brain/cbconvert/releases/latest/download/cbconvert-0.8.3-linux-x86_64.tar.gz)
* [Linux aarch64](https://github.com/gen2brain/cbconvert/releases/latest/download/cbconvert-0.8.3-linux-aarch64.tar.gz)
* [macOS x86_64](https://github.com/gen2brain/cbconvert/releases/latest/download/cbconvert-0.8.3-darwin-x86_64.zip)
* [macOS aarch64](https://github.com/gen2brain/cbconvert/releases/latest/download/cbconvert-0.8.3-darwin-aarch64.zip)
- [Windows GUI](https://github.com/gen2brain/cbconvert/releases/download/0.5.0/cbconvert-0.5.zip)
- [Windows CMD](https://github.com/gen2brain/cbconvert/releases/download/0.5.0/cbconvert-cmd-0.5.zip)
### Using cbconvert in file managers to generate FreeDesktop thumbnails
- [Linux 64bit GUI](https://github.com/gen2brain/cbconvert/releases/download/0.5.0/cbconvert-0.5.tar.gz)
- [Linux 64bit CMD](https://github.com/gen2brain/cbconvert/releases/download/0.5.0/cbconvert-cmd-0.5.tar.gz)
Using cbconvert in file managers to generate freedesktop thumbnails
-------------------------------------------------------------------
Just copy cbconvert cmd binary to your PATH and create file ~/.local/share/thumbnailers/cbconvert.thumbnailer :
Copy cbconvert cli binary to your PATH and create file ~/.local/share/thumbnailers/cbconvert.thumbnailer:
```
[Thumbnailer Entry]
TryExec=cbconvert
Exec=cbconvert thumbnail --quiet --width %s --outfile %o %i
MimeType=application/pdf;application/x-pdf;image/pdf;application/x-cbz;application/x-cbr;application/x-cb7;application/x-cbt;application/oxps;application/vnd.ms-xpsdocument;application/epub+zip;
MimeType=application/pdf;application/x-cb7;application/x-cbt;application/epub+zip;application/vnd.comicbook-rar;application/vnd.comicbook+zip;application/x-mobipocket-ebook;
```
This is how it looks like in PCManFM file manager:
This is what it looks like in the PCManFM file manager:
![thumbnails](http://cbconvert.com/thumbnails.png)
![thumbnails](https://bit.ly/3BaTvTV)
Using command line app
----------------------
### Using command line app
usage: cbconvert [<flags>] <command> [<args> ...]
Comic Book convert tool.
Flags:
--help Show context-sensitive help (also try --help-long and --help-man).
--version Show application version.
--outdir="." Output directory
--size=0 Process only files larger then size (in MB)
--recursive Process subdirectories recursively
--quiet Hide console output
Args:
<args> filename or directory
Commands:
help [<command>...]
Show help.
```
    Usage: cbconvert <command> [<flags>] [file1 dir1 ... fileOrDirN]
convert [<flags>] <args>...
Convert archive or document (default command)
    Commands:
--width=0 Image width
--height=0 Image height
--fit Best fit for required width and height
--format="jpeg" Image format, valid values are jpeg, png, gif, tiff, bmp
--quality=75 JPEG image quality
--filter=2 0=NearestNeighbor, 1=Box, 2=Linear, 3=MitchellNetravali, 4=CatmullRom, 6=Gaussian, 7=Lanczos
--cover Convert cover image (use --no-cover if you want to exclude cover)
--rgb Convert images that have RGB colorspace (use --no-rgb if you only want to convert grayscaled images)
--nonimage Leave non image files in archive (use --no-nonimage to remove non image files from archive)
--grayscale Convert images to grayscale (monochromatic)
--rotate=0 Rotate images, valid values are 0, 90, 180, 270
--flip="none" Flip images, valid values are none, horizontal, vertical
--brightness=0 Adjust brightness of the images, must be in range (-100, 100)
--contrast=0 Adjust contrast of the images, must be in range (-100, 100)
--suffix=SUFFIX Add suffix to file basename
--levels-inmin=0 Shadow input value
--levels-inmax=255 Highlight input value
--levels-gamma=1 Midpoint/Gamma
--levels-outmin=0 Shadow output value
--levels-outmax=255 Highlight output value
      convert
            Convert archive or document
cover [<flags>] <args>...
Extract cover
        --width
            Image width (default "0")
        --height
            Image height (default "0")
        --fit
            Best fit for required width and height (default "false")
        --format
            Image format, valid values are jpeg, png, tiff, bmp, webp, avif (default "jpeg")
--archive
Archive format, valid values are zip, tar (default "zip")
        --quality
            Image quality (default "75")
        --lossless
            Lossless compression (avif) (default "false")
        --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
--height=0 Image height
--fit Best fit for required width and height
--quality=75 JPEG image quality
--filter=2 0=NearestNeighbor, 1=Box, 2=Linear, 3=MitchellNetravali, 4=CatmullRom, 6=Gaussian, 7=Lanczos
      cover
            Extract cover
thumbnail [<flags>] <args>...
Extract cover thumbnail (freedesktop spec.)
        --width
            Image width (default "0")
        --height
            Image height (default "0")
        --fit
            Best fit for required width and height (default "false")
        --format
            Image format, valid values are jpeg, png, tiff, bmp, webp, avif (default "jpeg")
        --quality
            Image quality (default "75")
        --filter
            0=NearestNeighbor, 1=Box, 2=Linear, 3=MitchellNetravali, 4=CatmullRom, 6=Gaussian, 7=Lanczos (default "2")
        --outdir
            Output directory (default ".")
        --size
            Process only files larger than size (in MB) (default "0")
        --recursive
            Process subdirectories recursively (default "false")
        --quiet
            Hide console output (default "false")
--width=0 Image width
--height=0 Image height
--fit Best fit for required width and height
--filter=2 0=NearestNeighbor, 1=Box, 2=Linear, 3=MitchellNetravali, 4=CatmullRom, 6=Gaussian, 7=Lanczos
      thumbnail
            Extract cover thumbnail (freedesktop spec.)
[man page](https://en.wikipedia.org/wiki/Man_page) is also available:
        --width
            Image width (default "0")
        --height
            Image height (default "0")
        --fit
            Best fit for required width and height (default "false")
        --filter
            0=NearestNeighbor, 1=Box, 2=Linear, 3=MitchellNetravali, 4=CatmullRom, 6=Gaussian, 7=Lanczos (default "2")
        --outdir
            Output directory (default ".")
        --outfile
            Output file (default "")
        --size
            Process only files larger than size (in MB) (default "0")
        --recursive
            Process subdirectories recursively (default "false")
        --quiet
            Hide console output (default "false")
cbconvert --help-man | man /dev/stdin
meta
CBZ metadata
Examples
--------
--cover
Print cover name (default "false")
--comment
Print zip comment (default "false")
--comment-body
Set zip comment (default "")
--file-add
Add file to archive (default "")
--file-remove
Remove file(s) from archive (glob pattern, i.e. *.xml) (default "")
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/
### Examples
Convert all images in pdf to 4bit BMP image and save result in ~/comics directory:
Rescale images to 1200px for all supported files found in a directory with a size larger than 60MB:
cbconvert --bmp --outdir ~/comics /media/comics/Garfield/Garfield_01.pdf
`cbconvert --recursive --width 1200 --size 60 /media/comics/Thorgal/`
[BMP](http://en.wikipedia.org/wiki/BMP_file_format) format is very good choice for black&white pages. Archive size can be smaller 2-3x and file will be readable by comic readers.
Convert all images in pdf to 4bit BMP images and save the result in ~/comics directory:
Generate thumbnails by [freedesktop specification](http://specifications.freedesktop.org/thumbnail-spec/thumbnail-spec-latest.html) in ~/.thumbnails/normal directory with width 512:
`cbconvert --bmp --outdir ~/comics /media/comics/Garfield/Garfield_01.pdf`
cbconvert thumbnail --width 512 --outdir ~/.thumbnails/normal /media/comics/GrooTheWanderer/
[BMP](http://en.wikipedia.org/wiki/BMP_file_format) format is a very good choice for black&white pages. Archive size can be smaller 2-3x and the file will be readable by comic readers.
Extract covers to ~/covers dir for all supported files found in directory, Lanczos algorithm is used for resizing:
Extract covers to ~/covers dir for all supported files found in the directory, Lanczos algorithm is used for resizing:
cbconvert cover --outdir ~/covers --filter=7 /media/comics/GrooTheWanderer/
`cbconvert cover --outdir ~/covers --filter=7 /media/comics/GrooTheWanderer/`
Compile
-------
### Compile
Install imagemagick dev packages:
Install ImageMagick7 and libheif (with libaom) libraries and headers and then install to GOBIN:
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
`go install github.com/gen2brain/cbconvert/cmd/cbconvert@latest`
+1403 -676
View File
File diff suppressed because it is too large Load Diff
+29
View File
@@ -0,0 +1,29 @@
module github.com/gen2brain/cbconvert/cmd/cbconvert
go 1.21
toolchain go1.21.0
require (
github.com/gen2brain/cbconvert v0.0.0-20230824094314-e938b140f074
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.1.0 // indirect
github.com/gen2brain/go-fitz v1.23.1 // indirect
github.com/gen2brain/go-unarr v0.1.7 // 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
github.com/strukturag/libheif v1.15.2 // indirect
golang.org/x/image v0.11.0 // indirect
golang.org/x/sync v0.3.0 // indirect
golang.org/x/sys v0.5.0 // indirect
golang.org/x/term v0.5.0 // indirect
gopkg.in/gographics/imagick.v3 v3.4.3 // indirect
)
+80
View File
@@ -0,0 +1,80 @@
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.1.0 h1:fUmoe+HLsBTctBDoaBwpQo5N+nrCp8g/BjKb/6ZQmYw=
github.com/fvbommel/sortorder v1.1.0/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0=
github.com/gen2brain/cbconvert v0.0.0-20230824094314-e938b140f074 h1:fUW60MgEEH44Y4ZNpi4Y4GwHeUzNayvJy+bJ5J1QiJI=
github.com/gen2brain/cbconvert v0.0.0-20230824094314-e938b140f074/go.mod h1:8eCyHuxeUYNvyYr1nCS+M7zL0fjnb3CO5R5JIOgG4Tk=
github.com/gen2brain/go-fitz v1.23.1 h1:x69/szWZXpI3jZ57mMqCg7WqqvtYnQG0lXts3L6M1Fc=
github.com/gen2brain/go-fitz v1.23.1/go.mod h1:HU04vc+RisUh/kvEd2pB0LAxmK1oyXdN4ftyshUr9rQ=
github.com/gen2brain/go-unarr v0.1.7 h1:mEE7bPShJIsmAX67t6BW2ibpEUO7j5WK152KgNM9NbQ=
github.com/gen2brain/go-unarr v0.1.7/go.mod h1:MK9a3hddpaIxjEtrE1f/LA5yJ7gA34cS7Oyr325sY9s=
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=
github.com/strukturag/libheif v1.15.2 h1:pgdcpDHqtLKRXL9ETSTeht0CsJODB3BojpTsb3S/3Wg=
github.com/strukturag/libheif v1.15.2/go.mod h1:E/PNRlmVtrtj9j2AvBZlrO4dsBDu6KfwDZn7X1Ce8Ks=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.11.0 h1:ds2RoQvBvYTiJkwpSFDwCcDFNX7DqjL2WsUgTNk0Ooo=
golang.org/x/image v0.11.0/go.mod h1:bglhjqbqVuEb9e9+eNR45Jfu7D+T4Qan+NhQk8Ck2P8=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/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/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/gographics/imagick.v3 v3.4.3 h1:9plKFE/Us913jBN6KohtLG9FNW8LPvfpjiGAORIiEHg=
gopkg.in/gographics/imagick.v3 v3.4.3/go.mod h1:+Q9nyA2xRZXrDyTtJ/eko+8V/5E7bWYs08ndkZp8UmA=
+301
View File
@@ -0,0 +1,301 @@
package main
import (
"bufio"
"fmt"
"io"
"os"
"os/signal"
"path/filepath"
"syscall"
"github.com/gen2brain/cbconvert"
pb "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")
if err := os.RemoveAll(conv.Workdir); err != nil {
fmt.Println(err)
}
os.Exit(1)
}
}()
if _, err := os.Stat(opts.Outdir); err != nil {
if err := os.MkdirAll(opts.Outdir, 0775); err != nil {
fmt.Println(err)
}
os.Exit(1)
}
conv.Initialize()
defer conv.Terminate()
files, err := conv.Files(args)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
var bar *pb.ProgressBar
if opts.Cover || opts.Thumbnail || opts.Meta {
if !opts.Quiet {
bar = pb.NewOptions(conv.Nfiles,
pb.OptionShowCount(),
pb.OptionClearOnFinish(),
pb.OptionUseANSICodes(true),
pb.OptionSetPredictTime(false),
)
}
}
conv.OnStart = func() {
if !opts.Quiet {
bar = pb.NewOptions(conv.Ncontents,
pb.OptionShowCount(),
pb.OptionClearOnFinish(),
pb.OptionUseANSICodes(true),
pb.OptionSetDescription(fmt.Sprintf("Converting %d of %d:", conv.CurrFile, conv.Nfiles)),
pb.OptionSetPredictTime(false),
)
}
}
conv.OnProgress = func() {
if !opts.Quiet {
_ = bar.Add(1)
}
}
conv.OnCompress = func() {
if !opts.Quiet {
_, _ = fmt.Fprintf(os.Stderr, "Compressing %d of %d...\r", conv.CurrFile, conv.Nfiles)
}
}
for _, file := range files {
stat, err := os.Stat(file)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
switch {
case opts.Meta:
ret, err := conv.Meta(file)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
if opts.Cover {
fmt.Println(ret)
} else if opts.Comment {
fmt.Println(ret)
}
continue
case opts.Cover:
if err := conv.Cover(file, stat); err != nil {
fmt.Println(err)
os.Exit(1)
}
continue
case opts.Thumbnail:
if err = conv.Thumbnail(file, stat); err != nil {
fmt.Println(err)
os.Exit(1)
}
continue
}
if err := conv.Convert(file, stat); err != nil {
fmt.Println(err)
os.Exit(1)
}
}
_, _ = fmt.Fprintf(os.Stderr, "\r")
}
// parseFlags parses command line flags.
func parseFlags() (cbconvert.Options, []string) {
opts := cbconvert.Options{}
var args []string
convert := flag.NewFlagSet("convert", flag.ExitOnError)
convert.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, avif")
convert.StringVar(&opts.Archive, "archive", "zip", "Archive format, valid values are zip, tar")
convert.IntVar(&opts.Quality, "quality", 75, "Image quality")
convert.BoolVar(&opts.Lossless, "lossless", false, "Lossless compression (avif)")
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.StringVar(&opts.Format, "format", "jpeg", "Image format, valid values are jpeg, png, tiff, bmp, webp, avif")
cover.IntVar(&opts.Quality, "quality", 75, "Image quality")
cover.IntVar(&opts.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")
meta := flag.NewFlagSet("meta", flag.ExitOnError)
meta.SortFlags = false
meta.BoolVar(&opts.Cover, "cover", false, "Print cover name")
meta.BoolVar(&opts.Comment, "comment", false, "Print zip comment")
meta.StringVar(&opts.CommentBody, "comment-body", "", "Set zip comment")
meta.StringVar(&opts.FileAdd, "file-add", "", "Add file to archive")
meta.StringVar(&opts.FileRemove, "file-remove", "", "Remove file from archive (glob pattern, i.e. *.xml)")
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\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 meta\n \tCBZ metadata\n\n")
meta.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 command\n")
os.Exit(1)
}
pipe := piped()
if pipe {
args = lines(os.Stdin)
}
switch os.Args[1] {
case "convert":
_ = convert.Parse(os.Args[2:])
if !pipe {
args = convert.Args()
}
case "cover":
opts.Cover = true
_ = cover.Parse(os.Args[2:])
if !pipe {
args = cover.Args()
}
case "thumbnail":
opts.Thumbnail = true
_ = thumbnail.Parse(os.Args[2:])
if !pipe {
args = thumbnail.Args()
}
case "meta":
opts.Meta = true
_ = meta.Parse(os.Args[2:])
if !pipe {
args = meta.Args()
}
}
if len(args) == 0 {
convert.Usage()
_, _ = fmt.Fprintf(os.Stderr, "no arguments\n")
os.Exit(1)
}
return opts, args
}
// piped checks if we have a piped stdin.
func piped() bool {
f, err := os.Stdin.Stat()
if err != nil {
return false
}
if f.Mode()&os.ModeNamedPipe == 0 {
return false
}
return true
}
// lines returns slice of lines from reader.
func lines(r io.Reader) []string {
data := make([]string, 0)
scanner := bufio.NewScanner(r)
for scanner.Scan() {
text := scanner.Text()
data = append(data, text)
}
return data
}
+86
View File
@@ -0,0 +1,86 @@
#!/usr/bin/env bash
GLIBC_x86_64="/usr/x86_64-pc-linux-gnu-static"
MUSL_x86_64="/usr/x86_64-pc-linux-musl"
MUSL_aarch64="/usr/aarch64-pc-linux-musl"
MINGW_x86_64="/usr/x86_64-w64-mingw32"
MACOS_x86_64="/usr/x86_64-apple-darwin"
MACOS_aarch64="/usr/aarch64-apple-darwin"
VERSION="`git --git-dir ../../.git describe --tags --abbrev=0 2>/dev/null || echo '0.0.0'`"
BUILDDIR="cbconvert-${VERSION}"; mkdir -p ${BUILDDIR}
CC=x86_64-pc-linux-gnu-gcc \
PKG_CONFIG="x86_64-pc-linux-gnu-pkg-config" \
PKG_CONFIG_PATH="$GLIBC_x86_64/usr/lib64/pkgconfig" \
PKG_CONFIG_LIBDIR="$GLIBC_x86_64/usr/lib64/pkgconfig" \
CGO_CFLAGS="-I$GLIBC_x86_64/usr/include" \
CGO_LDFLAGS="-L$GLIBC_x86_64/usr/lib64" \
CGO_ENABLED=1 GOOS=linux GOARCH=amd64 \
go build -trimpath -tags 'extlib pkgconfig' -v -o ${BUILDDIR}/cbconvert -ldflags "-linkmode external -s -w '-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=x86_64-pc-linux-musl-gcc \
#PKG_CONFIG="x86_64-pc-linux-musl-pkg-config" \
#PKG_CONFIG_PATH="$MUSL_x86_64/usr/lib/pkgconfig" \
#PKG_CONFIG_LIBDIR="$MUSL_x86_64/usr/lib/pkgconfig" \
#CGO_CFLAGS="-I$MUSL_x86_64/usr/include" \
#CGO_LDFLAGS="-L$MUSL_x86_64/usr/lib" \
#CGO_ENABLED=1 GOOS=linux GOARCH=amd64 \
#go build -trimpath -tags 'extlib pkgconfig' -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=aarch64-pc-linux-musl-gcc \
PKG_CONFIG="aarch64-pc-linux-musl-pkg-config" \
PKG_CONFIG_PATH="$MUSL_aarch64/usr/lib/pkgconfig" \
PKG_CONFIG_LIBDIR="$MUSL_aarch64/usr/lib/pkgconfig" \
CGO_CFLAGS="-I$MUSL_aarch64/usr/include" \
CGO_LDFLAGS="-L$MUSL_aarch64/usr/lib" \
CGO_ENABLED=1 GOOS=linux GOARCH=arm64 \
go build -trimpath -tags 'extlib pkgconfig' -v -o ${BUILDDIR}/cbconvert -ldflags "-linkmode external -s -w '-extldflags=-static'" && \
cp ../../README.md ../../AUTHORS ../../COPYING ${BUILDDIR} && tar -czf "${BUILDDIR}-linux-aarch64.tar.gz" ${BUILDDIR}
rm -rf ${BUILDDIR}
BUILDDIR="cbconvert-${VERSION}"; mkdir -p ${BUILDDIR}
CC=x86_64-w64-mingw32-gcc \
PKG_CONFIG="/usr/bin/x86_64-w64-mingw32-pkg-config" \
PKG_CONFIG_PATH="$MINGW_x86_64/usr/lib/pkgconfig" \
PKG_CONFIG_LIBDIR="$MINGW_x86_64/usr/lib/pkgconfig" \
CGO_CFLAGS="-I$MINGW_x86_64/usr/include" \
CGO_LDFLAGS="-L$MINGW_x86_64/usr/lib" \
CGO_ENABLED=1 GOOS=windows GOARCH=amd64 \
go build -trimpath -tags 'extlib pkgconfig' -v -o ${BUILDDIR}/cbconvert.exe -ldflags "-s -w '-extldflags=-static -Wl,--allow-multiple-definition'" && \
cp ../../README.md ../../AUTHORS ../../COPYING ${BUILDDIR} && zip -rq "${BUILDDIR}-windows-x86_64.zip" ${BUILDDIR}
rm -rf ${BUILDDIR}
export OSXCROSS_PKG_CONFIG_USE_NATIVE_VARIABLES=1
BUILDDIR="cbconvert-${VERSION}"; mkdir -p ${BUILDDIR}
PATH=${PATH}:${MACOS_x86_64}/bin \
CC=x86_64-apple-darwin21.1-clang \
PKG_CONFIG="x86_64-apple-darwin21.1-pkg-config" \
PKG_CONFIG_PATH="$MACOS_x86_64/SDK/MacOSX12.1.sdk/usr/lib/pkgconfig" \
PKG_CONFIG_LIBDIR="$MACOS_x86_64/SDK/MacOSX12.1.sdk/usr/lib/pkgconfig" \
CGO_CFLAGS="-I$MACOS_x86_64/usr/include -I$MACOS_x86_64/macports/pkgs/opt/local/include" \
CGO_LDFLAGS="-L$MACOS_x86_64/SDK/MacOSX12.1.sdk/usr/lib -L$MACOS_x86_64/macports/pkgs/opt/local/lib -mmacosx-version-min=10.13" \
CGO_ENABLED=1 GOOS=darwin GOARCH=amd64 \
go build -trimpath -tags 'extlib pkgconfig' -v -o ${BUILDDIR}/cbconvert -ldflags "-linkmode external -s -w" && \
cp ../../README.md ../../AUTHORS ../../COPYING ${BUILDDIR} && zip -rq "${BUILDDIR}-darwin-x86_64.zip" ${BUILDDIR}
rm -rf ${BUILDDIR}
export OSXCROSS_PKG_CONFIG_USE_NATIVE_VARIABLES=1
BUILDDIR="cbconvert-${VERSION}"; mkdir -p ${BUILDDIR}
PATH=${PATH}:${MACOS_aarch64}/bin \
CC=aarch64-apple-darwin21.1-clang \
PKG_CONFIG="aarch64-apple-darwin21.1-pkg-config" \
PKG_CONFIG_PATH="$MACOS_aarch64/SDK/MacOSX12.1.sdk/usr/lib/pkgconfig" \
PKG_CONFIG_LIBDIR="$MACOS_aarch64/SDK/MacOSX12.1.sdk/usr/lib/pkgconfig" \
CGO_CFLAGS="-I$MACOS_aarch64/usr/include -I$MACOS_aarch64/macports/pkgs/opt/local/include" \
CGO_LDFLAGS="-L$MACOS_aarch64/SDK/MacOSX12.1.sdk/usr/lib -L$MACOS_aarch64/macports/pkgs/opt/local/lib -mmacosx-version-min=10.13" \
CGO_ENABLED=1 GOOS=darwin GOARCH=arm64 \
go build -trimpath -tags 'extlib pkgconfig' -v -o ${BUILDDIR}/cbconvert -ldflags "-linkmode external -s -w" && \
cp ../../README.md ../../AUTHORS ../../COPYING ${BUILDDIR} && zip -rq "${BUILDDIR}-darwin-aarch64.zip" ${BUILDDIR}
rm -rf ${BUILDDIR}
-149
View File
@@ -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)
}
}
-28
View File
@@ -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
-44
View File
@@ -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"
}
}
}
+16
View File
@@ -0,0 +1,16 @@
module github.com/gen2brain/cbconvert
go 1.21
require (
github.com/chai2010/webp v1.1.1
github.com/disintegration/imaging v1.6.2
github.com/fvbommel/sortorder v1.1.0
github.com/gen2brain/go-fitz v1.23.1
github.com/gen2brain/go-unarr v0.1.7
github.com/hotei/bmp v0.0.0-20150430041436-f620cebab0c7
github.com/strukturag/libheif v1.15.2
golang.org/x/image v0.11.0
golang.org/x/sync v0.3.0
gopkg.in/gographics/imagick.v3 v3.4.3
)
+52
View File
@@ -0,0 +1,52 @@
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.1.0 h1:fUmoe+HLsBTctBDoaBwpQo5N+nrCp8g/BjKb/6ZQmYw=
github.com/fvbommel/sortorder v1.1.0/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0=
github.com/gen2brain/go-fitz v1.23.1 h1:x69/szWZXpI3jZ57mMqCg7WqqvtYnQG0lXts3L6M1Fc=
github.com/gen2brain/go-fitz v1.23.1/go.mod h1:HU04vc+RisUh/kvEd2pB0LAxmK1oyXdN4ftyshUr9rQ=
github.com/gen2brain/go-unarr v0.1.7 h1:mEE7bPShJIsmAX67t6BW2ibpEUO7j5WK152KgNM9NbQ=
github.com/gen2brain/go-unarr v0.1.7/go.mod h1:MK9a3hddpaIxjEtrE1f/LA5yJ7gA34cS7Oyr325sY9s=
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/strukturag/libheif v1.15.2 h1:pgdcpDHqtLKRXL9ETSTeht0CsJODB3BojpTsb3S/3Wg=
github.com/strukturag/libheif v1.15.2/go.mod h1:E/PNRlmVtrtj9j2AvBZlrO4dsBDu6KfwDZn7X1Ce8Ks=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.11.0 h1:ds2RoQvBvYTiJkwpSFDwCcDFNX7DqjL2WsUgTNk0Ooo=
golang.org/x/image v0.11.0/go.mod h1:bglhjqbqVuEb9e9+eNR45Jfu7D+T4Qan+NhQk8Ck2P8=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/gographics/imagick.v3 v3.4.3 h1:9plKFE/Us913jBN6KohtLG9FNW8LPvfpjiGAORIiEHg=
gopkg.in/gographics/imagick.v3 v3.4.3/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

-965
View File
@@ -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
View File
@@ -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)
}
}
-32
View File
@@ -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
-44
View File
@@ -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"
}
}
}