2.2.0: la fecha de verdad manda, sin preguntar nada
- R.fecha_real: la fecha sale de lo más antiguo que sabe el disco. Al copiar en Windows la creación pasa a ser la de la copia y la modificación se conserva; al renderizar, las dos son nuevas. - R.base_derivado y _heredar_fechas: un render hereda la fecha del original, en la misma carpeta o en otra si el nombre es propio y único. Si hay dos candidatos iguales no hereda. - El plan archiva el material en el mes en que se grabó, no en el que dice la carpeta, y explica lo que hizo en vez de pedir una decisión. Los 727 GB de las dos carpetas que mentían se resuelven solos. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Este commit está contenido en:
@@ -34,7 +34,7 @@ import actualizador
|
||||
asistente.O = sys.modules[__name__]
|
||||
actualizador.O = sys.modules[__name__]
|
||||
|
||||
VERSION = "2.1.0"
|
||||
VERSION = "2.2.0"
|
||||
|
||||
CONGELADO = getattr(sys, "frozen", False)
|
||||
SIN_VENTANA = getattr(subprocess, "CREATE_NO_WINDOW", 0) # ffmpeg/ffprobe/net sin abrir consolas negras # empaquetado como .exe con PyInstaller
|
||||
@@ -465,7 +465,7 @@ def escanear(ruta):
|
||||
basura = 1 if (en_basura or rol == "basura") else 0
|
||||
proxy = 1 if (en_proxy or rol == "proxy") else 0
|
||||
stock = 1 if (en_stock or rol == "stock") else 0
|
||||
fecha, origen = R.fecha_de_nombre(nombre, st.st_mtime)
|
||||
fecha, origen = R.fecha_de_nombre(nombre, R.fecha_real(st))
|
||||
fy, fm = int(fecha[:4]), int(fecha[5:7])
|
||||
filas.append((e.path, raiz, path, nombre, ext, st.st_size, st.st_mtime, rol, firma or "",
|
||||
ctx or "", anio, mes, fecha, origen, basura, proxy, stock))
|
||||
@@ -487,6 +487,7 @@ def escanear(ruta):
|
||||
try:
|
||||
walk(raiz, False, False, False, "", "raiz", None, None)
|
||||
prog["fallos"] = len(fallos)
|
||||
_heredar_fechas(filas)
|
||||
con = db()
|
||||
# Antes se borraba el inventario y se guardaba lo recogido pasara lo que pasara. Si una unidad de
|
||||
# red se caía a media lectura, el inventario quedaba truncado y marcado como bueno, y a partir de
|
||||
@@ -543,6 +544,42 @@ def escanear(ruta):
|
||||
prog["fin"] = time.time()
|
||||
|
||||
|
||||
def _heredar_fechas(filas):
|
||||
"""Un render o una conversión se crean mucho después del rodaje, así que su fecha no dice nada del
|
||||
material: si el original está en la misma carpeta, el derivado toma SU fecha.
|
||||
|
||||
Así «C0001_10bit.mp4» se archiva en el mes en que se grabó «C0001.MP4», no en el mes en que se
|
||||
convirtió, y el mes de una carpeta se decide por el material de verdad."""
|
||||
I_RUTA, I_DIR, I_NOMBRE, I_FECHA, I_ORIGEN = 0, 2, 3, 12, 13
|
||||
por_dir = defaultdict(dict)
|
||||
en_todo = defaultdict(list)
|
||||
for f in filas:
|
||||
tallo = os.path.splitext(f[I_NOMBRE])[0].lower()
|
||||
por_dir[f[I_DIR].lower()][tallo] = f
|
||||
en_todo[tallo].append(f)
|
||||
heredadas = 0
|
||||
for i, f in enumerate(filas):
|
||||
base = R.base_derivado(f[I_NOMBRE])
|
||||
if not base:
|
||||
continue
|
||||
orig = por_dir[f[I_DIR].lower()].get(base.lower())
|
||||
if orig is None and len(base) >= 6:
|
||||
# el render puede estar en otra carpeta («_Exportados», «_Archivo»): vale si el nombre es
|
||||
# bastante propio y no hay dos candidatos que se llamen igual en toda la carpeta del cliente
|
||||
cands = en_todo.get(base.lower()) or []
|
||||
orig = cands[0] if len(cands) == 1 else None
|
||||
if orig is None or orig is f or orig[I_ORIGEN] == "derivado":
|
||||
continue
|
||||
if orig[I_FECHA] >= f[I_FECHA]:
|
||||
continue # el «original» no es más viejo: no hereda nada
|
||||
nueva = list(f)
|
||||
nueva[I_FECHA] = orig[I_FECHA]
|
||||
nueva[I_ORIGEN] = "derivado" # la fecha viene del archivo del que salió
|
||||
filas[i] = tuple(nueva)
|
||||
heredadas += 1
|
||||
return heredadas
|
||||
|
||||
|
||||
def lanzar_escaneo(ruta):
|
||||
raiz = os.path.normpath(ruta)
|
||||
p = PROGRESO.get(raiz)
|
||||
@@ -810,7 +847,8 @@ class Plan:
|
||||
if nombre != canon:
|
||||
motivo = "nombre de mes normalizado" + (" (el número no coincidía con el nombre: gana el nombre)" if conflicto else "")
|
||||
if discrepa:
|
||||
motivo += f" · OJO: lo que hay dentro parece de {R.nombre_mes(real[1])}"
|
||||
motivo += (f" · ojo: lo que hay dentro se grabó en {R.nombre_mes(real[1])}, y se archiva "
|
||||
f"en ese mes aunque la carpeta se llame así")
|
||||
self.agregar(e.path, os.path.join(padre, canon), "MES", "renombrar", motivo,
|
||||
"media" if (conflicto or discrepa) else "alta")
|
||||
self.plan_dir(e.path, "mes", anio, m)
|
||||
@@ -961,17 +999,23 @@ class Plan:
|
||||
elif R.detectar_mes(seg)[0]:
|
||||
mes_ruta = R.detectar_mes(seg)[0]
|
||||
if anio and mes:
|
||||
# La carpeta dice un mes, pero los archivos pueden decir otro. Medido el 16-sep-2026 sobre
|
||||
# el material real: 5 de las 10 carpetas de mes con material fechable no contenían el mes
|
||||
# que anunciaban (una llamada «02-marzo» tenía 119 clips de diciembre). Antes esto se movía
|
||||
# con confianza alta y sin aviso; ahora se avisa y baja a media.
|
||||
# La carpeta dice un mes y el archivo puede decir otro. Medido el 16-sep-2026 sobre el
|
||||
# material real: 5 de las 10 carpetas de mes con material fechable no contenían el mes que
|
||||
# anunciaban (una llamada «02-marzo» tenía 119 clips de diciembre de 2025).
|
||||
#
|
||||
# Manda el archivo, no la carpeta: el nombre lo pone la cámara y la fecha de creación la
|
||||
# pone el disco, mientras que el nombre de la carpeta lo escribió una persona con prisa.
|
||||
# Así no hay que preguntarle a nadie cuál es el mes bueno.
|
||||
ay, am, fconf = anio, mes, "alta"
|
||||
if _discrepa_mes(f["fecha"], f["fecha_origen"], anio, mes):
|
||||
fconf = "media"
|
||||
desacuerdo[(int(f["fecha"][:4]), int(f["fecha"][5:7]))] += 1
|
||||
ra, rm = int(f["fecha"][:4]), int(f["fecha"][5:7])
|
||||
if f["fecha_origen"] == "nombre+mtime":
|
||||
ra = anio if rm <= mes else anio - 1 # el nombre de la cámara no trae año
|
||||
ay, am, fconf = ra, rm, "media"
|
||||
desacuerdo[(ra, rm)] += 1
|
||||
else:
|
||||
ay, am = int(f["fecha"][:4]), int(f["fecha"][5:7])
|
||||
por_nombre = f["fecha_origen"] != "mtime"
|
||||
por_nombre = f["fecha_origen"] != "mtime" # «nombre», «nombre+mtime» o «derivado»
|
||||
fconf = "media" if por_nombre else "baja"
|
||||
# el año de una carpeta intermedia ("…/2023/…") manda sobre la fecha de modificación,
|
||||
# pero no sobre una fecha escrita en el nombre del archivo
|
||||
@@ -994,10 +1038,11 @@ class Plan:
|
||||
n_fechables = sum(1 for f in filas if not f["es_basura"] and not f["es_proxy"] and f["fecha_origen"] != "mtime")
|
||||
if desacuerdo and sum(desacuerdo.values()) >= 3 and sum(desacuerdo.values()) >= 0.25 * max(1, n_fechables):
|
||||
(da, dm), cuantos = desacuerdo.most_common(1)[0]
|
||||
aviso_fecha = (f" · OJO: la carpeta dice {R.nombre_mes(mes)} pero {cuantos} de sus archivos son de "
|
||||
f"{R.nombre_mes(dm)} según su propio nombre; comprueba antes de aplicar")
|
||||
self.avisos.append(f"«{self.rel(e.path)}» dice {R.nombre_mes(mes)} de {anio} y el nombre de {cuantos} de "
|
||||
f"sus archivos dice {R.nombre_mes(dm)}: se propone sin marcar para que lo revises.")
|
||||
aviso_fecha = (f" · la carpeta decía {R.nombre_mes(mes)}, pero {cuantos} de sus archivos son de "
|
||||
f"{R.nombre_mes(dm)} de {da}: van a su mes de verdad")
|
||||
self.avisos.append(f"«{self.rel(e.path)}» está dentro de {R.nombre_mes(mes)} de {anio}, pero {cuantos} de "
|
||||
f"sus archivos se grabaron en {R.nombre_mes(dm)} de {da}. Se archivan en el mes en que "
|
||||
f"se grabaron, no en el que dice la carpeta.")
|
||||
for (ay, am, fuente, fconf), archivos in grupos.items():
|
||||
sub = fuente if not etiqueta else os.path.join(etiqueta, fuente)
|
||||
destino = destino_plantilla(self.tipo, "GRABACION", anio=ay, mes=am, sub=sub, nombre=limpiar_nombre(nombre))
|
||||
@@ -1005,9 +1050,8 @@ class Plan:
|
||||
if all(os.path.normpath(os.path.dirname(a[0])).lower() == self.abs_(destino).lower() for a in archivos):
|
||||
continue
|
||||
mot = motivo + (" · repartido por fuente" if anio and mes else " · mes por la fecha de cada archivo" + (" (fecha de modificación)" if fconf == "baja" else ""))
|
||||
marcado = False if aviso_fecha else None
|
||||
self.agregar(e.path, self.abs_(destino), "GRABACION", "mover archivos", mot + aviso_fecha, fconf,
|
||||
archivos=archivos, marcado=marcado)
|
||||
archivos=archivos)
|
||||
|
||||
def plan_archivo(self, e, nivel, anio, mes, padre):
|
||||
fila = self.con.execute("SELECT * FROM archivos WHERE ruta = ?", (e.path,)).fetchone()
|
||||
|
||||
Referencia en una nueva incidencia
Block a user