feat: support CBR files in optimize and watch commands

Fixes #75
This commit is contained in:
Antoine Aflalo
2025-06-12 09:18:06 -04:00
parent 970b9019df
commit 989ca2450d
4 changed files with 94 additions and 32 deletions

View File

@@ -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,9 +112,12 @@ func ConvertCbzCommand(cmd *cobra.Command, args []string) error {
return err
}
if !info.IsDir() && strings.HasSuffix(strings.ToLower(info.Name()), ".cbz") {
if !info.IsDir() {
fileName := strings.ToLower(info.Name())
if strings.HasSuffix(fileName, ".cbz") || strings.HasSuffix(fileName, ".cbr") {
fileChan <- path
}
}
return nil
})

View File

@@ -46,12 +46,14 @@ 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") {
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 {
@@ -59,6 +61,7 @@ func TestConvertCbzCommand(t *testing.T) {
}
return os.WriteFile(destPath, data, info.Mode())
}
}
return nil
})
if err != 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,15 +90,48 @@ 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)
}
// 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
}
if info.IsDir() {
return nil
}
fileName := info.Name()
// 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)
@@ -112,11 +148,24 @@ func TestConvertCbzCommand(t *testing.T) {
if chapter.ConvertedTime.IsZero() {
t.Errorf("ConvertedTime is not set for chapter: %s", path)
}
t.Logf("CBZ file [%s] is converted: %s", path, chapter.ConvertedTime)
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)
}
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))
}

View File

@@ -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
}

View File

@@ -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 {
// 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 {