dimensionalpulsar commited on
Commit
067e4a7
·
1 Parent(s): 7d30adc

feat: add robust logging and diagnostics for ZeroGPU troubleshooting; fix '0 seconds' issue with explicit checks

Browse files
Files changed (3) hide show
  1. app.py +216 -460
  2. pipeline/inference.py +10 -2
  3. pipeline/separation.py +8 -0
app.py CHANGED
@@ -1,460 +1,216 @@
1
- import os
2
- import sys
3
- import logging
4
- import tempfile
5
- import shutil
6
- import gradio as gr
7
-
8
- try:
9
- import gradio_client.utils as _gc_utils
10
-
11
- _orig_get_type = _gc_utils.get_type
12
-
13
- def _patched_get_type(schema, *args, **kwargs):
14
- if not isinstance(schema, dict):
15
- return "Any"
16
- return _orig_get_type(schema, *args, **kwargs)
17
-
18
- _gc_utils.get_type = _patched_get_type
19
-
20
- _orig_json_schema = _gc_utils._json_schema_to_python_type
21
-
22
- def _patched_json_schema(schema, *args, **kwargs):
23
- if not isinstance(schema, dict):
24
- return "Any"
25
- return _orig_json_schema(schema, *args, **kwargs)
26
-
27
- _gc_utils._json_schema_to_python_type = _patched_json_schema
28
- _gc_utils.json_schema_to_python_type = lambda schema, defs=None: _patched_json_schema(
29
- schema, defs
30
- )
31
- except Exception:
32
- pass
33
-
34
- # Configuración de logs
35
- logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
36
- logger = logging.getLogger(__name__)
37
-
38
- # Inicio: clonar Seed-VC
39
- logger.info("Inicializando la aplicación...")
40
-
41
- from pipeline.setup import setup_seed_vc
42
- from pipeline.storage import init_storage, list_models, download_model, delete_model, get_reference_path
43
-
44
- try:
45
- setup_seed_vc()
46
- except Exception as e:
47
- logger.error("Error durante la configuración: {}".format(e))
48
-
49
- HF_MODELS_REPO = os.environ.get("HF_MODELS_REPO", "")
50
- if HF_MODELS_REPO:
51
- init_storage(HF_MODELS_REPO)
52
- logger.info("Almacenamiento de HuggingFace configurado: {}".format(HF_MODELS_REPO))
53
-
54
- from pipeline.training import save_voice_reference, _gpu_warmup
55
- from pipeline.separation import _separate_audio_impl
56
- from pipeline.inference import _convert_voice_impl
57
- from pipeline.mixing import mix_audio
58
-
59
- try:
60
- import spaces
61
- except ImportError:
62
- class spaces:
63
- @staticmethod
64
- def GPU(duration=60, **kwargs):
65
- def decorator(fn):
66
- return fn
67
- return decorator
68
-
69
- @spaces.GPU(duration=600)
70
- def _full_pipeline_gpu(song_file, reference_path, pitch, diffusion_steps, similarity,
71
- vocal_volume, instrumental_volume):
72
- """
73
- Master ZeroGPU function: runs Demucs + Seed-VC + Mix in a single GPU session.
74
- ZeroGPU does NOT allow calling one @spaces.GPU function from inside another,
75
- so we consolidate the entire pipeline here.
76
- """
77
- import torch
78
- import os
79
- import sys
80
-
81
- # Ensure app dir is in path for the ZeroGPU worker
82
- app_dir = os.path.dirname(os.path.abspath(__file__))
83
- if app_dir not in sys.path:
84
- sys.path.insert(0, app_dir)
85
- os.chdir(app_dir)
86
-
87
- # 1. Separate vocals / instruments (Demucs)
88
- vocals_path, instruments_path = _separate_audio_impl(song_file)
89
-
90
- # 2. Convert voice (Seed-VC)
91
- converted_path = _convert_voice_impl(
92
- audio_path=vocals_path,
93
- reference_path=reference_path,
94
- pitch=int(pitch),
95
- diffusion_steps=int(diffusion_steps),
96
- similarity=float(similarity),
97
- )
98
-
99
- # 3. Mix
100
- final_path = mix_audio(
101
- vocals_path=converted_path,
102
- instruments_path=instruments_path,
103
- vocal_volume=float(vocal_volume),
104
- instrumental_volume=float(instrumental_volume),
105
- )
106
-
107
- import librosa
108
- # Load back the audio data to return it directly.
109
- # This bypasses ZeroGPU filesystem sync issues.
110
- v_data, v_sr = librosa.load(vocals_path, sr=None)
111
- c_data, c_sr = librosa.load(converted_path, sr=None)
112
- f_data, f_sr = librosa.load(final_path, sr=None)
113
-
114
- return (v_sr, v_data), (c_sr, c_data), (f_sr, f_data)
115
-
116
-
117
- def train_voice_model(audio_file, model_name, progress=gr.Progress()):
118
- """Controlador: guardar referencia de voz."""
119
- if audio_file is None:
120
- return "Error: Por favor, sube un archivo de audio.", None
121
-
122
- if not model_name or not model_name.strip():
123
- return "Error: Por favor, ingresa un nombre para el modelo.", None
124
-
125
- model_name = model_name.strip().replace(" ", "_")
126
-
127
- def progress_callback(value, desc):
128
- progress(value, desc=desc)
129
-
130
- try:
131
- progress(0.0, desc="Iniciando...")
132
- pth_path, ref_path = save_voice_reference(
133
- audio_path=audio_file,
134
- model_name=model_name,
135
- progress_callback=progress_callback,
136
- )
137
-
138
- return "¡Referencia de voz '{}' guardada con éxito!".format(model_name), ref_path
139
-
140
- except Exception as e:
141
- import traceback
142
- tb = traceback.format_exc()
143
- logger.error("Error en el entrenamiento: {}".format(tb))
144
- return "Error : {}: {}\n\nDetalles:\n{}".format(
145
- type(e).__name__, str(e), tb[-500:]
146
- ), None
147
-
148
- def get_model_choices():
149
- """Obtener lista de nombres de modelos entrenados para el menú desplegable."""
150
- models = list_models()
151
- if not models:
152
- return ["(ningún modelo)"]
153
- return models
154
-
155
-
156
- def convert_song(
157
- model_choice,
158
- song_file,
159
- pitch,
160
- similarity,
161
- diffusion_steps,
162
- vocal_volume,
163
- instrumental_volume,
164
- progress=gr.Progress(),
165
- ):
166
- """Pipeline completo: separar + convertir + mezclar (single GPU session)."""
167
- if song_file is None:
168
- return "Error: Por favor, sube un archivo de audio.", None, None, None
169
-
170
- if model_choice == "(ningún modelo)" or not model_choice:
171
- return "Error: Por favor, guarda una referencia de voz primero.", None, None, None
172
-
173
- try:
174
- progress(0.05, desc="Cargando modelo...")
175
- pth_path, ref_or_index = download_model(model_choice)
176
- if not pth_path:
177
- return "Error: Modelo '{}' no encontrado.".format(model_choice), None, None, None
178
-
179
- reference_path = get_reference_path(model_choice)
180
- if not reference_path:
181
- return "Error: Audio de referencia no encontrado para '{}'.".format(model_choice), None, None, None
182
-
183
- progress(0.10, desc="Iniciando pipeline GPU (Demucs + Seed-VC + Mezcla)...")
184
-
185
- vocals_path, converted_path, final_path = _full_pipeline_gpu(
186
- song_file=song_file,
187
- reference_path=reference_path,
188
- pitch=pitch,
189
- diffusion_steps=diffusion_steps,
190
- similarity=similarity,
191
- vocal_volume=vocal_volume,
192
- instrumental_volume=instrumental_volume,
193
- )
194
-
195
- progress(1.0, desc="¡Terminado!")
196
-
197
- return (
198
- "¡Conversión completada con éxito!",
199
- vocals_path,
200
- converted_path,
201
- final_path,
202
- )
203
-
204
- except Exception as e:
205
- import traceback
206
- tb = traceback.format_exc()
207
- logger.error("Error en la conversión: {}".format(tb))
208
- return "Error : {}: {}\n\nDetalles:\n{}".format(
209
- type(e).__name__, str(e), tb[-800:]
210
- ), None, None, None
211
-
212
- def refresh_models():
213
- """Actualizar la lista de modelos como HTML."""
214
- models = list_models()
215
- if not models:
216
- return "<p style='color:gray;'>Ningún modelo guardado</p>"
217
- rows = "".join(
218
- "<tr><td>{}</td><td>Disponible</td></tr>".format(m) for m in models
219
- )
220
- return (
221
- "<table style='width:100%;border-collapse:collapse;'>"
222
- "<tr><th style='text-align:left;border-bottom:1px solid #555;padding:8px;'>Nombre</th>"
223
- "<th style='text-align:left;border-bottom:1px solid #555;padding:8px;'>Estado</th></tr>"
224
- "{}</table>".format(rows)
225
- )
226
-
227
-
228
- def delete_selected_model(model_name_to_delete):
229
- """Eliminar un modelo."""
230
- if not model_name_to_delete or model_name_to_delete == "(ningún modelo)":
231
- return "Por favor, selecciona un modelo para eliminar.", refresh_models()
232
- try:
233
- delete_model(model_name_to_delete)
234
- return "Modelo '{}' eliminado.".format(model_name_to_delete), refresh_models()
235
- except Exception as e:
236
- return "Error : {}".format(e), refresh_models()
237
-
238
- with gr.Blocks(
239
- title="Clon de Voz",
240
- theme=gr.themes.Soft(),
241
- ) as app:
242
-
243
- gr.Markdown(
244
- "# 🎤 Aplicación de Clonación de Voz (Seed-VC)\n"
245
- "> Powered by [Seed-VC](https://github.com/Plachta/seed-vc) + [Demucs](https://github.com/facebookresearch/demucs) · ZeroGPU · Zero-shot"
246
- )
247
-
248
- with gr.Tabs():
249
- # Pestaña 1: Referencia de voz
250
- with gr.TabItem("Mi voz"):
251
- gr.Markdown("### Guardar tu referencia de voz")
252
-
253
- with gr.Row():
254
- with gr.Column(scale=2):
255
- train_audio = gr.Audio(
256
- label="Extracto de tu voz (WAV o MP3, 3-30 segundos)",
257
- type="filepath",
258
- sources=["upload"],
259
- )
260
- train_model_name = gr.Textbox(
261
- label="Nombre del perfil",
262
- placeholder="ej: mi_voz",
263
- max_lines=1,
264
- )
265
- train_btn = gr.Button(
266
- "Guardar",
267
- variant="primary",
268
- size="lg",
269
- )
270
-
271
- with gr.Column(scale=1):
272
- train_status = gr.Textbox(
273
- label="Estado",
274
- interactive=False,
275
- lines=3,
276
- )
277
- train_download = gr.File(
278
- label="Archivo de referencia",
279
- interactive=False,
280
- )
281
-
282
- gr.Markdown(
283
- "**Consejos:**\n"
284
- "- Usa una grabación limpia (sin ruido de fondo, sin música)\n"
285
- "- Habla o canta naturalmente durante 3 a 30 segundos\n"
286
- "- Mientras más largo y variado sea el extracto, mejor será el resultado\n"
287
- "- Se aceptan formatos WAV o MP3"
288
- )
289
-
290
- train_btn.click(
291
- fn=train_voice_model,
292
- inputs=[train_audio, train_model_name],
293
- outputs=[train_status, train_download],
294
- )
295
-
296
- # Pestaña 2: Conversión
297
- with gr.TabItem("Convertir una canción"):
298
- gr.Markdown("### Reemplazar la voz de una canción por la tuya")
299
-
300
- with gr.Row():
301
- with gr.Column(scale=2):
302
- convert_model = gr.Dropdown(
303
- choices=get_model_choices(),
304
- label="Perfil de voz",
305
- interactive=True,
306
- )
307
- refresh_btn = gr.Button("Actualizar lista", size="sm")
308
- convert_audio = gr.Audio(
309
- label="Canción a convertir (WAV o MP3)",
310
- type="filepath",
311
- sources=["upload"],
312
- )
313
-
314
- with gr.Accordion("Parámetros avanzados", open=False):
315
- convert_pitch = gr.Slider(
316
- minimum=-24,
317
- maximum=24,
318
- value=0,
319
- step=1,
320
- label="Transposición (semitonos)",
321
- )
322
- convert_similarity = gr.Slider(
323
- minimum=0.0,
324
- maximum=1.0,
325
- value=0.7,
326
- step=0.05,
327
- label="Similitud de voz (0.5=natural, 0.7=equilibrado, 0.9=más fiel)",
328
- )
329
- convert_diffusion = gr.Slider(
330
- minimum=5,
331
- maximum=100,
332
- value=25,
333
- step=5,
334
- label="Calidad (10=rápido, 25=equilibrado, 50=alta calidad)",
335
- )
336
- convert_vocal_vol = gr.Slider(
337
- minimum=0.0,
338
- maximum=2.0,
339
- value=1.0,
340
- step=0.1,
341
- label="Volumen de la voz",
342
- )
343
- convert_inst_vol = gr.Slider(
344
- minimum=0.0,
345
- maximum=2.0,
346
- value=1.0,
347
- step=0.1,
348
- label="Volumen de los instrumentos",
349
- )
350
-
351
- convert_btn = gr.Button(
352
- "Convertir y mezclar",
353
- variant="primary",
354
- size="lg",
355
- )
356
-
357
- with gr.Column(scale=1):
358
- convert_status = gr.Textbox(
359
- label="Estado",
360
- interactive=False,
361
- lines=3,
362
- )
363
- gr.Markdown("**Vista previa de las pistas:**")
364
- preview_vocals = gr.Audio(
365
- label="Voz original (separada)",
366
- interactive=False,
367
- )
368
- preview_converted = gr.Audio(
369
- label="Voz convertida",
370
- interactive=False,
371
- )
372
- gr.Markdown("**Resultado final:**")
373
- final_output = gr.Audio(
374
- label="Canción final (voz + instrumentos)",
375
- interactive=False,
376
- )
377
-
378
- refresh_btn.click(
379
- fn=lambda: gr.Dropdown(choices=get_model_choices()),
380
- outputs=[convert_model],
381
- )
382
-
383
- convert_btn.click(
384
- fn=convert_song,
385
- inputs=[
386
- convert_model,
387
- convert_audio,
388
- convert_pitch,
389
- convert_similarity,
390
- convert_diffusion,
391
- convert_vocal_vol,
392
- convert_inst_vol,
393
- ],
394
- outputs=[convert_status, preview_vocals, preview_converted, final_output],
395
- )
396
-
397
- # Pestaña 3: Modelos
398
- with gr.TabItem("Mis modelos"):
399
- gr.Markdown("### Gestionar tus perfiles de voz")
400
-
401
- models_table = gr.HTML(
402
- value=refresh_models(),
403
- label="Modelos guardados",
404
- )
405
-
406
- with gr.Row():
407
- models_refresh_btn = gr.Button("Actualizar", size="sm")
408
- models_delete_name = gr.Dropdown(
409
- choices=get_model_choices(),
410
- label="Modelo a eliminar",
411
- interactive=True,
412
- )
413
- models_delete_btn = gr.Button("Eliminar", variant="stop", size="sm")
414
-
415
- models_delete_status = gr.Textbox(label="Estado", interactive=False)
416
-
417
- models_refresh_btn.click(
418
- fn=refresh_models,
419
- outputs=[models_table],
420
- )
421
- models_refresh_btn.click(
422
- fn=lambda: gr.Dropdown(choices=get_model_choices()),
423
- outputs=[models_delete_name],
424
- )
425
-
426
- models_delete_btn.click(
427
- fn=delete_selected_model,
428
- inputs=[models_delete_name],
429
- outputs=[models_delete_status, models_table],
430
- )
431
-
432
- # Pestaña 4: Debug (temporal)
433
- with gr.TabItem("Depuración GPU"):
434
- gr.Markdown("### Logs del Trabajador GPU (para diagnóstico)")
435
- debug_output = gr.Textbox(
436
- label="Últimos logs de GPU",
437
- interactive=False,
438
- lines=20,
439
- )
440
- debug_btn = gr.Button("Leer los logs", size="sm")
441
-
442
- def read_debug_log():
443
- log_path = "/home/user/app/debug_gpu.log"
444
- if os.path.exists(log_path):
445
- with open(log_path, "r") as f:
446
- return f.read()
447
- return "Ningún log disponible. Ejecuta una conversión primero."
448
-
449
- debug_btn.click(fn=read_debug_log, outputs=[debug_output])
450
-
451
-
452
- if __name__ == "__main__":
453
- os.makedirs("./results", exist_ok=True)
454
- os.makedirs("./checkpoints/models", exist_ok=True)
455
- app.launch(
456
- allowed_paths=[
457
- os.path.abspath("./results"),
458
- os.path.abspath("./checkpoints"),
459
- ]
460
- )
 
1
+ import os
2
+ import sys
3
+ import logging
4
+ import tempfile
5
+ import shutil
6
+ import gradio as gr
7
+ import gc
8
+ import time
9
+
10
+ # Patches para Gradio
11
+ try:
12
+ import gradio_client.utils as _gc_utils
13
+ _orig_get_type = _gc_utils.get_type
14
+ def _patched_get_type(schema, *args, **kwargs):
15
+ if not isinstance(schema, dict): return "Any"
16
+ return _orig_get_type(schema, *args, **kwargs)
17
+ _gc_utils.get_type = _patched_get_type
18
+ _orig_json_schema = _gc_utils._json_schema_to_python_type
19
+ def _patched_json_schema(schema, *args, **kwargs):
20
+ if not isinstance(schema, dict): return "Any"
21
+ return _orig_json_schema(schema, *args, **kwargs)
22
+ _gc_utils._json_schema_to_python_type = _patched_json_schema
23
+ _gc_utils.json_schema_to_python_type = lambda schema, defs=None: _patched_json_schema(schema, defs)
24
+ except Exception:
25
+ pass
26
+
27
+ # Configuración de logs
28
+ logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
29
+ logger = logging.getLogger(__name__)
30
+
31
+ from pipeline.setup import setup_seed_vc
32
+ from pipeline.storage import init_storage, list_models, download_model, delete_model, get_reference_path
33
+ from pipeline.training import save_voice_reference
34
+ from pipeline.separation import _separate_audio_impl
35
+ from pipeline.inference import _convert_voice_impl
36
+ from pipeline.mixing import mix_audio
37
+
38
+ try:
39
+ import spaces
40
+ except ImportError:
41
+ class spaces:
42
+ @staticmethod
43
+ def GPU(duration=60, **kwargs):
44
+ def decorator(fn): return fn
45
+ return decorator
46
+
47
+ def check_file(path, label, logs):
48
+ if os.path.exists(path):
49
+ size = os.path.getsize(path)
50
+ logs.append(f"✅ {label} generado: {os.path.basename(path)} ({size} bytes)")
51
+ return size > 44 # Min size for a WAV header
52
+ else:
53
+ logs.append(f"❌ ERROR: {label} NO se encontró en {path}")
54
+ return False
55
+
56
+ @spaces.GPU(duration=600)
57
+ def _full_pipeline_gpu(song_file, reference_path, pitch, diffusion_steps, similarity,
58
+ vocal_volume, instrumental_volume):
59
+ import torch
60
+ import librosa
61
+
62
+ logs = []
63
+ logs.append(f"🚀 Iniciando pipeline en GPU...")
64
+
65
+ # Asegurar directorio de trabajo
66
+ app_dir = os.path.dirname(os.path.abspath(__file__))
67
+ os.chdir(app_dir)
68
+
69
+ try:
70
+ # 1. Separación (Demucs)
71
+ logs.append("⏳ Paso 1/3: Separando voces e instrumentos (Demucs)...")
72
+ vocals_path, instruments_path = _separate_audio_impl(song_file)
73
+
74
+ if not check_file(vocals_path, "Vocales", logs):
75
+ return None, None, None, "\n".join(logs)
76
+
77
+ # Liberar memoria después de Demucs
78
+ torch.cuda.empty_cache()
79
+ gc.collect()
80
+
81
+ # 2. Conversión (Seed-VC)
82
+ logs.append("⏳ Paso 2/3: Convirtiendo voz (Seed-VC)...")
83
+ converted_path = _convert_voice_impl(
84
+ audio_path=vocals_path,
85
+ reference_path=reference_path,
86
+ pitch=int(pitch),
87
+ diffusion_steps=int(diffusion_steps),
88
+ similarity=float(similarity),
89
+ )
90
+
91
+ if not check_file(converted_path, "Voz convertida", logs):
92
+ return None, None, None, "\n".join(logs)
93
+
94
+ # Liberar memoria después de Seed-VC
95
+ torch.cuda.empty_cache()
96
+ gc.collect()
97
+
98
+ # 3. Mezcla
99
+ logs.append("⏳ Paso 3/3: Mezclando pistas finales...")
100
+ final_path = mix_audio(
101
+ vocals_path=converted_path,
102
+ instruments_path=instruments_path,
103
+ vocal_volume=float(vocal_volume),
104
+ instrumental_volume=float(instrumental_volume),
105
+ )
106
+
107
+ if not check_file(final_path, "Resultado final", logs):
108
+ return None, None, None, "\n".join(logs)
109
+
110
+ # 4. Cargar datos para retornar (Bypass ZeroGPU FS sync)
111
+ logs.append("📦 Cargando audios para salida...")
112
+
113
+ def safe_load(p):
114
+ data, sr = librosa.load(p, sr=None)
115
+ if data.size == 0:
116
+ logs.append(f"⚠️ Advertencia: El archivo {os.path.basename(p)} se cargó como un array VACÍO.")
117
+ return (sr, data)
118
+
119
+ v_out = safe_load(vocals_path)
120
+ c_out = safe_load(converted_path)
121
+ f_out = safe_load(final_path)
122
+
123
+ logs.append(" Pipeline completado con éxito.")
124
+ return v_out, c_out, f_out, "\n".join(logs)
125
+
126
+ except Exception as e:
127
+ import traceback
128
+ error_msg = f"💥 Error en Pipeline GPU: {str(e)}\n{traceback.format_exc()}"
129
+ logs.append(error_msg)
130
+ return None, None, None, "\n".join(logs)
131
+
132
+ def train_voice_model(audio_file, model_name, progress=gr.Progress()):
133
+ if audio_file is None: return "Error: Sube un audio.", None
134
+ if not model_name: return "Error: Ponle un nombre.", None
135
+
136
+ model_name = model_name.strip().replace(" ", "_")
137
+ try:
138
+ progress(0.1, desc="Guardando referencia...")
139
+ pth_path, ref_path = save_voice_reference(audio_path=audio_file, model_name=model_name)
140
+ return f"¡Perfil '{model_name}' guardado!", ref_path
141
+ except Exception as e:
142
+ return f"Error: {str(e)}", None
143
+
144
+ def convert_song(model_choice, song_file, pitch, similarity, diffusion_steps, vocal_volume, instrumental_volume, progress=gr.Progress()):
145
+ if not song_file: return "Error: Sube una canción.", None, None, None, "Esperando..."
146
+ if not model_choice or model_choice == "(ningún modelo)": return "Error: Elige un perfil.", None, None, None, "Esperando..."
147
+
148
+ try:
149
+ progress(0.1, desc="Preparando archivos...")
150
+ reference_path = get_reference_path(model_choice)
151
+ if not reference_path: return f"Error: No hay referencia para {model_choice}", None, None, None, "Error de modelo"
152
+
153
+ v_out, c_out, f_out, logs = _full_pipeline_gpu(
154
+ song_file, reference_path, pitch, diffusion_steps, similarity, vocal_volume, instrumental_volume
155
+ )
156
+
157
+ status = "✅ Completado" if f_out is not None else "❌ Falló"
158
+ return status, v_out, c_out, f_out, logs
159
+
160
+ except Exception as e:
161
+ import traceback
162
+ return f"Error: {str(e)}", None, None, None, traceback.format_exc()
163
+
164
+ # --- UI ---
165
+ with gr.Blocks(title="Voice Clone RVC/Seed-VC", theme=gr.themes.Soft()) as app:
166
+ gr.Markdown("# 🎤 Clonación de Voz Profesional (Seed-VC + ZeroGPU)")
167
+
168
+ with gr.Tabs():
169
+ with gr.TabItem("1. Crear Perfil"):
170
+ with gr.Row():
171
+ with gr.Column():
172
+ train_audio = gr.Audio(label="Tu voz (3-30 seg)", type="filepath")
173
+ train_name = gr.Textbox(label="Nombre del perfil", placeholder="ej: mi_voz")
174
+ train_btn = gr.Button("Guardar Referencia", variant="primary")
175
+ with gr.Column():
176
+ train_status = gr.Textbox(label="Estado", interactive=False)
177
+ train_file = gr.File(label="Archivo .pth")
178
+ train_btn.click(train_voice_model, [train_audio, train_name], [train_status, train_file])
179
+
180
+ with gr.TabItem("2. Convertir Canción"):
181
+ with gr.Row():
182
+ with gr.Column(scale=2):
183
+ model_sel = gr.Dropdown(choices=list_models() or ["(ningún modelo)"], label="Selecciona tu voz")
184
+ refresh_btn = gr.Button("🔄 Actualizar lista", size="sm")
185
+ song_input = gr.Audio(label="Canción original", type="filepath")
186
+
187
+ with gr.Accordion("Ajustes", open=False):
188
+ pitch_shift = gr.Slider(-12, 12, 0, step=1, label="Tono (Pitch)")
189
+ sim_slider = gr.Slider(0, 1, 0.7, step=0.1, label="Fidelidad")
190
+ diff_steps = gr.Slider(5, 50, 25, step=5, label="Pasos Difusión")
191
+ v_vol = gr.Slider(0, 2, 1, step=0.1, label="Volumen Voz")
192
+ i_vol = gr.Slider(0, 2, 1, step=0.1, label="Volumen Música")
193
+
194
+ convert_btn = gr.Button("🚀 Iniciar Proceso", variant="primary", size="lg")
195
+
196
+ with gr.Column(scale=3):
197
+ conv_status = gr.Textbox(label="Estado")
198
+ with gr.Row():
199
+ out_vocals = gr.Audio(label="Voz Original")
200
+ out_conv = gr.Audio(label="Voz Clonada")
201
+ out_final = gr.Audio(label="Resultado Final (Mix)")
202
+ debug_logs = gr.Textbox(label="🔍 Logs Detallados", lines=15)
203
+
204
+ refresh_btn.click(lambda: gr.Dropdown(choices=list_models()), outputs=model_sel)
205
+ convert_btn.click(convert_song,
206
+ [model_sel, song_input, pitch_shift, sim_slider, diff_steps, v_vol, i_vol],
207
+ [conv_status, out_vocals, out_conv, out_final, debug_logs])
208
+
209
+ with gr.TabItem("3. Gestión"):
210
+ models_list = gr.HTML(value="Cargando...")
211
+ del_btn = gr.Button("Eliminar Seleccionado", variant="stop")
212
+ app.load(lambda: f"Modelos: {', '.join(list_models())}", outputs=models_list)
213
+
214
+ if __name__ == "__main__":
215
+ setup_seed_vc()
216
+ app.launch()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
pipeline/inference.py CHANGED
@@ -434,7 +434,15 @@ def _convert_voice_core(audio_path, reference_path, pitch, diffusion_steps, simi
434
  processed_frames += vc_target.size(2) - overlap_frame_len
435
 
436
  # Concatenate and normalize to -18 dBFS RMS (standard vocal level before mixing)
437
- audio_out = np.concatenate(generated_wave_chunks)
 
 
 
 
 
 
 
 
438
  rms = np.sqrt(np.mean(audio_out ** 2))
439
  target_rms = 10 ** (-18.0 / 20.0) # -18 dBFS
440
  if rms > 1e-6:
@@ -444,5 +452,5 @@ def _convert_voice_core(audio_path, reference_path, pitch, diffusion_steps, simi
444
 
445
  # Save
446
  sf.write(output_path, audio_out, sr, subtype="PCM_16")
447
- logger.info("Conversion complete: {} ({:.1f}s)".format(output_path, len(audio_out) / sr))
448
  return output_path
 
434
  processed_frames += vc_target.size(2) - overlap_frame_len
435
 
436
  # Concatenate and normalize to -18 dBFS RMS (standard vocal level before mixing)
437
+ if not generated_wave_chunks:
438
+ logger.error("No audio chunks were generated by Seed-VC!")
439
+ # Create a tiny silence buffer to avoid crash but indicate failure
440
+ audio_out = np.zeros(sr)
441
+ else:
442
+ audio_out = np.concatenate(generated_wave_chunks)
443
+
444
+ logger.info(f"Concatenated {len(generated_wave_chunks)} chunks. Total samples: {len(audio_out)}")
445
+
446
  rms = np.sqrt(np.mean(audio_out ** 2))
447
  target_rms = 10 ** (-18.0 / 20.0) # -18 dBFS
448
  if rms > 1e-6:
 
452
 
453
  # Save
454
  sf.write(output_path, audio_out, sr, subtype="PCM_16")
455
+ logger.info("Conversion complete: {} ({:.1f}s, {} samples)".format(output_path, len(audio_out) / sr, len(audio_out)))
456
  return output_path
pipeline/separation.py CHANGED
@@ -91,9 +91,17 @@ def _separate_audio_impl(audio_path: str, model_name: str = "htdemucs_ft"):
91
  vocals_path = os.path.join(OUTPUT_DIR, f"{base_name}_vocals.wav")
92
  instruments_path = os.path.join(OUTPUT_DIR, f"{base_name}_instruments.wav")
93
 
 
 
 
 
94
  torchaudio.save(vocals_path, vocals, sr)
95
  torchaudio.save(instruments_path, instruments, sr)
96
 
 
 
 
 
97
  logger.info(f"Separation complete. Vocals: {vocals_path}, Instruments: {instruments_path}")
98
  return vocals_path, instruments_path
99
 
 
91
  vocals_path = os.path.join(OUTPUT_DIR, f"{base_name}_vocals.wav")
92
  instruments_path = os.path.join(OUTPUT_DIR, f"{base_name}_instruments.wav")
93
 
94
+ logger.info(f"Saving separated vocals to {vocals_path} (shape: {vocals.shape})")
95
+ if vocals.numel() == 0:
96
+ logger.error("Vocals tensor is EMPTY!")
97
+
98
  torchaudio.save(vocals_path, vocals, sr)
99
  torchaudio.save(instruments_path, instruments, sr)
100
 
101
+ # Cleanup GPU memory
102
+ del sources, model
103
+ torch.cuda.empty_cache()
104
+
105
  logger.info(f"Separation complete. Vocals: {vocals_path}, Instruments: {instruments_path}")
106
  return vocals_path, instruments_path
107