diff --git a/internal/epub/epub.go b/internal/epub/epub.go index 16ff0c1..acb5286 100644 --- a/internal/epub/epub.go +++ b/internal/epub/epub.go @@ -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 } } diff --git a/internal/epub/image/epub_image.go b/internal/epub/image/epub_image.go index 21eb47c..8770a42 100644 --- a/internal/epub/image/epub_image.go +++ b/internal/epub/image/epub_image.go @@ -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 diff --git a/internal/epub/imageprocessing/epub_image_processing.go b/internal/epub/imageprocessing/epub_image_processing.go index ab78178..605f3a9 100644 --- a/internal/epub/imageprocessing/epub_image_processing.go +++ b/internal/epub/imageprocessing/epub_image_processing.go @@ -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 diff --git a/internal/epub/zip/epub_zip.go b/internal/epub/zip/epub_zip.go index 8698d10..9e0ea6a 100644 --- a/internal/epub/zip/epub_zip.go +++ b/internal/epub/zip/epub_zip.go @@ -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(), diff --git a/internal/epub/imagedata/epub_image_data.go b/internal/epub/zip/epub_zip_image.go similarity index 71% rename from internal/epub/imagedata/epub_image_data.go rename to internal/epub/zip/epub_zip_image.go index 60ce499..b7559bf 100644 --- a/internal/epub/imagedata/epub_image_data.go +++ b/internal/epub/zip/epub_zip_image.go @@ -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),