From 989ca2450dee9931dfc5b0901642760153168235 Mon Sep 17 00:00:00 2001 From: Antoine Aflalo <197810+Belphemur@users.noreply.github.com> Date: Thu, 12 Jun 2025 09:18:06 -0400 Subject: [PATCH] feat: support CBR files in optimize and watch commands Fixes #75 --- cmd/cbzoptimizer/commands/optimize_command.go | 13 ++- .../commands/optimize_command_test.go | 91 ++++++++++++++----- cmd/cbzoptimizer/commands/watch_command.go | 9 +- internal/utils/optimize.go | 13 ++- 4 files changed, 94 insertions(+), 32 deletions(-) diff --git a/cmd/cbzoptimizer/commands/optimize_command.go b/cmd/cbzoptimizer/commands/optimize_command.go index 44d03ac..7b77d15 100644 --- a/cmd/cbzoptimizer/commands/optimize_command.go +++ b/cmd/cbzoptimizer/commands/optimize_command.go @@ -18,8 +18,8 @@ var converterType constant.ConversionFormat func init() { command := &cobra.Command{ Use: "optimize [folder]", - Short: "Optimize all CBZ files in a folder recursively", - Long: "Optimize all CBZ files in a folder recursively.\nIt will take all the different pages in the CBZ files and convert them to the given format.\nThe original CBZ files will be kept intact depending if you choose to override or not.", + Short: "Optimize all CBZ/CBR files in a folder recursively", + Long: "Optimize all CBZ/CBR files in a folder recursively.\nIt will take all the different pages in the CBZ/CBR files and convert them to the given format.\nThe original CBZ/CBR files will be kept intact depending if you choose to override or not.", RunE: ConvertCbzCommand, Args: cobra.ExactArgs(1), } @@ -28,7 +28,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("override", "o", false, "Override the original CBZ/CBR files") command.Flags().BoolP("split", "s", false, "Split long pages into smaller chunks") command.PersistentFlags().VarP( formatFlag, @@ -112,8 +112,11 @@ func ConvertCbzCommand(cmd *cobra.Command, args []string) error { return err } - if !info.IsDir() && strings.HasSuffix(strings.ToLower(info.Name()), ".cbz") { - fileChan <- path + if !info.IsDir() { + fileName := strings.ToLower(info.Name()) + if strings.HasSuffix(fileName, ".cbz") || strings.HasSuffix(fileName, ".cbr") { + fileChan <- path + } } return nil diff --git a/cmd/cbzoptimizer/commands/optimize_command_test.go b/cmd/cbzoptimizer/commands/optimize_command_test.go index 00f1527..ed27b02 100644 --- a/cmd/cbzoptimizer/commands/optimize_command_test.go +++ b/cmd/cbzoptimizer/commands/optimize_command_test.go @@ -46,18 +46,21 @@ func TestConvertCbzCommand(t *testing.T) { t.Fatalf("testdata directory not found") } - // Copy sample CBZ files from testdata to the temporary directory + // Copy sample CBZ/CBR files from testdata to the temporary directory err = filepath.Walk(testdataDir, func(path string, info os.FileInfo, err error) error { if err != nil { return err } - if !info.IsDir() && strings.HasSuffix(strings.ToLower(info.Name()), ".cbz") { - destPath := filepath.Join(tempDir, info.Name()) - data, err := os.ReadFile(path) - if err != nil { - return err + if !info.IsDir() { + fileName := strings.ToLower(info.Name()) + if strings.HasSuffix(fileName, ".cbz") || strings.HasSuffix(fileName, ".cbr") { + destPath := filepath.Join(tempDir, info.Name()) + data, err := os.ReadFile(path) + if err != nil { + return err + } + return os.WriteFile(destPath, data, info.Mode()) } - return os.WriteFile(destPath, data, info.Mode()) } return nil }) @@ -78,7 +81,7 @@ func TestConvertCbzCommand(t *testing.T) { } cmd.Flags().Uint8P("quality", "q", 85, "Quality for conversion (0-100)") cmd.Flags().IntP("parallelism", "n", 2, "Number of chapters to convert in parallel") - cmd.Flags().BoolP("override", "o", false, "Override the original CBZ files") + cmd.Flags().BoolP("override", "o", false, "Override the original CBZ/CBR files") cmd.Flags().BoolP("split", "s", false, "Split long pages into smaller chunks") // Execute the command @@ -87,36 +90,82 @@ func TestConvertCbzCommand(t *testing.T) { t.Fatalf("Command execution failed: %v", err) } - // Verify the results + // Track expected converted files for verification + expectedFiles := make(map[string]bool) + convertedFiles := make(map[string]bool) + + // First pass: identify original files and expected converted filenames err = filepath.Walk(tempDir, func(path string, info os.FileInfo, err error) error { if err != nil { return err } - if info.IsDir() || !strings.HasSuffix(info.Name(), "_converted.cbz") { + if info.IsDir() { return nil } - t.Logf("CBZ file found: %s", path) + fileName := strings.ToLower(info.Name()) + if strings.HasSuffix(fileName, ".cbz") || strings.HasSuffix(fileName, ".cbr") { + if !strings.Contains(fileName, "_converted") { + // This is an original file, determine expected converted filename + baseName := strings.TrimSuffix(info.Name(), filepath.Ext(info.Name())) + expectedConverted := baseName + "_converted.cbz" + expectedFiles[expectedConverted] = false // false means not yet found + } + } + return nil + }) + if err != nil { + t.Fatalf("Error identifying original files: %v", err) + } - // Load the converted chapter - chapter, err := cbz.LoadChapter(path) + // Second pass: verify converted files exist and are properly converted + err = filepath.Walk(tempDir, func(path string, info os.FileInfo, err error) error { if err != nil { return err } - - // Check if the chapter is marked as converted - if !chapter.IsConverted { - t.Errorf("Chapter is not marked as converted: %s", path) + if info.IsDir() { + return nil } + fileName := info.Name() - // Check if the ConvertedTime is set - if chapter.ConvertedTime.IsZero() { - t.Errorf("ConvertedTime is not set for chapter: %s", path) + // Check if this is a converted file (should only be .cbz, never .cbr) + if strings.HasSuffix(fileName, "_converted.cbz") { + convertedFiles[fileName] = true + expectedFiles[fileName] = true // Mark as found + t.Logf("Archive file found: %s", path) + + // Load the converted chapter + chapter, err := cbz.LoadChapter(path) + if err != nil { + return err + } + + // Check if the chapter is marked as converted + if !chapter.IsConverted { + t.Errorf("Chapter is not marked as converted: %s", path) + } + + // Check if the ConvertedTime is set + if chapter.ConvertedTime.IsZero() { + t.Errorf("ConvertedTime is not set for chapter: %s", path) + } + t.Logf("Archive file [%s] is converted: %s", path, chapter.ConvertedTime) + } else if strings.HasSuffix(fileName, "_converted.cbr") { + t.Errorf("Found incorrectly named converted file: %s (should be .cbz, not .cbr)", fileName) } - t.Logf("CBZ file [%s] is converted: %s", path, chapter.ConvertedTime) return nil }) if err != nil { t.Fatalf("Error verifying converted files: %v", err) } + + // Verify all expected files were found + for expectedFile, found := range expectedFiles { + if !found { + t.Errorf("Expected converted file not found: %s", expectedFile) + } + } + + // Log summary + t.Logf("Found %d converted files", len(convertedFiles)) } diff --git a/cmd/cbzoptimizer/commands/watch_command.go b/cmd/cbzoptimizer/commands/watch_command.go index ed47f69..fab353e 100644 --- a/cmd/cbzoptimizer/commands/watch_command.go +++ b/cmd/cbzoptimizer/commands/watch_command.go @@ -21,8 +21,8 @@ func init() { } command := &cobra.Command{ Use: "watch [folder]", - Short: "Watch a folder for new CBZ files", - Long: "Watch a folder for new CBZ files.\nIt will watch a folder for new CBZ files and optimize them.", + Short: "Watch a folder for new CBZ/CBR files", + Long: "Watch a folder for new CBZ/CBR files.\nIt will watch a folder for new CBZ/CBR files and optimize them.", RunE: WatchCommand, Args: cobra.ExactArgs(1), } @@ -32,7 +32,7 @@ func init() { command.Flags().Uint8P("quality", "q", 85, "Quality for conversion (0-100)") _ = viper.BindPFlag("quality", command.Flags().Lookup("quality")) - command.Flags().BoolP("override", "o", true, "Override the original CBZ files") + command.Flags().BoolP("override", "o", true, "Override the original CBZ/CBR files") _ = viper.BindPFlag("override", command.Flags().Lookup("override")) command.Flags().BoolP("split", "s", false, "Split long pages into smaller chunks") @@ -107,7 +107,8 @@ func WatchCommand(_ *cobra.Command, args []string) error { for event := range events { log.Printf("[Event]%s, %v\n", event.Filename, event.Events) - if !strings.HasSuffix(strings.ToLower(event.Filename), ".cbz") { + filename := strings.ToLower(event.Filename) + if !strings.HasSuffix(filename, ".cbz") && !strings.HasSuffix(filename, ".cbr") { continue } diff --git a/internal/utils/optimize.go b/internal/utils/optimize.go index 4de37c2..2cd2702 100644 --- a/internal/utils/optimize.go +++ b/internal/utils/optimize.go @@ -18,7 +18,7 @@ type OptimizeOptions struct { Split bool } -// Optimize optimizes a CBZ file using the specified converter. +// Optimize optimizes a CBZ/CBR file using the specified converter. func Optimize(options *OptimizeOptions) error { log.Printf("Processing file: %s\n", options.Path) @@ -54,7 +54,16 @@ func Optimize(options *OptimizeOptions) error { // Write the converted chapter back to a CBZ file outputPath := options.Path if !options.Override { - outputPath = strings.TrimSuffix(options.Path, ".cbz") + "_converted.cbz" + // Handle both .cbz and .cbr files - strip the extension and add _converted.cbz + pathLower := strings.ToLower(options.Path) + if strings.HasSuffix(pathLower, ".cbz") { + outputPath = strings.TrimSuffix(options.Path, ".cbz") + "_converted.cbz" + } else if strings.HasSuffix(pathLower, ".cbr") { + outputPath = strings.TrimSuffix(options.Path, ".cbr") + "_converted.cbz" + } else { + // Fallback for other extensions - just add _converted.cbz + outputPath = options.Path + "_converted.cbz" + } } err = cbz.WriteChapterToCBZ(convertedChapter, outputPath) if err != nil {