mirror of
https://github.com/gen2brain/cbconvert
synced 2025-10-13 18:18:52 +02:00
Split files
This commit is contained in:
390
cbconvert.go
390
cbconvert.go
@@ -4,30 +4,16 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/md5"
|
||||
"errors"
|
||||
"fmt"
|
||||
"image"
|
||||
_ "image/gif"
|
||||
"image/png"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/gen2brain/avif"
|
||||
"github.com/gen2brain/jpegli"
|
||||
"github.com/gen2brain/jpegxl"
|
||||
"github.com/gen2brain/webp"
|
||||
"github.com/jsummers/gobmp"
|
||||
"golang.org/x/image/tiff"
|
||||
|
||||
pngstructure "github.com/dsoprea/go-png-image-structure"
|
||||
"github.com/dustin/go-humanize"
|
||||
"github.com/gen2brain/go-fitz"
|
||||
"github.com/gen2brain/go-unarr"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
// Options type.
|
||||
@@ -151,382 +137,6 @@ func New(o Options) *Converter {
|
||||
return c
|
||||
}
|
||||
|
||||
// convertDocument converts PDF/EPUB document to CBZ.
|
||||
func (c *Converter) convertDocument(ctx context.Context, fileName string) error {
|
||||
var err error
|
||||
|
||||
c.Workdir, err = os.MkdirTemp(os.TempDir(), "cbc")
|
||||
if err != nil {
|
||||
return fmt.Errorf("convertDocument: %w", err)
|
||||
}
|
||||
|
||||
doc, err := fitz.New(fileName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("convertDocument: %w", err)
|
||||
}
|
||||
|
||||
defer doc.Close()
|
||||
|
||||
c.Ncontents = doc.NumPage()
|
||||
c.CurrContent = 0
|
||||
|
||||
if c.OnStart != nil {
|
||||
c.OnStart()
|
||||
}
|
||||
|
||||
eg, ctx := errgroup.WithContext(ctx)
|
||||
eg.SetLimit(runtime.NumCPU() + 1)
|
||||
|
||||
for n := 0; n < c.Ncontents; n++ {
|
||||
if ctx.Err() != nil {
|
||||
return fmt.Errorf("convertDocument: %w", ctx.Err())
|
||||
}
|
||||
|
||||
img, err := doc.Image(n)
|
||||
if err != nil {
|
||||
return fmt.Errorf("convertDocument: %w", err)
|
||||
}
|
||||
|
||||
if img != nil {
|
||||
eg.Go(func() error {
|
||||
return c.imageConvert(ctx, img, n, "")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
err = eg.Wait()
|
||||
if err != nil {
|
||||
return fmt.Errorf("convertDocument: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// convertArchive converts archive to CBZ.
|
||||
func (c *Converter) convertArchive(ctx context.Context, fileName string) error {
|
||||
var err error
|
||||
|
||||
c.Workdir, err = os.MkdirTemp(os.TempDir(), "cbc")
|
||||
if err != nil {
|
||||
return fmt.Errorf("convertArchive: %w", err)
|
||||
}
|
||||
|
||||
contents, err := c.archiveList(fileName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("convertArchive: %w", err)
|
||||
}
|
||||
|
||||
images := imagesFromSlice(contents)
|
||||
|
||||
c.Ncontents = len(images)
|
||||
c.CurrContent = 0
|
||||
|
||||
if c.OnStart != nil {
|
||||
c.OnStart()
|
||||
}
|
||||
|
||||
cover := c.coverName(images)
|
||||
|
||||
archive, err := unarr.NewArchive(fileName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("convertArchive: %w", err)
|
||||
}
|
||||
defer archive.Close()
|
||||
|
||||
eg, ctx := errgroup.WithContext(ctx)
|
||||
eg.SetLimit(runtime.NumCPU() + 1)
|
||||
|
||||
for {
|
||||
if ctx.Err() != nil {
|
||||
return fmt.Errorf("convertArchive: %w", ctx.Err())
|
||||
}
|
||||
|
||||
err := archive.Entry()
|
||||
if err != nil {
|
||||
if errors.Is(err, io.EOF) {
|
||||
break
|
||||
}
|
||||
|
||||
return fmt.Errorf("convertArchive: %w", err)
|
||||
}
|
||||
|
||||
data, err := archive.ReadAll()
|
||||
if err != nil {
|
||||
return fmt.Errorf("convertArchive: %w", err)
|
||||
}
|
||||
|
||||
pathName := archive.Name()
|
||||
|
||||
if isImage(pathName) {
|
||||
if c.Opts.NoConvert {
|
||||
if err = copyFile(bytes.NewReader(data), filepath.Join(c.Workdir, filepath.Base(pathName))); err != nil {
|
||||
return fmt.Errorf("convertArchive: %w", err)
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if cover == pathName && c.Opts.NoCover {
|
||||
if err = copyFile(bytes.NewReader(data), filepath.Join(c.Workdir, filepath.Base(pathName))); err != nil {
|
||||
return fmt.Errorf("convertArchive: %w", err)
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
var img image.Image
|
||||
img, err = c.imageDecode(bytes.NewReader(data))
|
||||
if err != nil {
|
||||
return fmt.Errorf("convertArchive: %w", err)
|
||||
}
|
||||
|
||||
if c.Opts.NoRGB && !isGrayScale(img) {
|
||||
if err = copyFile(bytes.NewReader(data), filepath.Join(c.Workdir, filepath.Base(pathName))); err != nil {
|
||||
return fmt.Errorf("convertArchive: %w", err)
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if img != nil {
|
||||
eg.Go(func() error {
|
||||
return c.imageConvert(ctx, img, 0, pathName)
|
||||
})
|
||||
}
|
||||
} else if !c.Opts.NoNonImage {
|
||||
if err = copyFile(bytes.NewReader(data), filepath.Join(c.Workdir, filepath.Base(pathName))); err != nil {
|
||||
return fmt.Errorf("convertArchive: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
err = eg.Wait()
|
||||
if err != nil {
|
||||
return fmt.Errorf("convertArchive: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// convertDirectory converts directory to CBZ.
|
||||
func (c *Converter) convertDirectory(ctx context.Context, dirPath string) error {
|
||||
var err error
|
||||
|
||||
c.Workdir, err = os.MkdirTemp(os.TempDir(), "cbc")
|
||||
if err != nil {
|
||||
return fmt.Errorf("convertDirectory: %w", err)
|
||||
}
|
||||
|
||||
contents, err := imagesFromPath(dirPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("convertDirectory: %w", err)
|
||||
}
|
||||
|
||||
images := imagesFromSlice(contents)
|
||||
c.Ncontents = len(images)
|
||||
c.CurrContent = 0
|
||||
|
||||
if c.OnStart != nil {
|
||||
c.OnStart()
|
||||
}
|
||||
|
||||
eg, ctx := errgroup.WithContext(ctx)
|
||||
eg.SetLimit(runtime.NumCPU() + 1)
|
||||
|
||||
for index, img := range contents {
|
||||
if ctx.Err() != nil {
|
||||
return fmt.Errorf("convertDirectory: %w", ctx.Err())
|
||||
}
|
||||
|
||||
file, err := os.Open(img)
|
||||
if err != nil {
|
||||
return fmt.Errorf("convertDirectory: %w", err)
|
||||
}
|
||||
|
||||
if isNonImage(img) && !c.Opts.NoNonImage {
|
||||
if err = copyFile(file, filepath.Join(c.Workdir, filepath.Base(img))); err != nil {
|
||||
return fmt.Errorf("convertDirectory: %w", err)
|
||||
}
|
||||
|
||||
if err = file.Close(); err != nil {
|
||||
return fmt.Errorf("convertDirectory: %w", err)
|
||||
}
|
||||
|
||||
continue
|
||||
} else if isImage(img) {
|
||||
if c.Opts.NoConvert {
|
||||
if err = copyFile(file, filepath.Join(c.Workdir, filepath.Base(img))); err != nil {
|
||||
return fmt.Errorf("convertDirectory: %w", err)
|
||||
}
|
||||
|
||||
if err = file.Close(); err != nil {
|
||||
return fmt.Errorf("convertDirectory: %w", err)
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
var i image.Image
|
||||
i, err = c.imageDecode(file)
|
||||
if err != nil {
|
||||
return fmt.Errorf("convertDirectory: %w", err)
|
||||
}
|
||||
|
||||
if c.Opts.NoRGB && !isGrayScale(i) {
|
||||
if err = copyFile(file, filepath.Join(c.Workdir, filepath.Base(img))); err != nil {
|
||||
return fmt.Errorf("convertDirectory: %w", err)
|
||||
}
|
||||
|
||||
if err = file.Close(); err != nil {
|
||||
return fmt.Errorf("convertDirectory: %w", err)
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if err = file.Close(); err != nil {
|
||||
return fmt.Errorf("convertDirectory: %w", err)
|
||||
}
|
||||
|
||||
if i != nil {
|
||||
eg.Go(func() error {
|
||||
return c.imageConvert(ctx, i, index, img)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
err = eg.Wait()
|
||||
if err != nil {
|
||||
return fmt.Errorf("convertDirectory: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// imageConvert converts image.Image.
|
||||
func (c *Converter) imageConvert(ctx context.Context, img image.Image, index int, pathName string) error {
|
||||
err := ctx.Err()
|
||||
if err != nil {
|
||||
return fmt.Errorf("imageConvert: %w", err)
|
||||
}
|
||||
|
||||
atomic.AddInt32(&c.CurrContent, 1)
|
||||
if c.OnProgress != nil {
|
||||
c.OnProgress()
|
||||
}
|
||||
|
||||
ext := c.Opts.Format
|
||||
if ext == "jpeg" {
|
||||
ext = "jpg"
|
||||
}
|
||||
|
||||
var fileName string
|
||||
if pathName != "" {
|
||||
fileName = filepath.Join(c.Workdir, fmt.Sprintf("%s.%s", baseNoExt(pathName), ext))
|
||||
} else {
|
||||
fileName = filepath.Join(c.Workdir, fmt.Sprintf("%03d.%s", index, ext))
|
||||
}
|
||||
|
||||
img = c.imageTransform(img)
|
||||
|
||||
w, err := os.Create(fileName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("imageConvert: %w", err)
|
||||
}
|
||||
defer w.Close()
|
||||
|
||||
if err := c.imageEncode(img, w); err != nil {
|
||||
return fmt.Errorf("imageConvert: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// imageTransform transforms image (resize, rotate, brightness, contrast).
|
||||
func (c *Converter) imageTransform(img image.Image) image.Image {
|
||||
var i = img
|
||||
|
||||
if c.Opts.Width > 0 || c.Opts.Height > 0 {
|
||||
if c.Opts.Fit {
|
||||
i = fit(i, c.Opts.Width, c.Opts.Height, filters[c.Opts.Filter])
|
||||
} else {
|
||||
i = resize(i, c.Opts.Width, c.Opts.Height, filters[c.Opts.Filter])
|
||||
}
|
||||
}
|
||||
|
||||
if c.Opts.Rotate > 0 {
|
||||
switch c.Opts.Rotate {
|
||||
case 90:
|
||||
i = rotate(i, 90)
|
||||
case 180:
|
||||
i = rotate(i, 180)
|
||||
case 270:
|
||||
i = rotate(i, 270)
|
||||
}
|
||||
}
|
||||
|
||||
if c.Opts.Brightness != 0 {
|
||||
i = brightness(i, float64(c.Opts.Brightness))
|
||||
}
|
||||
|
||||
if c.Opts.Contrast != 0 {
|
||||
i = contrast(i, float64(c.Opts.Contrast))
|
||||
}
|
||||
|
||||
if c.Opts.Grayscale {
|
||||
i = imageToGray(i)
|
||||
}
|
||||
|
||||
return i
|
||||
}
|
||||
|
||||
// imageDecode decodes image from reader.
|
||||
func (c *Converter) imageDecode(reader io.Reader) (image.Image, error) {
|
||||
img, _, err := image.Decode(reader)
|
||||
if err != nil {
|
||||
return img, fmt.Errorf("imageDecode: %w", err)
|
||||
}
|
||||
|
||||
return img, nil
|
||||
}
|
||||
|
||||
// imageEncode encodes image to file.
|
||||
func (c *Converter) imageEncode(img image.Image, w io.Writer) error {
|
||||
var err error
|
||||
|
||||
switch c.Opts.Format {
|
||||
case "png":
|
||||
err = png.Encode(w, img)
|
||||
case "tiff":
|
||||
err = tiff.Encode(w, img, &tiff.Options{Compression: tiff.Uncompressed})
|
||||
case "jpeg":
|
||||
opts := &jpegli.EncodingOptions{}
|
||||
opts.Quality = c.Opts.Quality
|
||||
opts.ChromaSubsampling = image.YCbCrSubsampleRatio420
|
||||
opts.ProgressiveLevel = 2
|
||||
opts.AdaptiveQuantization = true
|
||||
opts.DCTMethod = jpegli.DefaultDCTMethod
|
||||
err = jpegli.Encode(w, img, opts)
|
||||
case "webp":
|
||||
err = webp.Encode(w, img, webp.Options{Quality: c.Opts.Quality, Method: webp.DefaultMethod})
|
||||
case "avif":
|
||||
err = avif.Encode(w, img, avif.Options{Quality: c.Opts.Quality, Speed: avif.DefaultSpeed})
|
||||
case "jxl":
|
||||
err = jpegxl.Encode(w, img, jpegxl.Options{Quality: c.Opts.Quality, Effort: jpegxl.DefaultEffort})
|
||||
case "bmp":
|
||||
opts := &gobmp.EncoderOptions{}
|
||||
opts.SupportTransparency(false)
|
||||
err = gobmp.EncodeWithOptions(w, imageToPaletted(img), opts)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("imageEncode: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Cancel cancels the operation.
|
||||
func (c *Converter) Cancel() {
|
||||
if c.OnCancel != nil {
|
||||
|
401
cbconvert_convert.go
Normal file
401
cbconvert_convert.go
Normal file
@@ -0,0 +1,401 @@
|
||||
package cbconvert
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"image"
|
||||
"image/png"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/gen2brain/avif"
|
||||
"github.com/gen2brain/go-fitz"
|
||||
"github.com/gen2brain/go-unarr"
|
||||
"github.com/gen2brain/jpegli"
|
||||
"github.com/gen2brain/jpegxl"
|
||||
"github.com/gen2brain/webp"
|
||||
"github.com/jsummers/gobmp"
|
||||
"golang.org/x/image/tiff"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
// convertDocument converts PDF/EPUB document to CBZ.
|
||||
func (c *Converter) convertDocument(ctx context.Context, fileName string) error {
|
||||
var err error
|
||||
|
||||
c.Workdir, err = os.MkdirTemp(os.TempDir(), "cbc")
|
||||
if err != nil {
|
||||
return fmt.Errorf("convertDocument: %w", err)
|
||||
}
|
||||
|
||||
doc, err := fitz.New(fileName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("convertDocument: %w", err)
|
||||
}
|
||||
|
||||
defer doc.Close()
|
||||
|
||||
c.Ncontents = doc.NumPage()
|
||||
c.CurrContent = 0
|
||||
|
||||
if c.OnStart != nil {
|
||||
c.OnStart()
|
||||
}
|
||||
|
||||
eg, ctx := errgroup.WithContext(ctx)
|
||||
eg.SetLimit(runtime.NumCPU() + 1)
|
||||
|
||||
for n := 0; n < c.Ncontents; n++ {
|
||||
if ctx.Err() != nil {
|
||||
return fmt.Errorf("convertDocument: %w", ctx.Err())
|
||||
}
|
||||
|
||||
img, err := doc.Image(n)
|
||||
if err != nil {
|
||||
return fmt.Errorf("convertDocument: %w", err)
|
||||
}
|
||||
|
||||
if img != nil {
|
||||
eg.Go(func() error {
|
||||
return c.imageConvert(ctx, img, n, "")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
err = eg.Wait()
|
||||
if err != nil {
|
||||
return fmt.Errorf("convertDocument: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// convertArchive converts archive to CBZ.
|
||||
func (c *Converter) convertArchive(ctx context.Context, fileName string) error {
|
||||
var err error
|
||||
|
||||
c.Workdir, err = os.MkdirTemp(os.TempDir(), "cbc")
|
||||
if err != nil {
|
||||
return fmt.Errorf("convertArchive: %w", err)
|
||||
}
|
||||
|
||||
contents, err := c.archiveList(fileName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("convertArchive: %w", err)
|
||||
}
|
||||
|
||||
images := imagesFromSlice(contents)
|
||||
|
||||
c.Ncontents = len(images)
|
||||
c.CurrContent = 0
|
||||
|
||||
if c.OnStart != nil {
|
||||
c.OnStart()
|
||||
}
|
||||
|
||||
cover := c.coverName(images)
|
||||
|
||||
archive, err := unarr.NewArchive(fileName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("convertArchive: %w", err)
|
||||
}
|
||||
defer archive.Close()
|
||||
|
||||
eg, ctx := errgroup.WithContext(ctx)
|
||||
eg.SetLimit(runtime.NumCPU() + 1)
|
||||
|
||||
for {
|
||||
if ctx.Err() != nil {
|
||||
return fmt.Errorf("convertArchive: %w", ctx.Err())
|
||||
}
|
||||
|
||||
err := archive.Entry()
|
||||
if err != nil {
|
||||
if errors.Is(err, io.EOF) {
|
||||
break
|
||||
}
|
||||
|
||||
return fmt.Errorf("convertArchive: %w", err)
|
||||
}
|
||||
|
||||
data, err := archive.ReadAll()
|
||||
if err != nil {
|
||||
return fmt.Errorf("convertArchive: %w", err)
|
||||
}
|
||||
|
||||
pathName := archive.Name()
|
||||
|
||||
if isImage(pathName) {
|
||||
if c.Opts.NoConvert {
|
||||
if err = copyFile(bytes.NewReader(data), filepath.Join(c.Workdir, filepath.Base(pathName))); err != nil {
|
||||
return fmt.Errorf("convertArchive: %w", err)
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if cover == pathName && c.Opts.NoCover {
|
||||
if err = copyFile(bytes.NewReader(data), filepath.Join(c.Workdir, filepath.Base(pathName))); err != nil {
|
||||
return fmt.Errorf("convertArchive: %w", err)
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
var img image.Image
|
||||
img, err = c.imageDecode(bytes.NewReader(data))
|
||||
if err != nil {
|
||||
return fmt.Errorf("convertArchive: %w", err)
|
||||
}
|
||||
|
||||
if c.Opts.NoRGB && !isGrayScale(img) {
|
||||
if err = copyFile(bytes.NewReader(data), filepath.Join(c.Workdir, filepath.Base(pathName))); err != nil {
|
||||
return fmt.Errorf("convertArchive: %w", err)
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if img != nil {
|
||||
eg.Go(func() error {
|
||||
return c.imageConvert(ctx, img, 0, pathName)
|
||||
})
|
||||
}
|
||||
} else if !c.Opts.NoNonImage {
|
||||
if err = copyFile(bytes.NewReader(data), filepath.Join(c.Workdir, filepath.Base(pathName))); err != nil {
|
||||
return fmt.Errorf("convertArchive: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
err = eg.Wait()
|
||||
if err != nil {
|
||||
return fmt.Errorf("convertArchive: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// convertDirectory converts directory to CBZ.
|
||||
func (c *Converter) convertDirectory(ctx context.Context, dirPath string) error {
|
||||
var err error
|
||||
|
||||
c.Workdir, err = os.MkdirTemp(os.TempDir(), "cbc")
|
||||
if err != nil {
|
||||
return fmt.Errorf("convertDirectory: %w", err)
|
||||
}
|
||||
|
||||
contents, err := imagesFromPath(dirPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("convertDirectory: %w", err)
|
||||
}
|
||||
|
||||
images := imagesFromSlice(contents)
|
||||
c.Ncontents = len(images)
|
||||
c.CurrContent = 0
|
||||
|
||||
if c.OnStart != nil {
|
||||
c.OnStart()
|
||||
}
|
||||
|
||||
eg, ctx := errgroup.WithContext(ctx)
|
||||
eg.SetLimit(runtime.NumCPU() + 1)
|
||||
|
||||
for index, img := range contents {
|
||||
if ctx.Err() != nil {
|
||||
return fmt.Errorf("convertDirectory: %w", ctx.Err())
|
||||
}
|
||||
|
||||
file, err := os.Open(img)
|
||||
if err != nil {
|
||||
return fmt.Errorf("convertDirectory: %w", err)
|
||||
}
|
||||
|
||||
if isNonImage(img) && !c.Opts.NoNonImage {
|
||||
if err = copyFile(file, filepath.Join(c.Workdir, filepath.Base(img))); err != nil {
|
||||
return fmt.Errorf("convertDirectory: %w", err)
|
||||
}
|
||||
|
||||
if err = file.Close(); err != nil {
|
||||
return fmt.Errorf("convertDirectory: %w", err)
|
||||
}
|
||||
|
||||
continue
|
||||
} else if isImage(img) {
|
||||
if c.Opts.NoConvert {
|
||||
if err = copyFile(file, filepath.Join(c.Workdir, filepath.Base(img))); err != nil {
|
||||
return fmt.Errorf("convertDirectory: %w", err)
|
||||
}
|
||||
|
||||
if err = file.Close(); err != nil {
|
||||
return fmt.Errorf("convertDirectory: %w", err)
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
var i image.Image
|
||||
i, err = c.imageDecode(file)
|
||||
if err != nil {
|
||||
return fmt.Errorf("convertDirectory: %w", err)
|
||||
}
|
||||
|
||||
if c.Opts.NoRGB && !isGrayScale(i) {
|
||||
if err = copyFile(file, filepath.Join(c.Workdir, filepath.Base(img))); err != nil {
|
||||
return fmt.Errorf("convertDirectory: %w", err)
|
||||
}
|
||||
|
||||
if err = file.Close(); err != nil {
|
||||
return fmt.Errorf("convertDirectory: %w", err)
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if err = file.Close(); err != nil {
|
||||
return fmt.Errorf("convertDirectory: %w", err)
|
||||
}
|
||||
|
||||
if i != nil {
|
||||
eg.Go(func() error {
|
||||
return c.imageConvert(ctx, i, index, img)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
err = eg.Wait()
|
||||
if err != nil {
|
||||
return fmt.Errorf("convertDirectory: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// imageConvert converts image.Image.
|
||||
func (c *Converter) imageConvert(ctx context.Context, img image.Image, index int, pathName string) error {
|
||||
err := ctx.Err()
|
||||
if err != nil {
|
||||
return fmt.Errorf("imageConvert: %w", err)
|
||||
}
|
||||
|
||||
atomic.AddInt32(&c.CurrContent, 1)
|
||||
if c.OnProgress != nil {
|
||||
c.OnProgress()
|
||||
}
|
||||
|
||||
ext := c.Opts.Format
|
||||
if ext == "jpeg" {
|
||||
ext = "jpg"
|
||||
}
|
||||
|
||||
var fileName string
|
||||
if pathName != "" {
|
||||
fileName = filepath.Join(c.Workdir, fmt.Sprintf("%s.%s", baseNoExt(pathName), ext))
|
||||
} else {
|
||||
fileName = filepath.Join(c.Workdir, fmt.Sprintf("%03d.%s", index, ext))
|
||||
}
|
||||
|
||||
img = c.imageTransform(img)
|
||||
|
||||
w, err := os.Create(fileName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("imageConvert: %w", err)
|
||||
}
|
||||
defer w.Close()
|
||||
|
||||
if err := c.imageEncode(img, w); err != nil {
|
||||
return fmt.Errorf("imageConvert: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// imageTransform transforms image (resize, rotate, brightness, contrast).
|
||||
func (c *Converter) imageTransform(img image.Image) image.Image {
|
||||
var i = img
|
||||
|
||||
if c.Opts.Width > 0 || c.Opts.Height > 0 {
|
||||
if c.Opts.Fit {
|
||||
i = fit(i, c.Opts.Width, c.Opts.Height, filters[c.Opts.Filter])
|
||||
} else {
|
||||
i = resize(i, c.Opts.Width, c.Opts.Height, filters[c.Opts.Filter])
|
||||
}
|
||||
}
|
||||
|
||||
if c.Opts.Rotate > 0 {
|
||||
switch c.Opts.Rotate {
|
||||
case 90:
|
||||
i = rotate(i, 90)
|
||||
case 180:
|
||||
i = rotate(i, 180)
|
||||
case 270:
|
||||
i = rotate(i, 270)
|
||||
}
|
||||
}
|
||||
|
||||
if c.Opts.Brightness != 0 {
|
||||
i = brightness(i, float64(c.Opts.Brightness))
|
||||
}
|
||||
|
||||
if c.Opts.Contrast != 0 {
|
||||
i = contrast(i, float64(c.Opts.Contrast))
|
||||
}
|
||||
|
||||
if c.Opts.Grayscale {
|
||||
i = imageToGray(i)
|
||||
}
|
||||
|
||||
return i
|
||||
}
|
||||
|
||||
// imageDecode decodes image from reader.
|
||||
func (c *Converter) imageDecode(reader io.Reader) (image.Image, error) {
|
||||
img, _, err := image.Decode(reader)
|
||||
if err != nil {
|
||||
return img, fmt.Errorf("imageDecode: %w", err)
|
||||
}
|
||||
|
||||
return img, nil
|
||||
}
|
||||
|
||||
// imageEncode encodes image to file.
|
||||
func (c *Converter) imageEncode(img image.Image, w io.Writer) error {
|
||||
var err error
|
||||
|
||||
switch c.Opts.Format {
|
||||
case "png":
|
||||
err = png.Encode(w, img)
|
||||
case "tiff":
|
||||
err = tiff.Encode(w, img, &tiff.Options{Compression: tiff.Uncompressed})
|
||||
case "jpeg":
|
||||
opts := &jpegli.EncodingOptions{}
|
||||
opts.Quality = c.Opts.Quality
|
||||
opts.ChromaSubsampling = image.YCbCrSubsampleRatio420
|
||||
opts.ProgressiveLevel = 2
|
||||
opts.AdaptiveQuantization = true
|
||||
opts.DCTMethod = jpegli.DefaultDCTMethod
|
||||
err = jpegli.Encode(w, img, opts)
|
||||
case "webp":
|
||||
err = webp.Encode(w, img, webp.Options{Quality: c.Opts.Quality, Method: webp.DefaultMethod})
|
||||
case "avif":
|
||||
err = avif.Encode(w, img, avif.Options{Quality: c.Opts.Quality, Speed: avif.DefaultSpeed})
|
||||
case "jxl":
|
||||
err = jpegxl.Encode(w, img, jpegxl.Options{Quality: c.Opts.Quality, Effort: jpegxl.DefaultEffort})
|
||||
case "bmp":
|
||||
opts := &gobmp.EncoderOptions{}
|
||||
opts.SupportTransparency(false)
|
||||
err = gobmp.EncodeWithOptions(w, imageToPaletted(img), opts)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("imageEncode: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
Reference in New Issue
Block a user