mirror of
https://github.com/gen2brain/cbconvert
synced 2025-10-14 10:38:51 +02:00
Add support for AVIF
This commit is contained in:
14
README.md
14
README.md
@@ -10,7 +10,7 @@ It can convert comics to different formats to fit your various devices.
|
|||||||
|
|
||||||
* reads RAR, ZIP, 7Z, CBR, CBZ, CB7, CBT, PDF, EPUB, and plain directory
|
* reads RAR, ZIP, 7Z, CBR, CBZ, CB7, CBT, PDF, EPUB, and plain directory
|
||||||
* always saves processed comics in CBZ (ZIP) archive format
|
* always saves processed comics in CBZ (ZIP) archive format
|
||||||
* images can be converted to JPEG, PNG, TIFF, WEBP, or 4-Bit BMP (16 colors) file format
|
* 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
|
* rotate, flip, adjust brightness/contrast, adjust levels (Photoshop-like) or grayscale images
|
||||||
* resize algorithms (NearestNeighbor, Box, Linear, MitchellNetravali, CatmullRom, Gaussian, Lanczos)
|
* resize algorithms (NearestNeighbor, Box, Linear, MitchellNetravali, CatmullRom, Gaussian, Lanczos)
|
||||||
* export covers from comics
|
* export covers from comics
|
||||||
@@ -18,8 +18,10 @@ It can convert comics to different formats to fit your various devices.
|
|||||||
|
|
||||||
### Download
|
### Download
|
||||||
|
|
||||||
* [Windows](https://github.com/gen2brain/cbconvert/releases/download/0.7.0/cbconvert-0.7.0-windows-i686.zip)
|
* [Windows x86_64](https://github.com/gen2brain/cbconvert/releases/download/0.8.0/cbconvert-0.8.0-windows-x86_64.zip)
|
||||||
* [Linux](https://github.com/gen2brain/cbconvert/releases/download/0.7.0/cbconvert-0.7.0-linux-x86_64.tar.gz)
|
* [Linux x86_64](https://github.com/gen2brain/cbconvert/releases/download/0.8.0/cbconvert-0.8.0-linux-x86_64.tar.gz)
|
||||||
|
* [macOS x86_64](https://github.com/gen2brain/cbconvert/releases/download/0.8.0/cbconvert-0.8.0-darwin-x86_64.tar.gz)
|
||||||
|
* [macOS aarch64](https://github.com/gen2brain/cbconvert/releases/download/0.8.0/cbconvert-0.8.0-darwin-aarch64.tar.gz)
|
||||||
|
|
||||||
### Using cbconvert in file managers to generate FreeDesktop thumbnails
|
### Using cbconvert in file managers to generate FreeDesktop thumbnails
|
||||||
|
|
||||||
@@ -55,9 +57,11 @@ This is what it looks like in the PCManFM file manager:
|
|||||||
--fit
|
--fit
|
||||||
Best fit for required width and height (default "false")
|
Best fit for required width and height (default "false")
|
||||||
--format
|
--format
|
||||||
Image format, valid values are jpeg, png, tiff, bmp, webp (default "jpeg")
|
Image format, valid values are jpeg, png, tiff, bmp, webp, avif (default "jpeg")
|
||||||
--quality
|
--quality
|
||||||
JPEG image quality (default "75")
|
Image quality (default "75")
|
||||||
|
--lossless
|
||||||
|
Lossless compression (avif) (default "false")
|
||||||
--filter
|
--filter
|
||||||
0=NearestNeighbor, 1=Box, 2=Linear, 3=MitchellNetravali, 4=CatmullRom, 6=Gaussian, 7=Lanczos (default "2")
|
0=NearestNeighbor, 1=Box, 2=Linear, 3=MitchellNetravali, 4=CatmullRom, 6=Gaussian, 7=Lanczos (default "2")
|
||||||
--no-cover
|
--no-cover
|
||||||
|
70
cbconvert.go
70
cbconvert.go
@@ -24,6 +24,7 @@ import (
|
|||||||
|
|
||||||
"github.com/chai2010/webp"
|
"github.com/chai2010/webp"
|
||||||
_ "github.com/hotei/bmp"
|
_ "github.com/hotei/bmp"
|
||||||
|
"github.com/strukturag/libheif/go/heif"
|
||||||
"golang.org/x/image/tiff"
|
"golang.org/x/image/tiff"
|
||||||
|
|
||||||
"github.com/disintegration/imaging"
|
"github.com/disintegration/imaging"
|
||||||
@@ -64,10 +65,12 @@ var filters = map[int]imaging.ResampleFilter{
|
|||||||
|
|
||||||
// Options type.
|
// Options type.
|
||||||
type Options struct {
|
type Options struct {
|
||||||
// Image format, valid values are jpeg, png, tiff, bmp, webp
|
// Image format, valid values are jpeg, png, tiff, bmp, webp, avif
|
||||||
Format string
|
Format string
|
||||||
// JPEG image quality
|
// JPEG image quality
|
||||||
Quality int
|
Quality int
|
||||||
|
// Lossless compression (avif)
|
||||||
|
Lossless bool
|
||||||
// Image width
|
// Image width
|
||||||
Width int
|
Width int
|
||||||
// Image height
|
// Image height
|
||||||
@@ -208,6 +211,11 @@ func (c *Convertor) convertImage(ctx context.Context, img image.Image, index int
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
case "avif":
|
||||||
|
err = c.encodeImage(img, fileName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -579,6 +587,47 @@ func (c *Convertor) decodeImage(reader io.Reader, fileName string) (img image.Im
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// decodeIM decodes image from reader (ImageMagick).
|
||||||
|
func (c *Convertor) decodeIM(reader io.Reader, fileName string) (img image.Image, err error) {
|
||||||
|
imagick.Initialize()
|
||||||
|
|
||||||
|
mw := imagick.NewMagickWand()
|
||||||
|
defer mw.Destroy()
|
||||||
|
|
||||||
|
var data []byte
|
||||||
|
var out interface{}
|
||||||
|
|
||||||
|
data, err = io.ReadAll(reader)
|
||||||
|
if err != nil {
|
||||||
|
return img, fmt.Errorf("decodeIM: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = mw.SetFilename(fileName)
|
||||||
|
if err != nil {
|
||||||
|
return img, fmt.Errorf("decodeIM: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = mw.ReadImageBlob(data)
|
||||||
|
if err != nil {
|
||||||
|
return img, fmt.Errorf("decodeIM: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
w := mw.GetImageWidth()
|
||||||
|
h := mw.GetImageHeight()
|
||||||
|
|
||||||
|
out, err = mw.ExportImagePixels(0, 0, w, h, "RGBA", imagick.PIXEL_CHAR)
|
||||||
|
if err != nil {
|
||||||
|
return img, fmt.Errorf("decodeIM: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
b := image.Rect(0, 0, int(w), int(h))
|
||||||
|
rgba := image.NewRGBA(b)
|
||||||
|
rgba.Pix = out.([]byte)
|
||||||
|
img = rgba
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// encodeImage encodes image to file.
|
// encodeImage encodes image to file.
|
||||||
func (c *Convertor) encodeImage(img image.Image, fileName string) error {
|
func (c *Convertor) encodeImage(img image.Image, fileName string) error {
|
||||||
file, err := os.Create(fileName)
|
file, err := os.Create(fileName)
|
||||||
@@ -600,6 +649,18 @@ func (c *Convertor) encodeImage(img image.Image, fileName string) error {
|
|||||||
err = jpeg.Encode(file, img, &jpeg.Options{Quality: c.Opts.Quality})
|
err = jpeg.Encode(file, img, &jpeg.Options{Quality: c.Opts.Quality})
|
||||||
case ".webp":
|
case ".webp":
|
||||||
err = webp.Encode(file, img, &webp.Options{Quality: float32(c.Opts.Quality)})
|
err = webp.Encode(file, img, &webp.Options{Quality: float32(c.Opts.Quality)})
|
||||||
|
case ".avif":
|
||||||
|
img = imageToRGBA(img)
|
||||||
|
lossLess := heif.LosslessModeDisabled
|
||||||
|
if c.Opts.Lossless {
|
||||||
|
lossLess = heif.LosslessModeEnabled
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, err := heif.EncodeFromImage(img, heif.CompressionAV1, c.Opts.Quality, lossLess, 0)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("encodeImage: %w", err)
|
||||||
|
}
|
||||||
|
err = ctx.WriteToFile(fileName)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("encodeImage: %w", err)
|
return fmt.Errorf("encodeImage: %w", err)
|
||||||
@@ -647,6 +708,11 @@ func (c *Convertor) encodeIM(i image.Image, fileName string) error {
|
|||||||
_ = mw.WriteImage(fileName)
|
_ = mw.WriteImage(fileName)
|
||||||
case ".jpg", ".jpeg":
|
case ".jpg", ".jpeg":
|
||||||
_ = mw.SetImageFormat("JPEG")
|
_ = mw.SetImageFormat("JPEG")
|
||||||
|
_ = mw.SetImageCompressionQuality(uint(c.Opts.Quality))
|
||||||
|
_ = mw.WriteImage(fileName)
|
||||||
|
case ".avif":
|
||||||
|
_ = mw.SetImageFormat("AVIF")
|
||||||
|
_ = mw.SetImageCompressionQuality(uint(c.Opts.Quality))
|
||||||
_ = mw.WriteImage(fileName)
|
_ = mw.WriteImage(fileName)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -846,7 +912,7 @@ func (c *Convertor) isDocument(f string) bool {
|
|||||||
|
|
||||||
// isImage checks if file is image.
|
// isImage checks if file is image.
|
||||||
func (c *Convertor) isImage(f string) bool {
|
func (c *Convertor) isImage(f string) bool {
|
||||||
var types = []string{".jpg", ".jpeg", ".png", ".gif", ".bmp", ".tiff", ".tif", ".webp"}
|
var types = []string{".jpg", ".jpeg", ".png", ".gif", ".bmp", ".tiff", ".tif", ".webp", ".avif"}
|
||||||
for _, t := range types {
|
for _, t := range types {
|
||||||
if strings.ToLower(filepath.Ext(f)) == t {
|
if strings.ToLower(filepath.Ext(f)) == t {
|
||||||
return true
|
return true
|
||||||
|
@@ -20,6 +20,7 @@ require (
|
|||||||
github.com/mattn/go-runewidth v0.0.13 // indirect
|
github.com/mattn/go-runewidth v0.0.13 // indirect
|
||||||
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
|
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
|
||||||
github.com/rivo/uniseg v0.3.4 // indirect
|
github.com/rivo/uniseg v0.3.4 // indirect
|
||||||
|
github.com/strukturag/libheif v1.13.0 // indirect
|
||||||
golang.org/x/image v0.0.0-20220902085622-e7cb96979f69 // indirect
|
golang.org/x/image v0.0.0-20220902085622-e7cb96979f69 // indirect
|
||||||
golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde // indirect
|
golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde // indirect
|
||||||
golang.org/x/sys v0.0.0-20220829200755-d48e67d00261 // indirect
|
golang.org/x/sys v0.0.0-20220829200755-d48e67d00261 // indirect
|
||||||
|
@@ -31,6 +31,8 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An
|
|||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
github.com/strukturag/libheif v1.13.0 h1:SuLo/Fl/Nzbw0ixOya1YZSl0Xd27X4fgofGnJdvOHqI=
|
||||||
|
github.com/strukturag/libheif v1.13.0/go.mod h1:E/PNRlmVtrtj9j2AvBZlrO4dsBDu6KfwDZn7X1Ce8Ks=
|
||||||
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||||
golang.org/x/image v0.0.0-20220902085622-e7cb96979f69 h1:Lj6HJGCSn5AjxRAH2+r35Mir4icalbqku+CLUtjnvXY=
|
golang.org/x/image v0.0.0-20220902085622-e7cb96979f69 h1:Lj6HJGCSn5AjxRAH2+r35Mir4icalbqku+CLUtjnvXY=
|
||||||
golang.org/x/image v0.0.0-20220902085622-e7cb96979f69/go.mod h1:doUCurBvlfPMKfmIpRIywoHmhN3VyhnoFDbvIEWF4hY=
|
golang.org/x/image v0.0.0-20220902085622-e7cb96979f69/go.mod h1:doUCurBvlfPMKfmIpRIywoHmhN3VyhnoFDbvIEWF4hY=
|
||||||
|
@@ -120,8 +120,9 @@ func parseFlags() (cbconvert.Options, []string) {
|
|||||||
convert.IntVar(&opts.Width, "width", 0, "Image width")
|
convert.IntVar(&opts.Width, "width", 0, "Image width")
|
||||||
convert.IntVar(&opts.Height, "height", 0, "Image height")
|
convert.IntVar(&opts.Height, "height", 0, "Image height")
|
||||||
convert.BoolVar(&opts.Fit, "fit", false, "Best fit for required width and height")
|
convert.BoolVar(&opts.Fit, "fit", false, "Best fit for required width and height")
|
||||||
convert.StringVar(&opts.Format, "format", "jpeg", "Image format, valid values are jpeg, png, tiff, bmp, webp")
|
convert.StringVar(&opts.Format, "format", "jpeg", "Image format, valid values are jpeg, png, tiff, bmp, webp, avif")
|
||||||
convert.IntVar(&opts.Quality, "quality", 75, "Image quality")
|
convert.IntVar(&opts.Quality, "quality", 75, "Image quality")
|
||||||
|
convert.BoolVar(&opts.Lossless, "lossless", false, "Lossless compression (avif)")
|
||||||
convert.IntVar(&opts.Filter, "filter", 2, "0=NearestNeighbor, 1=Box, 2=Linear, 3=MitchellNetravali, 4=CatmullRom, 6=Gaussian, 7=Lanczos")
|
convert.IntVar(&opts.Filter, "filter", 2, "0=NearestNeighbor, 1=Box, 2=Linear, 3=MitchellNetravali, 4=CatmullRom, 6=Gaussian, 7=Lanczos")
|
||||||
convert.BoolVar(&opts.NoCover, "no-cover", false, "Do not convert the cover image")
|
convert.BoolVar(&opts.NoCover, "no-cover", false, "Do not convert the cover image")
|
||||||
convert.BoolVar(&opts.NoRGB, "no-rgb", false, "Do not convert images that have RGB colorspace")
|
convert.BoolVar(&opts.NoRGB, "no-rgb", false, "Do not convert images that have RGB colorspace")
|
||||||
|
1
go.mod
1
go.mod
@@ -9,6 +9,7 @@ require (
|
|||||||
github.com/gen2brain/go-fitz v1.20.1
|
github.com/gen2brain/go-fitz v1.20.1
|
||||||
github.com/gen2brain/go-unarr v0.1.6
|
github.com/gen2brain/go-unarr v0.1.6
|
||||||
github.com/hotei/bmp v0.0.0-20150430041436-f620cebab0c7
|
github.com/hotei/bmp v0.0.0-20150430041436-f620cebab0c7
|
||||||
|
github.com/strukturag/libheif v1.13.0
|
||||||
golang.org/x/image v0.0.0-20220902085622-e7cb96979f69
|
golang.org/x/image v0.0.0-20220902085622-e7cb96979f69
|
||||||
golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde
|
golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde
|
||||||
gopkg.in/gographics/imagick.v3 v3.4.1
|
gopkg.in/gographics/imagick.v3 v3.4.1
|
||||||
|
2
go.sum
2
go.sum
@@ -10,6 +10,8 @@ github.com/gen2brain/go-unarr v0.1.6 h1:2TtfIQ2dGuCkgEYa+vPE1ydcpkB3CtBbdYMfRSGL
|
|||||||
github.com/gen2brain/go-unarr v0.1.6/go.mod h1:P05CsEe8jVEXhxqXqp9mFKUKFV0BKpFmtgNWf8Mcoos=
|
github.com/gen2brain/go-unarr v0.1.6/go.mod h1:P05CsEe8jVEXhxqXqp9mFKUKFV0BKpFmtgNWf8Mcoos=
|
||||||
github.com/hotei/bmp v0.0.0-20150430041436-f620cebab0c7 h1:NlUATi3cllRJhpM4mfR9BxiLRXT83bcSLcOa+S8lrME=
|
github.com/hotei/bmp v0.0.0-20150430041436-f620cebab0c7 h1:NlUATi3cllRJhpM4mfR9BxiLRXT83bcSLcOa+S8lrME=
|
||||||
github.com/hotei/bmp v0.0.0-20150430041436-f620cebab0c7/go.mod h1:Hku3FQ2laCEwSv7Z8YkC0er38jLaUycUCbsFkWMr+z4=
|
github.com/hotei/bmp v0.0.0-20150430041436-f620cebab0c7/go.mod h1:Hku3FQ2laCEwSv7Z8YkC0er38jLaUycUCbsFkWMr+z4=
|
||||||
|
github.com/strukturag/libheif v1.13.0 h1:SuLo/Fl/Nzbw0ixOya1YZSl0Xd27X4fgofGnJdvOHqI=
|
||||||
|
github.com/strukturag/libheif v1.13.0/go.mod h1:E/PNRlmVtrtj9j2AvBZlrO4dsBDu6KfwDZn7X1Ce8Ks=
|
||||||
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||||
golang.org/x/image v0.0.0-20220902085622-e7cb96979f69 h1:Lj6HJGCSn5AjxRAH2+r35Mir4icalbqku+CLUtjnvXY=
|
golang.org/x/image v0.0.0-20220902085622-e7cb96979f69 h1:Lj6HJGCSn5AjxRAH2+r35Mir4icalbqku+CLUtjnvXY=
|
||||||
golang.org/x/image v0.0.0-20220902085622-e7cb96979f69/go.mod h1:doUCurBvlfPMKfmIpRIywoHmhN3VyhnoFDbvIEWF4hY=
|
golang.org/x/image v0.0.0-20220902085622-e7cb96979f69/go.mod h1:doUCurBvlfPMKfmIpRIywoHmhN3VyhnoFDbvIEWF4hY=
|
||||||
|
Reference in New Issue
Block a user