move compress image to zip package

This commit is contained in:
Celogeek 2023-04-27 17:20:37 +02:00
parent 66d6c22e55
commit ba538b431b
Signed by: celogeek
SSH Key Fingerprint: SHA256:njNJLzoLQdbV9PC6ehcruRb0QnEgxABoCYZ+0+aUIYc
5 changed files with 97 additions and 91 deletions

View File

@ -47,8 +47,8 @@ type ePub struct {
}
type epubPart struct {
Cover *epubimage.Image
Images []*epubimage.Image
Cover *epubimageprocessing.LoadedImage
LoadedImages epubimageprocessing.LoadedImages
}
// initialize epub
@ -80,19 +80,19 @@ func (e *ePub) render(templateString string, data map[string]any) string {
}
// write image to the zip
func (e *ePub) writeImage(wz *epubzip.EpubZip, img *epubimage.Image) error {
err := wz.WriteFile(
fmt.Sprintf("OEBPS/%s", img.TextPath()),
func (e *ePub) writeImage(wz *epubzip.EpubZip, img *epubimageprocessing.LoadedImage) error {
err := wz.WriteContent(
fmt.Sprintf("OEBPS/%s", img.Image.TextPath()),
[]byte(e.render(epubtemplates.Text, map[string]any{
"Title": fmt.Sprintf("Image %d Part %d", img.Id, img.Part),
"Title": fmt.Sprintf("Image %d Part %d", img.Image.Id, img.Image.Part),
"ViewPort": fmt.Sprintf("width=%d,height=%d", e.Image.ViewWidth, e.Image.ViewHeight),
"ImagePath": img.ImgPath(),
"ImageStyle": img.ImgStyle(e.Image.ViewWidth, e.Image.ViewHeight, e.Image.Manga),
"ImagePath": img.Image.ImgPath(),
"ImageStyle": img.Image.ImgStyle(e.Image.ViewWidth, e.Image.ViewHeight, e.Image.Manga),
})),
)
if err == nil {
err = wz.WriteImage(img.Data)
err = wz.WriteRaw(img.ZipImage)
}
return err
@ -100,7 +100,7 @@ func (e *ePub) writeImage(wz *epubzip.EpubZip, img *epubimage.Image) error {
// write blank page
func (e *ePub) writeBlank(wz *epubzip.EpubZip, img *epubimage.Image) error {
return wz.WriteFile(
return wz.WriteContent(
fmt.Sprintf("OEBPS/Text/%d_sp.xhtml", img.Id),
[]byte(e.render(epubtemplates.Blank, map[string]any{
"Title": fmt.Sprintf("Blank Page %d", img.Id),
@ -111,7 +111,7 @@ func (e *ePub) writeBlank(wz *epubzip.EpubZip, img *epubimage.Image) error {
// extract image and split it into part
func (e *ePub) getParts() ([]*epubPart, error) {
images, err := epubimageprocessing.LoadImages(&epubimageprocessing.Options{
loadedImages, err := epubimageprocessing.LoadImages(&epubimageprocessing.Options{
Input: e.Input,
SortPathMode: e.SortPathMode,
Quiet: e.Quiet,
@ -125,23 +125,23 @@ func (e *ePub) getParts() ([]*epubPart, error) {
}
// sort result by id and part
sort.Slice(images, func(i, j int) bool {
if images[i].Id == images[j].Id {
return images[i].Part < images[j].Part
sort.Slice(loadedImages, func(i, j int) bool {
if loadedImages[i].Image.Id == loadedImages[j].Image.Id {
return loadedImages[i].Image.Part < loadedImages[j].Image.Part
}
return images[i].Id < images[j].Id
return loadedImages[i].Image.Id < loadedImages[j].Image.Id
})
parts := make([]*epubPart, 0)
cover := images[0]
cover := loadedImages[0]
if e.Image.HasCover {
images = images[1:]
loadedImages = loadedImages[1:]
}
if e.Dry {
parts = append(parts, &epubPart{
Cover: cover,
Images: images,
Cover: cover,
LoadedImages: loadedImages,
})
return parts, nil
}
@ -150,36 +150,36 @@ func (e *ePub) getParts() ([]*epubPart, error) {
maxSize := uint64(e.LimitMb * 1024 * 1024)
xhtmlSize := uint64(1024)
// descriptor files + title
baseSize := uint64(16*1024) + cover.Data.CompressedSize()
baseSize := uint64(16*1024) + cover.ZipImage.CompressedSize()
if e.Image.HasCover {
baseSize += cover.Data.CompressedSize()
baseSize += cover.ZipImage.CompressedSize()
}
currentSize := baseSize
currentImages := make([]*epubimage.Image, 0)
currentImages := make([]*epubimageprocessing.LoadedImage, 0)
part := 1
for _, img := range images {
imgSize := img.Data.CompressedSize() + xhtmlSize
for _, img := range loadedImages {
imgSize := img.ZipImage.CompressedSize() + xhtmlSize
if maxSize > 0 && len(currentImages) > 0 && currentSize+imgSize > maxSize {
parts = append(parts, &epubPart{
Cover: cover,
Images: currentImages,
Cover: cover,
LoadedImages: currentImages,
})
part += 1
currentSize = baseSize
if !e.Image.HasCover {
currentSize += cover.Data.CompressedSize()
currentSize += cover.ZipImage.CompressedSize()
}
currentImages = make([]*epubimage.Image, 0)
currentImages = make([]*epubimageprocessing.LoadedImage, 0)
}
currentSize += imgSize
currentImages = append(currentImages, img)
}
if len(currentImages) > 0 {
parts = append(parts, &epubPart{
Cover: cover,
Images: currentImages,
Cover: cover,
LoadedImages: currentImages,
})
}
@ -220,12 +220,12 @@ func (e *ePub) Write() error {
if e.Dry {
p := epubParts[0]
fmt.Fprintf(os.Stderr, "TOC:\n - %s\n%s\n", e.Title, e.getTree(p.Images, true))
fmt.Fprintf(os.Stderr, "TOC:\n - %s\n%s\n", e.Title, e.getTree(p.LoadedImages.Images(), true))
if e.DryVerbose {
if e.Image.HasCover {
fmt.Fprintf(os.Stderr, "Cover:\n%s\n", e.getTree([]*epubimage.Image{p.Cover}, false))
fmt.Fprintf(os.Stderr, "Cover:\n%s\n", e.getTree([]*epubimage.Image{p.Cover.Image}, false))
}
fmt.Fprintf(os.Stderr, "Files:\n%s\n", e.getTree(p.Images, false))
fmt.Fprintf(os.Stderr, "Files:\n%s\n", e.getTree(p.LoadedImages.Images(), false))
}
return nil
}
@ -270,12 +270,12 @@ func (e *ePub) Write() error {
Publisher: e.Publisher,
UpdatedAt: e.UpdatedAt,
ImageOptions: e.Image,
Cover: part.Cover,
Images: part.Images,
Cover: part.Cover.Image,
Images: part.LoadedImages.Images(),
Current: i + 1,
Total: totalParts,
})},
{"OEBPS/toc.xhtml", epubtemplates.Toc(title, e.StripFirstDirectoryFromToc, part.Images)},
{"OEBPS/toc.xhtml", epubtemplates.Toc(title, e.StripFirstDirectoryFromToc, part.LoadedImages.Images())},
{"OEBPS/Text/style.css", e.render(epubtemplates.Style, map[string]any{
"PageWidth": e.Image.ViewWidth,
"PageHeight": e.Image.ViewHeight,
@ -284,7 +284,7 @@ func (e *ePub) Write() error {
"Title": title,
"ViewPort": fmt.Sprintf("width=%d,height=%d", e.Image.ViewWidth, e.Image.ViewHeight),
"ImagePath": "Images/title.jpg",
"ImageStyle": part.Cover.ImgStyle(e.Image.ViewWidth, e.Image.ViewHeight, e.Image.Manga),
"ImageStyle": part.Cover.Image.ImgStyle(e.Image.ViewWidth, e.Image.ViewHeight, e.Image.Manga),
})},
}
@ -292,11 +292,11 @@ func (e *ePub) Write() error {
return err
}
for _, c := range content {
if err := wz.WriteFile(c.Name, []byte(c.Content)); err != nil {
if err := wz.WriteContent(c.Name, []byte(c.Content)); err != nil {
return err
}
}
if err := wz.WriteImage(epubimageprocessing.CoverTitleData(part.Cover.Raw, title, e.Image.Quality)); err != nil {
if err := wz.WriteRaw(epubimageprocessing.CoverTitleData(part.Cover.Image.Raw, title, e.Image.Quality)); err != nil {
return err
}
@ -308,14 +308,14 @@ func (e *ePub) Write() error {
}
}
for i, img := range part.Images {
for i, img := range part.LoadedImages {
if err := e.writeImage(wz, img); err != nil {
return err
}
// Double Page or Last Image
if img.DoublePage || (i+1 == len(part.Images)) {
if err := e.writeBlank(wz, img); err != nil {
if img.Image.DoublePage || (i+1 == len(part.LoadedImages)) {
if err := e.writeBlank(wz, img.Image); err != nil {
return err
}
}

View File

@ -6,15 +6,12 @@ package epubimage
import (
"fmt"
"image"
epubimagedata "github.com/celogeek/go-comic-converter/v2/internal/epub/imagedata"
)
type Image struct {
Id int
Part int
Raw image.Image
Data *epubimagedata.ImageData
Width int
Height int
IsCover bool

View File

@ -4,18 +4,34 @@ Extract and transform image into a compressed jpeg.
package epubimageprocessing
import (
"fmt"
"image"
"path/filepath"
"strings"
"sync"
epubimage "github.com/celogeek/go-comic-converter/v2/internal/epub/image"
epubimagedata "github.com/celogeek/go-comic-converter/v2/internal/epub/imagedata"
epubimagefilters "github.com/celogeek/go-comic-converter/v2/internal/epub/imagefilters"
epubprogress "github.com/celogeek/go-comic-converter/v2/internal/epub/progress"
epubzip "github.com/celogeek/go-comic-converter/v2/internal/epub/zip"
"github.com/disintegration/gift"
)
type LoadedImage struct {
Image *epubimage.Image
ZipImage *epubzip.ZipImage
}
type LoadedImages []*LoadedImage
func (l LoadedImages) Images() []*epubimage.Image {
res := make([]*epubimage.Image, len(l))
for i, v := range l {
res[i] = v.Image
}
return res
}
// only accept jpg, png and webp as source file
func isSupportedImage(path string) bool {
switch strings.ToLower(filepath.Ext(path)) {
@ -28,8 +44,8 @@ func isSupportedImage(path string) bool {
}
// extract and convert images
func LoadImages(o *Options) ([]*epubimage.Image, error) {
images := make([]*epubimage.Image, 0)
func LoadImages(o *Options) (LoadedImages, error) {
images := make(LoadedImages, 0)
imageCount, imageInput, err := o.Load()
if err != nil {
@ -39,17 +55,19 @@ func LoadImages(o *Options) ([]*epubimage.Image, error) {
// dry run, skip convertion
if o.Dry {
for img := range imageInput {
images = append(images, &epubimage.Image{
Id: img.Id,
Path: img.Path,
Name: img.Name,
images = append(images, &LoadedImage{
Image: &epubimage.Image{
Id: img.Id,
Path: img.Path,
Name: img.Name,
},
})
}
return images, nil
}
imageOutput := make(chan *epubimage.Image)
imageOutput := make(chan *LoadedImage)
// processing
bar := epubprogress.New(epubprogress.Options{
@ -75,17 +93,19 @@ func LoadImages(o *Options) ([]*epubimage.Image, error) {
raw = dst
}
imageOutput <- &epubimage.Image{
Id: img.Id,
Part: part,
Raw: raw,
Data: epubimagedata.New(img.Id, part, dst, o.Image.Quality),
Width: dst.Bounds().Dx(),
Height: dst.Bounds().Dy(),
IsCover: img.Id == 0 && part == 0,
DoublePage: part == 0 && src.Bounds().Dx() > src.Bounds().Dy(),
Path: img.Path,
Name: img.Name,
imageOutput <- &LoadedImage{
Image: &epubimage.Image{
Id: img.Id,
Part: part,
Raw: raw,
Width: dst.Bounds().Dx(),
Height: dst.Bounds().Dy(),
IsCover: img.Id == 0 && part == 0,
DoublePage: part == 0 && src.Bounds().Dx() > src.Bounds().Dy(),
Path: img.Path,
Name: img.Name,
},
ZipImage: epubzip.CompressImage(fmt.Sprintf("OEBPS/Images/%d_p%d.jpg", img.Id, part), dst, o.Image.Quality),
}
}
}
@ -97,14 +117,14 @@ func LoadImages(o *Options) ([]*epubimage.Image, error) {
close(imageOutput)
}()
for img := range imageOutput {
if img.Part == 0 {
for output := range imageOutput {
if output.Image.Part == 0 {
bar.Add(1)
}
if o.Image.NoBlankPage && img.Width == 1 && img.Height == 1 {
if o.Image.NoBlankPage && output.Image.Width == 1 && output.Image.Height == 1 {
continue
}
images = append(images, img)
images = append(images, output)
}
bar.Close()
@ -116,13 +136,13 @@ func LoadImages(o *Options) ([]*epubimage.Image, error) {
}
// create a title page with the cover
func CoverTitleData(img image.Image, title string, quality int) *epubimagedata.ImageData {
func CoverTitleData(img image.Image, title string, quality int) *epubzip.ZipImage {
// Create a blur version of the cover
g := gift.New(epubimagefilters.CoverTitle(title))
dst := image.NewGray(g.Bounds(img.Bounds()))
g.Draw(dst, img)
return epubimagedata.NewRaw("OEBPS/Images/title.jpg", dst, quality)
return epubzip.CompressImage("OEBPS/Images/title.jpg", dst, quality)
}
// transform image into 1 or 3 images

View File

@ -9,8 +9,6 @@ import (
"archive/zip"
"os"
"time"
epubimagedata "github.com/celogeek/go-comic-converter/v2/internal/epub/imagedata"
)
type EpubZip struct {
@ -61,17 +59,17 @@ func (e *EpubZip) WriteMagic() error {
}
// Write image. They are already compressed, so we write them down directly.
func (e *EpubZip) WriteImage(image *epubimagedata.ImageData) error {
m, err := e.wz.CreateRaw(image.Header)
func (e *EpubZip) WriteRaw(raw *ZipImage) error {
m, err := e.wz.CreateRaw(raw.Header)
if err != nil {
return err
}
_, err = m.Write(image.Data)
_, err = m.Write(raw.Data)
return err
}
// Write file. Compressed it using deflate.
func (e *EpubZip) WriteFile(file string, content []byte) error {
func (e *EpubZip) WriteContent(file string, content []byte) error {
m, err := e.wz.CreateHeader(&zip.FileHeader{
Name: file,
Modified: time.Now(),

View File

@ -1,7 +1,4 @@
/*
prepare image to be store in a zip file.
*/
package epubimagedata
package epubzip
import (
"archive/zip"
@ -15,13 +12,13 @@ import (
"time"
)
type ImageData struct {
type ZipImage struct {
Header *zip.FileHeader
Data []byte
}
// compressed size of the image with the header
func (img *ImageData) CompressedSize() uint64 {
func (img *ZipImage) CompressedSize() uint64 {
return img.Header.CompressedSize64 + 30 + uint64(len(img.Header.Name))
}
@ -30,14 +27,8 @@ func exitWithError(err error) {
os.Exit(1)
}
// create a new data image with file name based on id and part
func New(id int, part int, img image.Image, quality int) *ImageData {
name := fmt.Sprintf("OEBPS/Images/%d_p%d.jpg", id, part)
return NewRaw(name, img, quality)
}
// create gzip encoded jpeg
func NewRaw(name string, img image.Image, quality int) *ImageData {
func CompressImage(filename string, img image.Image, quality int) *ZipImage {
var (
data, cdata bytes.Buffer
err error
@ -64,9 +55,9 @@ func NewRaw(name string, img image.Image, quality int) *ImageData {
}
t := time.Now()
return &ImageData{
return &ZipImage{
&zip.FileHeader{
Name: name,
Name: filename,
CompressedSize64: uint64(cdata.Len()),
UncompressedSize64: uint64(data.Len()),
CRC32: crc32.Checksum(data.Bytes(), crc32.IEEETable),