audio-denoise / report.py
Azuredraco's picture
Despliegue desde GitHub
d903372 verified
Raw History Blame Contribute Delete
6.02 kB
"""
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