Spaces:
Running on Zero
Running on Zero
File size: 6,021 Bytes
7e8e229 2e63d99 d903372 7e8e229 2e63d99 d903372 7e8e229 d903372 7e8e229 d903372 7e8e229 2e63d99 7e8e229 d903372 7e8e229 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 | """
report.py — Exportación del informe a Excel (.xlsx) y CSV (separador «;», listo para Excel en español).
"""
from __future__ import annotations
import csv
import math
from pathlib import Path
from openpyxl import Workbook
from openpyxl.styles import Alignment, Font, PatternFill
from openpyxl.utils import get_column_letter
COLUMNS = [
("archivo", "Archivo"),
("duracion", "Duración"),
("estado_inicial", "Estado inicial"),
("estado_final", "Estado final"),
("fallos", "Qué fallaba"),
("nivel", "Nivel de procesado usado"),
("cambios", "Qué se ha cambiado"),
("notas", "Qué se ha hecho (detalle)"),
("formato", "Formato original"),
("pico_antes", "Pico antes (dBFS)"), ("pico_despues", "Pico después (dBFS)"),
("rms_antes", "RMS voz antes (dBFS)"), ("rms_despues", "RMS voz después (dBFS)"),
("ruido_rel_antes", "Ruido relativo antes (dB)"), ("ruido_rel_despues", "Ruido relativo después (dB)"),
("snr_antes", "SNR antes (dB)"), ("snr_despues", "SNR después (dB)"),
("sil_ini_antes", "Silencio inicial antes (s)"), ("sil_ini_despues", "Silencio inicial después (s)"),
("sil_fin_antes", "Silencio final antes (s)"), ("sil_fin_despues", "Silencio final después (s)"),
("dnsmos_antes", "Nota de calidad antes"), ("dnsmos_despues", "Nota de calidad después"),
("d_pico", "Δ Pico (dB)"), ("d_rms", "Δ RMS voz (dB)"), ("d_ruido", "Δ Ruido de fondo (dB)"),
("d_snr", "Δ SNR (dB)"), ("d_dnsmos", "Δ Nota de calidad"),
("ruidos_quitados", "Ruidos sueltos quitados"),
]
def _num(v, nd=2):
if v is None or (isinstance(v, float) and (math.isnan(v) or math.isinf(v))):
return None
return round(float(v), nd)
def _delta(a, b):
a, b = _num(a), _num(b)
return None if a is None or b is None else round(b - a, 2)
def build_rows(items: list[dict]) -> list[dict]:
"""items: [{'nombre','duracion_s','estado_inicial','estado_final','formato','antes':m,'despues':m,'fallos','notas'}]"""
rows = []
for it in items:
a, d = it.get("antes") or {}, it.get("despues") or {}
row = {
"archivo": it["nombre"],
"duracion": (it.get("duracion_s") or 0) / 86400, # fracción de día = formato hora de Excel
"duracion_s": it.get("duracion_s") or 0,
"estado_inicial": it.get("estado_inicial", ""),
"estado_final": it.get("estado_final", ""),
"formato": it.get("formato", ""),
"fallos": ", ".join(it.get("fallos") or []),
"notas": " · ".join(it.get("notas") or []),
}
for key, mk in (("pico", "pico_dbfs"), ("rms", "rms_voz_dbfs"), ("ruido_rel", "ruido_relativo_db"),
("snr", "snr_db"), ("sil_ini", "silencio_ini_s"), ("sil_fin", "silencio_fin_s")):
row[f"{key}_antes"] = _num(a.get(mk))
row[f"{key}_despues"] = _num(d.get(mk))
row["dnsmos_antes"] = _num((a.get("dnsmos") or {}).get("OVRL"))
row["dnsmos_despues"] = _num((d.get("dnsmos") or {}).get("OVRL"))
for key, mk in (("d_pico", "pico_dbfs"), ("d_rms", "rms_voz_dbfs"), ("d_ruido", "ruido_fondo_dbfs"),
("d_snr", "snr_db")):
row[key] = _delta(a.get(mk), d.get(mk))
row["d_dnsmos"] = _delta(row["dnsmos_antes"], row["dnsmos_despues"])
row["nivel"] = it.get("nivel", "")
row["cambios"] = ", ".join(it.get("cambios") or [])
row["ruidos_quitados"] = next((n.split("—", 1)[-1].strip() for n in it.get("notas") or []
if n.startswith("Ruidos sueltos en las pausas sustituidos")), "")
rows.append(row)
return rows
def write_xlsx(path: Path, rows: list[dict], perfil: str, resumen: list[tuple[str, str]]) -> Path:
wb = Workbook()
ws = wb.active
ws.title = "Ficheros"
head_fill = PatternFill("solid", fgColor="01696F")
ws.append([label for _, label in COLUMNS])
for c in ws[1]:
c.font = Font(bold=True, color="FFFFFF")
c.fill = head_fill
c.alignment = Alignment(wrap_text=True, vertical="center")
ok_fill, bad_fill = PatternFill("solid", fgColor="E3F1D9"), PatternFill("solid", fgColor="F6DCEB")
for r in rows:
ws.append([r.get(k) for k, _ in COLUMNS])
i = ws.max_row
ws.cell(i, 2).number_format = "[h]:mm:ss"
for col in (3, 4):
cell = ws.cell(i, col)
if cell.value:
cell.fill = ok_fill if str(cell.value).startswith(("PASA", "CORREGIDO", "YA CUMPL", "CUMPL")) else bad_fill
n = ws.max_row
ws.append(["TOTAL", f"=SUM(B2:B{n})"])
ws.cell(ws.max_row, 1).font = Font(bold=True)
ws.cell(ws.max_row, 2).number_format = "[h]:mm:ss"
ws.cell(ws.max_row, 2).font = Font(bold=True)
widths = {1: 34, 2: 11, 3: 16, 4: 24, 5: 40, 6: 14, 7: 40, 8: 90, 9: 34}
for i in range(1, len(COLUMNS) + 1):
ws.column_dimensions[get_column_letter(i)].width = widths.get(i, 13)
ws.freeze_panes = "B2"
ws2 = wb.create_sheet("Resumen")
ws2.append(["Perfil", perfil])
for k, v in resumen:
ws2.append([k, v])
ws2.column_dimensions["A"].width = 30
ws2.column_dimensions["B"].width = 60
for row in ws2.iter_rows(min_col=1, max_col=1):
row[0].font = Font(bold=True)
wb.save(path)
return path
def write_csv(path: Path, rows: list[dict]) -> Path:
def fmt(v):
if isinstance(v, float):
return f"{v:.2f}".replace(".", ",")
return "" if v is None else str(v)
keys = [k for k, _ in COLUMNS]
with open(path, "w", newline="", encoding="utf-8-sig") as f:
w = csv.writer(f, delimiter=";")
w.writerow([label for _, label in COLUMNS] + ["Duración (s)"])
for r in rows:
vals = [fmt(r.get(k)) for k in keys]
s = int(round(r["duracion_s"]))
vals[1] = f"{s // 3600}:{s % 3600 // 60:02d}:{s % 60:02d}"
w.writerow(vals + [fmt(r["duracion_s"])])
return path
|