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