mirror of
https://github.com/gen2brain/cbconvert
synced 2025-10-14 02:28:51 +02:00
Refactor
This commit is contained in:
631
cbconvert.go
631
cbconvert.go
@@ -96,6 +96,10 @@ type Options struct {
|
||||
Thumbnail bool
|
||||
// CBZ metadata
|
||||
Meta bool
|
||||
// ZIP comment
|
||||
Comment bool
|
||||
// ZIP comment body
|
||||
CommentBody string
|
||||
// Output file
|
||||
Outfile string
|
||||
// Output directory
|
||||
@@ -157,134 +161,6 @@ func New(o Options) *Convertor {
|
||||
return c
|
||||
}
|
||||
|
||||
// convertImage converts image.Image.
|
||||
func (c *Convertor) convertImage(ctx context.Context, img image.Image, index int, pathName string) error {
|
||||
err := ctx.Err()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
atomic.AddInt32(&c.CurrContent, 1)
|
||||
if c.OnProgress != nil {
|
||||
c.OnProgress()
|
||||
}
|
||||
|
||||
var fileName string
|
||||
if pathName != "" {
|
||||
fileName = filepath.Join(c.Workdir, fmt.Sprintf("%s.%s", c.baseNoExt(pathName), c.Opts.Format))
|
||||
} else {
|
||||
fileName = filepath.Join(c.Workdir, fmt.Sprintf("%03d.%s", index, c.Opts.Format))
|
||||
}
|
||||
|
||||
img = c.transformImage(img)
|
||||
|
||||
if c.Opts.LevelsInMin != 0 || c.Opts.LevelsInMax != 255 || c.Opts.LevelsGamma != 1.0 ||
|
||||
c.Opts.LevelsOutMin != 0 || c.Opts.LevelsOutMax != 255 {
|
||||
img, err = c.levelImage(img)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
switch c.Opts.Format {
|
||||
case "jpeg", "png", "tiff", "webp", "avif":
|
||||
if err := c.encodeImage(img, fileName); err != nil {
|
||||
return err
|
||||
}
|
||||
case "bmp":
|
||||
// convert image to 4-Bit BMP (16 colors)
|
||||
if err := c.encodeIM(img, fileName); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// transformImage transforms image (resize, rotate, flip, brightness, contrast).
|
||||
func (c *Convertor) transformImage(img image.Image) image.Image {
|
||||
var i = img
|
||||
|
||||
if c.Opts.Width > 0 || c.Opts.Height > 0 {
|
||||
if c.Opts.Fit {
|
||||
i = imaging.Fit(i, c.Opts.Width, c.Opts.Height, filters[c.Opts.Filter])
|
||||
} else {
|
||||
i = imaging.Resize(i, c.Opts.Width, c.Opts.Height, filters[c.Opts.Filter])
|
||||
}
|
||||
}
|
||||
|
||||
if c.Opts.Rotate > 0 {
|
||||
switch c.Opts.Rotate {
|
||||
case 90:
|
||||
i = imaging.Rotate90(i)
|
||||
case 180:
|
||||
i = imaging.Rotate180(i)
|
||||
case 270:
|
||||
i = imaging.Rotate270(i)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
if c.Opts.Contrast != 0 {
|
||||
i = imaging.AdjustContrast(i, c.Opts.Contrast)
|
||||
}
|
||||
|
||||
return i
|
||||
}
|
||||
|
||||
// levelImage applies a Photoshop-like levels operation on an image.
|
||||
func (c *Convertor) levelImage(img image.Image) (image.Image, error) {
|
||||
mw := imagick.NewMagickWand()
|
||||
defer mw.Destroy()
|
||||
|
||||
rgba := imageToRGBA(img)
|
||||
err := mw.ConstituteImage(uint(img.Bounds().Dx()), uint(img.Bounds().Dy()), "RGBA", imagick.PIXEL_CHAR, rgba.Pix)
|
||||
if err != nil {
|
||||
return img, fmt.Errorf("levelImage: %w", err)
|
||||
}
|
||||
|
||||
_, 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
|
||||
|
||||
if err := mw.LevelImage(inmin, c.Opts.LevelsGamma, inmax); err != nil {
|
||||
return img, fmt.Errorf("levelImage: %w", err)
|
||||
}
|
||||
|
||||
if err := mw.LevelImage(-outmin, 1.0, quantumRange+(quantumRange-outmax)); err != nil {
|
||||
return img, fmt.Errorf("levelImage: %w", err)
|
||||
}
|
||||
|
||||
blob := mw.GetImageBlob()
|
||||
|
||||
var i image.Image
|
||||
i, err = c.decodeImage(bytes.NewReader(blob), "levels")
|
||||
if err != nil {
|
||||
i, err = c.decodeIM(bytes.NewReader(blob), "levels")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("levelImage: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return i, nil
|
||||
}
|
||||
|
||||
// convertDocument converts PDF/EPUB document to CBZ.
|
||||
func (c *Convertor) convertDocument(fileName string) error {
|
||||
var err error
|
||||
@@ -319,7 +195,7 @@ func (c *Convertor) convertDocument(fileName string) error {
|
||||
|
||||
if img != nil {
|
||||
eg.Go(func() error {
|
||||
return c.convertImage(ctx, img, n, "")
|
||||
return c.imageConvert(ctx, img, n, "")
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -336,7 +212,7 @@ func (c *Convertor) convertArchive(fileName string) error {
|
||||
return fmt.Errorf("convertArchive: %w", err)
|
||||
}
|
||||
|
||||
contents, err := c.listArchive(fileName)
|
||||
contents, err := c.archiveList(fileName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("convertArchive: %w", err)
|
||||
}
|
||||
@@ -388,17 +264,17 @@ func (c *Convertor) convertArchive(fileName string) error {
|
||||
}
|
||||
|
||||
var img image.Image
|
||||
img, err = c.decodeImage(bytes.NewReader(data), pathName)
|
||||
img, err = c.imageDecode(bytes.NewReader(data), pathName)
|
||||
if err != nil {
|
||||
img, err = c.decodeIM(bytes.NewReader(data), pathName)
|
||||
img, err = c.imDecode(bytes.NewReader(data), pathName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("convertArchive: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if cover == pathName && c.Opts.NoCover {
|
||||
img = c.transformImage(img)
|
||||
if err = c.encodeImage(img, filepath.Join(c.Workdir, filepath.Base(pathName))); err != nil {
|
||||
img = c.imageTransform(img)
|
||||
if err = c.imageEncode(img, filepath.Join(c.Workdir, filepath.Base(pathName))); err != nil {
|
||||
return fmt.Errorf("convertArchive: %w", err)
|
||||
}
|
||||
|
||||
@@ -406,8 +282,8 @@ func (c *Convertor) convertArchive(fileName string) error {
|
||||
}
|
||||
|
||||
if c.Opts.NoRGB && !c.isGrayScale(img) {
|
||||
img = c.transformImage(img)
|
||||
if err = c.encodeImage(img, filepath.Join(c.Workdir, filepath.Base(pathName))); err != nil {
|
||||
img = c.imageTransform(img)
|
||||
if err = c.imageEncode(img, filepath.Join(c.Workdir, filepath.Base(pathName))); err != nil {
|
||||
return fmt.Errorf("convertArchive: %w", err)
|
||||
}
|
||||
|
||||
@@ -416,7 +292,7 @@ func (c *Convertor) convertArchive(fileName string) error {
|
||||
|
||||
if img != nil {
|
||||
eg.Go(func() error {
|
||||
return c.convertImage(ctx, img, 0, pathName)
|
||||
return c.imageConvert(ctx, img, 0, pathName)
|
||||
})
|
||||
}
|
||||
} else {
|
||||
@@ -486,17 +362,17 @@ func (c *Convertor) convertDirectory(dirPath string) error {
|
||||
}
|
||||
|
||||
var i image.Image
|
||||
i, err = c.decodeImage(file, img)
|
||||
i, err = c.imageDecode(file, img)
|
||||
if err != nil {
|
||||
i, err = c.decodeIM(file, img)
|
||||
i, err = c.imDecode(file, img)
|
||||
if err != nil {
|
||||
return fmt.Errorf("coverDirectory: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if c.Opts.NoRGB && !c.isGrayScale(i) {
|
||||
i = c.transformImage(i)
|
||||
if err = c.encodeImage(i, filepath.Join(c.Workdir, filepath.Base(img))); err != nil {
|
||||
i = c.imageTransform(i)
|
||||
if err = c.imageEncode(i, filepath.Join(c.Workdir, filepath.Base(img))); err != nil {
|
||||
return fmt.Errorf("convertDirectory: %w", err)
|
||||
}
|
||||
|
||||
@@ -513,7 +389,7 @@ func (c *Convertor) convertDirectory(dirPath string) error {
|
||||
|
||||
if i != nil {
|
||||
eg.Go(func() error {
|
||||
return c.convertImage(ctx, i, index, img)
|
||||
return c.imageConvert(ctx, i, index, img)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -522,90 +398,146 @@ func (c *Convertor) convertDirectory(dirPath string) error {
|
||||
return eg.Wait()
|
||||
}
|
||||
|
||||
// saveArchive saves workdir to CBZ archive.
|
||||
func (c *Convertor) saveArchive(fileName string) error {
|
||||
if c.OnCompress != nil {
|
||||
c.OnCompress()
|
||||
}
|
||||
|
||||
zipname := filepath.Join(c.Opts.Outdir, fmt.Sprintf("%s%s.cbz", c.baseNoExt(fileName), c.Opts.Suffix))
|
||||
|
||||
zipfile, err := os.Create(zipname)
|
||||
// imageConvert converts image.Image.
|
||||
func (c *Convertor) imageConvert(ctx context.Context, img image.Image, index int, pathName string) error {
|
||||
err := ctx.Err()
|
||||
if err != nil {
|
||||
return fmt.Errorf("saveArchive: %w", err)
|
||||
return err
|
||||
}
|
||||
|
||||
z := zip.NewWriter(zipfile)
|
||||
atomic.AddInt32(&c.CurrContent, 1)
|
||||
if c.OnProgress != nil {
|
||||
c.OnProgress()
|
||||
}
|
||||
|
||||
files, err := os.ReadDir(c.Workdir)
|
||||
var fileName string
|
||||
if pathName != "" {
|
||||
fileName = filepath.Join(c.Workdir, fmt.Sprintf("%s.%s", c.baseNoExt(pathName), c.Opts.Format))
|
||||
} else {
|
||||
fileName = filepath.Join(c.Workdir, fmt.Sprintf("%03d.%s", index, c.Opts.Format))
|
||||
}
|
||||
|
||||
img = c.imageTransform(img)
|
||||
|
||||
if c.Opts.LevelsInMin != 0 || c.Opts.LevelsInMax != 255 || c.Opts.LevelsGamma != 1.0 ||
|
||||
c.Opts.LevelsOutMin != 0 || c.Opts.LevelsOutMax != 255 {
|
||||
img, err = c.imageLevel(img)
|
||||
if err != nil {
|
||||
return fmt.Errorf("saveArchive: %w", err)
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
r, err := os.ReadFile(filepath.Join(c.Workdir, file.Name()))
|
||||
if err != nil {
|
||||
return fmt.Errorf("saveArchive: %w", err)
|
||||
}
|
||||
|
||||
info, err := file.Info()
|
||||
if err != nil {
|
||||
return fmt.Errorf("saveArchive: %w", err)
|
||||
}
|
||||
|
||||
zipinfo, err := zip.FileInfoHeader(info)
|
||||
if err != nil {
|
||||
return fmt.Errorf("saveArchive: %w", err)
|
||||
}
|
||||
|
||||
zipinfo.Method = zip.Deflate
|
||||
w, err := z.CreateHeader(zipinfo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("saveArchive: %w", err)
|
||||
}
|
||||
|
||||
_, err = w.Write(r)
|
||||
if err != nil {
|
||||
return fmt.Errorf("saveArchive: %w", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err = z.Close(); err != nil {
|
||||
return fmt.Errorf("saveArchive: %w", err)
|
||||
switch c.Opts.Format {
|
||||
case "jpeg", "png", "tiff", "webp", "avif":
|
||||
if err := c.imageEncode(img, fileName); err != nil {
|
||||
return err
|
||||
}
|
||||
case "bmp":
|
||||
// convert image to 4-Bit BMP (16 colors)
|
||||
if err := c.imEncode(img, fileName); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err = zipfile.Close(); err != nil {
|
||||
return fmt.Errorf("saveArchive: %w", err)
|
||||
}
|
||||
|
||||
return os.RemoveAll(c.Workdir)
|
||||
return nil
|
||||
}
|
||||
|
||||
// listArchive lists contents of archive.
|
||||
func (c *Convertor) listArchive(fileName string) ([]string, error) {
|
||||
var contents []string
|
||||
// imageTransform transforms image (resize, rotate, flip, brightness, contrast).
|
||||
func (c *Convertor) imageTransform(img image.Image) image.Image {
|
||||
var i = img
|
||||
|
||||
archive, err := unarr.NewArchive(fileName)
|
||||
if err != nil {
|
||||
return contents, err
|
||||
if c.Opts.Width > 0 || c.Opts.Height > 0 {
|
||||
if c.Opts.Fit {
|
||||
i = imaging.Fit(i, c.Opts.Width, c.Opts.Height, filters[c.Opts.Filter])
|
||||
} else {
|
||||
i = imaging.Resize(i, c.Opts.Width, c.Opts.Height, filters[c.Opts.Filter])
|
||||
}
|
||||
}
|
||||
defer archive.Close()
|
||||
|
||||
return archive.List()
|
||||
if c.Opts.Rotate > 0 {
|
||||
switch c.Opts.Rotate {
|
||||
case 90:
|
||||
i = imaging.Rotate90(i)
|
||||
case 180:
|
||||
i = imaging.Rotate180(i)
|
||||
case 270:
|
||||
i = imaging.Rotate270(i)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
if c.Opts.Contrast != 0 {
|
||||
i = imaging.AdjustContrast(i, c.Opts.Contrast)
|
||||
}
|
||||
|
||||
return i
|
||||
}
|
||||
|
||||
// decodeImage decodes image from reader.
|
||||
func (c *Convertor) decodeImage(reader io.Reader, fileName string) (img image.Image, err error) {
|
||||
// imageLevel applies a Photoshop-like levels operation on an image.
|
||||
func (c *Convertor) imageLevel(img image.Image) (image.Image, error) {
|
||||
mw := imagick.NewMagickWand()
|
||||
defer mw.Destroy()
|
||||
|
||||
rgba := imageToRGBA(img)
|
||||
err := mw.ConstituteImage(uint(img.Bounds().Dx()), uint(img.Bounds().Dy()), "RGBA", imagick.PIXEL_CHAR, rgba.Pix)
|
||||
if err != nil {
|
||||
return img, fmt.Errorf("imageLevel: %w", err)
|
||||
}
|
||||
|
||||
_, 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
|
||||
|
||||
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 {
|
||||
return img, fmt.Errorf("imageLevel: %w", err)
|
||||
}
|
||||
|
||||
blob := mw.GetImageBlob()
|
||||
|
||||
var i image.Image
|
||||
i, err = c.imageDecode(bytes.NewReader(blob), "levels")
|
||||
if err != nil {
|
||||
i, err = c.imDecode(bytes.NewReader(blob), "levels")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("imageLevel: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return i, nil
|
||||
}
|
||||
|
||||
// imageDecode decodes image from reader.
|
||||
func (c *Convertor) imageDecode(reader io.Reader, fileName string) (img image.Image, err error) {
|
||||
img, _, err = image.Decode(reader)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("decodeImage: %s: %w", fileName, err)
|
||||
err = fmt.Errorf("imageDecode: %s: %w", fileName, err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// decodeIM decodes image from reader (ImageMagick).
|
||||
func (c *Convertor) decodeIM(reader io.Reader, fileName string) (img image.Image, err error) {
|
||||
// imDecode decodes image from reader (ImageMagick).
|
||||
func (c *Convertor) imDecode(reader io.Reader, fileName string) (img image.Image, err error) {
|
||||
mw := imagick.NewMagickWand()
|
||||
defer mw.Destroy()
|
||||
|
||||
@@ -614,15 +546,15 @@ func (c *Convertor) decodeIM(reader io.Reader, fileName string) (img image.Image
|
||||
|
||||
data, err = io.ReadAll(reader)
|
||||
if err != nil {
|
||||
return img, fmt.Errorf("decodeIM: %w", err)
|
||||
return img, fmt.Errorf("imDecode: %w", err)
|
||||
}
|
||||
|
||||
if err = mw.SetFilename(fileName); err != nil {
|
||||
return img, fmt.Errorf("decodeIM: %w", err)
|
||||
return img, fmt.Errorf("imDecode: %w", err)
|
||||
}
|
||||
|
||||
if err = mw.ReadImageBlob(data); err != nil {
|
||||
return img, fmt.Errorf("decodeIM: %w", err)
|
||||
return img, fmt.Errorf("imDecode: %w", err)
|
||||
}
|
||||
|
||||
w := mw.GetImageWidth()
|
||||
@@ -630,7 +562,7 @@ func (c *Convertor) decodeIM(reader io.Reader, fileName string) (img image.Image
|
||||
|
||||
out, err = mw.ExportImagePixels(0, 0, w, h, "RGBA", imagick.PIXEL_CHAR)
|
||||
if err != nil {
|
||||
return img, fmt.Errorf("decodeIM: %w", err)
|
||||
return img, fmt.Errorf("imDecode: %w", err)
|
||||
}
|
||||
|
||||
b := image.Rect(0, 0, int(w), int(h))
|
||||
@@ -641,11 +573,11 @@ func (c *Convertor) decodeIM(reader io.Reader, fileName string) (img image.Image
|
||||
return
|
||||
}
|
||||
|
||||
// encodeImage encodes image to file.
|
||||
func (c *Convertor) encodeImage(img image.Image, fileName string) error {
|
||||
// imageEncode encodes image to file.
|
||||
func (c *Convertor) imageEncode(img image.Image, fileName string) error {
|
||||
file, err := os.Create(fileName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("encodeImage: %w", err)
|
||||
return fmt.Errorf("imageEncode: %w", err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
@@ -671,47 +603,47 @@ func (c *Convertor) encodeImage(img image.Image, fileName string) error {
|
||||
|
||||
ctx, err := heif.EncodeFromImage(img, heif.CompressionAV1, c.Opts.Quality, lossLess, 0)
|
||||
if err != nil {
|
||||
return fmt.Errorf("encodeImage: %w", err)
|
||||
return fmt.Errorf("imageEncode: %w", err)
|
||||
}
|
||||
err = ctx.WriteToFile(fileName)
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("encodeImage: %w", err)
|
||||
return fmt.Errorf("imageEncode: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// encodeIM encodes image to file (ImageMagick).
|
||||
func (c *Convertor) encodeIM(i image.Image, fileName string) error {
|
||||
// imEncode encodes image to file (ImageMagick).
|
||||
func (c *Convertor) imEncode(i image.Image, fileName string) error {
|
||||
mw := imagick.NewMagickWand()
|
||||
defer mw.Destroy()
|
||||
|
||||
rgba := imageToRGBA(i)
|
||||
if err := mw.ConstituteImage(uint(i.Bounds().Dx()), uint(i.Bounds().Dy()), "RGBA", imagick.PIXEL_CHAR, rgba.Pix); err != nil {
|
||||
return fmt.Errorf("encodeIM: %w", err)
|
||||
return fmt.Errorf("imEncode: %w", err)
|
||||
}
|
||||
|
||||
if c.Opts.Grayscale {
|
||||
if err := mw.TransformImageColorspace(imagick.COLORSPACE_GRAY); err != nil {
|
||||
return fmt.Errorf("encodeIM: %w", err)
|
||||
return fmt.Errorf("imEncode: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
switch filepath.Ext(fileName) {
|
||||
case ".png":
|
||||
if err := mw.SetImageFormat("PNG"); err != nil {
|
||||
return fmt.Errorf("encodeIM: %w", err)
|
||||
return fmt.Errorf("imEncode: %w", err)
|
||||
}
|
||||
if err := mw.WriteImage(fileName); err != nil {
|
||||
return fmt.Errorf("encodeIM: %w", err)
|
||||
return fmt.Errorf("imEncode: %w", err)
|
||||
}
|
||||
case ".tif", ".tiff":
|
||||
if err := mw.SetImageFormat("TIFF"); err != nil {
|
||||
return fmt.Errorf("encodeIM: %w", err)
|
||||
return fmt.Errorf("imEncode: %w", err)
|
||||
}
|
||||
if err := mw.WriteImage(fileName); err != nil {
|
||||
return fmt.Errorf("encodeIM: %w", err)
|
||||
return fmt.Errorf("imEncode: %w", err)
|
||||
}
|
||||
case ".bmp":
|
||||
pw := imagick.NewPixelWand()
|
||||
@@ -719,59 +651,146 @@ func (c *Convertor) encodeIM(i image.Image, fileName string) error {
|
||||
defer pw.Destroy()
|
||||
|
||||
if err := mw.SetImageFormat("BMP3"); err != nil {
|
||||
return fmt.Errorf("encodeIM: %w", err)
|
||||
return fmt.Errorf("imEncode: %w", err)
|
||||
}
|
||||
if err := mw.SetImageBackgroundColor(pw); err != nil {
|
||||
return fmt.Errorf("encodeIM: %w", err)
|
||||
return fmt.Errorf("imEncode: %w", err)
|
||||
}
|
||||
if err := mw.SetImageAlphaChannel(imagick.ALPHA_CHANNEL_REMOVE); err != nil {
|
||||
return fmt.Errorf("encodeIM: %w", err)
|
||||
return fmt.Errorf("imEncode: %w", err)
|
||||
}
|
||||
if err := mw.SetImageAlphaChannel(imagick.ALPHA_CHANNEL_DEACTIVATE); err != nil {
|
||||
return fmt.Errorf("encodeIM: %w", err)
|
||||
return fmt.Errorf("imEncode: %w", err)
|
||||
}
|
||||
if err := mw.SetImageMatte(false); err != nil {
|
||||
return fmt.Errorf("encodeIM: %w", err)
|
||||
return fmt.Errorf("imEncode: %w", err)
|
||||
}
|
||||
if err := mw.SetImageCompression(imagick.COMPRESSION_NO); err != nil {
|
||||
return fmt.Errorf("encodeIM: %w", err)
|
||||
return fmt.Errorf("imEncode: %w", err)
|
||||
}
|
||||
if err := mw.QuantizeImage(16, mw.GetImageColorspace(), 1, imagick.DITHER_METHOD_FLOYD_STEINBERG, true); err != nil {
|
||||
return fmt.Errorf("encodeIM: %w", err)
|
||||
return fmt.Errorf("imEncode: %w", err)
|
||||
}
|
||||
if err := mw.WriteImage(fileName); err != nil {
|
||||
return fmt.Errorf("encodeIM: %w", err)
|
||||
return fmt.Errorf("imEncode: %w", err)
|
||||
}
|
||||
case ".jpg", ".jpeg":
|
||||
if err := mw.SetImageFormat("JPEG"); err != nil {
|
||||
return fmt.Errorf("encodeIM: %w", err)
|
||||
return fmt.Errorf("imEncode: %w", err)
|
||||
}
|
||||
if err := mw.SetImageCompressionQuality(uint(c.Opts.Quality)); err != nil {
|
||||
return fmt.Errorf("encodeIM: %w", err)
|
||||
return fmt.Errorf("imEncode: %w", err)
|
||||
}
|
||||
if err := mw.WriteImage(fileName); err != nil {
|
||||
return fmt.Errorf("encodeIM: %w", err)
|
||||
return fmt.Errorf("imEncode: %w", err)
|
||||
}
|
||||
case ".avif":
|
||||
if err := mw.SetImageFormat("AVIF"); err != nil {
|
||||
return fmt.Errorf("encodeIM: %w", err)
|
||||
return fmt.Errorf("imEncode: %w", err)
|
||||
}
|
||||
if err := mw.SetImageCompressionQuality(uint(c.Opts.Quality)); err != nil {
|
||||
return fmt.Errorf("encodeIM: %w", err)
|
||||
return fmt.Errorf("imEncode: %w", err)
|
||||
}
|
||||
if err := mw.WriteImage(fileName); err != nil {
|
||||
return fmt.Errorf("encodeIM: %w", err)
|
||||
return fmt.Errorf("imEncode: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// archiveSave saves workdir to CBZ archive.
|
||||
func (c *Convertor) archiveSave(fileName string) error {
|
||||
if c.OnCompress != nil {
|
||||
c.OnCompress()
|
||||
}
|
||||
|
||||
zipName := filepath.Join(c.Opts.Outdir, fmt.Sprintf("%s%s.cbz", c.baseNoExt(fileName), c.Opts.Suffix))
|
||||
|
||||
zipFile, err := os.Create(zipName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("archiveSave: %w", err)
|
||||
}
|
||||
|
||||
z := zip.NewWriter(zipFile)
|
||||
|
||||
files, err := os.ReadDir(c.Workdir)
|
||||
if err != nil {
|
||||
return fmt.Errorf("archiveSave: %w", err)
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
r, err := os.ReadFile(filepath.Join(c.Workdir, file.Name()))
|
||||
if err != nil {
|
||||
return fmt.Errorf("archiveSave: %w", err)
|
||||
}
|
||||
|
||||
info, err := file.Info()
|
||||
if err != nil {
|
||||
return fmt.Errorf("archiveSave: %w", err)
|
||||
}
|
||||
|
||||
zipInfo, err := zip.FileInfoHeader(info)
|
||||
if err != nil {
|
||||
return fmt.Errorf("archiveSave: %w", err)
|
||||
}
|
||||
|
||||
zipInfo.Method = zip.Deflate
|
||||
w, err := z.CreateHeader(zipInfo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("archiveSave: %w", err)
|
||||
}
|
||||
|
||||
_, err = w.Write(r)
|
||||
if err != nil {
|
||||
return fmt.Errorf("archiveSave: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if err = z.Close(); err != nil {
|
||||
return fmt.Errorf("archiveSave: %w", err)
|
||||
}
|
||||
|
||||
if err = zipFile.Close(); err != nil {
|
||||
return fmt.Errorf("archiveSave: %w", err)
|
||||
}
|
||||
|
||||
return os.RemoveAll(c.Workdir)
|
||||
}
|
||||
|
||||
// archiveList lists contents of archive.
|
||||
func (c *Convertor) archiveList(fileName string) ([]string, error) {
|
||||
var contents []string
|
||||
|
||||
archive, err := unarr.NewArchive(fileName)
|
||||
if err != nil {
|
||||
return contents, err
|
||||
}
|
||||
defer archive.Close()
|
||||
|
||||
return archive.List()
|
||||
}
|
||||
|
||||
// archiveComment returns ZIP comment.
|
||||
func (c *Convertor) archiveComment(fileName string) (string, error) {
|
||||
r, err := zip.OpenReader(fileName)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return r.Comment, nil
|
||||
}
|
||||
|
||||
// archiveSetComment sets ZIP comment.
|
||||
func (c *Convertor) archiveSetComment(fileName, commentBody string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// coverArchive extracts cover from archive.
|
||||
func (c *Convertor) coverArchive(fileName string) (image.Image, error) {
|
||||
var images []string
|
||||
|
||||
contents, err := c.listArchive(fileName)
|
||||
contents, err := c.archiveList(fileName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("coverArchive: %w", err)
|
||||
}
|
||||
@@ -800,9 +819,9 @@ func (c *Convertor) coverArchive(fileName string) (image.Image, error) {
|
||||
}
|
||||
|
||||
var img image.Image
|
||||
img, err = c.decodeImage(bytes.NewReader(data), cover)
|
||||
img, err = c.imageDecode(bytes.NewReader(data), cover)
|
||||
if err != nil {
|
||||
img, err = c.decodeIM(bytes.NewReader(data), cover)
|
||||
img, err = c.imDecode(bytes.NewReader(data), cover)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("coverArchive: %w", err)
|
||||
}
|
||||
@@ -845,9 +864,9 @@ func (c *Convertor) coverDirectory(dir string) (image.Image, error) {
|
||||
defer file.Close()
|
||||
|
||||
var img image.Image
|
||||
img, err = c.decodeImage(file, cover)
|
||||
img, err = c.imageDecode(file, cover)
|
||||
if err != nil {
|
||||
img, err = c.decodeIM(file, cover)
|
||||
img, err = c.imDecode(file, cover)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("coverDirectory: %w", err)
|
||||
}
|
||||
@@ -856,6 +875,61 @@ func (c *Convertor) coverDirectory(dir string) (image.Image, error) {
|
||||
return img, nil
|
||||
}
|
||||
|
||||
// coverName returns the filename that is the most likely to be the cover.
|
||||
func (c *Convertor) coverName(images []string) string {
|
||||
if len(images) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
lower := make([]string, 0)
|
||||
for idx, img := range images {
|
||||
img = strings.ToLower(img)
|
||||
lower = append(lower, img)
|
||||
ext := c.baseNoExt(img)
|
||||
|
||||
if strings.HasPrefix(img, "cover") || strings.HasPrefix(img, "front") ||
|
||||
strings.HasSuffix(ext, "cover") || strings.HasSuffix(ext, "front") {
|
||||
return filepath.ToSlash(images[idx])
|
||||
}
|
||||
}
|
||||
|
||||
sort.Sort(sortorder.Natural(lower))
|
||||
cover := lower[0]
|
||||
|
||||
for idx, img := range images {
|
||||
img = strings.ToLower(img)
|
||||
if img == cover {
|
||||
return filepath.ToSlash(images[idx])
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// coverImage returns cover as image.Image.
|
||||
func (c *Convertor) coverImage(fileName string, fileInfo os.FileInfo) (image.Image, error) {
|
||||
var err error
|
||||
var cover image.Image
|
||||
|
||||
if fileInfo.IsDir() {
|
||||
cover, err = c.coverDirectory(fileName)
|
||||
} else if c.isDocument(fileName) {
|
||||
cover, err = c.coverDocument(fileName)
|
||||
} else {
|
||||
cover, err = c.coverArchive(fileName)
|
||||
}
|
||||
|
||||
if c.OnProgress != nil {
|
||||
c.OnProgress()
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("coverImage: %w", err)
|
||||
}
|
||||
|
||||
return cover, nil
|
||||
}
|
||||
|
||||
// imagesFromPath returns list of found image files for given directory.
|
||||
func (c *Convertor) imagesFromPath(path string) ([]string, error) {
|
||||
var images []string
|
||||
@@ -970,7 +1044,7 @@ func (c *Convertor) isImage(f string) bool {
|
||||
|
||||
// isNonImage checks for allowed files in archive.
|
||||
func (c *Convertor) isNonImage(f string) bool {
|
||||
var types = []string{".nfo", ".xml"}
|
||||
var types = []string{".nfo", ".xml", ".txt"}
|
||||
for _, t := range types {
|
||||
if strings.ToLower(filepath.Ext(f)) == t {
|
||||
return true
|
||||
@@ -1027,61 +1101,6 @@ func (c *Convertor) copyFile(reader io.Reader, filename string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// coverName returns the filename that is the most likely to be the cover.
|
||||
func (c *Convertor) coverName(images []string) string {
|
||||
if len(images) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
lower := make([]string, 0)
|
||||
for idx, img := range images {
|
||||
img = strings.ToLower(img)
|
||||
lower = append(lower, img)
|
||||
ext := c.baseNoExt(img)
|
||||
|
||||
if strings.HasPrefix(img, "cover") || strings.HasPrefix(img, "front") ||
|
||||
strings.HasSuffix(ext, "cover") || strings.HasSuffix(ext, "front") {
|
||||
return filepath.ToSlash(images[idx])
|
||||
}
|
||||
}
|
||||
|
||||
sort.Sort(sortorder.Natural(lower))
|
||||
cover := lower[0]
|
||||
|
||||
for idx, img := range images {
|
||||
img = strings.ToLower(img)
|
||||
if img == cover {
|
||||
return filepath.ToSlash(images[idx])
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// coverImage returns cover as image.Image.
|
||||
func (c *Convertor) coverImage(fileName string, fileInfo os.FileInfo) (image.Image, error) {
|
||||
var err error
|
||||
var cover image.Image
|
||||
|
||||
if fileInfo.IsDir() {
|
||||
cover, err = c.coverDirectory(fileName)
|
||||
} else if c.isDocument(fileName) {
|
||||
cover, err = c.coverDocument(fileName)
|
||||
} else {
|
||||
cover, err = c.coverArchive(fileName)
|
||||
}
|
||||
|
||||
if c.OnProgress != nil {
|
||||
c.OnProgress()
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("coverImage: %w", err)
|
||||
}
|
||||
|
||||
return cover, nil
|
||||
}
|
||||
|
||||
// Initialize inits ImageMagick.
|
||||
func (c *Convertor) Initialize() {
|
||||
imagick.Initialize()
|
||||
@@ -1166,7 +1185,7 @@ func (c *Convertor) Cover(fileName string, fileInfo os.FileInfo) error {
|
||||
|
||||
cover, err := c.coverImage(fileName, fileInfo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("extractCover: %w", err)
|
||||
return fmt.Errorf("cover: %w", err)
|
||||
}
|
||||
|
||||
if c.Opts.Width > 0 || c.Opts.Height > 0 {
|
||||
@@ -1181,11 +1200,11 @@ func (c *Convertor) Cover(fileName string, fileInfo os.FileInfo) error {
|
||||
|
||||
switch c.Opts.Format {
|
||||
case "jpeg", "png", "tiff", "webp", "avif":
|
||||
if err := c.encodeImage(cover, fName); err != nil {
|
||||
if err := c.imageEncode(cover, fName); err != nil {
|
||||
return err
|
||||
}
|
||||
case "bmp":
|
||||
if err := c.encodeIM(cover, fName); err != nil {
|
||||
if err := c.imEncode(cover, fName); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -1264,7 +1283,7 @@ func (c *Convertor) Meta(fileName string, info os.FileInfo) (any, error) {
|
||||
if c.Opts.Cover {
|
||||
var images []string
|
||||
|
||||
contents, err := c.listArchive(fileName)
|
||||
contents, err := c.archiveList(fileName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -1276,6 +1295,14 @@ func (c *Convertor) Meta(fileName string, info os.FileInfo) (any, error) {
|
||||
}
|
||||
|
||||
return c.coverName(images), nil
|
||||
} else if c.Opts.Comment {
|
||||
comment, err := c.archiveComment(fileName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return comment, nil
|
||||
} else if c.Opts.CommentBody != "" {
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
@@ -1289,21 +1316,21 @@ func (c *Convertor) Convert(filename string, info os.FileInfo) error {
|
||||
if err := c.convertDirectory(filename); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.saveArchive(filename); err != nil {
|
||||
if err := c.archiveSave(filename); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if c.isDocument(filename) {
|
||||
if err := c.convertDocument(filename); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.saveArchive(filename); err != nil {
|
||||
if err := c.archiveSave(filename); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
} else if c.isArchive(filename) {
|
||||
if err := c.convertArchive(filename); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.saveArchive(filename); err != nil {
|
||||
if err := c.archiveSave(filename); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
@@ -96,6 +96,8 @@ func main() {
|
||||
|
||||
if opts.Cover {
|
||||
fmt.Println(ret)
|
||||
} else if opts.Comment {
|
||||
fmt.Println(ret)
|
||||
}
|
||||
|
||||
continue
|
||||
@@ -182,6 +184,8 @@ func parseFlags() (cbconvert.Options, []string) {
|
||||
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 comment")
|
||||
meta.StringVar(&opts.CommentBody, "comment-body", "", "Set comment body (file or string)")
|
||||
|
||||
convert.Usage = func() {
|
||||
_, _ = fmt.Fprintf(os.Stderr, "Usage: %s <command> [<flags>] [file1 dir1 ... fileOrDirN]\n\n", filepath.Base(os.Args[0]))
|
||||
|
10
go.mod
10
go.mod
@@ -1,6 +1,6 @@
|
||||
module github.com/gen2brain/cbconvert
|
||||
|
||||
go 1.19
|
||||
go 1.21
|
||||
|
||||
require (
|
||||
github.com/chai2010/webp v1.1.1
|
||||
@@ -9,8 +9,8 @@ require (
|
||||
github.com/gen2brain/go-fitz v1.22.2
|
||||
github.com/gen2brain/go-unarr v0.1.7
|
||||
github.com/hotei/bmp v0.0.0-20150430041436-f620cebab0c7
|
||||
github.com/strukturag/libheif v1.13.0
|
||||
golang.org/x/image v0.7.0
|
||||
golang.org/x/sync v0.2.0
|
||||
gopkg.in/gographics/imagick.v3 v3.4.2
|
||||
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
|
||||
)
|
||||
|
18
go.sum
18
go.sum
@@ -10,14 +10,14 @@ github.com/gen2brain/go-unarr v0.1.7 h1:mEE7bPShJIsmAX67t6BW2ibpEUO7j5WK152KgNM9
|
||||
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.13.0 h1:SuLo/Fl/Nzbw0ixOya1YZSl0Xd27X4fgofGnJdvOHqI=
|
||||
github.com/strukturag/libheif v1.13.0/go.mod h1:E/PNRlmVtrtj9j2AvBZlrO4dsBDu6KfwDZn7X1Ce8Ks=
|
||||
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.7.0 h1:gzS29xtG1J5ybQlv0PuyfE3nmc6R4qB73m6LUUmvFuw=
|
||||
golang.org/x/image v0.7.0/go.mod h1:nd/q4ef1AKKYl/4kft7g+6UyGbdiqWqTP1ZAbRoV7Rg=
|
||||
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=
|
||||
@@ -27,8 +27,8 @@ 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.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI=
|
||||
golang.org/x/sync v0.2.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=
|
||||
@@ -42,11 +42,11 @@ 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.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
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.2 h1:vk6oildvhRBVSBfQ4X3raJstApYSeK6CZsyzoSOZk58=
|
||||
gopkg.in/gographics/imagick.v3 v3.4.2/go.mod h1:+Q9nyA2xRZXrDyTtJ/eko+8V/5E7bWYs08ndkZp8UmA=
|
||||
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=
|
||||
|
Reference in New Issue
Block a user