mirror of
https://github.com/gen2brain/cbconvert
synced 2026-06-30 09:11:54 +02:00
Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f01e858a24 | |||
| 8c21b430a9 | |||
| 5fbd462a78 | |||
| 0797c8456f | |||
| c3f6c5a499 | |||
| fa162972b2 | |||
| aa65bfa4f4 | |||
| 6c293adc1f | |||
| 6936f697b4 | |||
| 83d7a1ea6e | |||
| 3d56765415 | |||
| b1fcf530da | |||
| 7dc21fc0b4 | |||
| fdfa80875e | |||
| 50b0911586 | |||
| 8db1690d3c | |||
| 91e336f772 |
@@ -0,0 +1,78 @@
|
||||
on: [push, pull_request]
|
||||
name: Test
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
- name: Setup cmake
|
||||
uses: jwlawson/actions-setup-cmake@v1.13
|
||||
with:
|
||||
cmake-version: '3.27.x'
|
||||
|
||||
- name: Wget libaom
|
||||
uses: wei/wget@v1
|
||||
with:
|
||||
args: https://storage.googleapis.com/aom-releases/libaom-3.7.0.tar.gz
|
||||
- name: Unpack libaom
|
||||
run: |
|
||||
tar -xpf libaom-3.7.0.tar.gz && mkdir build
|
||||
- name: Configure libaom
|
||||
working-directory: build
|
||||
run: |
|
||||
cmake -DCMAKE_INSTALL_PREFIX=/usr -DBUILD_SHARED_LIBS=ON -DCMAKE_BUILD_TYPE=Release -DENABLE_DOCS=OFF \
|
||||
-DENABLE_EXAMPLES=OFF -DENABLE_NASM=OFF -DENABLE_TESTS=OFF -DENABLE_TOOLS=OFF -DENABLE_WERROR=OFF \
|
||||
-DAOM_TARGET_CPU=generic ../libaom-3.7.0
|
||||
- name: Install libaom
|
||||
working-directory: build
|
||||
run: |
|
||||
make -j3 && sudo make install
|
||||
|
||||
- name: Wget libheif
|
||||
uses: wei/wget@v1
|
||||
with:
|
||||
args: https://github.com/strukturag/libheif/releases/download/v1.15.2/libheif-1.15.2.tar.gz
|
||||
- name: Unpack libheif
|
||||
run: |
|
||||
tar -xpf libheif-1.15.2.tar.gz
|
||||
- name: Configure libheif
|
||||
working-directory: libheif-1.15.2
|
||||
run: |
|
||||
./configure --prefix=/usr --libdir=/usr/lib/x86_64-linux-gnu --enable-shared --disable-static --disable-libde265 \
|
||||
--disable-dav1d --disable-go --enable-aom --disable-gdk-pixbuf --disable-rav1e --disable-tests --disable-x265
|
||||
- name: Install libheif
|
||||
working-directory: libheif-1.15.2
|
||||
run: |
|
||||
make -j3 && sudo make install
|
||||
|
||||
- name: Wget ImageMagick
|
||||
uses: wei/wget@v1
|
||||
with:
|
||||
args: -O ImageMagick-7.1.1-15.tar.gz https://github.com/ImageMagick/ImageMagick/archive/refs/tags/7.1.1-15.tar.gz
|
||||
- name: Unpack ImageMagick
|
||||
run: |
|
||||
tar -xpf ImageMagick-7.1.1-15.tar.gz
|
||||
- name: Configure ImageMagick
|
||||
working-directory: ImageMagick-7.1.1-15
|
||||
run: |
|
||||
./configure --prefix=/usr --libdir=/usr/lib/x86_64-linux-gnu --enable-shared --disable-static --enable-zero-configuration \
|
||||
--without-frozenpaths --without-utilities --disable-hdri --disable-opencl --without-modules --without-magick-plus-plus --without-perl \
|
||||
--without-bzlib --without-x --without-zip --with-zlib --without-dps --without-djvu --without-autotrace --without-fftw \
|
||||
--without-fpx --without-fontconfig --without-freetype --without-gslib --without-gvc --without-jbig --without-openjp2 \
|
||||
--without-jxl --without-lcms --without-lqr --without-lzma --without-openexr --without-pango --without-raw \
|
||||
--without-rsvg --without-wmf --without-xml --disable-openmp --with-jpeg --with-heic --with-png --with-tiff --with-webp
|
||||
- name: Install ImageMagick
|
||||
working-directory: ImageMagick-7.1.1-15
|
||||
run: |
|
||||
make -j3 && sudo make install
|
||||
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.21
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Test
|
||||
run: go test
|
||||
@@ -8,21 +8,21 @@ It can convert comics to different formats to fit your various devices.
|
||||
|
||||
### 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)
|
||||
* reads CBR (RAR), CBZ (ZIP), CB7 (7Z), CBT (TAR), PDF, EPUB, MOBI and plain directory
|
||||
* saves processed comics in ZIP archive format or 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)
|
||||
* rotate, adjust brightness/contrast, adjust levels (Photoshop-like) or grayscale images
|
||||
* resize filters (NearestNeighbor, Box, Linear, MitchellNetravali, CatmullRom, Gaussian, Lanczos)
|
||||
* export covers from comics
|
||||
* create thumbnails from covers by [FreeDesktop](http://specifications.freedesktop.org/thumbnail-spec/thumbnail-spec-latest.html) specification
|
||||
|
||||
### 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 x86_64](https://github.com/gen2brain/cbconvert/releases/latest/download/cbconvert-0.9.0-windows-x86_64.zip)
|
||||
* [Linux x86_64](https://github.com/gen2brain/cbconvert/releases/latest/download/cbconvert-0.9.0-linux-x86_64.tar.gz)
|
||||
* [Linux aarch64](https://github.com/gen2brain/cbconvert/releases/latest/download/cbconvert-0.9.0-linux-aarch64.tar.gz)
|
||||
* [macOS x86_64](https://github.com/gen2brain/cbconvert/releases/latest/download/cbconvert-0.9.0-darwin-x86_64.zip)
|
||||
* [macOS aarch64](https://github.com/gen2brain/cbconvert/releases/latest/download/cbconvert-0.9.0-darwin-aarch64.zip)
|
||||
|
||||
### Using cbconvert in file managers to generate FreeDesktop thumbnails
|
||||
|
||||
@@ -79,8 +79,6 @@ This is what it looks like in the PCManFM file manager:
|
||||
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
|
||||
@@ -170,22 +168,36 @@ This is what it looks like in the PCManFM file manager:
|
||||
|
||||
### 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 a directory with a size larger than 60MB:
|
||||
|
||||
`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 images and save the result in ~/comics directory:
|
||||
|
||||
`cbconvert --bmp --outdir ~/comics /media/comics/Garfield/Garfield_01.pdf`
|
||||
`cbconvert --format 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.
|
||||
|
||||
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 the directory, Lanczos algorithm is used for resizing:
|
||||
|
||||
`cbconvert cover --outdir ~/covers --filter=7 /media/comics/GrooTheWanderer/`
|
||||
|
||||
* Convert all images to AVIF format:
|
||||
|
||||
`cbconvert --format avif --quality 50 --width 1280 --outdir ~/comics /media/comics/Misc/`
|
||||
|
||||
### Quality settings
|
||||
|
||||
This table maps quality settings for JPEG to the respective AVIF and WEBP quality settings:
|
||||
|
||||
| | | | | |
|
||||
|--------------|----|----|----|----|
|
||||
| JPEG quality | 50 | 60 | 70 | 80 |
|
||||
| AVIF quality | 48 | 51 | 56 | 64 |
|
||||
| WEBP quality | 55 | 64 | 72 | 82 |
|
||||
|
||||
### Compile
|
||||
|
||||
Install ImageMagick7 and libheif (with libaom) libraries and headers and then install to GOBIN:
|
||||
Install ImageMagick7, MuPDF and libheif (with libaom) libraries and headers and then install to GOBIN:
|
||||
|
||||
`go install github.com/gen2brain/cbconvert/cmd/cbconvert@latest`
|
||||
`go install -tags extlib github.com/gen2brain/cbconvert/cmd/cbconvert@latest`
|
||||
|
||||
+186
-78
@@ -6,10 +6,11 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/md5"
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"mime"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
@@ -26,11 +27,12 @@ import (
|
||||
"image/png"
|
||||
|
||||
"github.com/chai2010/webp"
|
||||
_ "github.com/hotei/bmp" // allow bmp decoding
|
||||
_ "github.com/hotei/bmp" // allow 4-bit bmp decoding
|
||||
"github.com/strukturag/libheif/go/heif"
|
||||
"golang.org/x/image/tiff"
|
||||
|
||||
"github.com/disintegration/imaging"
|
||||
"github.com/dustin/go-humanize"
|
||||
"github.com/fvbommel/sortorder"
|
||||
"github.com/gen2brain/go-fitz"
|
||||
"github.com/gen2brain/go-unarr"
|
||||
@@ -100,6 +102,8 @@ type Options struct {
|
||||
Thumbnail bool
|
||||
// CBZ metadata
|
||||
Meta bool
|
||||
// Version
|
||||
Version bool
|
||||
// ZIP comment
|
||||
Comment bool
|
||||
// ZIP comment body
|
||||
@@ -109,35 +113,33 @@ type Options struct {
|
||||
// Remove file
|
||||
FileRemove string
|
||||
// Output file
|
||||
Outfile string
|
||||
OutFile string
|
||||
// Output directory
|
||||
Outdir string
|
||||
OutDir string
|
||||
// Convert images to grayscale (monochromatic)
|
||||
Grayscale bool
|
||||
// Rotate images, valid values are 0, 90, 180, 270
|
||||
Rotate int
|
||||
// Flip images, valid values are none, horizontal, vertical
|
||||
Flip string
|
||||
// Adjust the brightness of the images, must be in the range (-100, 100)
|
||||
Brightness float64
|
||||
Brightness int
|
||||
// Adjust the contrast of the images, must be in the range (-100, 100)
|
||||
Contrast float64
|
||||
Contrast int
|
||||
// Process subdirectories recursively
|
||||
Recursive bool
|
||||
// Process only files larger than size (in MB)
|
||||
Size int64
|
||||
Size int
|
||||
// Hide console output
|
||||
Quiet bool
|
||||
// Shadow input value
|
||||
LevelsInMin float64
|
||||
LevelsInMin int
|
||||
// Highlight input value
|
||||
LevelsInMax float64
|
||||
LevelsInMax int
|
||||
// Midpoint/gamma
|
||||
LevelsGamma float64
|
||||
// Shadow output value
|
||||
LevelsOutMin float64
|
||||
LevelsOutMin int
|
||||
// Highlight output value
|
||||
LevelsOutMax float64
|
||||
LevelsOutMax int
|
||||
}
|
||||
|
||||
// Convertor type.
|
||||
@@ -162,6 +164,22 @@ type Convertor struct {
|
||||
OnCompress func()
|
||||
}
|
||||
|
||||
// File type.
|
||||
type File struct {
|
||||
Name string
|
||||
Path string
|
||||
Stat os.FileInfo
|
||||
SizeHuman string
|
||||
}
|
||||
|
||||
// Image type.
|
||||
type Image struct {
|
||||
Image image.Image
|
||||
Width int
|
||||
Height int
|
||||
SizeHuman string
|
||||
}
|
||||
|
||||
// New returns new convertor.
|
||||
func New(o Options) *Convertor {
|
||||
c := &Convertor{}
|
||||
@@ -282,9 +300,10 @@ func (c *Convertor) convertArchive(fileName string) error {
|
||||
var img image.Image
|
||||
img, err = c.imageDecode(bytes.NewReader(data), pathName)
|
||||
if err != nil {
|
||||
e := err
|
||||
img, err = c.imDecode(bytes.NewReader(data), pathName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("convertArchive: %w", err)
|
||||
return fmt.Errorf("convertArchive: %w: %w", e, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -383,9 +402,15 @@ func (c *Convertor) convertDirectory(dirPath string) error {
|
||||
var i image.Image
|
||||
i, err = c.imageDecode(file, img)
|
||||
if err != nil {
|
||||
e := err
|
||||
_, err = file.Seek(0, io.SeekStart)
|
||||
if err != nil {
|
||||
return fmt.Errorf("convertDirectory: %w: %w", e, err)
|
||||
}
|
||||
|
||||
i, err = c.imDecode(file, img)
|
||||
if err != nil {
|
||||
return fmt.Errorf("convertDirectory: %w", err)
|
||||
return fmt.Errorf("convertDirectory: %w: %w", e, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -469,7 +494,7 @@ func (c *Convertor) imageConvert(ctx context.Context, img image.Image, index int
|
||||
return nil
|
||||
}
|
||||
|
||||
// imageTransform transforms image (resize, rotate, flip, brightness, contrast).
|
||||
// imageTransform transforms image (resize, rotate, brightness, contrast).
|
||||
func (c *Convertor) imageTransform(img image.Image) image.Image {
|
||||
var i = img
|
||||
|
||||
@@ -492,21 +517,16 @@ func (c *Convertor) imageTransform(img image.Image) image.Image {
|
||||
}
|
||||
}
|
||||
|
||||
if c.Opts.Flip != "none" {
|
||||
switch c.Opts.Flip {
|
||||
case "horizontal":
|
||||
i = imaging.FlipH(i)
|
||||
case "vertical":
|
||||
i = imaging.FlipV(i)
|
||||
}
|
||||
}
|
||||
|
||||
if c.Opts.Brightness != 0 {
|
||||
i = imaging.AdjustBrightness(i, c.Opts.Brightness)
|
||||
i = imaging.AdjustBrightness(i, float64(c.Opts.Brightness))
|
||||
}
|
||||
|
||||
if c.Opts.Contrast != 0 {
|
||||
i = imaging.AdjustContrast(i, c.Opts.Contrast)
|
||||
i = imaging.AdjustContrast(i, float64(c.Opts.Contrast))
|
||||
}
|
||||
|
||||
if c.Opts.Grayscale {
|
||||
i = imageToGray(i)
|
||||
}
|
||||
|
||||
return i
|
||||
@@ -526,16 +546,16 @@ func (c *Convertor) imageLevel(img image.Image) (image.Image, error) {
|
||||
_, qrange := imagick.GetQuantumRange()
|
||||
quantumRange := float64(qrange)
|
||||
|
||||
inmin := (quantumRange * c.Opts.LevelsInMin) / 255
|
||||
inmax := (quantumRange * c.Opts.LevelsInMax) / 255
|
||||
outmin := (quantumRange * c.Opts.LevelsOutMin) / 255
|
||||
outmax := (quantumRange * c.Opts.LevelsOutMax) / 255
|
||||
inMin := (quantumRange * float64(c.Opts.LevelsInMin)) / 255
|
||||
inMax := (quantumRange * float64(c.Opts.LevelsInMax)) / 255
|
||||
outMin := (quantumRange * float64(c.Opts.LevelsOutMin)) / 255
|
||||
outMax := (quantumRange * float64(c.Opts.LevelsOutMax)) / 255
|
||||
|
||||
if err := mw.LevelImage(inmin, c.Opts.LevelsGamma, inmax); err != nil {
|
||||
if err := mw.LevelImage(inMin, c.Opts.LevelsGamma, inMax); err != nil {
|
||||
return img, fmt.Errorf("imageLevel: %w", err)
|
||||
}
|
||||
|
||||
if err := mw.LevelImage(-outmin, 1.0, quantumRange+(quantumRange-outmax)); err != nil {
|
||||
if err := mw.LevelImage(-outMin, 1.0, quantumRange+(quantumRange-outMax)); err != nil {
|
||||
return img, fmt.Errorf("imageLevel: %w", err)
|
||||
}
|
||||
|
||||
@@ -544,9 +564,10 @@ func (c *Convertor) imageLevel(img image.Image) (image.Image, error) {
|
||||
var i image.Image
|
||||
i, err = c.imageDecode(bytes.NewReader(blob), "levels")
|
||||
if err != nil {
|
||||
e := err
|
||||
i, err = c.imDecode(bytes.NewReader(blob), "levels")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("imageLevel: %w", err)
|
||||
return nil, fmt.Errorf("imageLevel: %w: %w", e, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -614,10 +635,6 @@ func (c *Convertor) imageEncode(img image.Image, fileName string) error {
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
if c.Opts.Grayscale {
|
||||
img = imageToGray(img)
|
||||
}
|
||||
|
||||
switch filepath.Ext(fileName) {
|
||||
case ".png":
|
||||
err = png.Encode(file, img)
|
||||
@@ -659,12 +676,6 @@ func (c *Convertor) imEncode(i image.Image, fileName string) error {
|
||||
return fmt.Errorf("imEncode: %w", err)
|
||||
}
|
||||
|
||||
if c.Opts.Grayscale {
|
||||
if err := mw.TransformImageColorspace(imagick.COLORSPACE_GRAY); err != nil {
|
||||
return fmt.Errorf("imEncode: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
switch filepath.Ext(fileName) {
|
||||
case ".png":
|
||||
if err := mw.SetImageFormat("PNG"); err != nil {
|
||||
@@ -753,14 +764,14 @@ func (c *Convertor) archiveSaveZip(fileName string) error {
|
||||
|
||||
var zipName string
|
||||
if c.Opts.Recursive {
|
||||
err := os.MkdirAll(filepath.Join(c.Opts.Outdir, filepath.Dir(fileName)), 0755)
|
||||
err := os.MkdirAll(filepath.Join(c.Opts.OutDir, filepath.Dir(fileName)), 0755)
|
||||
if err != nil {
|
||||
return fmt.Errorf("archiveSaveZip: %w", err)
|
||||
}
|
||||
|
||||
zipName = filepath.Join(c.Opts.Outdir, filepath.Dir(fileName), fmt.Sprintf("%s%s.cbz", c.baseNoExt(fileName), c.Opts.Suffix))
|
||||
zipName = filepath.Join(c.Opts.OutDir, filepath.Dir(fileName), fmt.Sprintf("%s%s.cbz", c.baseNoExt(fileName), c.Opts.Suffix))
|
||||
} else {
|
||||
zipName = filepath.Join(c.Opts.Outdir, fmt.Sprintf("%s%s.cbz", c.baseNoExt(fileName), c.Opts.Suffix))
|
||||
zipName = filepath.Join(c.Opts.OutDir, fmt.Sprintf("%s%s.cbz", c.baseNoExt(fileName), c.Opts.Suffix))
|
||||
}
|
||||
|
||||
zipFile, err := os.Create(zipName)
|
||||
@@ -827,14 +838,14 @@ func (c *Convertor) archiveSaveTar(fileName string) error {
|
||||
|
||||
var tarName string
|
||||
if c.Opts.Recursive {
|
||||
err := os.MkdirAll(filepath.Join(c.Opts.Outdir, filepath.Dir(fileName)), 0755)
|
||||
err := os.MkdirAll(filepath.Join(c.Opts.OutDir, filepath.Dir(fileName)), 0755)
|
||||
if err != nil {
|
||||
return fmt.Errorf("archiveSaveTar: %w", err)
|
||||
}
|
||||
|
||||
tarName = filepath.Join(c.Opts.Outdir, filepath.Dir(fileName), fmt.Sprintf("%s%s.cbt", c.baseNoExt(fileName), c.Opts.Suffix))
|
||||
tarName = filepath.Join(c.Opts.OutDir, filepath.Dir(fileName), fmt.Sprintf("%s%s.cbt", c.baseNoExt(fileName), c.Opts.Suffix))
|
||||
} else {
|
||||
tarName = filepath.Join(c.Opts.Outdir, fmt.Sprintf("%s%s.cbt", c.baseNoExt(fileName), c.Opts.Suffix))
|
||||
tarName = filepath.Join(c.Opts.OutDir, fmt.Sprintf("%s%s.cbt", c.baseNoExt(fileName), c.Opts.Suffix))
|
||||
}
|
||||
|
||||
tarFile, err := os.Create(tarName)
|
||||
@@ -916,6 +927,7 @@ func (c *Convertor) archiveComment(fileName string) (string, error) {
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("archiveComment: %w", err)
|
||||
}
|
||||
defer zr.Close()
|
||||
|
||||
return zr.Comment, nil
|
||||
}
|
||||
@@ -1178,9 +1190,10 @@ func (c *Convertor) coverArchive(fileName string) (image.Image, error) {
|
||||
var img image.Image
|
||||
img, err = c.imageDecode(bytes.NewReader(data), cover)
|
||||
if err != nil {
|
||||
e := err
|
||||
img, err = c.imDecode(bytes.NewReader(data), cover)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("coverArchive: %w", err)
|
||||
return nil, fmt.Errorf("coverArchive: %w: %w", e, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1223,9 +1236,15 @@ func (c *Convertor) coverDirectory(dir string) (image.Image, error) {
|
||||
var img image.Image
|
||||
img, err = c.imageDecode(file, cover)
|
||||
if err != nil {
|
||||
e := err
|
||||
_, err = file.Seek(0, io.SeekStart)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("coverDirectory: %w: %w", e, err)
|
||||
}
|
||||
|
||||
img, err = c.imDecode(file, cover)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("coverDirectory: %w", err)
|
||||
return nil, fmt.Errorf("coverDirectory: %w: %w", e, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1416,7 +1435,7 @@ func (c *Convertor) isNonImage(f string) bool {
|
||||
// isSize checks size of file.
|
||||
func (c *Convertor) isSize(size int64) bool {
|
||||
if c.Opts.Size > 0 {
|
||||
if size < c.Opts.Size*(1024*1024) {
|
||||
if size < int64(c.Opts.Size)*(1024*1024) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -1439,6 +1458,13 @@ func (c *Convertor) baseNoExt(filename string) string {
|
||||
return strings.TrimSuffix(filepath.Base(filename), filepath.Ext(filename))
|
||||
}
|
||||
|
||||
// tempName generates a temporary name.
|
||||
func (c *Convertor) tempName(prefix, suffix string) string {
|
||||
randBytes := make([]byte, 16)
|
||||
_, _ = rand.Read(randBytes)
|
||||
return filepath.Join(os.TempDir(), prefix+hex.EncodeToString(randBytes)+suffix)
|
||||
}
|
||||
|
||||
// copyFile copies reader to file.
|
||||
func (c *Convertor) copyFile(reader io.Reader, filename string) error {
|
||||
err := os.MkdirAll(filepath.Dir(filename), 0755)
|
||||
@@ -1471,8 +1497,17 @@ func (c *Convertor) Terminate() {
|
||||
}
|
||||
|
||||
// Files returns list of found comic files.
|
||||
func (c *Convertor) Files(args []string) ([]string, error) {
|
||||
var files []string
|
||||
func (c *Convertor) Files(args []string) ([]File, error) {
|
||||
var files []File
|
||||
|
||||
toFile := func(fp string, f os.FileInfo) File {
|
||||
var file File
|
||||
file.Name = filepath.Base(fp)
|
||||
file.Path = fp
|
||||
file.Stat = f
|
||||
file.SizeHuman = humanize.IBytes(uint64(f.Size()))
|
||||
return file
|
||||
}
|
||||
|
||||
walkFiles := func(fp string, f os.FileInfo, err error) error {
|
||||
if f.IsDir() {
|
||||
@@ -1480,7 +1515,7 @@ func (c *Convertor) Files(args []string) ([]string, error) {
|
||||
}
|
||||
if c.isArchive(fp) || c.isDocument(fp) {
|
||||
if c.isSize(f.Size()) {
|
||||
files = append(files, fp)
|
||||
files = append(files, toFile(fp, f))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1502,7 +1537,7 @@ func (c *Convertor) Files(args []string) ([]string, error) {
|
||||
}
|
||||
|
||||
if count > 1 {
|
||||
files = append(files, fp)
|
||||
files = append(files, toFile(fp, f))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1523,7 +1558,7 @@ func (c *Convertor) Files(args []string) ([]string, error) {
|
||||
if !stat.IsDir() {
|
||||
if c.isArchive(path) || c.isDocument(path) {
|
||||
if c.isSize(stat.Size()) {
|
||||
files = append(files, path)
|
||||
files = append(files, toFile(path, stat))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -1544,7 +1579,7 @@ func (c *Convertor) Files(args []string) ([]string, error) {
|
||||
return files, fmt.Errorf("%s: %w", arg, err)
|
||||
}
|
||||
if c.isSize(info.Size()) {
|
||||
files = append(files, filepath.Join(path, f.Name()))
|
||||
files = append(files, toFile(filepath.Join(path, f.Name()), info))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1557,7 +1592,7 @@ func (c *Convertor) Files(args []string) ([]string, error) {
|
||||
return files, fmt.Errorf("%s: %w", arg, err)
|
||||
}
|
||||
} else {
|
||||
files = append(files, path)
|
||||
files = append(files, toFile(path, stat))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1587,14 +1622,14 @@ func (c *Convertor) Cover(fileName string, fileInfo os.FileInfo) error {
|
||||
|
||||
var fName string
|
||||
if c.Opts.Recursive {
|
||||
err := os.MkdirAll(filepath.Join(c.Opts.Outdir, filepath.Dir(fileName)), 0755)
|
||||
err := os.MkdirAll(filepath.Join(c.Opts.OutDir, filepath.Dir(fileName)), 0755)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: %w", fileName, err)
|
||||
}
|
||||
|
||||
fName = filepath.Join(c.Opts.Outdir, filepath.Dir(fileName), fmt.Sprintf("%s.%s", c.baseNoExt(fileName), c.Opts.Format))
|
||||
fName = filepath.Join(c.Opts.OutDir, filepath.Dir(fileName), fmt.Sprintf("%s.%s", c.baseNoExt(fileName), c.Opts.Format))
|
||||
} else {
|
||||
fName = filepath.Join(c.Opts.Outdir, fmt.Sprintf("%s.%s", c.baseNoExt(fileName), c.Opts.Format))
|
||||
fName = filepath.Join(c.Opts.OutDir, fmt.Sprintf("%s.%s", c.baseNoExt(fileName), c.Opts.Format))
|
||||
}
|
||||
|
||||
switch c.Opts.Format {
|
||||
@@ -1612,10 +1647,10 @@ func (c *Convertor) Cover(fileName string, fileInfo os.FileInfo) error {
|
||||
}
|
||||
|
||||
// Thumbnail extracts thumbnail.
|
||||
func (c *Convertor) Thumbnail(fileName string, info os.FileInfo) error {
|
||||
func (c *Convertor) Thumbnail(fileName string, fileInfo os.FileInfo) error {
|
||||
c.CurrFile++
|
||||
|
||||
cover, err := c.coverImage(fileName, info)
|
||||
cover, err := c.coverImage(fileName, fileInfo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: %w", fileName, err)
|
||||
}
|
||||
@@ -1642,21 +1677,21 @@ func (c *Convertor) Thumbnail(fileName string, info os.FileInfo) error {
|
||||
var fName string
|
||||
var fURI string
|
||||
|
||||
if c.Opts.Outfile == "" {
|
||||
if c.Opts.OutFile == "" {
|
||||
fURI = "file://" + fileName
|
||||
|
||||
if c.Opts.Recursive {
|
||||
err := os.MkdirAll(filepath.Join(c.Opts.Outdir, filepath.Dir(fileName)), 0755)
|
||||
err := os.MkdirAll(filepath.Join(c.Opts.OutDir, filepath.Dir(fileName)), 0755)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: %w", fileName, err)
|
||||
}
|
||||
|
||||
fName = filepath.Join(c.Opts.Outdir, filepath.Dir(fileName), fmt.Sprintf("%x.png", md5.Sum([]byte(fURI))))
|
||||
fName = filepath.Join(c.Opts.OutDir, filepath.Dir(fileName), fmt.Sprintf("%x.png", md5.Sum([]byte(fURI))))
|
||||
} else {
|
||||
fName = filepath.Join(c.Opts.Outdir, fmt.Sprintf("%x.png", md5.Sum([]byte(fURI))))
|
||||
fName = filepath.Join(c.Opts.OutDir, fmt.Sprintf("%x.png", md5.Sum([]byte(fURI))))
|
||||
}
|
||||
} else {
|
||||
abs, _ := filepath.Abs(c.Opts.Outfile)
|
||||
abs, _ := filepath.Abs(c.Opts.OutFile)
|
||||
fURI = "file://" + abs
|
||||
fName = abs
|
||||
}
|
||||
@@ -1667,19 +1702,16 @@ func (c *Convertor) Thumbnail(fileName string, info os.FileInfo) error {
|
||||
if err := mw.SetImageProperty("Software", "CBconvert"); err != nil {
|
||||
return fmt.Errorf("%s: %w", fileName, err)
|
||||
}
|
||||
if err := mw.SetImageProperty("Description", "Thumbnail of "+fURI); err != nil {
|
||||
if err := mw.SetImageProperty("Description", "Thumbnail of "+fileName); err != nil {
|
||||
return fmt.Errorf("%s: %w", fileName, err)
|
||||
}
|
||||
if err := mw.SetImageProperty("Thumb::URI", fURI); err != nil {
|
||||
return fmt.Errorf("%s: %w", fileName, err)
|
||||
}
|
||||
if err := mw.SetImageProperty("Thumb::MTime", strconv.FormatInt(info.ModTime().Unix(), 10)); err != nil {
|
||||
if err := mw.SetImageProperty("Thumb::MTime", strconv.FormatInt(fileInfo.ModTime().Unix(), 10)); err != nil {
|
||||
return fmt.Errorf("%s: %w", fileName, err)
|
||||
}
|
||||
if err := mw.SetImageProperty("Thumb::Size", strconv.FormatInt(info.Size(), 10)); err != nil {
|
||||
return fmt.Errorf("%s: %w", fileName, err)
|
||||
}
|
||||
if err := mw.SetImageProperty("Thumb::Mimetype", mime.TypeByExtension(filepath.Ext(fileName))); err != nil {
|
||||
if err := mw.SetImageProperty("Thumb::Size", strconv.FormatInt(fileInfo.Size(), 10)); err != nil {
|
||||
return fmt.Errorf("%s: %w", fileName, err)
|
||||
}
|
||||
|
||||
@@ -1738,12 +1770,88 @@ func (c *Convertor) Meta(fileName string) (any, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// Preview returns image preview.
|
||||
func (c *Convertor) Preview(fileName string, fileInfo os.FileInfo, width, height int) (Image, error) {
|
||||
var img Image
|
||||
|
||||
i, err := c.coverImage(fileName, fileInfo)
|
||||
if err != nil {
|
||||
return img, fmt.Errorf("%s: %w", fileName, err)
|
||||
}
|
||||
|
||||
i = c.imageTransform(i)
|
||||
|
||||
if c.Opts.LevelsInMin != 0 || c.Opts.LevelsInMax != 255 || c.Opts.LevelsGamma != 1.0 ||
|
||||
c.Opts.LevelsOutMin != 0 || c.Opts.LevelsOutMax != 255 {
|
||||
i, err = c.imageLevel(i)
|
||||
if err != nil {
|
||||
return img, fmt.Errorf("%s: %w", fileName, err)
|
||||
}
|
||||
}
|
||||
|
||||
tmpName := c.tempName("cbc", "."+c.Opts.Format)
|
||||
|
||||
switch c.Opts.Format {
|
||||
case "jpeg", "png", "tiff", "webp", "avif":
|
||||
if err := c.imageEncode(i, tmpName); err != nil {
|
||||
return img, fmt.Errorf("%s: %w", fileName, err)
|
||||
}
|
||||
case "bmp":
|
||||
if err := c.imEncode(i, tmpName); err != nil {
|
||||
return img, fmt.Errorf("%s: %w", fileName, err)
|
||||
}
|
||||
}
|
||||
|
||||
stat, err := os.Stat(tmpName)
|
||||
if err != nil {
|
||||
return img, fmt.Errorf("%s: %w", fileName, err)
|
||||
}
|
||||
|
||||
img.Width = i.Bounds().Dx()
|
||||
img.Height = i.Bounds().Dy()
|
||||
img.SizeHuman = humanize.IBytes(uint64(stat.Size()))
|
||||
|
||||
f, err := os.Open(tmpName)
|
||||
if err != nil {
|
||||
return img, fmt.Errorf("%s: %w", fileName, err)
|
||||
}
|
||||
|
||||
defer os.Remove(tmpName)
|
||||
|
||||
dec, err := c.imageDecode(f, tmpName)
|
||||
if err != nil {
|
||||
e := err
|
||||
_, err = f.Seek(0, io.SeekStart)
|
||||
if err != nil {
|
||||
return img, fmt.Errorf("%s: %w: %w", tmpName, e, err)
|
||||
}
|
||||
|
||||
dec, err = c.imDecode(f, tmpName)
|
||||
if err != nil {
|
||||
return img, fmt.Errorf("%s: %w: %w", tmpName, e, err)
|
||||
}
|
||||
}
|
||||
|
||||
err = f.Close()
|
||||
if err != nil {
|
||||
return img, fmt.Errorf("%s: %w", fileName, err)
|
||||
}
|
||||
|
||||
if width != 0 && height != 0 {
|
||||
dec = imaging.Fit(dec, width, height, filters[c.Opts.Filter])
|
||||
}
|
||||
|
||||
img.Image = dec
|
||||
|
||||
return img, nil
|
||||
}
|
||||
|
||||
// Convert converts comic book.
|
||||
func (c *Convertor) Convert(fileName string, info os.FileInfo) error {
|
||||
func (c *Convertor) Convert(fileName string, fileInfo os.FileInfo) error {
|
||||
c.CurrFile++
|
||||
|
||||
switch {
|
||||
case info.IsDir():
|
||||
case fileInfo.IsDir():
|
||||
if err := c.convertDirectory(fileName); err != nil {
|
||||
return fmt.Errorf("%s: %w", fileName, err)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
package cbconvert
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestConvert(t *testing.T) {
|
||||
tmpDir, err := os.MkdirTemp(os.TempDir(), "cbc")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
opts := Options{}
|
||||
opts.OutDir = tmpDir
|
||||
opts.Archive = "zip"
|
||||
opts.Quality = 75
|
||||
opts.Filter = 2
|
||||
|
||||
conv := New(opts)
|
||||
|
||||
conv.Initialize()
|
||||
defer conv.Terminate()
|
||||
|
||||
files, err := conv.Files([]string{"testdata/test", "testdata"})
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
for _, format := range []string{"jpeg", "png", "tiff", "bmp", "webp", "avif"} {
|
||||
conv.Opts.Format = format
|
||||
|
||||
for _, file := range files {
|
||||
conv.Opts.Suffix = fmt.Sprintf("_%s%s", format, filepath.Ext(file.Path))
|
||||
|
||||
err = conv.Convert(file.Path, file.Stat)
|
||||
if err != nil {
|
||||
t.Errorf("format %s: file %s: %v", format, file.Name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
err = os.RemoveAll(tmpDir)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCover(t *testing.T) {
|
||||
tmpDir, err := os.MkdirTemp(os.TempDir(), "cbc")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
opts := Options{}
|
||||
opts.OutDir = tmpDir
|
||||
opts.Quality = 75
|
||||
opts.Filter = 2
|
||||
opts.Format = "jpeg"
|
||||
|
||||
conv := New(opts)
|
||||
|
||||
conv.Initialize()
|
||||
defer conv.Terminate()
|
||||
|
||||
files, err := conv.Files([]string{"testdata/test.cbt"})
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
err = conv.Cover(file.Path, file.Stat)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
err = os.RemoveAll(tmpDir)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestThumbnail(t *testing.T) {
|
||||
tmpDir, err := os.MkdirTemp(os.TempDir(), "cbc")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
opts := Options{}
|
||||
opts.OutDir = tmpDir
|
||||
opts.Filter = 2
|
||||
|
||||
conv := New(opts)
|
||||
|
||||
conv.Initialize()
|
||||
defer conv.Terminate()
|
||||
|
||||
files, err := conv.Files([]string{"testdata/test.pdf"})
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
err = conv.Thumbnail(file.Path, file.Stat)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
err = os.RemoveAll(tmpDir)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@ go 1.21
|
||||
toolchain go1.21.0
|
||||
|
||||
require (
|
||||
github.com/gen2brain/cbconvert v0.0.0-20230824094314-e938b140f074
|
||||
github.com/gen2brain/cbconvert v0.0.0-20230902080208-6936f697b466
|
||||
github.com/schollz/progressbar/v3 v3.10.0
|
||||
github.com/spf13/pflag v1.0.5
|
||||
)
|
||||
@@ -13,6 +13,7 @@ require (
|
||||
require (
|
||||
github.com/chai2010/webp v1.1.1 // indirect
|
||||
github.com/disintegration/imaging v1.6.2 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // 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
|
||||
|
||||
@@ -5,10 +5,12 @@ 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/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/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/cbconvert v0.0.0-20230902080208-6936f697b466 h1:xrc1+L/Wc9BHokp+smaBt/A2R2+nfK0Ws0cvyzTHE7I=
|
||||
github.com/gen2brain/cbconvert v0.0.0-20230902080208-6936f697b466/go.mod h1:5CIPgEHKNZtfH7FR0zzGMAzkFwagUyH7FcjIUDEx7Ps=
|
||||
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=
|
||||
|
||||
+67
-38
@@ -7,6 +7,7 @@ import (
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"runtime/debug"
|
||||
"syscall"
|
||||
|
||||
"github.com/gen2brain/cbconvert"
|
||||
@@ -14,8 +15,43 @@ import (
|
||||
flag "github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
var appVersion string
|
||||
|
||||
func init() {
|
||||
if appVersion != "" {
|
||||
return
|
||||
}
|
||||
|
||||
info, ok := debug.ReadBuildInfo()
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
if info.Main.Version != "" {
|
||||
appVersion = info.Main.Version
|
||||
}
|
||||
|
||||
for _, kv := range info.Settings {
|
||||
if kv.Value == "" {
|
||||
continue
|
||||
}
|
||||
if kv.Key == "vcs.revision" {
|
||||
appVersion = kv.Value
|
||||
if len(appVersion) > 10 {
|
||||
appVersion = kv.Value[:10]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
opts, args := parseFlags()
|
||||
|
||||
if opts.Version {
|
||||
fmt.Println(filepath.Base(os.Args[0]), appVersion)
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
conv := cbconvert.New(opts)
|
||||
|
||||
c := make(chan os.Signal, 2)
|
||||
@@ -30,8 +66,8 @@ func main() {
|
||||
}
|
||||
}()
|
||||
|
||||
if _, err := os.Stat(opts.Outdir); err != nil {
|
||||
if err := os.MkdirAll(opts.Outdir, 0775); err != nil {
|
||||
if _, err := os.Stat(opts.OutDir); err != nil {
|
||||
if err := os.MkdirAll(opts.OutDir, 0775); err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
os.Exit(1)
|
||||
@@ -83,15 +119,9 @@ func main() {
|
||||
}
|
||||
|
||||
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)
|
||||
ret, err := conv.Meta(file.Path)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
@@ -105,14 +135,14 @@ func main() {
|
||||
|
||||
continue
|
||||
case opts.Cover:
|
||||
if err := conv.Cover(file, stat); err != nil {
|
||||
if err := conv.Cover(file.Path, file.Stat); err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
continue
|
||||
case opts.Thumbnail:
|
||||
if err = conv.Thumbnail(file, stat); err != nil {
|
||||
if err = conv.Thumbnail(file.Path, file.Stat); err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
@@ -120,7 +150,7 @@ func main() {
|
||||
continue
|
||||
}
|
||||
|
||||
if err := conv.Convert(file, stat); err != nil {
|
||||
if err := conv.Convert(file.Path, file.Stat); err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
@@ -150,17 +180,16 @@ func parseFlags() (cbconvert.Options, []string) {
|
||||
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.IntVar(&opts.Brightness, "brightness", 0, "Adjust the brightness of the images, must be in the range (-100, 100)")
|
||||
convert.IntVar(&opts.Contrast, "contrast", 0, "Adjust the contrast of the images, must be in the range (-100, 100)")
|
||||
convert.StringVar(&opts.Suffix, "suffix", "", "Add suffix to file basename")
|
||||
convert.Float64Var(&opts.LevelsInMin, "levels-inmin", 0, "Shadow input value")
|
||||
convert.IntVar(&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.IntVar(&opts.LevelsInMax, "levels-inmax", 255, "Highlight input value")
|
||||
convert.IntVar(&opts.LevelsOutMin, "levels-outmin", 0, "Shadow output value")
|
||||
convert.IntVar(&opts.LevelsOutMax, "levels-outmax", 255, "Highlight output value")
|
||||
convert.StringVar(&opts.OutDir, "outdir", ".", "Output directory")
|
||||
convert.IntVar(&opts.Size, "size", 0, "Process only files larger than size (in MB)")
|
||||
convert.BoolVar(&opts.Recursive, "recursive", false, "Process subdirectories recursively")
|
||||
convert.BoolVar(&opts.Quiet, "quiet", false, "Hide console output")
|
||||
|
||||
@@ -172,8 +201,8 @@ func parseFlags() (cbconvert.Options, []string) {
|
||||
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.StringVar(&opts.OutDir, "outdir", ".", "Output directory")
|
||||
cover.IntVar(&opts.Size, "size", 0, "Process only files larger than size (in MB)")
|
||||
cover.BoolVar(&opts.Recursive, "recursive", false, "Process subdirectories recursively")
|
||||
cover.BoolVar(&opts.Quiet, "quiet", false, "Hide console output")
|
||||
|
||||
@@ -183,9 +212,9 @@ func parseFlags() (cbconvert.Options, []string) {
|
||||
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.StringVar(&opts.OutDir, "outdir", ".", "Output directory")
|
||||
thumbnail.StringVar(&opts.OutFile, "outfile", "", "Output file")
|
||||
thumbnail.IntVar(&opts.Size, "size", 0, "Process only files larger than size (in MB)")
|
||||
thumbnail.BoolVar(&opts.Recursive, "recursive", false, "Process subdirectories recursively")
|
||||
thumbnail.BoolVar(&opts.Quiet, "quiet", false, "Hide console output")
|
||||
|
||||
@@ -197,34 +226,32 @@ func parseFlags() (cbconvert.Options, []string) {
|
||||
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() {
|
||||
flag.NewFlagSet("version", flag.ExitOnError)
|
||||
|
||||
flag.Usage = func() {
|
||||
_, _ = fmt.Fprintf(os.Stderr, "Usage: %s <command> [<flags>] [file1 dir1 ... fileOrDirN]\n\n", filepath.Base(os.Args[0]))
|
||||
_, _ = fmt.Fprintf(os.Stderr, "\nCommands:\n")
|
||||
_, _ = fmt.Fprintf(os.Stderr, "\n convert\n \tConvert archive or document\n\n")
|
||||
convert.VisitAll(func(f *flag.Flag) {
|
||||
_, _ = fmt.Fprintf(os.Stderr, " --%s", f.Name)
|
||||
_, _ = fmt.Fprintf(os.Stderr, "\n \t")
|
||||
_, _ = fmt.Fprintf(os.Stderr, " --%s\n \t", f.Name)
|
||||
_, _ = fmt.Fprintf(os.Stderr, "%v (default %q)\n", f.Usage, f.DefValue)
|
||||
})
|
||||
_, _ = fmt.Fprintf(os.Stderr, "\n cover\n \tExtract cover\n\n")
|
||||
cover.VisitAll(func(f *flag.Flag) {
|
||||
_, _ = fmt.Fprintf(os.Stderr, " --%s", f.Name)
|
||||
_, _ = fmt.Fprintf(os.Stderr, "\n \t")
|
||||
_, _ = fmt.Fprintf(os.Stderr, " --%s\n \t", f.Name)
|
||||
_, _ = fmt.Fprintf(os.Stderr, "%v (default %q)\n", f.Usage, f.DefValue)
|
||||
})
|
||||
_, _ = fmt.Fprintf(os.Stderr, "\n thumbnail\n \tExtract cover thumbnail (freedesktop spec.)\n\n")
|
||||
thumbnail.VisitAll(func(f *flag.Flag) {
|
||||
_, _ = fmt.Fprintf(os.Stderr, " --%s", f.Name)
|
||||
_, _ = fmt.Fprintf(os.Stderr, "\n \t")
|
||||
_, _ = fmt.Fprintf(os.Stderr, " --%s\n \t", f.Name)
|
||||
_, _ = fmt.Fprintf(os.Stderr, "%v (default %q)\n", f.Usage, f.DefValue)
|
||||
})
|
||||
_, _ = fmt.Fprintf(os.Stderr, "\n meta\n \tCBZ metadata\n\n")
|
||||
meta.VisitAll(func(f *flag.Flag) {
|
||||
_, _ = fmt.Fprintf(os.Stderr, " --%s", f.Name)
|
||||
_, _ = fmt.Fprintf(os.Stderr, "\n \t")
|
||||
_, _ = fmt.Fprintf(os.Stderr, " --%s\n \t", f.Name)
|
||||
_, _ = fmt.Fprintf(os.Stderr, "%v (default %q)\n", f.Usage, f.DefValue)
|
||||
})
|
||||
_, _ = fmt.Fprintf(os.Stderr, "\n")
|
||||
_, _ = fmt.Fprintf(os.Stderr, "\n version\n \tPrint version\n\n")
|
||||
}
|
||||
|
||||
if len(os.Args) < 2 {
|
||||
@@ -262,10 +289,12 @@ func parseFlags() (cbconvert.Options, []string) {
|
||||
if !pipe {
|
||||
args = meta.Args()
|
||||
}
|
||||
case "version":
|
||||
opts.Version = true
|
||||
}
|
||||
|
||||
if len(args) == 0 {
|
||||
convert.Usage()
|
||||
if len(args) == 0 && !opts.Version {
|
||||
flag.Usage()
|
||||
_, _ = fmt.Fprintf(os.Stderr, "no arguments\n")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
+5
-18
@@ -1,7 +1,6 @@
|
||||
#!/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"
|
||||
@@ -17,22 +16,10 @@ 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'" && \
|
||||
go build -trimpath -tags 'extlib pkgconfig' -v -o ${BUILDDIR}/cbconvert -ldflags "-linkmode external -s -w -X main.appVersion=${VERSION} '-extldflags=-static'" && \
|
||||
cp ../../README.md ../../AUTHORS ../../COPYING ${BUILDDIR} && tar -czf "${BUILDDIR}-linux-x86_64.tar.gz" ${BUILDDIR}
|
||||
rm -rf ${BUILDDIR}
|
||||
|
||||
#BUILDDIR="cbconvert-${VERSION}"; mkdir -p ${BUILDDIR}
|
||||
#CC=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" \
|
||||
@@ -41,7 +28,7 @@ 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'" && \
|
||||
go build -trimpath -tags 'extlib pkgconfig' -v -o ${BUILDDIR}/cbconvert -ldflags "-linkmode external -s -w -X main.appVersion=${VERSION} '-extldflags=-static'" && \
|
||||
cp ../../README.md ../../AUTHORS ../../COPYING ${BUILDDIR} && tar -czf "${BUILDDIR}-linux-aarch64.tar.gz" ${BUILDDIR}
|
||||
rm -rf ${BUILDDIR}
|
||||
|
||||
@@ -53,7 +40,7 @@ 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'" && \
|
||||
go build -trimpath -tags 'extlib pkgconfig' -v -o ${BUILDDIR}/cbconvert.exe -ldflags "-s -w -X main.appVersion=${VERSION} '-extldflags=-static -Wl,--allow-multiple-definition'" && \
|
||||
cp ../../README.md ../../AUTHORS ../../COPYING ${BUILDDIR} && zip -rq "${BUILDDIR}-windows-x86_64.zip" ${BUILDDIR}
|
||||
rm -rf ${BUILDDIR}
|
||||
|
||||
@@ -67,7 +54,7 @@ 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" && \
|
||||
go build -trimpath -tags 'extlib pkgconfig' -v -o ${BUILDDIR}/cbconvert -ldflags "-linkmode external -s -w -X main.appVersion=${VERSION}" && \
|
||||
cp ../../README.md ../../AUTHORS ../../COPYING ${BUILDDIR} && zip -rq "${BUILDDIR}-darwin-x86_64.zip" ${BUILDDIR}
|
||||
rm -rf ${BUILDDIR}
|
||||
|
||||
@@ -81,6 +68,6 @@ 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" && \
|
||||
go build -trimpath -tags 'extlib pkgconfig' -v -o ${BUILDDIR}/cbconvert -ldflags "-linkmode external -s -w -X main.appVersion=${VERSION}" && \
|
||||
cp ../../README.md ../../AUTHORS ../../COPYING ${BUILDDIR} && zip -rq "${BUILDDIR}-darwin-aarch64.zip" ${BUILDDIR}
|
||||
rm -rf ${BUILDDIR}
|
||||
|
||||
@@ -5,6 +5,7 @@ go 1.21
|
||||
require (
|
||||
github.com/chai2010/webp v1.1.1
|
||||
github.com/disintegration/imaging v1.6.2
|
||||
github.com/dustin/go-humanize v1.0.1
|
||||
github.com/fvbommel/sortorder v1.1.0
|
||||
github.com/gen2brain/go-fitz v1.23.1
|
||||
github.com/gen2brain/go-unarr v0.1.7
|
||||
|
||||
@@ -2,6 +2,8 @@ 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/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/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=
|
||||
|
||||
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
|
After Width: | Height: | Size: 229 KiB |
Vendored
BIN
Binary file not shown.
|
After Width: | Height: | Size: 387 KiB |
Reference in New Issue
Block a user