File size: 11,833 Bytes
cb5f078
86fb0e9
 
 
 
cb5f078
86fb0e9
7e8e229
86fb0e9
 
7e8e229
cb5f078
7e8e229
fc527e7
7e8e229
fc527e7
 
7e8e229
fc527e7
7e8e229
fc527e7
7e8e229
fc527e7
 
 
 
 
 
 
 
 
 
 
7e8e229
fc527e7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7e8e229
fc527e7
 
 
 
 
 
 
 
 
 
c6a2c55
fc527e7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4c6ff29
fc527e7
 
 
 
 
4c6ff29
fc527e7
 
 
 
4c6ff29
fc527e7
 
 
 
 
 
 
 
4c6ff29
fc527e7
 
 
 
 
 
c6a2c55
fc527e7
 
 
 
 
 
 
 
 
 
 
7e8e229
fc527e7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c6a2c55
4c6ff29
fc527e7
 
c6a2c55
7e8e229
fc527e7
7e8e229
fc527e7
7e8e229
 
 
fc527e7
7e8e229
 
 
 
 
fc527e7
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
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
---
title: Audio Denoise
emoji: 🎙️
colorFrom: blue
colorTo: green
sdk: gradio
sdk_version: "6.9.0"
python_version: "3.12"
app_file: app.py
pinned: false
short_description: Limpieza de voz y control de entregas de audio
---

# 🎙️ Limpieza y control de voz — documentación técnica

> **¿Vas a usar la app?** Todo lo que necesitas está en la pestaña **❓ Ayuda** de la propia app.
> Este documento es para mantenerla: cómo está hecha, cómo crear proyectos nuevos y cómo publicarla.

La app reduce el ruido de grabaciones de voz sin perder naturalidad y comprueba que cada audio cumple la especificación técnica de un cliente (un *perfil*). Si no cumple, genera la versión corregida y un informe en Excel. Funciona en Hugging Face con **ZeroGPU**; si no hay cuota de GPU, sigue en CPU automáticamente.

---

## 1. Publicar cambios en Hugging Face

El código vive en GitHub y el Space es una copia que se actualiza con un botón.

1. Los cambios tienen que estar en la rama `main` de GitHub. Si vienen en otra rama, primero hay que fusionar el *pull request* con **Merge pull request**.
2. En GitHub: **Actions** → **Desplegar en Hugging Face** → **Run workflow** → **Run workflow**.
3. Al cabo de un minuto aparece ✅. El Space se reconstruye solo: con cambios en `requirements.txt` o `packages.txt` tarda 5–10 minutos; si no, menos de uno.

Configuración, una sola vez (ya hecha):

| Dónde | Qué |
|---|---|
| GitHub → Settings → Secrets and variables → Actions → **Secrets** | `HF_TOKEN`: token de Hugging Face de tipo **Write** |
| GitHub → … → **Variables** (opcional) | `HF_SPACE`: si no existe, se usa `Azuredraco/audio-denoise` |

Alternativa desde el PC: `python scripts/deploy_hf.py --space Azuredraco/audio-denoise`. Usa el token de `hf auth login` o el de la variable de entorno `HF_TOKEN`.

El Space queda como copia exacta del repositorio, sin `tests/`, `scripts/` ni `.github/`.

### Versión para Windows

Cada vez que entra un cambio en `main`, GitHub construye **solo** la versión de escritorio (workflow *Versión para Windows*). La prueba en un Windows real y la publica en **https://huggingface.co/Azuredraco/limpieza-de-voz-windows**. El Space tiene un enlace a esa página. Tarda unos 15–20 minutos. También se puede lanzar a mano desde **Actions → Versión para Windows → Run workflow**.

- **Instalador** `Instalar-Limpieza-de-voz.exe`: no pide permisos de administrador. Crea accesos directos en el escritorio y en el menú Inicio, y al ejecutar uno nuevo actualiza encima.
- **Portable** `Limpieza-de-voz-portable.zip`: se descomprime y se abre con `Iniciar.bat`.
- Lleva dentro su propio Python 3.11, las dependencias (PyTorch CPU) y los modelos: **funciona sin internet**. Ocupa ~1 GB instalado.
- Por dentro es la misma app (`app.py`) en `127.0.0.1`. `desktop/iniciar.py` la arranca y abre el navegador; la ventana negra es el servidor.
- Ficheros: `desktop/` (lanzador, prueba de humo, icono, script de Inno Setup y publicación) y `requirements-desktop.txt`.

El repositorio de GitHub es privado, por eso el instalador se publica en Hugging Face: así se descarga sin necesidad de cuenta.

---

## 2. Perfiles de proyecto (`perfiles/*.yaml`)

Cada proyecto o cliente es un fichero YAML. Al crear uno nuevo, aparece solo en el desplegable «Proyecto».

| Fichero | Nombre en la app | Uso |
|---|---|---|
| `multiling_es-es_01.yaml` | MultiLing · voz femenina es-ES (40 min) | Encargo MultiLing: WAV PCM, 48 kHz, ≥ 24 bits, SNR ≥ 50 dB. Sin recortes ni cambios de volumen (el cliente quiere la toma entera). Controla los 6 entregables y el objetivo de 40 min. |
| `analizador_v4.yaml` | Proyecto 12 h 30 min · mismas reglas que el analizador v4 | Las mismas reglas que el *Analizador de audio v4* en HTML: WAV PCM 24 bits, 48 kHz, mono, pico ≤ −3 dBFS, RMS de voz −30…−20 dBFS, ruido ≤ −40 dB y silencios de 0,5–0,7 s. |
| `asr_dialogo_dos_pistas.yaml` | ASR · diálogo a dos pistas | Encargo ASR Dual-Track: WAV PCM 16 bits, 48 kHz, mono, espectro > 22 kHz, ruido de fondo ≤ −50 dBFS, voz > 5000 (escala 16 bits), sin saturación. Arranca en nivel «Solo formato» (el PDF pide sin procesado). Comprueba parejas de pistas (misma duración, sin voz cruzada). |
| `libre.yaml` | Solo limpiar ruido · sin reglas de cliente | Solo limpieza; conserva formato, frecuencia y canales. |

### Crear un perfil nuevo
Copia el que más se parezca, cámbiale el nombre al fichero y edita los valores. Todas las secciones son opcionales: lo que no pongas no se comprueba.

```yaml
nombre: "Cliente X · audiolibro (10 h)"      # lo que se ve en el desplegable
descripcion: "WAV 24 bits · 44,1 kHz · …"    # resumen técnico de una línea
explicacion: |                               # lo que ve el usuario bajo el desplegable (Markdown)
  - **Qué pide el cliente:** …
  - **Qué hace la corrección:** …
orden: 3                                     # posición en el desplegable
procesar_siempre: false                      # true = limpiar aunque ya cumpla

formato:
  contenedor: WAV         # WAV | FLAC | AIFF
  pcm_entero: true        # exige PCM entero (no coma flotante)
  bits: 24                # exactamente 24…  (o bien)  bits_min: 24  → 24 o más
  frecuencia: 48000       # Hz; la corrección remuestrea
  ancho_banda_min_hz: 22000   # el espectro debe llegar más allá (detecta audio filtrado, comprimido o de 44,1 kHz)
  canales: 1              # 1 = exige mono (la casilla «Convertir a mono» sale marcada); null = sin requisito

niveles:
  pico_max_dbfs: -3.0     # techo de picos (se aplica limitador)
  rms_voz: {min: -30, max: -20, objetivo: -25}   # volumen medio de la voz; corrige al objetivo
  amplitud_voz_min: 5000  # muestra más alta de la voz en escala de 16 bits (0–32767)

ruido:
  snr_min_db: 50          # voz activa frente a ruido de las pausas
  ruido_relativo_max_db: -40   # primeros/últimos 0,4 s frente a la voz (criterio del analizador v4)
  ruido_fondo_max_dbfs: -50    # nivel absoluto del ruido de las pausas

silencios:                # null = no tocar inicio ni final
  min_s: 0.5
  max_s: 0.7
  objetivo_s: 0.6
  relleno: tono_sala      # tono_sala (ruido ambiente real, recomendado) | digital (ceros)

saturacion_max_muestras: 0
pistas_pareadas: false    # true = diálogo a dos pistas: comprueba duraciones idénticas y voz cruzada

procesado:                # valores por defecto de «Opciones avanzadas»
  reduccion_db: 24        # 12 Suave · 24 Natural · 36 Fuerte · null Máximo
  paso_alto_hz: 70        # null = sin filtro
  conservar_ultrasonidos: true
  salida: {contenedor: WAV, subtipo: PCM_24}   # null = como el original
  modo_defecto: ninja     # nivel de procesado inicial: formato | permitido | ninja (el usuario lo cambia en la app)

entregables:              # opcional: control de ficheros y duración total
  objetivo_total_s: 2400
  ficheros:
    - {nombre: "nonverbal_laughter", duracion_min_s: 30}
    - {nombre: "full recording_part 1", duracion_aprox_s: 760}
```

Los nombres de los entregables se comparan sin distinguir mayúsculas, extensión, espacios ni guiones bajos.

---

## 3. Cómo funciona

### Cadena de corrección (`pipeline.py`)
1. **Canales:** se conservan, salvo que se marque «Convertir a mono».
2. **Paso alto** Butterworth de fase cero, a 70 Hz por defecto. Quita retumbes, golpes graves y DC.
3. **Remuestreo** con SoX VHQ (fase lineal, sin desfase) a la frecuencia exigida.
4. **DeepFilterNet3** a 48 kHz, por bloques de 60 s con 5 s de contexto y fundido cruzado. Así los ficheros de 30 min o más tienen memoria acotada y llamadas a GPU cortas.
   - Si no se alcanza el ruido exigido, se escala la reducción: 24 → 36 dB → sin límite.
   - Como último recurso, un expansor de −15 dB solo en las pausas, con 100 ms de margen antes de la voz y 200 ms después.
5. **Más de 48 kHz:** la banda > 20 kHz no pasa por el modelo. Se conserva del original mediante un cruce FIR complementario, con una ganancia que sigue la reducción aplicada en 12–20 kHz.
6. **Silencios inicial/final:** recorte, o relleno con tono de sala (el tramo sin voz más silencioso de la propia toma, con fundidos de potencia constante).
7. **Nivel:** ganancia hasta el RMS objetivo y limitador de picos con anticipación. El limitador es un mínimo deslizante suavizado, sin sobreoscilación garantizada.
8. **Escritura** (dither TPDF si se baja a 16 bits) y **nueva medición del fichero escrito**: la columna «Después» es real.

### Métricas (`metrics.py`, `dnsmos.py`)
- **Pico** (dBFS) y **pico real** (dBTP, sobremuestreo ×4, por bloques).
- **RMS de voz** entre la primera y la última voz, como el analizador v4.
- **Ruido relativo** de los primeros/últimos 0,4 s frente a la voz.
- **SNR**: voz activa frente a ruido de pausas.
- **Silencios**: la detección de voz ignora clics y golpes de menos de 120 ms.
- **Saturación** y **LUFS** (BS.1770-4; coeficientes verificados con la tabla ITU).
- **DNSMOS** P.835 de Microsoft: SIG (voz), BAK (fondo) y OVRL (global), de 1 a 5. Usa como mucho 10 ventanas de 9 s repartidas por el audio. Es opcional («⭐ Poner nota de calidad al corregir», desmarcada por defecto) porque en CPUs modestas triplica el tiempo.

### ZeroGPU
- `denoise_core.py` coloca el modelo en `cuda` a nivel de módulo (patrón oficial de ZeroGPU) y decora la inferencia con `@spaces.GPU` y duración dinámica. Cada llamada cubre hasta 5 min de audio.
- **La GPU solo se usa con audios de 90 s o más.** Para audios cortos, la espera de ZeroGPU (cola, asignación, comprobación de cuota) cuesta más que procesar en CPU.
- Si una llamada falla (cuota, cola, timeout), el resto del fichero va por CPU con el mismo resultado. Durante 15 minutos no se vuelve a intentar con GPU, para no repetir la espera.
- Si el original tiene mucho más ruido del permitido, la corrección empieza directamente por el escalón de reducción necesario. Así no se pasa el modelo varias veces.
- Cuota diaria según la documentación de HF: 2 min sin sesión, 5 min con cuenta gratuita, 40 min con PRO.

### Ficheros
| Fichero | Contenido |
|---|---|
| `app.py` | Interfaz Gradio y textos de ayuda para el usuario |
| `ui_html.py` | Tarjetas de resultados, CSS (móvil, modo claro/oscuro) |
| `pipeline.py` | Analizar y corregir según un perfil |
| `denoise_core.py` | DeepFilterNet3 + ZeroGPU + respaldo en CPU |
| `dsp.py` | Remuestreo, filtros, detección de voz, silencios, limitador, expansor |
| `metrics.py` · `dnsmos.py` | Mediciones |
| `profiles.py` · `perfiles/` | Carga de perfiles y comprobaciones |
| `vad.py` | Detector de voz neuronal (Silero VAD) para los ruidos sueltos |
| `pairs.py` | Parejas de pistas: misma duración y voz cruzada |
| `report.py` | Informe Excel y CSV |
| `audio_io.py` | Lectura (soundfile o ffmpeg) y escritura |
| `models.py` | Descarga de modelos (DeepFilterNet3, DNSMOS, Silero VAD) verificada por SHA-256 |

---

## 4. Trabajo en local (Windows / PyCharm)

```bash
python -m venv .venv
.venv\Scripts\activate            # Linux/Mac: source .venv/bin/activate
pip install -r requirements-dev.txt
python app.py                     # http://localhost:7860
python -m pytest -q tests
```

`deepfilternet` 0.5.6 compila una parte en Rust si no hay paquete precompilado para tu sistema; en Windows hay que instalar [Rust](https://rustup.rs) la primera vez. Las versiones están fijadas en `requirements.txt`: `deepfilternet` exige `numpy < 2` y ZeroGPU exige `torch ≥ 2.8`. Los modelos se descargan la primera vez en `~/.cache/audio-denoise`.

## Créditos
- [DeepFilterNet](https://github.com/Rikorose/DeepFilterNet) (H. Schröter et al.), reducción de ruido.
- [DNSMOS](https://github.com/microsoft/DNS-Challenge/tree/master/DNSMOS) (Microsoft), calidad percibida.