From 5ac59a93c56d15f2d732b9d37d5b39787c5576d0 Mon Sep 17 00:00:00 2001 From: Antoine Aflalo <197810+Belphemur@users.noreply.github.com> Date: Wed, 28 Aug 2024 09:06:49 -0400 Subject: [PATCH] feat(split): Make the split configurable for the optimize command --- cmd/optimize_command.go | 14 +++++++++++++- converter/converter.go | 2 +- converter/converter_test.go | 8 ++++++-- converter/webp/webp_converter.go | 26 ++++++++++++++------------ utils/optimize.go | 24 ++++++++++++++++-------- 5 files changed, 50 insertions(+), 24 deletions(-) diff --git a/cmd/optimize_command.go b/cmd/optimize_command.go index 6b7243e..f1d00b5 100644 --- a/cmd/optimize_command.go +++ b/cmd/optimize_command.go @@ -29,6 +29,7 @@ func init() { command.Flags().Uint8P("quality", "q", 85, "Quality for conversion (0-100)") command.Flags().IntP("parallelism", "n", 2, "Number of chapters to convert in parallel") command.Flags().BoolP("override", "o", false, "Override the original CBZ files") + command.Flags().BoolP("split", "s", false, "Split long pages into smaller chunks") command.PersistentFlags().VarP( formatFlag, "format", "f", @@ -58,6 +59,11 @@ func ConvertCbzCommand(cmd *cobra.Command, args []string) error { return fmt.Errorf("invalid quality value") } + split, err := cmd.Flags().GetBool("split") + if err != nil { + return fmt.Errorf("invalid split value") + } + parallelism, err := cmd.Flags().GetInt("parallelism") if err != nil || parallelism < 1 { return fmt.Errorf("invalid parallelism value") @@ -86,7 +92,13 @@ func ConvertCbzCommand(cmd *cobra.Command, args []string) error { go func() { defer wg.Done() for path := range fileChan { - err := utils.Optimize(chapterConverter, path, quality, override) + err := utils.Optimize(&utils.OptimizeOptions{ + ChapterConverter: chapterConverter, + Path: path, + Quality: quality, + Override: override, + Split: split, + }) if err != nil { errorChan <- fmt.Errorf("error processing file %s: %w", path, err) } diff --git a/converter/converter.go b/converter/converter.go index c353a52..b512378 100644 --- a/converter/converter.go +++ b/converter/converter.go @@ -12,7 +12,7 @@ import ( type Converter interface { // Format of the converter Format() (format constant.ConversionFormat) - ConvertChapter(chapter *manga.Chapter, quality uint8, progress func(message string, current uint32, total uint32)) (*manga.Chapter, error) + ConvertChapter(chapter *manga.Chapter, quality uint8, split bool, progress func(message string, current uint32, total uint32)) (*manga.Chapter, error) PrepareConverter() error } diff --git a/converter/converter_test.go b/converter/converter_test.go index 5775207..9429260 100644 --- a/converter/converter_test.go +++ b/converter/converter_test.go @@ -14,18 +14,22 @@ func TestConvertChapter(t *testing.T) { testCases := []struct { name string genTestChapter func(path string) (*manga.Chapter, error) + split bool }{ { name: "All split pages", genTestChapter: genBigPages, + split: true, }, { name: "No split pages", genTestChapter: genSmallPages, + split: false, }, { name: "Mix of split and no split pages", genTestChapter: genMixSmallBig, + split: true, }, } // Load test genTestChapter from testdata @@ -50,11 +54,11 @@ func TestConvertChapter(t *testing.T) { quality := uint8(80) - progress := func(msg string) { + progress := func(msg string, current uint32, total uint32) { t.Log(msg) } - convertedChapter, err := converter.ConvertChapter(chapter, quality, progress) + convertedChapter, err := converter.ConvertChapter(chapter, quality, false, progress) if err != nil { t.Fatalf("failed to convert genTestChapter: %v", err) } diff --git a/converter/webp/webp_converter.go b/converter/webp/webp_converter.go index b28c719..2fb64b2 100644 --- a/converter/webp/webp_converter.go +++ b/converter/webp/webp_converter.go @@ -4,7 +4,7 @@ import ( "bytes" "fmt" "github.com/belphemur/CBZOptimizer/converter/constant" - packer2 "github.com/belphemur/CBZOptimizer/manga" + "github.com/belphemur/CBZOptimizer/manga" "github.com/oliamb/cutter" "golang.org/x/exp/slices" _ "golang.org/x/image/webp" @@ -48,7 +48,7 @@ func (converter *Converter) PrepareConverter() error { return nil } -func (converter *Converter) ConvertChapter(chapter *packer2.Chapter, quality uint8, progress func(message string, current uint32, total uint32)) (*packer2.Chapter, error) { +func (converter *Converter) ConvertChapter(chapter *manga.Chapter, quality uint8, split bool, progress func(message string, current uint32, total uint32)) (*manga.Chapter, error) { err := converter.PrepareConverter() if err != nil { return nil, err @@ -57,7 +57,7 @@ func (converter *Converter) ConvertChapter(chapter *packer2.Chapter, quality uin var wgConvertedPages sync.WaitGroup maxGoroutines := runtime.NumCPU() - pagesChan := make(chan *packer2.PageContainer, maxGoroutines) + pagesChan := make(chan *manga.PageContainer, maxGoroutines) errChan := make(chan error, maxGoroutines) var wgPages sync.WaitGroup @@ -65,13 +65,13 @@ func (converter *Converter) ConvertChapter(chapter *packer2.Chapter, quality uin guard := make(chan struct{}, maxGoroutines) pagesMutex := sync.Mutex{} - var pages []*packer2.Page + var pages []*manga.Page var totalPages = uint32(len(chapter.Pages)) go func() { for page := range pagesChan { guard <- struct{}{} // would block if guard channel is already filled - go func(pageToConvert *packer2.PageContainer) { + go func(pageToConvert *manga.PageContainer) { defer wgConvertedPages.Done() convertedPage, err := converter.convertPage(pageToConvert, quality) if err != nil { @@ -101,10 +101,12 @@ func (converter *Converter) ConvertChapter(chapter *packer2.Chapter, quality uin }() for _, page := range chapter.Pages { - go func(page *packer2.Page) { + go func(page *manga.Page) { defer wgPages.Done() splitNeeded, img, format, err := converter.checkPageNeedsSplit(page) + // Respect choice to split or not + splitNeeded = split && splitNeeded if err != nil { errChan <- fmt.Errorf("error checking if page %d of genTestChapter %s needs split: %v", page.Index, chapter.FilePath, err) return @@ -112,7 +114,7 @@ func (converter *Converter) ConvertChapter(chapter *packer2.Chapter, quality uin if !splitNeeded { wgConvertedPages.Add(1) - pagesChan <- packer2.NewContainer(page, img, format) + pagesChan <- manga.NewContainer(page, img, format) return } images, err := converter.cropImage(img) @@ -123,9 +125,9 @@ func (converter *Converter) ConvertChapter(chapter *packer2.Chapter, quality uin atomic.AddUint32(&totalPages, uint32(len(images)-1)) for i, img := range images { - page := &packer2.Page{Index: page.Index, IsSplitted: true, SplitPartIndex: uint16(i)} + page := &manga.Page{Index: page.Index, IsSplitted: true, SplitPartIndex: uint16(i)} wgConvertedPages.Add(1) - pagesChan <- packer2.NewContainer(page, img, "N/A") + pagesChan <- manga.NewContainer(page, img, "N/A") } }(page) } @@ -144,7 +146,7 @@ func (converter *Converter) ConvertChapter(chapter *packer2.Chapter, quality uin return nil, fmt.Errorf("encountered errors: %v", errList) } - slices.SortFunc(pages, func(a, b *packer2.Page) int { + slices.SortFunc(pages, func(a, b *manga.Page) int { if a.Index == b.Index { return int(b.SplitPartIndex - a.SplitPartIndex) } @@ -190,7 +192,7 @@ func (converter *Converter) cropImage(img image.Image) ([]image.Image, error) { return parts, nil } -func (converter *Converter) checkPageNeedsSplit(page *packer2.Page) (bool, image.Image, string, error) { +func (converter *Converter) checkPageNeedsSplit(page *manga.Page) (bool, image.Image, string, error) { reader := io.Reader(bytes.NewBuffer(page.Contents.Bytes())) img, format, err := image.Decode(reader) if err != nil { @@ -203,7 +205,7 @@ func (converter *Converter) checkPageNeedsSplit(page *packer2.Page) (bool, image return height >= converter.maxHeight, img, format, nil } -func (converter *Converter) convertPage(container *packer2.PageContainer, quality uint8) (*packer2.PageContainer, error) { +func (converter *Converter) convertPage(container *manga.PageContainer, quality uint8) (*manga.PageContainer, error) { if container.Format == "webp" { return container, nil } diff --git a/utils/optimize.go b/utils/optimize.go index 78ebc1a..378b624 100644 --- a/utils/optimize.go +++ b/utils/optimize.go @@ -8,23 +8,31 @@ import ( "strings" ) +type OptimizeOptions struct { + ChapterConverter converter.Converter + Path string + Quality uint8 + Override bool + Split bool +} + // Optimize optimizes a CBZ file using the specified converter. -func Optimize(chapterConverter converter.Converter, path string, quality uint8, override bool) error { - log.Printf("Processing file: %s\n", path) +func Optimize(options *OptimizeOptions) error { + log.Printf("Processing file: %s\n", options.Path) // Load the chapter - chapter, err := cbz.LoadChapter(path) + chapter, err := cbz.LoadChapter(options.Path) if err != nil { return fmt.Errorf("failed to load chapter: %v", err) } if chapter.IsConverted { - log.Printf("Chapter already converted: %s", path) + log.Printf("Chapter already converted: %s", options.Path) return nil } // Convert the chapter - convertedChapter, err := chapterConverter.ConvertChapter(chapter, quality, func(msg string, current uint32, total uint32) { + convertedChapter, err := options.ChapterConverter.ConvertChapter(chapter, options.Quality, options.Split, func(msg string, current uint32, total uint32) { if current%10 == 0 || current == total { log.Printf("[%s] Converting: %d/%d", chapter.FilePath, current, total) } @@ -35,9 +43,9 @@ func Optimize(chapterConverter converter.Converter, path string, quality uint8, convertedChapter.SetConverted() // Write the converted chapter back to a CBZ file - outputPath := path - if !override { - outputPath = strings.TrimSuffix(path, ".cbz") + "_converted.cbz" + outputPath := options.Path + if !options.Override { + outputPath = strings.TrimSuffix(options.Path, ".cbz") + "_converted.cbz" } err = cbz.WriteChapterToCBZ(convertedChapter, outputPath) if err != nil {