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:
Labstream
2026-09-16 22:01:33 -05:00
padre 1e1187d28a
commit 99d0ba2034
Se han modificado 4 ficheros con 113 adiciones y 26 borrados

Ver fichero

@@ -9,6 +9,7 @@ estructura canónica a la que se lleva cada cosa según el tipo de cliente.
El diccionario de carpetas se siembra desde aquí la primera vez y después
se guarda en datos/diccionario.json, donde la app va aprendiendo.
"""
import os
import re
import unicodedata
import datetime
@@ -257,6 +258,32 @@ _RX_BM = re.compile(r"^[A-Z]\d{3}_(\d{2})(\d{2})\d{4}_C\d{3}", re.I)
_RX_WA = re.compile(r"WhatsApp \w+ (20\d{2})-(\d{2})-(\d{2})", re.I)
# sufijos que la propia app (o el editor) le pone a un archivo derivado de otro: el derivado se
# genera mucho después, pero la fecha que importa es la del material original
SUFIJOS_DERIVADO = ("_10bit", "_ligero", "_h264", "_proxy", "_lt", "_hq", "_prores", "_convertido",
"_export", "_render", "_master", "_final")
def base_derivado(nombre: str):
"""Nombre del archivo del que este parece salir, o None. «C0001_10bit.mp4» -> «C0001»."""
base = os.path.splitext(nombre)[0]
b = base.lower()
for suf in SUFIJOS_DERIVADO:
if b.endswith(suf) and len(base) > len(suf):
return base[:-len(suf)]
return None
def fecha_real(st):
"""El instante más antiguo que el disco sabe de este archivo.
Al copiar en Windows, la fecha de creación pasa a ser la de la copia y la de modificación se
conserva; al renderizar o reexportar, las dos son nuevas. Quedarse con la más antigua de las dos
da siempre la mejor estimación de cuándo se grabó, sin tener que elegir una de antemano."""
c = getattr(st, "st_birthtime", None) or st.st_ctime
return min(st.st_mtime, c) if c and c > 0 else st.st_mtime
def fecha_de_nombre(nombre: str, mtime: float):
"""(YYYY-MM-DD, origen) — origen 'nombre' si sale del nombre, 'mtime' si no."""
for rx in (_RX_YMD, _RX_YMD_HMS, _RX_Y_M_D, _RX_WA):