3.8.1: se convierte todo lo que ahorre al menos un 30 %, con la estimación medida por códec de origen

Munka: «si un archivo se puede optimizar así sea un 30 % se convierta». La regla deja de mirar si el clip pasa de
1 o 2 veces la tasa objetivo y mira el ahorro estimado (ajustable, 30 % de entrada). La estimación sale de las 286
conversiones hechas: H.264 y AV1 de cámara quedan en una fracción, H.265 casi igual. Entran los 11 AV1 de Línea
Gene; dejan de ofrecerse los H.265 que no adelgazan y lo apartado en Duplicados.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Este commit está contenido en:
Labstream
2026-10-14 10:12:17 -05:00
padre 7eb5899f04
commit eafd1984f3
Se han modificado 5 ficheros con 142 adiciones y 48 borrados

Ver fichero

@@ -800,38 +800,65 @@ class Convertidor(unittest.TestCase):
con.close()
return t
def test_lo_ya_comprimido_no_se_reconvierte(self):
"""9 clips que ya eran H.265 se reconvirtieron para quedarse en el 95 % de su peso."""
def test_se_convierte_lo_que_ahorra_un_30(self):
"""Munka, 14-oct-2026: «si un archivo se puede optimizar así sea un 30 % se convierta». Medido sobre las 286
conversiones hechas: un H.265 de cámara se queda en el 85-99 % (6 se reconvirtieron para nada), y en cambio
los AV1 de 85 Mb/s que no se ofrecían quedan en la décima parte."""
raiz = cliente("Comprimidos")
g = "2025/06- Junio/00_Grabacion/"
for n in ("A014_06170738_C019.mp4", "C0001.MP4", "C0002.MP4", "C0003.MP4", "C0004.MP4", "C0005.MP4"):
for n in ("A014_06170738_C019.mp4", "C0001.MP4", "C0002.MP4", "C0003.MP4", "C0004.MP4", "C0005.MP4", "C0006.MP4",
"A015_05250930_C004.mp4", "Duplicados/C0003.MP4"):
mk(raiz, g + n, 2025, 6, 17)
O.escanear(raiz)
self.leidos(raiz, {
"A014_06170738_C019.mp4": {"codec": "h264", "perfil": "Main", "ancho": 4608, "alto": 2592, "kbps": 25000},
"C0001.MP4": {"codec": "h264", "perfil": "High 4:2:2 Intra", "pix_fmt": "yuv422p10le", "fps": 59.94, "kbps": 596000},
"C0002.MP4": {"codec": "hevc", "perfil": "Main 10", "pix_fmt": "yuv420p10le", "fps": 59.94, "kbps": 72000},
"C0003.MP4": {"codec": "h264", "perfil": "High", "kbps": 120000},
"C0004.MP4": {"codec": "av1", "perfil": "Main", "kbps": 30000},
"C0004.MP4": {"codec": "av1", "perfil": "Main", "kbps": 22000},
"C0005.MP4": {"codec": "prores", "perfil": "HQ", "pix_fmt": "yuv422p10le", "kbps": 870000},
"C0006.MP4": {"codec": "hevc", "perfil": "Main 10", "pix_fmt": "yuv420p10le", "kbps": 200000},
"A015_05250930_C004.mp4": {"codec": "av1", "perfil": "Main", "ancho": 2160, "alto": 3840, "fps": 59.94, "kbps": 83000},
})
con = O.db()
with con:
con.execute("UPDATE archivos SET analizado = 1, codec = 'h264', perfil = 'High', pix_fmt = 'yuv420p', ancho = 3840, alto = 2160, "
"fps = 30, dur = 60, kbps = 120000 WHERE nombre = 'C0003.MP4'") # el clip y su copia en Duplicados
con.close()
nr = os.path.normpath(raiz)
cs = {os.path.basename(c["ruta"]): c["excluido"] for c in O.candidatos(nr, 0, "equilibrado", 0)}
self.assertTrue(cs["A014_06170738_C019.mp4"].startswith("ya viene comprimido (H.264 a 25 Mb/s)"), cs) # por debajo del objetivo (30)
self.assertTrue(cs["C0002.MP4"].startswith("ya viene comprimido (H.265 a 72 Mb/s)"), cs)
self.assertTrue(cs["C0004.MP4"].startswith("ya viene comprimido (AV1 a 30 Mb/s)"), cs)
# lo que guarda cada cuadro entero (XAVC-I, ProRes) y el H.264 muy holgado sí adelgazan de verdad
cands = {os.path.relpath(c["ruta"], os.path.join(nr, g.replace("/", S))): c for c in O.candidatos(nr, 0, "equilibrado", 0)}
cs = {k: c["excluido"] for k, c in cands.items()}
# un H.265 de cámara casi no adelgaza: no se ofrece; uno muy holgado (el techo es 1,8 veces el objetivo), sí
self.assertTrue(cs["C0002.MP4"].startswith("convertido ahorraría solo un 7 % (H.265 a 72 Mb/s): se convierte desde un 30 %"), cs)
self.assertEqual(cs["C0006.MP4"], "")
# el H.264 por debajo del objetivo y los AV1 (el de móvil a 83 Mb/s, uno a 22), que antes no se ofrecían, ahora sí
self.assertEqual([cs["A014_06170738_C019.mp4"], cs["A015_05250930_C004.mp4"], cs["C0004.MP4"]], ["", "", ""])
# lo que guarda cada cuadro entero (XAVC-I, ProRes) y el H.264 muy holgado, como siempre
self.assertEqual([cs["C0001.MP4"], cs["C0003.MP4"], cs["C0005.MP4"]], ["", "", ""])
# lo apartado como repetido no se convierte: se borra
self.assertTrue(cs[S.join(("Duplicados", "C0003.MP4"))].startswith("está apartado como repetido"), cs)
# lo que se estima: un AV1 a 83 Mb/s, como mucho el objetivo (49 % menos); un H.264 a 25, como mucho 0,6 veces
a = cands["A015_05250930_C004.mp4"]
self.assertAlmostEqual(a["estimado"] / (83000 * 1000 / 8 * 60), 1 - 0.49, delta=0.01)
# el mínimo se cambia: pidiendo un 45 %, el H.264 a 25 Mb/s (39 %) ya no entra
cs45 = {os.path.basename(c["ruta"]): c["excluido"] for c in O.candidatos(nr, 0, "equilibrado", 0, ahorro=45)}
self.assertTrue(cs45["A014_06170738_C019.mp4"].startswith("convertido ahorraría solo un 39 %"), cs45)
self.assertEqual(cs45["A015_05250930_C004.mp4"], "")
# a un formato de edición no se le aplica: ahí no se busca que pese menos
cs = {os.path.basename(c["ruta"]): c["excluido"] for c in O.candidatos(nr, 0, "equilibrado", 0, False, "prores_lt")}
self.assertEqual(cs["A014_06170738_C019.mp4"], "")
# y la lista de acciones cuenta solo lo que se puede aligerar
self.assertEqual(cs["C0002.MP4"], "")
# la lista de acciones cuenta lo que se puede aligerar
ajustes = dict(O.CONFIG["optimizar"])
self.addCleanup(lambda: O.CONFIG["optimizar"].update(ajustes))
O.CONFIG["optimizar"].update(min_mb=0, meses_minimos=0)
conv = [a for a in O.acciones(raiz)["acciones"] if a["clave"] == "convertir"]
self.assertEqual(len(conv), 1)
self.assertTrue(conv[0]["cifra"].startswith("3 clips"), conv[0])
self.assertTrue(conv[0]["cifra"].startswith("7 clips"), conv[0])
# y después de convertir se descarta lo que ahorre menos del mínimo con 10 puntos de margen (con 30, del 20 %)
t = {"formato": "h265_10bit", "ruta": "C0002.MP4", "bytes_in": 100, "bytes_out": 81}
self.assertTrue(O._no_compensa(t).startswith("no compensa: pesa el 81 %"))
self.assertEqual(O._no_compensa(dict(t, bytes_out=79)), "")
O.CONFIG["optimizar"]["ahorro_minimo"] = 50
self.assertTrue(O._no_compensa(dict(t, bytes_out=61)).startswith("no compensa"))
def test_un_nombre_parecido_no_es_una_conversion(self):
"""El sufijo solo no basta: «Entrevista_HQ.mp4» en H.264 no lo hizo este programa."""