feat(converter): add --keep-filenames flag to preserve original page filenames

Add a new `--keep-filenames` boolean flag (no shorthand, default false) on
both `optimize` and `watch` commands. When enabled, page entries in the
output CBZ preserve their original base filenames instead of being
renumbered to `%04d` sequential indices. Extension is swapped on format
conversion (e.g. `001.png` → `001.webp`), split pages get `-NN` suffixes
on the original stem, and duplicate base names fall back to
`<stem>_<index>.<ext>`. Watch mode supports the flag via the
`keep-filenames` viper config key.

Design: data-driven `OriginalName` field on `manga.PageFile`, set at
extraction in `cbz_loader.ExtractChapter`. The `Converter` interface is
unchanged. Flag off → behavior identical to before.

Closes #216
This commit is contained in:
Antoine Aflalo
2026-07-21 19:38:17 -04:00
parent 650623b340
commit f3ba585c13
12 changed files with 245 additions and 37 deletions
+14 -4
View File
@@ -142,7 +142,13 @@ func IsAlreadyConverted(ctx context.Context, filePath string) (converted bool, e
// ExtractChapter extracts an archive (CBZ/CBR) to a temp directory on disk.
// Pages are streamed directly to files — no image data is held in memory.
// Returns a Chapter with PageFile entries pointing to extracted files.
func ExtractChapter(ctx context.Context, filePath string) (*manga.Chapter, error) {
//
// When keepFilenames is true, each PageFile has its OriginalName set to the
// base filename of the entry inside the archive. Downstream code uses that
// name to preserve the original page identity in the output CBZ (with the
// extension swapped for format conversion). When false, OriginalName stays
// empty and the sequential %04d naming convention is used instead.
func ExtractChapter(ctx context.Context, filePath string, keepFilenames bool) (*manga.Chapter, error) {
log.Debug().Str("file_path", filePath).Msg("Extracting chapter to disk")
// Create temp directory for extraction
@@ -279,6 +285,9 @@ func ExtractChapter(ctx context.Context, filePath string) (*manga.Chapter, error
Extension: ext,
FilePath: outputPath,
}
if keepFilenames {
page.OriginalName = filepath.Base(path)
}
chapter.Pages = append(chapter.Pages, page)
log.Debug().
@@ -320,8 +329,9 @@ func isJunkFile(path string) bool {
}
// LoadChapter extracts the chapter from a CBZ/CBR file to disk.
// It delegates to ExtractChapter and always extracts all pages.
// Use IsAlreadyConverted for a fast conversion status check without extraction.
// It delegates to ExtractChapter with keepFilenames=false and always
// extracts all pages. Use IsAlreadyConverted for a fast conversion status
// check without extraction.
func LoadChapter(filePath string) (*manga.Chapter, error) {
return ExtractChapter(context.Background(), filePath)
return ExtractChapter(context.Background(), filePath, false)
}