backup
This commit is contained in:
@@ -1,3 +1,5 @@
|
|||||||
|
backup/
|
||||||
|
|
||||||
# ---> Python
|
# ---> Python
|
||||||
# Byte-compiled / optimized / DLL files
|
# Byte-compiled / optimized / DLL files
|
||||||
__pycache__/
|
__pycache__/
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
# core/backup.py
|
||||||
|
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
from core.paths import get_project_root
|
||||||
|
|
||||||
|
def move_to_backup(path):
|
||||||
|
"""Mueve un archivo al directorio centralizado /backup."""
|
||||||
|
root = get_project_root()
|
||||||
|
backup_dir = os.path.join(root, "backup")
|
||||||
|
os.makedirs(backup_dir, exist_ok=True)
|
||||||
|
|
||||||
|
target = os.path.join(backup_dir, os.path.basename(path))
|
||||||
|
shutil.move(path, target)
|
||||||
|
return target
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
# core/paths.py
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
def get_project_root():
|
||||||
|
"""Devuelve la ruta donde está main.py."""
|
||||||
|
return os.path.dirname(os.path.abspath(__file__ + "/.."))
|
||||||
@@ -9,54 +9,15 @@ from processors.standardizer import standardize_comic
|
|||||||
|
|
||||||
|
|
||||||
def parse_args():
|
def parse_args():
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(description="Gestor de cómics CBR/CBZ")
|
||||||
description="Gestor de colección de cómics CBR/CBZ"
|
|
||||||
)
|
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument("--ruta", required=True)
|
||||||
"--ruta",
|
parser.add_argument("--listar", action="store_true")
|
||||||
type=str,
|
parser.add_argument("--validar", action="store_true")
|
||||||
required=True,
|
parser.add_argument("--limpiar", action="store_true")
|
||||||
help="Ruta base donde buscar los cómics"
|
parser.add_argument("--convertir", action="store_true")
|
||||||
)
|
parser.add_argument("--estandarizar", action="store_true")
|
||||||
|
parser.add_argument("--formato", choices=["cbz", "cbr"], default="cbz")
|
||||||
parser.add_argument(
|
|
||||||
"--listar",
|
|
||||||
action="store_true",
|
|
||||||
help="Listar todos los archivos CBR/CBZ encontrados"
|
|
||||||
)
|
|
||||||
|
|
||||||
parser.add_argument(
|
|
||||||
"--validar",
|
|
||||||
action="store_true",
|
|
||||||
help="Validar los archivos encontrados"
|
|
||||||
)
|
|
||||||
|
|
||||||
parser.add_argument(
|
|
||||||
"--limpiar",
|
|
||||||
action="store_true",
|
|
||||||
help="Eliminar archivos basura y reconstruir CBZ limpios"
|
|
||||||
)
|
|
||||||
|
|
||||||
parser.add_argument(
|
|
||||||
"--convertir",
|
|
||||||
action="store_true",
|
|
||||||
help="Convertir los archivos al formato indicado con --formato"
|
|
||||||
)
|
|
||||||
|
|
||||||
parser.add_argument(
|
|
||||||
"--formato",
|
|
||||||
type=str,
|
|
||||||
choices=["cbz", "cbr"],
|
|
||||||
default="cbz",
|
|
||||||
help="Formato final deseado (por defecto: cbz)"
|
|
||||||
)
|
|
||||||
|
|
||||||
parser.add_argument(
|
|
||||||
"--estandarizar",
|
|
||||||
action="store_true",
|
|
||||||
help="Pipeline completo: limpiar, convertir y normalizar"
|
|
||||||
)
|
|
||||||
|
|
||||||
return parser.parse_args()
|
return parser.parse_args()
|
||||||
|
|
||||||
@@ -64,74 +25,38 @@ def parse_args():
|
|||||||
def main():
|
def main():
|
||||||
args = parse_args()
|
args = parse_args()
|
||||||
|
|
||||||
print(f"Buscando archivos CBR/CBZ en: {args.ruta}\n")
|
|
||||||
comic_files = find_comic_files(args.ruta)
|
comic_files = find_comic_files(args.ruta)
|
||||||
|
|
||||||
if not comic_files:
|
|
||||||
print("No se encontraron archivos .cbr o .cbz")
|
|
||||||
return
|
|
||||||
|
|
||||||
# ------------------------------------------------------------
|
|
||||||
# LISTAR
|
|
||||||
# ------------------------------------------------------------
|
|
||||||
if args.listar:
|
if args.listar:
|
||||||
print("Archivos encontrados:")
|
|
||||||
for f in comic_files:
|
for f in comic_files:
|
||||||
print(f" - {f}")
|
print(f)
|
||||||
print()
|
|
||||||
|
|
||||||
# ------------------------------------------------------------
|
|
||||||
# VALIDAR
|
|
||||||
# ------------------------------------------------------------
|
|
||||||
if args.validar:
|
if args.validar:
|
||||||
print("Validando archivos...\n")
|
|
||||||
for f in comic_files:
|
for f in comic_files:
|
||||||
result = validate_comic(f)
|
print(f"Validando: {f}")
|
||||||
print(result)
|
print(validate_comic(f))
|
||||||
|
|
||||||
if result.errors:
|
|
||||||
print(" Errores:")
|
|
||||||
for e in result.errors:
|
|
||||||
print(f" - {e}")
|
|
||||||
|
|
||||||
if result.warnings:
|
|
||||||
print(" Avisos:")
|
|
||||||
for w in result.warnings:
|
|
||||||
print(f" - {w}")
|
|
||||||
|
|
||||||
print()
|
print()
|
||||||
|
|
||||||
# ------------------------------------------------------------
|
|
||||||
# LIMPIAR
|
|
||||||
# ------------------------------------------------------------
|
|
||||||
if args.limpiar:
|
if args.limpiar:
|
||||||
print("Limpiando archivos...\n")
|
|
||||||
for f in comic_files:
|
for f in comic_files:
|
||||||
result = clean_comic(f)
|
print(f"Limpieza: {f}")
|
||||||
print(result)
|
print(clean_comic(f))
|
||||||
print()
|
print()
|
||||||
|
|
||||||
# ------------------------------------------------------------
|
|
||||||
# CONVERTIR
|
|
||||||
# ------------------------------------------------------------
|
|
||||||
if args.convertir:
|
if args.convertir:
|
||||||
print(f"Convirtiendo archivos al formato {args.formato}...\n")
|
|
||||||
for f in comic_files:
|
for f in comic_files:
|
||||||
|
print(f"Convirtiendo: {f}")
|
||||||
info = convert_comic(f, args.formato)
|
info = convert_comic(f, args.formato)
|
||||||
if info["needs_conversion"]:
|
if info["needs_conversion"]:
|
||||||
print(f"Convertido: {f} → {info['target_path']}")
|
print(f" → Convertido a {args.formato.upper()}: {info['target_path']}")
|
||||||
else:
|
else:
|
||||||
print(f"Sin cambios: {f}")
|
print(f" → Sin cambios (ya era {args.formato.upper()})")
|
||||||
print()
|
print()
|
||||||
|
|
||||||
# ------------------------------------------------------------
|
|
||||||
# ESTANDARIZAR
|
|
||||||
# ------------------------------------------------------------
|
|
||||||
if args.estandarizar:
|
if args.estandarizar:
|
||||||
print("Estandarizando archivos...\n")
|
|
||||||
for f in comic_files:
|
for f in comic_files:
|
||||||
result = standardize_comic(f, args.formato)
|
print(f"Estandarizando: {f}")
|
||||||
print(result)
|
print(standardize_comic(f, args.formato))
|
||||||
print()
|
print()
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+33
-94
@@ -7,7 +7,7 @@ import tempfile
|
|||||||
import shutil
|
import shutil
|
||||||
|
|
||||||
from core.constants import TRASH_FILES
|
from core.constants import TRASH_FILES
|
||||||
from processors.validator import try_open_rar, try_open_zip
|
from processors.validator import validate_comic
|
||||||
|
|
||||||
|
|
||||||
class CleanResult:
|
class CleanResult:
|
||||||
@@ -16,116 +16,48 @@ class CleanResult:
|
|||||||
self.cleaned_path = None
|
self.cleaned_path = None
|
||||||
self.removed_files = []
|
self.removed_files = []
|
||||||
self.repacked = False
|
self.repacked = False
|
||||||
self.converted_to_cbz = False
|
|
||||||
|
|
||||||
def pretty_removed_files(self):
|
|
||||||
if not self.removed_files:
|
|
||||||
return " No se eliminaron archivos\n"
|
|
||||||
msg = f" Archivos eliminados ({len(self.removed_files)}):\n"
|
|
||||||
for f in self.removed_files:
|
|
||||||
msg += f" - {f}\n"
|
|
||||||
return msg
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
msg = f"Limpieza de: {self.original_path}\n"
|
msg = f"Limpieza de: {self.original_path}\n"
|
||||||
msg += self.pretty_removed_files()
|
msg += f" Archivos eliminados: {len(self.removed_files)}\n"
|
||||||
if self.repacked:
|
msg += f" Resultado final: {self.cleaned_path}\n"
|
||||||
msg += " Archivo reconstruido\n"
|
|
||||||
if self.converted_to_cbz:
|
|
||||||
msg += " Convertido a CBZ\n"
|
|
||||||
msg += f" Resultado final: {self.cleaned_path}"
|
|
||||||
return msg
|
return msg
|
||||||
|
|
||||||
|
|
||||||
# ------------------------------------------------------------
|
def clean_comic(path):
|
||||||
# 1) Limpieza de una carpeta ya extraída
|
validation = validate_comic(path)
|
||||||
# ------------------------------------------------------------
|
|
||||||
|
|
||||||
def clean_folder(folder_path):
|
if validation.errors:
|
||||||
"""
|
raise Exception(f"Archivo corrupto: {path}")
|
||||||
Elimina archivos basura dentro de una carpeta ya extraída.
|
|
||||||
Devuelve una lista con las rutas relativas de los archivos eliminados.
|
|
||||||
"""
|
|
||||||
removed = []
|
|
||||||
|
|
||||||
for root, _, files in os.walk(folder_path):
|
real = validation.real_format
|
||||||
for f in files:
|
|
||||||
if f.lower() in TRASH_FILES:
|
|
||||||
full = os.path.join(root, f)
|
|
||||||
rel = os.path.relpath(full, folder_path)
|
|
||||||
os.remove(full)
|
|
||||||
removed.append(rel)
|
|
||||||
|
|
||||||
return removed
|
# Abrir según formato real
|
||||||
|
archive = zipfile.ZipFile(path, "r") if real == "zip" else rarfile.RarFile(path, "r")
|
||||||
|
|
||||||
|
|
||||||
# ------------------------------------------------------------
|
|
||||||
# 2) Limpieza de un archivo completo (modo actual)
|
|
||||||
# ------------------------------------------------------------
|
|
||||||
|
|
||||||
def clean_comic(path, output_path=None):
|
|
||||||
"""
|
|
||||||
Limpia un archivo CBR/CBZ:
|
|
||||||
- elimina basura
|
|
||||||
- convierte CBR → CBZ
|
|
||||||
- reconstruye solo si es necesario
|
|
||||||
"""
|
|
||||||
result = CleanResult(path)
|
|
||||||
ext = os.path.splitext(path)[1].lower()
|
|
||||||
|
|
||||||
# 1) Abrir archivo
|
|
||||||
if ext == ".cbr":
|
|
||||||
archive = try_open_rar(path)
|
|
||||||
if archive:
|
|
||||||
real_format = "rar"
|
|
||||||
else:
|
|
||||||
archive = try_open_zip(path)
|
|
||||||
if archive:
|
|
||||||
real_format = "zip"
|
|
||||||
else:
|
|
||||||
raise Exception(f"No se puede abrir {path}")
|
|
||||||
|
|
||||||
elif ext == ".cbz":
|
|
||||||
archive = try_open_zip(path)
|
|
||||||
if archive:
|
|
||||||
real_format = "zip"
|
|
||||||
else:
|
|
||||||
raise Exception(f"No se puede abrir {path}")
|
|
||||||
|
|
||||||
# 2) Extraer a carpeta temporal
|
|
||||||
temp_dir = tempfile.mkdtemp()
|
temp_dir = tempfile.mkdtemp()
|
||||||
archive.extractall(temp_dir)
|
archive.extractall(temp_dir)
|
||||||
archive.close()
|
archive.close()
|
||||||
|
|
||||||
# 3) Limpiar carpeta
|
removed = []
|
||||||
removed = clean_folder(temp_dir)
|
for root, _, files in os.walk(temp_dir):
|
||||||
result.removed_files = removed
|
for f in files:
|
||||||
|
if f.lower() in TRASH_FILES:
|
||||||
|
full = os.path.join(root, f)
|
||||||
|
os.remove(full)
|
||||||
|
removed.append(os.path.relpath(full, temp_dir))
|
||||||
|
|
||||||
# 4) Determinar si hay que reconstruir
|
# Si no se eliminó nada → no reconstruir
|
||||||
changes_needed = False
|
if not removed:
|
||||||
|
|
||||||
if removed:
|
|
||||||
changes_needed = True
|
|
||||||
|
|
||||||
if ext == ".cbr":
|
|
||||||
changes_needed = True
|
|
||||||
result.converted_to_cbz = True
|
|
||||||
|
|
||||||
if not changes_needed:
|
|
||||||
result.cleaned_path = path
|
|
||||||
shutil.rmtree(temp_dir)
|
shutil.rmtree(temp_dir)
|
||||||
|
result = CleanResult(path)
|
||||||
|
result.cleaned_path = path
|
||||||
return result
|
return result
|
||||||
|
|
||||||
# 5) Ruta final
|
# Reconstruir como CBZ
|
||||||
if output_path:
|
base, _ = os.path.splitext(path)
|
||||||
final_path = output_path
|
final_path = base + ".cbz"
|
||||||
else:
|
|
||||||
base, _ = os.path.splitext(path)
|
|
||||||
final_path = base + ".cbz"
|
|
||||||
|
|
||||||
result.cleaned_path = final_path
|
|
||||||
|
|
||||||
# 6) Reempaquetar como CBZ
|
|
||||||
with zipfile.ZipFile(final_path, "w", zipfile.ZIP_DEFLATED) as new_zip:
|
with zipfile.ZipFile(final_path, "w", zipfile.ZIP_DEFLATED) as new_zip:
|
||||||
for root, _, files in os.walk(temp_dir):
|
for root, _, files in os.walk(temp_dir):
|
||||||
for f in files:
|
for f in files:
|
||||||
@@ -133,7 +65,14 @@ def clean_comic(path, output_path=None):
|
|||||||
rel = os.path.relpath(full, temp_dir)
|
rel = os.path.relpath(full, temp_dir)
|
||||||
new_zip.write(full, rel)
|
new_zip.write(full, rel)
|
||||||
|
|
||||||
result.repacked = True
|
|
||||||
|
|
||||||
shutil.rmtree(temp_dir)
|
shutil.rmtree(temp_dir)
|
||||||
|
|
||||||
|
# Mover original a backup
|
||||||
|
from core.backup import move_to_backup
|
||||||
|
move_to_backup(path)
|
||||||
|
|
||||||
|
result = CleanResult(path)
|
||||||
|
result.cleaned_path = final_path
|
||||||
|
result.removed_files = removed
|
||||||
|
result.repacked = True
|
||||||
return result
|
return result
|
||||||
|
|||||||
+22
-40
@@ -6,53 +6,33 @@ import rarfile
|
|||||||
import tempfile
|
import tempfile
|
||||||
import shutil
|
import shutil
|
||||||
|
|
||||||
|
from processors.validator import validate_comic
|
||||||
def decide_target_format(original_path, desired_format="cbz"):
|
|
||||||
"""
|
|
||||||
Decide si el archivo debe convertirse y cuál será su ruta final.
|
|
||||||
No realiza la conversión.
|
|
||||||
"""
|
|
||||||
base, _ = os.path.splitext(original_path)
|
|
||||||
target_path = f"{base}.{desired_format.lower()}"
|
|
||||||
|
|
||||||
needs_conversion = not original_path.lower().endswith(desired_format.lower())
|
|
||||||
|
|
||||||
return {
|
|
||||||
"needs_conversion": needs_conversion,
|
|
||||||
"target_format": desired_format.lower(),
|
|
||||||
"target_path": target_path
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def convert_comic(path, desired_format="cbz"):
|
def convert_comic(path, desired_format="cbz"):
|
||||||
"""
|
validation = validate_comic(path)
|
||||||
Convierte un archivo CBR/CBZ al formato deseado.
|
|
||||||
NO limpia basura.
|
|
||||||
NO renombra páginas.
|
|
||||||
NO reordena nada.
|
|
||||||
"""
|
|
||||||
info = decide_target_format(path, desired_format)
|
|
||||||
|
|
||||||
if not info["needs_conversion"]:
|
if validation.errors:
|
||||||
return info # Nada que hacer
|
raise Exception(f"Archivo corrupto: {path}")
|
||||||
|
|
||||||
ext = os.path.splitext(path)[1].lower()
|
real = validation.real_format
|
||||||
|
|
||||||
# 1) Abrir archivo original
|
# Si ya está en el formato deseado → nada que hacer
|
||||||
if ext == ".cbr":
|
if desired_format == "cbz" and real == "zip":
|
||||||
archive = rarfile.RarFile(path, "r")
|
return {"needs_conversion": False, "target_path": path}
|
||||||
elif ext == ".cbz":
|
|
||||||
archive = zipfile.ZipFile(path, "r")
|
if desired_format == "cbr" and real == "rar":
|
||||||
else:
|
return {"needs_conversion": False, "target_path": path}
|
||||||
raise Exception("Formato no soportado")
|
|
||||||
|
# Abrir según formato real
|
||||||
|
archive = zipfile.ZipFile(path, "r") if real == "zip" else rarfile.RarFile(path, "r")
|
||||||
|
|
||||||
# 2) Extraer a carpeta temporal
|
|
||||||
temp_dir = tempfile.mkdtemp()
|
temp_dir = tempfile.mkdtemp()
|
||||||
archive.extractall(temp_dir)
|
archive.extractall(temp_dir)
|
||||||
archive.close()
|
archive.close()
|
||||||
|
|
||||||
# 3) Reempaquetar en el formato deseado
|
base, _ = os.path.splitext(path)
|
||||||
target_path = info["target_path"]
|
target_path = f"{base}.{desired_format}"
|
||||||
|
|
||||||
if desired_format == "cbz":
|
if desired_format == "cbz":
|
||||||
with zipfile.ZipFile(target_path, "w", zipfile.ZIP_DEFLATED) as new_zip:
|
with zipfile.ZipFile(target_path, "w", zipfile.ZIP_DEFLATED) as new_zip:
|
||||||
@@ -63,10 +43,12 @@ def convert_comic(path, desired_format="cbz"):
|
|||||||
new_zip.write(full, rel)
|
new_zip.write(full, rel)
|
||||||
|
|
||||||
elif desired_format == "cbr":
|
elif desired_format == "cbr":
|
||||||
# rarfile no puede crear RAR → hay que usar "rar" externo
|
raise NotImplementedError("Crear CBR requiere 'rar' instalado")
|
||||||
raise NotImplementedError("Crear CBR requiere la herramienta 'rar' instalada")
|
|
||||||
|
|
||||||
# 4) Limpiar temporal
|
|
||||||
shutil.rmtree(temp_dir)
|
shutil.rmtree(temp_dir)
|
||||||
|
|
||||||
return info
|
# Mover original a backup
|
||||||
|
from core.backup import move_to_backup
|
||||||
|
move_to_backup(path)
|
||||||
|
|
||||||
|
return {"needs_conversion": True, "target_path": target_path}
|
||||||
|
|||||||
+13
-24
@@ -1,55 +1,44 @@
|
|||||||
# processors/standardizer.py
|
# processors/standardizer.py
|
||||||
|
|
||||||
|
from processors.validator import validate_comic
|
||||||
from processors.cleaner import clean_comic
|
from processors.cleaner import clean_comic
|
||||||
from processors.converter import convert_comic
|
from processors.converter import convert_comic
|
||||||
|
|
||||||
|
|
||||||
class StandardizeResult:
|
class StandardizeResult:
|
||||||
def __init__(self, original_path):
|
def __init__(self, path):
|
||||||
self.original_path = original_path
|
self.path = path
|
||||||
self.cleaned = None
|
self.cleaned = None
|
||||||
self.converted = None
|
self.converted = None
|
||||||
self.final_path = None
|
self.final_path = None
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
msg = f"Estandarización de: {self.original_path}\n"
|
msg = f"Estandarización de: {self.path}\n"
|
||||||
if self.cleaned:
|
if self.cleaned:
|
||||||
msg += f" Limpieza: OK ({len(self.cleaned.removed_files)} archivos eliminados)\n"
|
msg += f" Limpieza: OK ({len(self.cleaned.removed_files)} eliminados)\n"
|
||||||
else:
|
|
||||||
msg += " Limpieza: no realizada\n"
|
|
||||||
|
|
||||||
if self.converted:
|
if self.converted:
|
||||||
if self.converted["needs_conversion"]:
|
if self.converted["needs_conversion"]:
|
||||||
msg += f" Conversión: OK → {self.converted['target_path']}\n"
|
msg += f" Conversión: OK → {self.converted['target_path']}\n"
|
||||||
else:
|
else:
|
||||||
msg += " Conversión: no necesaria\n"
|
msg += " Conversión: no necesaria\n"
|
||||||
|
|
||||||
msg += f" Resultado final: {self.final_path}\n"
|
msg += f" Resultado final: {self.final_path}\n"
|
||||||
return msg
|
return msg
|
||||||
|
|
||||||
|
|
||||||
def standardize_comic(path, desired_format="cbz"):
|
def standardize_comic(path, desired_format="cbz"):
|
||||||
"""
|
validation = validate_comic(path)
|
||||||
Pipeline básico:
|
|
||||||
1. Limpiar
|
if validation.errors:
|
||||||
2. Convertir
|
raise Exception(f"Archivo corrupto: {path}")
|
||||||
"""
|
|
||||||
result = StandardizeResult(path)
|
result = StandardizeResult(path)
|
||||||
|
|
||||||
# 1) Limpiar
|
|
||||||
clean_result = clean_comic(path)
|
clean_result = clean_comic(path)
|
||||||
result.cleaned = clean_result
|
result.cleaned = clean_result
|
||||||
|
|
||||||
# El archivo resultante tras limpiar
|
convert_result = convert_comic(clean_result.cleaned_path, desired_format)
|
||||||
cleaned_path = clean_result.cleaned_path
|
|
||||||
|
|
||||||
# 2) Convertir
|
|
||||||
convert_result = convert_comic(cleaned_path, desired_format)
|
|
||||||
result.converted = convert_result
|
result.converted = convert_result
|
||||||
|
|
||||||
# Ruta final
|
result.final_path = convert_result["target_path"] if convert_result["needs_conversion"] else clean_result.cleaned_path
|
||||||
if convert_result["needs_conversion"]:
|
|
||||||
result.final_path = convert_result["target_path"]
|
|
||||||
else:
|
|
||||||
result.final_path = cleaned_path
|
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|||||||
+33
-68
@@ -3,99 +3,64 @@
|
|||||||
import os
|
import os
|
||||||
import zipfile
|
import zipfile
|
||||||
import rarfile
|
import rarfile
|
||||||
from core.constants import IMAGE_EXTENSIONS, TRASH_FILES
|
|
||||||
|
|
||||||
|
|
||||||
IMAGE_EXTENSIONS = {".jpg", ".jpeg", ".png", ".webp"}
|
|
||||||
TRASH_FILES = {"thumbs.db", ".ds_store"}
|
|
||||||
|
|
||||||
class ValidationResult:
|
class ValidationResult:
|
||||||
def __init__(self, path):
|
def __init__(self, path):
|
||||||
self.path = path
|
self.path = path
|
||||||
self.is_valid = True
|
self.real_format = None # "zip", "rar", None
|
||||||
|
self.extension = None # ".cbz" o ".cbr"
|
||||||
self.errors = []
|
self.errors = []
|
||||||
self.warnings = []
|
self.warnings = []
|
||||||
self.images = []
|
|
||||||
self.real_format = None # "rar", "zip", None
|
|
||||||
|
|
||||||
def add_error(self, msg):
|
|
||||||
self.is_valid = False
|
|
||||||
self.errors.append(msg)
|
|
||||||
|
|
||||||
def add_warning(self, msg):
|
|
||||||
self.warnings.append(msg)
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
status = "OK" if self.is_valid else "ERROR"
|
msg = f"Validación de: {self.path}\n"
|
||||||
return f"[{status}] {self.path}"
|
msg += f" Formato real: {self.real_format}\n"
|
||||||
|
msg += f" Extensión: {self.extension}\n"
|
||||||
|
if self.errors:
|
||||||
|
msg += " Errores:\n"
|
||||||
|
for e in self.errors:
|
||||||
|
msg += f" - {e}\n"
|
||||||
|
if self.warnings:
|
||||||
|
msg += " Avisos:\n"
|
||||||
|
for w in self.warnings:
|
||||||
|
msg += f" - {w}\n"
|
||||||
|
return msg
|
||||||
|
|
||||||
|
|
||||||
def try_open_rar(path):
|
def detect_real_format(path):
|
||||||
|
"""Devuelve 'zip', 'rar' o None."""
|
||||||
try:
|
try:
|
||||||
archive = rarfile.RarFile(path, "r")
|
zipfile.ZipFile(path).close()
|
||||||
archive.namelist() # fuerza lectura
|
return "zip"
|
||||||
return archive
|
|
||||||
except:
|
except:
|
||||||
return None
|
pass
|
||||||
|
|
||||||
|
|
||||||
def try_open_zip(path):
|
|
||||||
try:
|
try:
|
||||||
archive = zipfile.ZipFile(path, "r")
|
rarfile.RarFile(path).close()
|
||||||
archive.namelist()
|
return "rar"
|
||||||
return archive
|
|
||||||
except:
|
except:
|
||||||
return None
|
pass
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def validate_comic(path):
|
def validate_comic(path):
|
||||||
result = ValidationResult(path)
|
result = ValidationResult(path)
|
||||||
ext = os.path.splitext(path)[1].lower()
|
ext = os.path.splitext(path)[1].lower()
|
||||||
|
result.extension = ext
|
||||||
|
|
||||||
archive = None
|
real = detect_real_format(path)
|
||||||
|
result.real_format = real
|
||||||
|
|
||||||
# 1) Intentar abrir como RAR
|
if real is None:
|
||||||
if ext == ".cbr":
|
result.errors.append("Archivo corrupto o ilegible")
|
||||||
archive = try_open_rar(path)
|
|
||||||
if archive:
|
|
||||||
result.real_format = "rar"
|
|
||||||
else:
|
|
||||||
# 2) Intentar abrir como ZIP (CBZ renombrado)
|
|
||||||
archive = try_open_zip(path)
|
|
||||||
if archive:
|
|
||||||
result.real_format = "zip"
|
|
||||||
result.add_warning("El archivo tiene extensión .cbr pero es un ZIP (CBZ renombrado)")
|
|
||||||
else:
|
|
||||||
result.add_error("No se pudo abrir como RAR ni como ZIP")
|
|
||||||
return result
|
|
||||||
|
|
||||||
# 3) Si es CBZ, abrir como ZIP
|
|
||||||
elif ext == ".cbz":
|
|
||||||
archive = try_open_zip(path)
|
|
||||||
if archive:
|
|
||||||
result.real_format = "zip"
|
|
||||||
else:
|
|
||||||
result.add_error("No se pudo abrir el archivo ZIP")
|
|
||||||
return result
|
|
||||||
|
|
||||||
else:
|
|
||||||
result.add_error("Extensión no reconocida")
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
# 4) Validar contenido
|
if ext == ".cbz" and real == "rar":
|
||||||
file_list = archive.namelist()
|
result.warnings.append("Extensión incorrecta: debería ser .cbr")
|
||||||
|
|
||||||
for f in file_list:
|
if ext == ".cbr" and real == "zip":
|
||||||
name = f.lower()
|
result.warnings.append("Extensión incorrecta: debería ser .cbz")
|
||||||
_, fext = os.path.splitext(name)
|
|
||||||
|
|
||||||
if fext in IMAGE_EXTENSIONS:
|
|
||||||
result.images.append(f)
|
|
||||||
|
|
||||||
if os.path.basename(name) in TRASH_FILES:
|
|
||||||
result.add_warning(f"Archivo basura encontrado: {f}")
|
|
||||||
|
|
||||||
if not result.images:
|
|
||||||
result.add_error("No contiene imágenes válidas")
|
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|||||||
Reference in New Issue
Block a user