gelpi01 commited on
Commit
c48e3b9
·
1 Parent(s): c55aa05

Implementación completa de audio_pipeline y app Gradio

Browse files
Files changed (2) hide show
  1. app.py +17 -8
  2. audio_pipeline.py +58 -95
app.py CHANGED
@@ -1,3 +1,5 @@
 
 
1
  import os
2
  import gradio as gr
3
  from audio_pipeline import (
@@ -8,22 +10,29 @@ from audio_pipeline import (
8
  )
9
 
10
  def procesar_wav(input_wav_path):
11
- separar_audio_demucs(input_wav_path, output_dir="separated")
12
- base_name = os.path.splitext(os.path.basename(input_wav_path))[0]
13
- stems_dir = os.path.join("separated", base_name)
 
14
  limpiar_stems(stems_dir)
 
 
15
  combinar_stems_sin_vocales(stems_dir)
16
- wav_base = os.path.join(stems_dir, "base_instrumental.wav")
17
- wav_clean = os.path.join(stems_dir, "base_instrumental_clean.wav")
18
- reducir_ruido(wav_base, wav_clean)
19
- return wav_clean
 
 
 
 
20
 
21
  demo = gr.Interface(
22
  fn=procesar_wav,
23
  inputs=gr.Audio(label="Sube un archivo .wav", type="filepath"),
24
  outputs=gr.Audio(label="Base instrumental limpia", type="filepath"),
25
  title="Procesador de WAV a Base Instrumental",
26
- description="Sube tu archivo WAV, separo stems, limpio vocales y reduzco ruido."
27
  )
28
 
29
  if __name__ == "__main__":
 
1
+ # app.py
2
+
3
  import os
4
  import gradio as gr
5
  from audio_pipeline import (
 
10
  )
11
 
12
  def procesar_wav(input_wav_path):
13
+ # 1) Separar con Demucs y obtener carpeta de stems
14
+ stems_dir = separar_audio_demucs(input_wav_path)
15
+
16
+ # 2) Limpiar stems (_cleaned.wav)
17
  limpiar_stems(stems_dir)
18
+
19
+ # 3) Combinar stems sin vocales en base_instrumental.wav
20
  combinar_stems_sin_vocales(stems_dir)
21
+
22
+ # 4) Reducir ruido sobre la base resultante
23
+ base_instru = os.path.join(stems_dir, "base_instrumental.wav")
24
+ clean_instru = os.path.join(stems_dir, "base_instrumental_clean.wav")
25
+ reducir_ruido(base_instru, clean_instru)
26
+
27
+ # 5) Devolver la ruta del WAV limpio
28
+ return clean_instru
29
 
30
  demo = gr.Interface(
31
  fn=procesar_wav,
32
  inputs=gr.Audio(label="Sube un archivo .wav", type="filepath"),
33
  outputs=gr.Audio(label="Base instrumental limpia", type="filepath"),
34
  title="Procesador de WAV a Base Instrumental",
35
+ description="Sube tu WAV y obtén su base limpia sin vocales."
36
  )
37
 
38
  if __name__ == "__main__":
audio_pipeline.py CHANGED
@@ -1,124 +1,87 @@
1
- import yt_dlp
 
 
2
  import subprocess
3
  import sys
4
- import os
5
  import librosa
6
  import numpy as np
7
  import soundfile as sf
8
  import noisereduce as nr
9
- import scipy.signal as signal
10
 
11
- # ###########################################
12
- # Funciones de Filtros y Efectos
13
- # ###########################################
14
 
15
  def highpass_filter(audio, sr, cutoff=80, order=4):
16
  """Aplica un filtro Butterworth de paso alto al audio."""
17
  sos = signal.butter(order, cutoff, btype='highpass', fs=sr, output='sos')
18
- filtered = signal.sosfilt(sos, audio)
19
- return filtered
20
-
21
- # ###########################################
22
- # Etapa 1: Descarga y Separación con Demucs
23
- # ###########################################
24
-
25
- def descargar_audio(url, output_filename='audio'):
26
- """Descarga el audio de YouTube y lo convierte a WAV."""
27
- opciones = {
28
- 'format': 'bestaudio/best',
29
- 'outtmpl': f'{output_filename}.%(ext)s',
30
- 'postprocessors': [{
31
- 'key': 'FFmpegExtractAudio',
32
- 'preferredcodec': 'wav',
33
- }],
34
- }
35
- with yt_dlp.YoutubeDL(opciones) as ydl:
36
- ydl.download([url])
37
-
38
-
39
- def separar_audio_demucs(input_file, output_dir="separated"):
40
- """Separa stems usando Demucs."""
41
- comando = [
42
- sys.executable, '-m', 'demucs',
43
  input_file,
44
- '--out', output_dir
 
45
  ]
46
- try:
47
- subprocess.run(comando, check=True)
48
- print("Separación con Demucs completada.")
49
- except subprocess.CalledProcessError as e:
50
- print(f"Error durante la separación con Demucs: {e}")
51
- sys.exit(1)
52
 
53
- # ###########################################
54
- # Función para limpiar cada stem (reducción de ruido, etc.)
55
- # ###########################################
 
 
 
56
 
57
  def limpiar_stems(stems_dir):
58
- """Aplica reducción de ruido a cada stem en stems_dir."""
59
  for archivo in os.listdir(stems_dir):
60
- if archivo.endswith('.wav'):
61
- file_path = os.path.join(stems_dir, archivo)
62
- y, sr = librosa.load(file_path, sr=None)
63
  reduced = nr.reduce_noise(y=y, sr=sr)
64
- sf.write(file_path.replace('.wav', '_cleaned.wav'), reduced, sr)
65
-
66
- # ###########################################
67
- # Combina stems excluyendo la parte vocal
68
- # ###########################################
69
 
70
  def combinar_stems_sin_vocales(stems_dir):
71
- """Mezcla todos los stems excepto los que contienen vocales."""
72
- archivos = [f for f in os.listdir(stems_dir) if f.endswith('.wav')]
73
- archivos_clean = [f for f in archivos if 'cleaned' in f.lower() and 'vocal' not in f.lower()]
74
- if archivos_clean:
75
- lista_archivos = archivos_clean
76
- else:
77
- lista_archivos = [f for f in archivos if 'vocal' not in f.lower()]
 
 
 
 
 
 
 
 
78
  signals = []
79
- for archivo in lista_archivos:
80
- file_path = os.path.join(stems_dir, archivo)
81
- print(f"Incluyendo: {archivo}")
82
- y, sr = librosa.load(file_path, sr=None)
83
  signals.append(y)
84
  if not signals:
85
- print("No se encontraron stems para combinar (excluyendo vocales).")
86
- return
87
- max_len = max(len(s) for s in signals)
88
- mezclado = sum(np.pad(s, (0, max_len - len(s))) for s in signals) / len(signals)
89
- sf.write(os.path.join(stems_dir, 'base_instrumental.wav'), mezclado, sr)
90
-
91
- # ###########################################
92
- # Reducción de ruido en archivo de audio
93
- # ###########################################
94
 
95
  def reducir_ruido(input_file, output_file, noise_duration=0.5):
96
  """Aplica reducción de ruido basada en los primeros segundos de audio."""
97
  y, sr = librosa.load(input_file, sr=None)
98
- noise = y[:int(noise_duration * sr)]
99
  reduced = nr.reduce_noise(y=y, sr=sr, y_noise=noise)
100
  sf.write(output_file, reduced, sr)
101
-
102
- # ###########################################
103
- # Función principal
104
- # ###########################################
105
-
106
- def main():
107
- url = input("Introduce la URL de YouTube: ")
108
- output_name = input("Nombre base para archivos (sin extensión): ")
109
- descargar_audio(url, output_filename=output_name)
110
- audio_file = f"{output_name}.wav"
111
- separar_audio_demucs(audio_file, output_dir="separated")
112
- stems_dir = os.path.join("separated", output_name)
113
- limpiar_stems(stems_dir)
114
-
115
- output_base = os.path.join(stems_dir, 'base_instrumental.wav')
116
- print("Combinando stems 'cleaned' para generar la base instrumental...")
117
- combinar_stems_sin_vocales(stems_dir)
118
-
119
- output_clean = os.path.join(stems_dir, 'base_instrumental_clean.wav')
120
- print("Aplicando reducción de ruido...")
121
- reducir_ruido(output_base, output_clean, noise_duration=0.5)
122
-
123
- # Se omite la etapa de mastering para mantener la calidad original de la mezcla
124
- print("Proceso completado. Revisa el archivo 'base_instrumental_clean.wav' para escuchar el resultado final.")
 
1
+ # audio_pipeline.py
2
+
3
+ import os
4
  import subprocess
5
  import sys
6
+ import torch
7
  import librosa
8
  import numpy as np
9
  import soundfile as sf
10
  import noisereduce as nr
 
11
 
12
+ # Directorio base donde guardaremos todos los stems
13
+ BASE_STEMS_DIR = "data/stems"
 
14
 
15
  def highpass_filter(audio, sr, cutoff=80, order=4):
16
  """Aplica un filtro Butterworth de paso alto al audio."""
17
  sos = signal.butter(order, cutoff, btype='highpass', fs=sr, output='sos')
18
+ return signal.sosfilt(sos, audio)
19
+
20
+ def separar_audio_demucs(input_file, model="htdemucs"):
21
+ """
22
+ Separa stems usando Demucs, los deja en:
23
+ data/stems/<modelo>/<base_name>/
24
+ y devuelve la ruta al folder con los stems.
25
+ """
26
+ # 1) Crear directorio raíz para ese modelo
27
+ out_root = os.path.join(BASE_STEMS_DIR, model)
28
+ os.makedirs(out_root, exist_ok=True)
29
+
30
+ # 2) Ejecutar Demucs (en GPU si está disponible)
31
+ device = "cuda" if torch.cuda.is_available() else "cpu"
32
+ cmd = [
33
+ sys.executable, "-m", "demucs",
 
 
 
 
 
 
 
 
 
34
  input_file,
35
+ "--out", out_root,
36
+ "--device", device
37
  ]
38
+ subprocess.run(cmd, check=True)
 
 
 
 
 
39
 
40
+ # 3) Detectar carpeta generada con los stems
41
+ base = os.path.splitext(os.path.basename(input_file))[0]
42
+ stems_dir = os.path.join(out_root, base)
43
+ if not os.path.isdir(stems_dir):
44
+ raise FileNotFoundError(f"No existe el folder de stems en {stems_dir}")
45
+ return stems_dir
46
 
47
  def limpiar_stems(stems_dir):
48
+ """Aplica reducción de ruido a cada stem dentro de stems_dir."""
49
  for archivo in os.listdir(stems_dir):
50
+ if archivo.endswith(".wav"):
51
+ ruta = os.path.join(stems_dir, archivo)
52
+ y, sr = librosa.load(ruta, sr=None)
53
  reduced = nr.reduce_noise(y=y, sr=sr)
54
+ sf.write(ruta.replace(".wav", "_cleaned.wav"), reduced, sr)
 
 
 
 
55
 
56
  def combinar_stems_sin_vocales(stems_dir):
57
+ """Mezcla todos los stems (limpios) excepto los de vocales."""
58
+ # Primero, buscar stems *_cleaned.wav que no contengan 'vocal'
59
+ wavs = [
60
+ f for f in os.listdir(stems_dir)
61
+ if f.endswith(".wav") and
62
+ "cleaned" in f.lower() and
63
+ "vocal" not in f.lower()
64
+ ]
65
+ # Si no hay cleaned, fallback a stems originales sin 'vocal'
66
+ if not wavs:
67
+ wavs = [
68
+ f for f in os.listdir(stems_dir)
69
+ if f.endswith(".wav") and "vocal" not in f.lower()
70
+ ]
71
+
72
  signals = []
73
+ for w in wavs:
74
+ y, sr = librosa.load(os.path.join(stems_dir, w), sr=None)
 
 
75
  signals.append(y)
76
  if not signals:
77
+ raise RuntimeError("No se encontraron stems para combinar.")
78
+ maxlen = max(len(s) for s in signals)
79
+ mix = sum(np.pad(s, (0, maxlen - len(s))) for s in signals) / len(signals)
80
+ sf.write(os.path.join(stems_dir, "base_instrumental.wav"), mix, sr)
 
 
 
 
 
81
 
82
  def reducir_ruido(input_file, output_file, noise_duration=0.5):
83
  """Aplica reducción de ruido basada en los primeros segundos de audio."""
84
  y, sr = librosa.load(input_file, sr=None)
85
+ noise = y[:int(sr * noise_duration)]
86
  reduced = nr.reduce_noise(y=y, sr=sr, y_noise=noise)
87
  sf.write(output_file, reduced, sr)