From 0f8f904b97f4b85a708b0f75a8858ae7263b68d9 Mon Sep 17 00:00:00 2001 From: Sergio Date: Thu, 19 Feb 2026 09:46:32 +0100 Subject: [PATCH] =?UTF-8?q?afegits=20fixes=20especifics=20amb=20confirmaci?= =?UTF-8?q?=C3=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/pipeline.py | 88 +++++++++++++++++++++++----------- main.py | 81 ++++++++++++++++++++++++++++++- processors/image_normalizer.py | 21 ++++++++ processors/page_normalizer.py | 26 ++++++++++ 4 files changed, 188 insertions(+), 28 deletions(-) diff --git a/core/pipeline.py b/core/pipeline.py index 18fdce9..00e2665 100644 --- a/core/pipeline.py +++ b/core/pipeline.py @@ -16,8 +16,8 @@ from processors.checks import ( check_image_extensions, check_comicinfo, ) -from processors.page_normalizer import normalize_pages -from processors.image_normalizer import normalize_images +from processors.page_normalizer import normalize_pages, preview_normalize_pages +from processors.image_normalizer import normalize_images, preview_normalize_images class Pipeline: @@ -35,7 +35,30 @@ class Pipeline: self.collision_policy = collision_policy self.dry_run = dry_run - def run(self, path: str) -> ComicResult: + def _compute_preview(self, step: str, temp_dir: str, step_results: list) -> dict: + if step == "clean": + trash_result = next((r for r in step_results if r.step == "check_trash"), None) + if trash_result: + prefix = "Basura detectada: " + items = [w.removeprefix(prefix) for w in trash_result.warnings if w.startswith(prefix)] + else: + items = [] + return {"items": items} + + elif step == "normalize_pages": + renames = preview_normalize_pages(temp_dir) + return {"renames": renames} + + elif step == "normalize_images": + conversions = preview_normalize_images(temp_dir, self.desired_image_format) + return {"conversions": conversions, "target_ext": self.desired_image_format} + + elif step == "convert": + return {"target_format": self.desired_format.upper()} + + return {} + + def run(self, path: str, confirm_fn=None) -> ComicResult: step_results = [] # 1. Validar siempre, antes de extraer @@ -63,36 +86,47 @@ class Pipeline: any_changed = False if "clean" in self.steps: - clean_result = clean_directory(temp_dir) - step_results.append(clean_result) - if clean_result.changed: - any_changed = True + preview = self._compute_preview("clean", temp_dir, step_results) + if preview.get("items"): + if confirm_fn is None or confirm_fn("clean", preview): + clean_result = clean_directory(temp_dir) + step_results.append(clean_result) + if clean_result.changed: + any_changed = True if "normalize_pages" in self.steps: - norm_result = normalize_pages(temp_dir) - step_results.append(norm_result) - if norm_result.changed: - any_changed = True + preview = self._compute_preview("normalize_pages", temp_dir, step_results) + if preview.get("renames"): + if confirm_fn is None or confirm_fn("normalize_pages", preview): + norm_result = normalize_pages(temp_dir) + step_results.append(norm_result) + if norm_result.changed: + any_changed = True if "normalize_images" in self.steps: - img_result = normalize_images(temp_dir, self.desired_image_format) - step_results.append(img_result) - if img_result.errors: - return ComicResult( - original_path=path, final_path=None, steps=step_results - ) - if img_result.changed: - any_changed = True + preview = self._compute_preview("normalize_images", temp_dir, step_results) + if preview.get("conversions"): + if confirm_fn is None or confirm_fn("normalize_images", preview): + img_result = normalize_images(temp_dir, self.desired_image_format) + step_results.append(img_result) + if img_result.errors: + return ComicResult( + original_path=path, final_path=None, steps=step_results + ) + if img_result.changed: + any_changed = True if "convert" in self.steps: - conv_result = conversion_step_result(real_format, self.desired_format) - step_results.append(conv_result) - if conv_result.errors: - return ComicResult( - original_path=path, final_path=None, steps=step_results - ) - if conv_result.changed: - any_changed = True + preview = self._compute_preview("convert", temp_dir, step_results) + if confirm_fn is None or confirm_fn("convert", preview): + conv_result = conversion_step_result(real_format, self.desired_format) + step_results.append(conv_result) + if conv_result.errors: + return ComicResult( + original_path=path, final_path=None, steps=step_results + ) + if conv_result.changed: + any_changed = True # 5. Reempaquetar si hubo cambios o conversión de formato needs_repack = any_changed or ( diff --git a/main.py b/main.py index 48f1ec5..41e9cfa 100644 --- a/main.py +++ b/main.py @@ -4,6 +4,55 @@ import argparse from core.scanner import find_comic_files from core.pipeline import Pipeline +_COL_W = 30 +_SEP = "─" * 44 + + +def _print_preview(step: str, preview: dict, formato: str) -> None: + fmt = formato.upper() + + if step == "clean": + print("Ficheros basura a eliminar:") + for item in preview["items"]: + print(f" - {item}") + print(f"Formato final del archivo: {fmt}") + + elif step == "normalize_pages": + renames = preview["renames"] + n = len(renames) + print(f"Renombrado de páginas ({n} páginas):") + print(f" {'Nombre actual':<{_COL_W}} → Nombre final") + print(f" {_SEP}") + display = renames[:10] if n > 20 else renames + for orig, final in display: + print(f" {orig:<{_COL_W}} {final}") + if n > 20: + print(f" ... y {n - 10} más") + print(f"Formato final del archivo: {fmt}") + + elif step == "normalize_images": + conversions = preview["conversions"] + target_ext = preview["target_ext"].lstrip(".") + lossless = " (sin pérdida)" if target_ext.lower() == "png" else "" + print(f"Conversión de imágenes a {target_ext.upper()}{lossless}:") + print(f" {'Imagen actual':<{_COL_W}} → Imagen final") + print(f" {_SEP}") + for orig, final in conversions: + print(f" {orig:<{_COL_W}} {final}") + print(f"Formato final del archivo: {fmt}") + + elif step == "convert": + print(f"Conversión de formato de archivo a {preview['target_format']}.") + + +def make_confirm_fn(args): + def confirm_fn(step: str, preview: dict) -> bool: + print() + _print_preview(step, preview, args.formato) + answer = input("¿Aplicar? [s/N] ").strip().lower() + return answer in ("s", "si", "sí", "y", "yes") + return confirm_fn + def parse_args(): parser = argparse.ArgumentParser(description="Gestor de cómics CBR/CBZ") @@ -17,7 +66,14 @@ def parse_args(): parser.add_argument("--formato", choices=["cbz", "cbr"], default="cbz") parser.add_argument("--renumerar", action="store_true") parser.add_argument("--uniformizar-imagenes", action="store_true") - parser.add_argument("--formato-imagen", choices=["jpg", "png", "webp"], default="jpg") + parser.add_argument("--formato-imagen", choices=["jpg", "png", "webp"], default="png") + + # Fix flags (interactivos) + parser.add_argument("--fix_remove_trash", action="store_true") + parser.add_argument("--fix_page_numbering", action="store_true") + parser.add_argument("--fix_image_extensions", action="store_true") + parser.add_argument("--fix_all", action="store_true") + parser.add_argument("--no-preguntar", action="store_true") return parser.parse_args() @@ -40,6 +96,29 @@ def main(): print() return + # --- Fix flags (interactivos) --- + fix_steps = [] + if args.fix_remove_trash or args.fix_all: + fix_steps.append("clean") + if args.fix_page_numbering or args.fix_all: + fix_steps.append("normalize_pages") + if args.fix_image_extensions or args.fix_all: + fix_steps.append("normalize_images") + + if fix_steps: + confirm_fn = None if args.no_preguntar else make_confirm_fn(args) + pipeline = Pipeline( + steps=fix_steps, + desired_format=args.formato, + desired_image_format="." + args.formato_imagen, + ) + for f in comic_files: + print(f"\n=== {f} ===") + result = pipeline.run(f, confirm_fn=confirm_fn) + print(result.summary()) + return + + # --- Flags clásicos (sin confirmación) --- steps = [] if args.limpiar or args.estandarizar: steps.append("clean") diff --git a/processors/image_normalizer.py b/processors/image_normalizer.py index f3ca1ae..6713d13 100644 --- a/processors/image_normalizer.py +++ b/processors/image_normalizer.py @@ -6,6 +6,27 @@ from core.constants import IMAGE_EXTENSIONS from core.result import StepResult +def preview_normalize_images(work_dir: str, target_ext: str) -> list[tuple[str, str]]: + """Devuelve lista de (nombre_actual, nombre_final) sin convertir nada.""" + target_ext = target_ext.lower() + if not target_ext.startswith("."): + target_ext = "." + target_ext + + result = [] + for root, _, files in os.walk(work_dir): + for f in files: + ext = os.path.splitext(f)[1].lower() + normalized_ext = ".jpg" if ext == ".jpeg" else ext + if ext not in IMAGE_EXTENSIONS: + continue + if normalized_ext == target_ext: + continue + stem = os.path.splitext(f)[0] + result.append((f, stem + target_ext)) + + return result + + def normalize_images(work_dir: str, target_ext: str = ".jpg") -> StepResult: """ Convierte todas las imágenes al formato indicado por target_ext. diff --git a/processors/page_normalizer.py b/processors/page_normalizer.py index e31d406..c50da30 100644 --- a/processors/page_normalizer.py +++ b/processors/page_normalizer.py @@ -12,6 +12,32 @@ def _natural_sort_key(name: str): return [int(p) if p.isdigit() else p.lower() for p in parts] +def preview_normalize_pages(work_dir: str) -> list[tuple[str, str]]: + """Devuelve lista de (nombre_actual, nombre_final) sin modificar nada.""" + images = [] + for root, _, files in os.walk(work_dir): + for f in files: + ext = os.path.splitext(f)[1].lower() + if ext in IMAGE_EXTENSIONS: + images.append(os.path.join(root, f)) + + images.sort(key=lambda p: _natural_sort_key(os.path.splitext(os.path.basename(p))[0])) + total = len(images) + if total == 0: + return [] + + width = len(str(total)) + result = [] + for i, src in enumerate(images): + ext = os.path.splitext(src)[1].lower() + original_name = os.path.basename(src) + final_name = f"{str(i + 1).zfill(width)}{ext}" + if original_name != final_name: + result.append((original_name, final_name)) + + return result + + def normalize_pages(work_dir: str) -> StepResult: """ Renombra las imágenes del cómic a una numeración secuencial con zero-padding.