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

View File

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

View File

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

View File

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

View File

@ -1,7 +1,4 @@
/* package epubzip
prepare image to be store in a zip file.
*/
package epubimagedata
import ( import (
"archive/zip" "archive/zip"
@ -15,13 +12,13 @@ import (
"time" "time"
) )
type ImageData struct { type ZipImage struct {
Header *zip.FileHeader Header *zip.FileHeader
Data []byte Data []byte
} }
// compressed size of the image with the header // 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)) return img.Header.CompressedSize64 + 30 + uint64(len(img.Header.Name))
} }
@ -30,14 +27,8 @@ func exitWithError(err error) {
os.Exit(1) 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 // 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 ( var (
data, cdata bytes.Buffer data, cdata bytes.Buffer
err error err error
@ -64,9 +55,9 @@ func NewRaw(name string, img image.Image, quality int) *ImageData {
} }
t := time.Now() t := time.Now()
return &ImageData{ return &ZipImage{
&zip.FileHeader{ &zip.FileHeader{
Name: name, Name: filename,
CompressedSize64: uint64(cdata.Len()), CompressedSize64: uint64(cdata.Len()),
UncompressedSize64: uint64(data.Len()), UncompressedSize64: uint64(data.Len()),
CRC32: crc32.Checksum(data.Bytes(), crc32.IEEETable), CRC32: crc32.Checksum(data.Bytes(), crc32.IEEETable),