diff --git a/internal/epub/epub.go b/internal/epub/epub.go index c2b6c49..431017c 100644 --- a/internal/epub/epub.go +++ b/internal/epub/epub.go @@ -14,7 +14,7 @@ import ( "time" epubimage "github.com/celogeek/go-comic-converter/v2/internal/epub/image" - epubimageprocessing "github.com/celogeek/go-comic-converter/v2/internal/epub/imageprocessing" + epubimageprocessor "github.com/celogeek/go-comic-converter/v2/internal/epub/imageprocessor" epuboptions "github.com/celogeek/go-comic-converter/v2/internal/epub/options" epubprogress "github.com/celogeek/go-comic-converter/v2/internal/epub/progress" epubtemplates "github.com/celogeek/go-comic-converter/v2/internal/epub/templates" @@ -30,11 +30,12 @@ type ePub struct { UpdatedAt string templateProcessor *template.Template + imageProcessor *epubimageprocessor.EpubImageProcessor } type epubPart struct { - Cover *epubimageprocessing.LoadedImage - LoadedImages epubimageprocessing.LoadedImages + Cover *epubimageprocessor.LoadedImage + LoadedImages epubimageprocessor.LoadedImages } // initialize epub @@ -52,6 +53,7 @@ func New(options *epuboptions.Options) *ePub { Publisher: "GO Comic Converter", UpdatedAt: time.Now().UTC().Format("2006-01-02T15:04:05Z"), templateProcessor: tmpl, + imageProcessor: epubimageprocessor.New(options), } } @@ -66,7 +68,7 @@ func (e *ePub) render(templateString string, data map[string]any) string { } // write image to the zip -func (e *ePub) writeImage(wz *epubzip.EpubZip, img *epubimageprocessing.LoadedImage) error { +func (e *ePub) writeImage(wz *epubzip.EpubZip, img *epubimageprocessor.LoadedImage) error { err := wz.WriteContent( fmt.Sprintf("OEBPS/%s", img.Image.TextPath()), []byte(e.render(epubtemplates.Text, map[string]any{ @@ -97,7 +99,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) { - loadedImages, err := epubimageprocessing.LoadImages(e.Options) + loadedImages, err := e.imageProcessor.Load() if err != nil { return nil, err @@ -135,7 +137,7 @@ func (e *ePub) getParts() ([]*epubPart, error) { } currentSize := baseSize - currentImages := make([]*epubimageprocessing.LoadedImage, 0) + currentImages := make([]*epubimageprocessor.LoadedImage, 0) part := 1 for _, img := range loadedImages { @@ -150,7 +152,7 @@ func (e *ePub) getParts() ([]*epubPart, error) { if !e.Image.HasCover { currentSize += cover.ZipImage.CompressedSize() } - currentImages = make([]*epubimageprocessing.LoadedImage, 0) + currentImages = make([]*epubimageprocessor.LoadedImage, 0) } currentSize += imgSize currentImages = append(currentImages, img) @@ -275,7 +277,7 @@ func (e *ePub) Write() error { return err } } - if err := wz.WriteRaw(epubimageprocessing.CoverTitleData(part.Cover.Image.Raw, title, e.Image.Quality)); err != nil { + if err := wz.WriteRaw(e.imageProcessor.CoverTitleData(part.Cover.Image.Raw, title)); err != nil { return err } diff --git a/internal/epub/imageprocessing/epub_image_processing.go b/internal/epub/imageprocessor/epub_image_processor.go similarity index 71% rename from internal/epub/imageprocessing/epub_image_processing.go rename to internal/epub/imageprocessor/epub_image_processor.go index 3314e98..f36773f 100644 --- a/internal/epub/imageprocessing/epub_image_processing.go +++ b/internal/epub/imageprocessor/epub_image_processor.go @@ -1,13 +1,11 @@ /* Extract and transform image into a compressed jpeg. */ -package epubimageprocessing +package epubimageprocessor import ( "fmt" "image" - "path/filepath" - "strings" "sync" epubimage "github.com/celogeek/go-comic-converter/v2/internal/epub/image" @@ -33,28 +31,25 @@ func (l LoadedImages) Images() []*epubimage.Image { return res } -// only accept jpg, png and webp as source file -func isSupportedImage(path string) bool { - switch strings.ToLower(filepath.Ext(path)) { - case ".jpg", ".jpeg", ".png", ".webp": - { - return true - } - } - return false +type EpubImageProcessor struct { + *epuboptions.Options +} + +func New(o *epuboptions.Options) *EpubImageProcessor { + return &EpubImageProcessor{o} } // extract and convert images -func LoadImages(o *epuboptions.Options) (LoadedImages, error) { +func (e *EpubImageProcessor) Load() (LoadedImages, error) { images := make(LoadedImages, 0) - imageCount, imageInput, err := Load(o) + imageCount, imageInput, err := e.load() if err != nil { return nil, err } // dry run, skip convertion - if o.Dry { + if e.Dry { for img := range imageInput { images = append(images, &LoadedImage{ Image: &epubimage.Image{ @@ -72,7 +67,7 @@ func LoadImages(o *epuboptions.Options) (LoadedImages, error) { // processing bar := epubprogress.New(epubprogress.Options{ - Quiet: o.Quiet, + Quiet: e.Quiet, Max: imageCount, Description: "Processing", CurrentJob: 1, @@ -80,7 +75,7 @@ func LoadImages(o *epuboptions.Options) (LoadedImages, error) { }) wg := &sync.WaitGroup{} - for i := 0; i < o.WorkersRatio(50); i++ { + for i := 0; i < e.WorkersRatio(50); i++ { wg.Add(1) go func() { defer wg.Done() @@ -88,7 +83,7 @@ func LoadImages(o *epuboptions.Options) (LoadedImages, error) { for input := range imageInput { src := input.Image - for part, dst := range TransformImage(src, input.Id, o.Image) { + for part, dst := range e.transformImage(src, input.Id) { var raw image.Image if input.Id == 0 && part == 0 { raw = dst @@ -107,7 +102,7 @@ func LoadImages(o *epuboptions.Options) (LoadedImages, error) { } imageOutput <- &LoadedImage{ Image: img, - ZipImage: epubzip.CompressImage(fmt.Sprintf("OEBPS/%s", img.ImgPath()), dst, o.Image.Quality), + ZipImage: epubzip.CompressImage(fmt.Sprintf("OEBPS/%s", img.ImgPath()), dst, e.Image.Quality), } } } @@ -123,7 +118,7 @@ func LoadImages(o *epuboptions.Options) (LoadedImages, error) { if output.Image.Part == 0 { bar.Add(1) } - if o.Image.NoBlankPage && output.Image.Width == 1 && output.Image.Height == 1 { + if e.Image.NoBlankPage && output.Image.Width == 1 && output.Image.Height == 1 { continue } images = append(images, output) @@ -137,52 +132,42 @@ func LoadImages(o *epuboptions.Options) (LoadedImages, error) { return images, nil } -// create a title page with the cover -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 epubzip.CompressImage("OEBPS/Images/title.jpg", dst, quality) -} - // transform image into 1 or 3 images // only doublepage with autosplit has 3 versions -func TransformImage(src image.Image, srcId int, o *epuboptions.Image) []image.Image { +func (e *EpubImageProcessor) transformImage(src image.Image, srcId int) []image.Image { var filters, splitFilter []gift.Filter var images []image.Image - if o.Crop.Enabled { + if e.Image.Crop.Enabled { f := epubimagefilters.AutoCrop( src, - o.Crop.Left, - o.Crop.Up, - o.Crop.Right, - o.Crop.Bottom, + e.Image.Crop.Left, + e.Image.Crop.Up, + e.Image.Crop.Right, + e.Image.Crop.Bottom, ) filters = append(filters, f) splitFilter = append(splitFilter, f) } - if o.AutoRotate && src.Bounds().Dx() > src.Bounds().Dy() { + if e.Image.AutoRotate && src.Bounds().Dx() > src.Bounds().Dy() { filters = append(filters, gift.Rotate90()) } - if o.Contrast != 0 { - f := gift.Contrast(float32(o.Contrast)) + if e.Image.Contrast != 0 { + f := gift.Contrast(float32(e.Image.Contrast)) filters = append(filters, f) splitFilter = append(splitFilter, f) } - if o.Brightness != 0 { - f := gift.Brightness(float32(o.Brightness)) + if e.Image.Brightness != 0 { + f := gift.Brightness(float32(e.Image.Brightness)) filters = append(filters, f) splitFilter = append(splitFilter, f) } filters = append(filters, - epubimagefilters.Resize(o.View.Width, o.View.Height, gift.LanczosResampling), + epubimagefilters.Resize(e.Image.View.Width, e.Image.View.Height, gift.LanczosResampling), epubimagefilters.Pixel(), ) @@ -195,7 +180,7 @@ func TransformImage(src image.Image, srcId int, o *epuboptions.Image) []image.Im } // auto split off - if !o.AutoSplitDoublePage { + if !e.Image.AutoSplitDoublePage { return images } @@ -205,16 +190,16 @@ func TransformImage(src image.Image, srcId int, o *epuboptions.Image) []image.Im } // cover - if o.HasCover && srcId == 0 { + if e.Image.HasCover && srcId == 0 { return images } // convert double page - for _, b := range []bool{o.Manga, !o.Manga} { + for _, b := range []bool{e.Image.Manga, !e.Image.Manga} { g := gift.New(splitFilter...) g.Add( epubimagefilters.CropSplitDoublePage(b), - epubimagefilters.Resize(o.View.Width, o.View.Height, gift.LanczosResampling), + epubimagefilters.Resize(e.Image.View.Width, e.Image.View.Height, gift.LanczosResampling), ) dst := image.NewGray(g.Bounds(src.Bounds())) g.Draw(dst, src) @@ -223,3 +208,13 @@ func TransformImage(src image.Image, srcId int, o *epuboptions.Image) []image.Im return images } + +// create a title page with the cover +func (e *EpubImageProcessor) CoverTitleData(img image.Image, title string) *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 epubzip.CompressImage("OEBPS/Images/title.jpg", dst, e.Image.Quality) +} diff --git a/internal/epub/imageprocessing/epub_image_processing_loader.go b/internal/epub/imageprocessor/epub_image_processor_loader.go similarity index 78% rename from internal/epub/imageprocessing/epub_image_processing_loader.go rename to internal/epub/imageprocessor/epub_image_processor_loader.go index 0148a81..094bff1 100644 --- a/internal/epub/imageprocessing/epub_image_processing_loader.go +++ b/internal/epub/imageprocessor/epub_image_processor_loader.go @@ -1,4 +1,4 @@ -package epubimageprocessing +package epubimageprocessor import ( "archive/zip" @@ -18,7 +18,6 @@ import ( _ "golang.org/x/image/webp" - epuboptions "github.com/celogeek/go-comic-converter/v2/internal/epub/options" "github.com/celogeek/go-comic-converter/v2/internal/sortpath" "github.com/nwaples/rardecode/v2" pdfimage "github.com/raff/pdfreader/image" @@ -34,24 +33,35 @@ type tasks struct { var errNoImagesFound = errors.New("no images found") +// only accept jpg, png and webp as source file +func (e *EpubImageProcessor) isSupportedImage(path string) bool { + switch strings.ToLower(filepath.Ext(path)) { + case ".jpg", ".jpeg", ".png", ".webp": + { + return true + } + } + return false +} + // Load images from input -func Load(o *epuboptions.Options) (totalImages int, output chan *tasks, err error) { - fi, err := os.Stat(o.Input) +func (e *EpubImageProcessor) load() (totalImages int, output chan *tasks, err error) { + fi, err := os.Stat(e.Input) if err != nil { return } // get all images though a channel of bytes if fi.IsDir() { - return loadDir(o) + return e.loadDir() } else { - switch ext := strings.ToLower(filepath.Ext(o.Input)); ext { + switch ext := strings.ToLower(filepath.Ext(e.Input)); ext { case ".cbz", ".zip": - return loadCbz(o) + return e.loadCbz() case ".cbr", ".rar": - return loadCbr(o) + return e.loadCbr() case ".pdf": - return loadPdf(o) + return e.loadPdf() default: err = fmt.Errorf("unknown file format (%s): support .cbz, .zip, .cbr, .rar, .pdf", ext) return @@ -60,15 +70,15 @@ func Load(o *epuboptions.Options) (totalImages int, output chan *tasks, err erro } // load a directory of images -func loadDir(o *epuboptions.Options) (totalImages int, output chan *tasks, err error) { +func (e *EpubImageProcessor) loadDir() (totalImages int, output chan *tasks, err error) { images := make([]string, 0) - input := filepath.Clean(o.Input) + input := filepath.Clean(e.Input) err = filepath.WalkDir(input, func(path string, d fs.DirEntry, err error) error { if err != nil { return err } - if !d.IsDir() && isSupportedImage(path) { + if !d.IsDir() && e.isSupportedImage(path) { images = append(images, path) } return nil @@ -86,7 +96,7 @@ func loadDir(o *epuboptions.Options) (totalImages int, output chan *tasks, err e return } - sort.Sort(sortpath.By(images, o.SortPathMode)) + sort.Sort(sortpath.By(images, e.SortPathMode)) // Queue all file with id type job struct { @@ -102,15 +112,15 @@ func loadDir(o *epuboptions.Options) (totalImages int, output chan *tasks, err e }() // read in parallel and get an image - output = make(chan *tasks, o.Workers) + output = make(chan *tasks, e.Workers) wg := &sync.WaitGroup{} - for j := 0; j < o.WorkersRatio(50); j++ { + for j := 0; j < e.WorkersRatio(50); j++ { wg.Add(1) go func() { defer wg.Done() for job := range jobs { var img image.Image - if !o.Dry { + if !e.Dry { f, err := os.Open(job.Path) if err != nil { fmt.Fprintf(os.Stderr, "\nerror processing image %s: %s\n", job.Path, err) @@ -150,15 +160,15 @@ func loadDir(o *epuboptions.Options) (totalImages int, output chan *tasks, err e } // load a zip file that include images -func loadCbz(o *epuboptions.Options) (totalImages int, output chan *tasks, err error) { - r, err := zip.OpenReader(o.Input) +func (e *EpubImageProcessor) loadCbz() (totalImages int, output chan *tasks, err error) { + r, err := zip.OpenReader(e.Input) if err != nil { return } images := make([]*zip.File, 0) for _, f := range r.File { - if !f.FileInfo().IsDir() && isSupportedImage(f.Name) { + if !f.FileInfo().IsDir() && e.isSupportedImage(f.Name) { images = append(images, f) } } @@ -175,7 +185,7 @@ func loadCbz(o *epuboptions.Options) (totalImages int, output chan *tasks, err e for _, img := range images { names = append(names, img.Name) } - sort.Sort(sortpath.By(names, o.SortPathMode)) + sort.Sort(sortpath.By(names, e.SortPathMode)) indexedNames := make(map[string]int) for i, name := range names { @@ -194,15 +204,15 @@ func loadCbz(o *epuboptions.Options) (totalImages int, output chan *tasks, err e } }() - output = make(chan *tasks, o.Workers) + output = make(chan *tasks, e.Workers) wg := &sync.WaitGroup{} - for j := 0; j < o.WorkersRatio(50); j++ { + for j := 0; j < e.WorkersRatio(50); j++ { wg.Add(1) go func() { defer wg.Done() for job := range jobs { var img image.Image - if !o.Dry { + if !e.Dry { f, err := job.F.Open() if err != nil { fmt.Fprintf(os.Stderr, "\nerror processing image %s: %s\n", job.F.Name, err) @@ -236,16 +246,16 @@ func loadCbz(o *epuboptions.Options) (totalImages int, output chan *tasks, err e } // load a rar file that include images -func loadCbr(o *epuboptions.Options) (totalImages int, output chan *tasks, err error) { +func (e *EpubImageProcessor) loadCbr() (totalImages int, output chan *tasks, err error) { var isSolid bool - files, err := rardecode.List(o.Input) + files, err := rardecode.List(e.Input) if err != nil { return } names := make([]string, 0) for _, f := range files { - if !f.IsDir && isSupportedImage(f.Name) { + if !f.IsDir && e.isSupportedImage(f.Name) { if f.Solid { isSolid = true } @@ -259,7 +269,7 @@ func loadCbr(o *epuboptions.Options) (totalImages int, output chan *tasks, err e return } - sort.Sort(sortpath.By(names, o.SortPathMode)) + sort.Sort(sortpath.By(names, e.SortPathMode)) indexedNames := make(map[string]int) for i, name := range names { @@ -275,10 +285,10 @@ func loadCbr(o *epuboptions.Options) (totalImages int, output chan *tasks, err e jobs := make(chan *job) go func() { defer close(jobs) - if isSolid && !o.Dry { - r, rerr := rardecode.OpenReader(o.Input) + if isSolid && !e.Dry { + r, rerr := rardecode.OpenReader(e.Input) if rerr != nil { - fmt.Fprintf(os.Stderr, "\nerror processing image %s: %s\n", o.Input, rerr) + fmt.Fprintf(os.Stderr, "\nerror processing image %s: %s\n", e.Input, rerr) os.Exit(1) } defer r.Close() @@ -313,15 +323,15 @@ func loadCbr(o *epuboptions.Options) (totalImages int, output chan *tasks, err e }() // send file to the queue - output = make(chan *tasks, o.Workers) + output = make(chan *tasks, e.Workers) wg := &sync.WaitGroup{} - for j := 0; j < o.WorkersRatio(50); j++ { + for j := 0; j < e.WorkersRatio(50); j++ { wg.Add(1) go func() { defer wg.Done() for job := range jobs { var img image.Image - if !o.Dry { + if !e.Dry { f, err := job.Open() if err != nil { fmt.Fprintf(os.Stderr, "\nerror processing image %s: %s\n", job.Name, err) @@ -353,8 +363,8 @@ func loadCbr(o *epuboptions.Options) (totalImages int, output chan *tasks, err e } // extract image from a pdf -func loadPdf(o *epuboptions.Options) (totalImages int, output chan *tasks, err error) { - pdf := pdfread.Load(o.Input) +func (e *EpubImageProcessor) loadPdf() (totalImages int, output chan *tasks, err error) { + pdf := pdfread.Load(e.Input) if pdf == nil { err = fmt.Errorf("can't read pdf") return @@ -368,7 +378,7 @@ func loadPdf(o *epuboptions.Options) (totalImages int, output chan *tasks, err e defer pdf.Close() for i := 0; i < totalImages; i++ { var img image.Image - if !o.Dry { + if !e.Dry { img, err = pdfimage.Extract(pdf, i+1) if err != nil { fmt.Fprintln(os.Stderr, err)