From 32c009ed9b62eb3c277f89c6e1219f190f8cdb91 Mon Sep 17 00:00:00 2001 From: Antoine Aflalo <197810+Belphemur@users.noreply.github.com> Date: Tue, 26 Aug 2025 21:16:54 -0400 Subject: [PATCH] feat: integrate zerolog for enhanced logging across multiple components --- cmd/cbzoptimizer/commands/optimize_command.go | 65 +++++++-- internal/cbz/cbz_creator.go | 50 ++++++- internal/cbz/cbz_loader.go | 42 +++++- internal/utils/optimize.go | 57 +++++++- pkg/converter/webp/webp_converter.go | 123 +++++++++++++++++- 5 files changed, 316 insertions(+), 21 deletions(-) diff --git a/cmd/cbzoptimizer/commands/optimize_command.go b/cmd/cbzoptimizer/commands/optimize_command.go index 7b77d15..8c34624 100644 --- a/cmd/cbzoptimizer/commands/optimize_command.go +++ b/cmd/cbzoptimizer/commands/optimize_command.go @@ -2,15 +2,17 @@ package commands import ( "fmt" - utils2 "github.com/belphemur/CBZOptimizer/v2/internal/utils" - "github.com/belphemur/CBZOptimizer/v2/pkg/converter" - "github.com/belphemur/CBZOptimizer/v2/pkg/converter/constant" - "github.com/spf13/cobra" - "github.com/thediveo/enumflag/v2" "os" "path/filepath" "strings" "sync" + + utils2 "github.com/belphemur/CBZOptimizer/v2/internal/utils" + "github.com/belphemur/CBZOptimizer/v2/pkg/converter" + "github.com/belphemur/CBZOptimizer/v2/pkg/converter/constant" + "github.com/rs/zerolog/log" + "github.com/spf13/cobra" + "github.com/thediveo/enumflag/v2" ) var converterType constant.ConversionFormat @@ -40,44 +42,67 @@ func init() { } func ConvertCbzCommand(cmd *cobra.Command, args []string) error { + log.Info().Str("command", "optimize").Msg("Starting optimize command") + path := args[0] if path == "" { + log.Error().Msg("Path argument is required but empty") return fmt.Errorf("path is required") } + log.Debug().Str("input_path", path).Msg("Validating input path") if !utils2.IsValidFolder(path) { + log.Error().Str("input_path", path).Msg("Path validation failed - not a valid folder") return fmt.Errorf("the path needs to be a folder") } + log.Debug().Str("input_path", path).Msg("Input path validated successfully") + + log.Debug().Msg("Parsing command-line flags") quality, err := cmd.Flags().GetUint8("quality") if err != nil || quality <= 0 || quality > 100 { + log.Error().Err(err).Uint8("quality", quality).Msg("Invalid quality value") return fmt.Errorf("invalid quality value") } + log.Debug().Uint8("quality", quality).Msg("Quality parameter validated") override, err := cmd.Flags().GetBool("override") if err != nil { + log.Error().Err(err).Msg("Failed to parse override flag") return fmt.Errorf("invalid quality value") } + log.Debug().Bool("override", override).Msg("Override parameter parsed") split, err := cmd.Flags().GetBool("split") if err != nil { + log.Error().Err(err).Msg("Failed to parse split flag") return fmt.Errorf("invalid split value") } + log.Debug().Bool("split", split).Msg("Split parameter parsed") parallelism, err := cmd.Flags().GetInt("parallelism") if err != nil || parallelism < 1 { + log.Error().Err(err).Int("parallelism", parallelism).Msg("Invalid parallelism value") return fmt.Errorf("invalid parallelism value") } + log.Debug().Int("parallelism", parallelism).Msg("Parallelism parameter validated") + log.Debug().Str("converter_format", converterType.String()).Msg("Initializing converter") chapterConverter, err := converter.Get(converterType) if err != nil { + log.Error().Str("converter_format", converterType.String()).Err(err).Msg("Failed to get chapter converter") return fmt.Errorf("failed to get chapterConverter: %v", err) } + log.Debug().Str("converter_format", converterType.String()).Msg("Converter initialized successfully") + log.Debug().Msg("Preparing converter") err = chapterConverter.PrepareConverter() if err != nil { + log.Error().Err(err).Msg("Failed to prepare converter") return fmt.Errorf("failed to prepare converter: %v", err) } + log.Debug().Msg("Converter prepared successfully") + // Channel to manage the files to process fileChan := make(chan string) // Channel to collect errors @@ -87,11 +112,14 @@ func ConvertCbzCommand(cmd *cobra.Command, args []string) error { var wg sync.WaitGroup // Start worker goroutines + log.Debug().Int("worker_count", parallelism).Msg("Starting worker goroutines") for i := 0; i < parallelism; i++ { wg.Add(1) - go func() { + go func(workerID int) { defer wg.Done() + log.Debug().Int("worker_id", workerID).Msg("Worker started") for path := range fileChan { + log.Debug().Int("worker_id", workerID).Str("file_path", path).Msg("Worker processing file") err := utils2.Optimize(&utils2.OptimizeOptions{ ChapterConverter: chapterConverter, Path: path, @@ -100,22 +128,30 @@ func ConvertCbzCommand(cmd *cobra.Command, args []string) error { Split: split, }) if err != nil { + log.Error().Int("worker_id", workerID).Str("file_path", path).Err(err).Msg("Worker encountered error") errorChan <- fmt.Errorf("error processing file %s: %w", path, err) + } else { + log.Debug().Int("worker_id", workerID).Str("file_path", path).Msg("Worker completed file successfully") } } - }() + log.Debug().Int("worker_id", workerID).Msg("Worker finished") + }(i) } + log.Debug().Int("worker_count", parallelism).Msg("All worker goroutines started") // Walk the path and send files to the channel - err = filepath.WalkDir(path, func(path string, info os.DirEntry, err error) error { + log.Debug().Str("search_path", path).Msg("Starting filesystem walk for CBZ/CBR files") + err = filepath.WalkDir(path, func(filePath string, info os.DirEntry, err error) error { if err != nil { + log.Error().Str("file_path", filePath).Err(err).Msg("Error during filesystem walk") return err } if !info.IsDir() { fileName := strings.ToLower(info.Name()) if strings.HasSuffix(fileName, ".cbz") || strings.HasSuffix(fileName, ".cbr") { - fileChan <- path + log.Debug().Str("file_path", filePath).Str("file_name", fileName).Msg("Found CBZ/CBR file") + fileChan <- filePath } } @@ -123,21 +159,28 @@ func ConvertCbzCommand(cmd *cobra.Command, args []string) error { }) if err != nil { + log.Error().Str("search_path", path).Err(err).Msg("Filesystem walk failed") return fmt.Errorf("error walking the path: %w", err) } + log.Debug().Str("search_path", path).Msg("Filesystem walk completed") - close(fileChan) // Close the channel to signal workers to stop - wg.Wait() // Wait for all workers to finish + close(fileChan) // Close the channel to signal workers to stop + log.Debug().Msg("File channel closed, waiting for workers to complete") + wg.Wait() // Wait for all workers to finish + log.Debug().Msg("All workers completed") close(errorChan) // Close the error channel var errs []error for err := range errorChan { errs = append(errs, err) + log.Error().Err(err).Msg("Collected processing error") } if len(errs) > 0 { + log.Error().Int("error_count", len(errs)).Msg("Command completed with errors") return fmt.Errorf("encountered errors: %v", errs) } + log.Info().Str("search_path", path).Msg("Optimize command completed successfully") return nil } diff --git a/internal/cbz/cbz_creator.go b/internal/cbz/cbz_creator.go index c2fa16b..94f92ea 100644 --- a/internal/cbz/cbz_creator.go +++ b/internal/cbz/cbz_creator.go @@ -3,28 +3,42 @@ package cbz import ( "archive/zip" "fmt" - "github.com/belphemur/CBZOptimizer/v2/internal/manga" - "github.com/belphemur/CBZOptimizer/v2/internal/utils/errs" "os" "time" + + "github.com/belphemur/CBZOptimizer/v2/internal/manga" + "github.com/belphemur/CBZOptimizer/v2/internal/utils/errs" + "github.com/rs/zerolog/log" ) func WriteChapterToCBZ(chapter *manga.Chapter, outputFilePath string) error { + log.Debug(). + Str("chapter_file", chapter.FilePath). + Str("output_path", outputFilePath). + Int("page_count", len(chapter.Pages)). + Bool("is_converted", chapter.IsConverted). + Msg("Starting CBZ file creation") + // Create a new ZIP file + log.Debug().Str("output_path", outputFilePath).Msg("Creating output CBZ file") zipFile, err := os.Create(outputFilePath) if err != nil { + log.Error().Str("output_path", outputFilePath).Err(err).Msg("Failed to create CBZ file") return fmt.Errorf("failed to create .cbz file: %w", err) } defer errs.Capture(&err, zipFile.Close, "failed to close .cbz file") // Create a new ZIP writer + log.Debug().Str("output_path", outputFilePath).Msg("Creating ZIP writer") zipWriter := zip.NewWriter(zipFile) if err != nil { + log.Error().Str("output_path", outputFilePath).Err(err).Msg("Failed to create ZIP writer") return err } defer errs.Capture(&err, zipWriter.Close, "failed to close .cbz writer") // Write each page to the ZIP archive + log.Debug().Str("output_path", outputFilePath).Int("pages_to_write", len(chapter.Pages)).Msg("Writing pages to CBZ archive") for _, page := range chapter.Pages { // Construct the file name for the page var fileName string @@ -36,6 +50,15 @@ func WriteChapterToCBZ(chapter *manga.Chapter, outputFilePath string) error { fileName = fmt.Sprintf("%04d%s", page.Index, page.Extension) } + log.Debug(). + Str("output_path", outputFilePath). + Uint16("page_index", page.Index). + Bool("is_splitted", page.IsSplitted). + Uint16("split_part", page.SplitPartIndex). + Str("filename", fileName). + Int("size", len(page.Contents.Bytes())). + Msg("Writing page to CBZ archive") + // Create a new file in the ZIP archive fileWriter, err := zipWriter.CreateHeader(&zip.FileHeader{ Name: fileName, @@ -43,41 +66,58 @@ func WriteChapterToCBZ(chapter *manga.Chapter, outputFilePath string) error { Modified: time.Now(), }) if err != nil { + log.Error().Str("output_path", outputFilePath).Str("filename", fileName).Err(err).Msg("Failed to create file in CBZ archive") return fmt.Errorf("failed to create file in .cbz: %w", err) } // Write the page contents to the file - _, err = fileWriter.Write(page.Contents.Bytes()) + bytesWritten, err := fileWriter.Write(page.Contents.Bytes()) if err != nil { + log.Error().Str("output_path", outputFilePath).Str("filename", fileName).Err(err).Msg("Failed to write page contents") return fmt.Errorf("failed to write page contents: %w", err) } + + log.Debug(). + Str("output_path", outputFilePath). + Str("filename", fileName). + Int("bytes_written", bytesWritten). + Msg("Page written successfully") } // Optionally, write the ComicInfo.xml file if present if chapter.ComicInfoXml != "" { + log.Debug().Str("output_path", outputFilePath).Int("xml_size", len(chapter.ComicInfoXml)).Msg("Writing ComicInfo.xml to CBZ archive") comicInfoWriter, err := zipWriter.CreateHeader(&zip.FileHeader{ Name: "ComicInfo.xml", Method: zip.Deflate, Modified: time.Now(), }) if err != nil { + log.Error().Str("output_path", outputFilePath).Err(err).Msg("Failed to create ComicInfo.xml in CBZ archive") return fmt.Errorf("failed to create ComicInfo.xml in .cbz: %w", err) } - _, err = comicInfoWriter.Write([]byte(chapter.ComicInfoXml)) + bytesWritten, err := comicInfoWriter.Write([]byte(chapter.ComicInfoXml)) if err != nil { + log.Error().Str("output_path", outputFilePath).Err(err).Msg("Failed to write ComicInfo.xml contents") return fmt.Errorf("failed to write ComicInfo.xml contents: %w", err) } + log.Debug().Str("output_path", outputFilePath).Int("bytes_written", bytesWritten).Msg("ComicInfo.xml written successfully") + } else { + log.Debug().Str("output_path", outputFilePath).Msg("No ComicInfo.xml to write") } if chapter.IsConverted { - convertedString := fmt.Sprintf("%s\nThis chapter has been converted by CBZOptimizer.", chapter.ConvertedTime) + log.Debug().Str("output_path", outputFilePath).Str("comment", convertedString).Msg("Setting CBZ comment for converted chapter") err = zipWriter.SetComment(convertedString) if err != nil { + log.Error().Str("output_path", outputFilePath).Err(err).Msg("Failed to write CBZ comment") return fmt.Errorf("failed to write comment: %w", err) } + log.Debug().Str("output_path", outputFilePath).Msg("CBZ comment set successfully") } + log.Debug().Str("output_path", outputFilePath).Msg("CBZ file creation completed successfully") return nil } diff --git a/internal/cbz/cbz_loader.go b/internal/cbz/cbz_loader.go index a76b3c1..97e886a 100644 --- a/internal/cbz/cbz_loader.go +++ b/internal/cbz/cbz_loader.go @@ -15,9 +15,12 @@ import ( "github.com/belphemur/CBZOptimizer/v2/internal/manga" "github.com/belphemur/CBZOptimizer/v2/internal/utils/errs" "github.com/mholt/archives" + "github.com/rs/zerolog/log" ) func LoadChapter(filePath string) (*manga.Chapter, error) { + log.Debug().Str("file_path", filePath).Msg("Starting chapter loading") + ctx := context.Background() chapter := &manga.Chapter{ @@ -26,32 +29,45 @@ func LoadChapter(filePath string) (*manga.Chapter, error) { // First, try to read the comment using zip.OpenReader for CBZ files if strings.ToLower(filepath.Ext(filePath)) == ".cbz" { + log.Debug().Str("file_path", filePath).Msg("Checking CBZ comment for conversion status") r, err := zip.OpenReader(filePath) if err == nil { defer errs.Capture(&err, r.Close, "failed to close zip reader for comment") // Check for comment if r.Comment != "" { + log.Debug().Str("file_path", filePath).Str("comment", r.Comment).Msg("Found CBZ comment") scanner := bufio.NewScanner(strings.NewReader(r.Comment)) if scanner.Scan() { convertedTime := scanner.Text() + log.Debug().Str("file_path", filePath).Str("converted_time", convertedTime).Msg("Parsing conversion timestamp") chapter.ConvertedTime, err = dateparse.ParseAny(convertedTime) if err == nil { chapter.IsConverted = true + log.Debug().Str("file_path", filePath).Time("converted_time", chapter.ConvertedTime).Msg("Chapter marked as previously converted") + } else { + log.Debug().Str("file_path", filePath).Err(err).Msg("Failed to parse conversion timestamp") } } + } else { + log.Debug().Str("file_path", filePath).Msg("No CBZ comment found") } + } else { + log.Debug().Str("file_path", filePath).Err(err).Msg("Failed to open CBZ file for comment reading") } // Continue even if comment reading fails } // Open the archive using archives library for file operations + log.Debug().Str("file_path", filePath).Msg("Opening archive file system") fsys, err := archives.FileSystem(ctx, filePath, nil) if err != nil { + log.Error().Str("file_path", filePath).Err(err).Msg("Failed to open archive file system") return nil, fmt.Errorf("failed to open archive file: %w", err) } // Walk through all files in the filesystem + log.Debug().Str("file_path", filePath).Msg("Starting filesystem walk") err = fs.WalkDir(fsys, ".", func(path string, d fs.DirEntry, err error) error { if err != nil { return err @@ -74,31 +90,41 @@ func LoadChapter(filePath string) (*manga.Chapter, error) { fileName := strings.ToLower(filepath.Base(path)) if ext == ".xml" && fileName == "comicinfo.xml" { + log.Debug().Str("file_path", filePath).Str("archive_file", path).Msg("Found ComicInfo.xml") // Read the ComicInfo.xml file content xmlContent, err := io.ReadAll(file) if err != nil { + log.Error().Str("file_path", filePath).Str("archive_file", path).Err(err).Msg("Failed to read ComicInfo.xml") return fmt.Errorf("failed to read ComicInfo.xml content: %w", err) } chapter.ComicInfoXml = string(xmlContent) + log.Debug().Str("file_path", filePath).Int("xml_size", len(xmlContent)).Msg("ComicInfo.xml loaded") } else if !chapter.IsConverted && ext == ".txt" && fileName == "converted.txt" { + log.Debug().Str("file_path", filePath).Str("archive_file", path).Msg("Found converted.txt") textContent, err := io.ReadAll(file) if err != nil { + log.Error().Str("file_path", filePath).Str("archive_file", path).Err(err).Msg("Failed to read converted.txt") return fmt.Errorf("failed to read converted.txt content: %w", err) } scanner := bufio.NewScanner(bytes.NewReader(textContent)) if scanner.Scan() { convertedTime := scanner.Text() + log.Debug().Str("file_path", filePath).Str("converted_time", convertedTime).Msg("Parsing converted.txt timestamp") chapter.ConvertedTime, err = dateparse.ParseAny(convertedTime) if err != nil { + log.Error().Str("file_path", filePath).Err(err).Msg("Failed to parse converted time from converted.txt") return fmt.Errorf("failed to parse converted time: %w", err) } chapter.IsConverted = true + log.Debug().Str("file_path", filePath).Time("converted_time", chapter.ConvertedTime).Msg("Chapter marked as converted from converted.txt") } } else { // Read the file contents for page + log.Debug().Str("file_path", filePath).Str("archive_file", path).Str("extension", ext).Msg("Processing page file") buf := new(bytes.Buffer) - _, err = io.Copy(buf, file) + bytesCopied, err := io.Copy(buf, file) if err != nil { + log.Error().Str("file_path", filePath).Str("archive_file", path).Err(err).Msg("Failed to read page file contents") return fmt.Errorf("failed to read file contents: %w", err) } @@ -113,14 +139,28 @@ func LoadChapter(filePath string) (*manga.Chapter, error) { // Add the page to the chapter chapter.Pages = append(chapter.Pages, page) + log.Debug(). + Str("file_path", filePath). + Str("archive_file", path). + Uint16("page_index", page.Index). + Int64("bytes_read", bytesCopied). + Msg("Page loaded successfully") } return nil }() }) if err != nil { + log.Error().Str("file_path", filePath).Err(err).Msg("Failed during filesystem walk") return nil, err } + log.Debug(). + Str("file_path", filePath). + Int("pages_loaded", len(chapter.Pages)). + Bool("is_converted", chapter.IsConverted). + Bool("has_comic_info", chapter.ComicInfoXml != ""). + Msg("Chapter loading completed successfully") + return chapter, nil } diff --git a/internal/utils/optimize.go b/internal/utils/optimize.go index 3c2973f..4dcad81 100644 --- a/internal/utils/optimize.go +++ b/internal/utils/optimize.go @@ -24,12 +24,25 @@ type OptimizeOptions struct { // Optimize optimizes a CBZ/CBR file using the specified converter. func Optimize(options *OptimizeOptions) error { log.Info().Str("file", options.Path).Msg("Processing file") + log.Debug(). + Str("file", options.Path). + Uint8("quality", options.Quality). + Bool("override", options.Override). + Bool("split", options.Split). + Msg("Optimization parameters") // Load the chapter + log.Debug().Str("file", options.Path).Msg("Loading chapter") chapter, err := cbz.LoadChapter(options.Path) if err != nil { + log.Error().Str("file", options.Path).Err(err).Msg("Failed to load chapter") return fmt.Errorf("failed to load chapter: %v", err) } + log.Debug(). + Str("file", options.Path). + Int("pages", len(chapter.Pages)). + Bool("converted", chapter.IsConverted). + Msg("Chapter loaded successfully") if chapter.IsConverted { log.Info().Str("file", options.Path).Msg("Chapter already converted") @@ -37,24 +50,48 @@ func Optimize(options *OptimizeOptions) error { } // Convert the chapter + log.Debug(). + Str("file", chapter.FilePath). + Int("pages", len(chapter.Pages)). + Uint8("quality", options.Quality). + Bool("split", options.Split). + Msg("Starting chapter conversion") + convertedChapter, err := options.ChapterConverter.ConvertChapter(chapter, options.Quality, options.Split, func(msg string, current uint32, total uint32) { if current%10 == 0 || current == total { log.Info().Str("file", chapter.FilePath).Uint32("current", current).Uint32("total", total).Msg("Converting") + } else { + log.Debug().Str("file", chapter.FilePath).Uint32("current", current).Uint32("total", total).Msg("Converting page") } }) if err != nil { var pageIgnoredError *errors2.PageIgnoredError - if !errors.As(err, &pageIgnoredError) { + if errors.As(err, &pageIgnoredError) { + log.Debug().Str("file", chapter.FilePath).Err(err).Msg("Page conversion error (non-fatal)") + } else { + log.Error().Str("file", chapter.FilePath).Err(err).Msg("Chapter conversion failed") return fmt.Errorf("failed to convert chapter: %v", err) } } if convertedChapter == nil { + log.Error().Str("file", chapter.FilePath).Msg("Conversion returned nil chapter") return fmt.Errorf("failed to convert chapter") } + log.Debug(). + Str("file", chapter.FilePath). + Int("original_pages", len(chapter.Pages)). + Int("converted_pages", len(convertedChapter.Pages)). + Msg("Chapter conversion completed") + convertedChapter.SetConverted() // Determine output path and handle CBR override logic + log.Debug(). + Str("input_path", options.Path). + Bool("override", options.Override). + Msg("Determining output path") + outputPath := options.Path originalPath := options.Path isCbrOverride := false @@ -66,8 +103,16 @@ func Optimize(options *OptimizeOptions) error { // Convert CBR to CBZ: change extension and mark for deletion outputPath = strings.TrimSuffix(options.Path, filepath.Ext(options.Path)) + ".cbz" isCbrOverride = true + log.Debug(). + Str("original_path", originalPath). + Str("output_path", outputPath). + Msg("CBR to CBZ conversion: will delete original after conversion") + } else { + log.Debug(). + Str("original_path", originalPath). + Str("output_path", outputPath). + Msg("CBZ override mode: will overwrite original file") } - // For CBZ files, outputPath remains the same (overwrite) } else { // Handle both .cbz and .cbr files - strip the extension and add _converted.cbz pathLower := strings.ToLower(options.Path) @@ -79,16 +124,24 @@ func Optimize(options *OptimizeOptions) error { // Fallback for other extensions - just add _converted.cbz outputPath = options.Path + "_converted.cbz" } + log.Debug(). + Str("original_path", originalPath). + Str("output_path", outputPath). + Msg("Non-override mode: creating converted file alongside original") } // Write the converted chapter to CBZ file + log.Debug().Str("output_path", outputPath).Msg("Writing converted chapter to CBZ file") err = cbz.WriteChapterToCBZ(convertedChapter, outputPath) if err != nil { + log.Error().Str("output_path", outputPath).Err(err).Msg("Failed to write converted chapter") return fmt.Errorf("failed to write converted chapter: %v", err) } + log.Debug().Str("output_path", outputPath).Msg("Successfully wrote converted chapter") // If we're overriding a CBR file, delete the original CBR after successful write if isCbrOverride { + log.Debug().Str("file", originalPath).Msg("Attempting to delete original CBR file") err = os.Remove(originalPath) if err != nil { // Log the error but don't fail the operation since conversion succeeded diff --git a/pkg/converter/webp/webp_converter.go b/pkg/converter/webp/webp_converter.go index 4d2aaa8..e2701f5 100644 --- a/pkg/converter/webp/webp_converter.go +++ b/pkg/converter/webp/webp_converter.go @@ -15,6 +15,7 @@ import ( "github.com/belphemur/CBZOptimizer/v2/pkg/converter/constant" converterrors "github.com/belphemur/CBZOptimizer/v2/pkg/converter/errors" "github.com/oliamb/cutter" + "github.com/rs/zerolog/log" "golang.org/x/exp/slices" _ "golang.org/x/image/webp" ) @@ -53,8 +54,17 @@ func (converter *Converter) PrepareConverter() error { } func (converter *Converter) ConvertChapter(chapter *manga.Chapter, quality uint8, split bool, progress func(message string, current uint32, total uint32)) (*manga.Chapter, error) { + log.Debug(). + Str("chapter", chapter.FilePath). + Int("pages", len(chapter.Pages)). + Uint8("quality", quality). + Bool("split", split). + Int("max_goroutines", runtime.NumCPU()). + Msg("Starting chapter conversion") + err := converter.PrepareConverter() if err != nil { + log.Error().Str("chapter", chapter.FilePath).Err(err).Msg("Failed to prepare converter") return nil, err } @@ -73,6 +83,12 @@ func (converter *Converter) ConvertChapter(chapter *manga.Chapter, quality uint8 var pages []*manga.Page var totalPages = uint32(len(chapter.Pages)) + log.Debug(). + Str("chapter", chapter.FilePath). + Int("total_pages", len(chapter.Pages)). + Int("worker_count", maxGoroutines). + Msg("Initialized conversion worker pool") + // Start the worker pool go func() { for page := range pagesChan { @@ -165,6 +181,15 @@ func (converter *Converter) ConvertChapter(chapter *manga.Chapter, quality uint8 var aggregatedError error = nil if len(errList) > 0 { aggregatedError = errors.Join(errList...) + log.Debug(). + Str("chapter", chapter.FilePath). + Int("error_count", len(errList)). + Msg("Conversion completed with errors") + } else { + log.Debug(). + Str("chapter", chapter.FilePath). + Int("pages_converted", len(pages)). + Msg("Conversion completed successfully") } slices.SortFunc(pages, func(a, b *manga.Page) int { @@ -175,7 +200,13 @@ func (converter *Converter) ConvertChapter(chapter *manga.Chapter, quality uint8 }) chapter.Pages = pages + log.Debug(). + Str("chapter", chapter.FilePath). + Int("final_page_count", len(pages)). + Msg("Pages sorted and chapter updated") + runtime.GC() + log.Debug().Str("chapter", chapter.FilePath).Msg("Garbage collection completed") return chapter, aggregatedError } @@ -183,12 +214,20 @@ func (converter *Converter) ConvertChapter(chapter *manga.Chapter, quality uint8 func (converter *Converter) cropImage(img image.Image) ([]image.Image, error) { bounds := img.Bounds() height := bounds.Dy() + width := bounds.Dx() numParts := height / converter.cropHeight if height%converter.cropHeight != 0 { numParts++ } + log.Debug(). + Int("original_width", width). + Int("original_height", height). + Int("crop_height", converter.cropHeight). + Int("num_parts", numParts). + Msg("Starting image cropping for page splitting") + parts := make([]image.Image, numParts) for i := 0; i < numParts; i++ { @@ -197,6 +236,12 @@ func (converter *Converter) cropImage(img image.Image) ([]image.Image, error) { partHeight = height - i*converter.cropHeight } + log.Debug(). + Int("part_index", i). + Int("part_height", partHeight). + Int("y_offset", i*converter.cropHeight). + Msg("Cropping image part") + part, err := cutter.Crop(img, cutter.Config{ Width: bounds.Dx(), Height: partHeight, @@ -204,45 +249,119 @@ func (converter *Converter) cropImage(img image.Image) ([]image.Image, error) { Mode: cutter.TopLeft, }) if err != nil { + log.Error(). + Int("part_index", i). + Err(err). + Msg("Failed to crop image part") return nil, fmt.Errorf("error cropping part %d: %v", i+1, err) } parts[i] = part + + log.Debug(). + Int("part_index", i). + Int("cropped_width", part.Bounds().Dx()). + Int("cropped_height", part.Bounds().Dy()). + Msg("Image part cropped successfully") } + log.Debug(). + Int("total_parts", len(parts)). + Msg("Image cropping completed") + return parts, nil } func (converter *Converter) checkPageNeedsSplit(page *manga.Page, splitRequested bool) (bool, image.Image, string, error) { + log.Debug(). + Uint16("page_index", page.Index). + Bool("split_requested", splitRequested). + Int("page_size", len(page.Contents.Bytes())). + Msg("Analyzing page for splitting") + reader := bytes.NewBuffer(page.Contents.Bytes()) img, format, err := image.Decode(reader) if err != nil { + log.Debug().Uint16("page_index", page.Index).Err(err).Msg("Failed to decode page image") return false, nil, format, err } bounds := img.Bounds() height := bounds.Dy() + width := bounds.Dx() + + log.Debug(). + Uint16("page_index", page.Index). + Int("width", width). + Int("height", height). + Str("format", format). + Int("max_height", converter.maxHeight). + Int("webp_max_height", webpMaxHeight). + Msg("Page dimensions analyzed") if height >= webpMaxHeight && !splitRequested { + log.Debug(). + Uint16("page_index", page.Index). + Int("height", height). + Int("webp_max", webpMaxHeight). + Msg("Page too tall for WebP format, would be ignored") return false, img, format, converterrors.NewPageIgnored(fmt.Sprintf("page %d is too tall [max: %dpx] to be converted to webp format", page.Index, webpMaxHeight)) } - return height >= converter.maxHeight && splitRequested, img, format, nil + + needsSplit := height >= converter.maxHeight && splitRequested + log.Debug(). + Uint16("page_index", page.Index). + Bool("needs_split", needsSplit). + Msg("Page splitting decision made") + + return needsSplit, img, format, nil } func (converter *Converter) convertPage(container *manga.PageContainer, quality uint8) (*manga.PageContainer, error) { + log.Debug(). + Uint16("page_index", container.Page.Index). + Str("format", container.Format). + Bool("to_be_converted", container.IsToBeConverted). + Uint8("quality", quality). + Msg("Converting page") + // Fix WebP format detection (case insensitive) if container.Format == "webp" || container.Format == "WEBP" { + log.Debug(). + Uint16("page_index", container.Page.Index). + Msg("Page already in WebP format, skipping conversion") container.Page.Extension = ".webp" return container, nil } if !container.IsToBeConverted { + log.Debug(). + Uint16("page_index", container.Page.Index). + Msg("Page marked as not to be converted, skipping") return container, nil } + + log.Debug(). + Uint16("page_index", container.Page.Index). + Uint8("quality", quality). + Msg("Encoding page to WebP format") + converted, err := converter.convert(container.Image, uint(quality)) if err != nil { + log.Error(). + Uint16("page_index", container.Page.Index). + Err(err). + Msg("Failed to convert page to WebP") return nil, err } - container.SetConverted(converted, ".webp"); + + container.SetConverted(converted, ".webp") + + log.Debug(). + Uint16("page_index", container.Page.Index). + Int("original_size", len(container.Page.Contents.Bytes())). + Int("converted_size", len(converted.Bytes())). + Msg("Page conversion completed") + return container, nil }