PlayerBPlaytime commited on
Commit
63eb7aa
Β·
verified Β·
1 Parent(s): 92e4143

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +38 -50
app.py CHANGED
@@ -9,7 +9,7 @@ from pydub import AudioSegment
9
  import io
10
 
11
  def convert_to_wav(input_path):
12
- """Convierte cualquier formato a WAV usando pydub/ffmpeg"""
13
  ext = Path(input_path).suffix.lower()
14
 
15
  format_map = {
@@ -34,7 +34,7 @@ def convert_to_wav(input_path):
34
  return temp_wav
35
 
36
  def get_channel_name(index, total_channels):
37
- """Asigna nombres a los canales segΓΊn la configuraciΓ³n Atmos/Surround"""
38
 
39
  channel_maps = {
40
  1: ["Mono"],
@@ -74,28 +74,27 @@ def get_channel_name(index, total_channels):
74
  return f"Channel_{index + 1}"
75
 
76
  def extract_stems(audio_file, output_format, normalize):
77
- """Extrae todos los stems/canales de un archivo de audio multicanal"""
78
 
79
  if audio_file is None:
80
- return None, "❌ Por favor, sube un archivo de audio"
81
 
82
  converted_wav = None
83
 
84
  try:
85
- # ── 1. Convertir a WAV si hace falta ──────────────────────────
86
  ext = Path(audio_file).suffix.lower()
87
 
88
  if ext not in [".wav", ".flac", ".aiff", ".aif"]:
89
- info_text = "⏳ Convirtiendo formato... por favor espera\n\n"
90
  converted_wav = convert_to_wav(audio_file)
91
  read_path = converted_wav
92
  else:
93
  read_path = audio_file
94
 
95
- # ── 2. Leer el archivo ────────────────────────────────────────
96
  audio_data, sample_rate = sf.read(read_path)
97
 
98
- # Asegurar que sea 2D
99
  if len(audio_data.shape) == 1:
100
  num_channels = 1
101
  audio_data = audio_data.reshape(-1, 1)
@@ -105,21 +104,19 @@ def extract_stems(audio_file, output_format, normalize):
105
  duration = len(audio_data) / sample_rate
106
  file_name = Path(audio_file).stem
107
 
108
- # ── 3. Info del archivo ───────────────────────────────────────
109
- info_text = f"""## πŸ“Š InformaciΓ³n del archivo
110
-
111
- | Campo | Valor |
112
  |-------|-------|
113
- | **Nombre** | {Path(audio_file).name} |
114
- | **Canales detectados** | {num_channels} |
115
  | **Sample Rate** | {sample_rate} Hz |
116
- | **DuraciΓ³n** | {int(duration//60)}:{int(duration%60):02d} min |
117
- | **Bits** | {audio_data.dtype} |
118
- | **Formato salida** | {output_format.upper()} |
119
-
120
- ## 🎚️ Stems extraídos:\n\n"""
121
 
122
- # ── 4. Extraer cada canal ─────────────────────────────────────
123
  temp_dir = tempfile.mkdtemp()
124
  stem_files = []
125
 
@@ -127,13 +124,11 @@ def extract_stems(audio_file, output_format, normalize):
127
  channel_name = get_channel_name(i, num_channels)
128
  channel_data = audio_data[:, i].copy().astype(np.float32)
129
 
130
- # Normalizar si se pide
131
  if normalize:
132
  max_val = np.max(np.abs(channel_data))
133
  if max_val > 0:
134
  channel_data = channel_data / max_val * 0.95
135
 
136
- # Calcular volumen RMS del canal
137
  rms = np.sqrt(np.mean(channel_data**2))
138
  rms_db = 20 * np.log10(rms + 1e-10)
139
 
@@ -143,7 +138,6 @@ def extract_stems(audio_file, output_format, normalize):
143
  sf.write(stem_path, channel_data, sample_rate)
144
  stem_files.append(stem_path)
145
 
146
- # Emoji segΓΊn tipo de canal
147
  emoji = "πŸ”Š"
148
  if "LFE" in channel_name or "Sub" in channel_name:
149
  emoji = "πŸ’₯"
@@ -160,7 +154,7 @@ def extract_stems(audio_file, output_format, normalize):
160
 
161
  info_text += f"{emoji} **{channel_name}** β†’ `{stem_filename}` | RMS: `{rms_db:.1f} dBFS`\n\n"
162
 
163
- # ── 5. Crear ZIP ──────────────────────────────────────────────
164
  zip_filename = f"{file_name}_stems.zip"
165
  zip_path = os.path.join(temp_dir, zip_filename)
166
 
@@ -169,7 +163,7 @@ def extract_stems(audio_file, output_format, normalize):
169
  zipf.write(stem_file, os.path.basename(stem_file))
170
 
171
  zip_size = os.path.getsize(zip_path) / (1024 * 1024)
172
- info_text += f"\n---\n## πŸ“¦ ZIP listo\n`{zip_filename}` β€” **{zip_size:.1f} MB** con {num_channels} stems"
173
 
174
  return zip_path, info_text
175
 
@@ -178,13 +172,12 @@ def extract_stems(audio_file, output_format, normalize):
178
  return None, f"❌ Error: {str(e)}\n\n```\n{traceback.format_exc()}\n```"
179
 
180
  finally:
181
- # Limpiar WAV temporal si se creΓ³
182
  if converted_wav and os.path.exists(converted_wav):
183
  os.remove(converted_wav)
184
 
185
 
186
  def create_demo_51():
187
- """Crea un archivo demo 5.1 Surround"""
188
  temp_dir = tempfile.mkdtemp()
189
  demo_path = os.path.join(temp_dir, "demo_5.1_surround.wav")
190
 
@@ -192,19 +185,18 @@ def create_demo_51():
192
  t = np.linspace(0, 4, sr * 4)
193
 
194
  channels = [
195
- np.sin(2 * np.pi * 440 * t) * 0.6, # L
196
- np.sin(2 * np.pi * 554 * t) * 0.6, # R
197
- np.sin(2 * np.pi * 330 * t) * 0.7, # C
198
- np.sin(2 * np.pi * 55 * t) * 0.9, # LFE
199
- np.sin(2 * np.pi * 392 * t) * 0.4, # Ls
200
- np.sin(2 * np.pi * 494 * t) * 0.4, # Rs
201
  ]
202
 
203
  sf.write(demo_path, np.column_stack(channels).astype(np.float32), sr)
204
  return demo_path
205
 
206
 
207
- # ── UI ────────────────────────────────────────────────────────────────────────
208
  with gr.Blocks(
209
  title="🎡 Atmos Stem Extractor",
210
  theme=gr.themes.Soft(primary_hue="purple", secondary_hue="blue"),
@@ -216,14 +208,13 @@ with gr.Blocks(
216
 
217
  gr.Markdown("""
218
  # 🎡 Dolby Atmos Β· Stem Extractor
219
- **Extrae cada canal de tus archivos multicanal** β€” Atmos, 5.1, 7.1, 7.1.4 y mΓ‘s
220
  """)
221
 
222
  with gr.Row():
223
- # ── Columna izquierda ─────────────────────────────────────────
224
  with gr.Column(scale=1):
225
  audio_input = gr.File(
226
- label="πŸ“ Sube tu archivo de audio",
227
  file_types=[".wav", ".flac", ".aiff", ".aif",
228
  ".m4a", ".mp3", ".aac", ".ogg",
229
  ".mp4", ".wma"],
@@ -234,26 +225,25 @@ with gr.Blocks(
234
  output_format = gr.Radio(
235
  choices=["wav", "flac", "ogg"],
236
  value="wav",
237
- label="🎚️ Formato de salida"
238
  )
239
  normalize = gr.Checkbox(
240
  value=False,
241
- label="πŸ“Ά Normalizar canales"
242
  )
243
 
244
- extract_btn = gr.Button("πŸš€ Extraer Stems", variant="primary", size="lg")
245
- demo_btn = gr.Button("🎹 Generar demo 5.1", variant="secondary")
246
 
247
  gr.Markdown("""
248
- ### πŸ“‹ Formatos soportados
249
- | Entrada | Salida |
250
  |---------|--------|
251
  | WAV, FLAC, AIFF | WAV |
252
  | **M4A, MP3, AAC** | FLAC |
253
  | OGG, MP4, WMA | OGG |
254
-
255
- ### πŸŽ›οΈ Configuraciones detectadas
256
- | Config | Canales |
257
  |--------|---------|
258
  | Stereo | 2 |
259
  | 5.1 Surround | 6 |
@@ -262,12 +252,10 @@ with gr.Blocks(
262
  | 9.1.6 Atmos | 16 |
263
  """)
264
 
265
- # ── Columna derecha ───────────────────────────────────────────
266
  with gr.Column(scale=1):
267
- output_file = gr.File(label="πŸ“¦ Descargar ZIP con todos los stems")
268
- info_output = gr.Markdown(value="*Sube un archivo para comenzar...*")
269
 
270
- # ── Eventos ───────────────────────────────────────────────────────
271
  extract_btn.click(
272
  fn=extract_stems,
273
  inputs=[audio_input, output_format, normalize],
 
9
  import io
10
 
11
  def convert_to_wav(input_path):
12
+ """Converts any format to WAV using pydub/ffmpeg"""
13
  ext = Path(input_path).suffix.lower()
14
 
15
  format_map = {
 
34
  return temp_wav
35
 
36
  def get_channel_name(index, total_channels):
37
+ """Assigns names to channels based on Atmos/Surround configuration"""
38
 
39
  channel_maps = {
40
  1: ["Mono"],
 
74
  return f"Channel_{index + 1}"
75
 
76
  def extract_stems(audio_file, output_format, normalize):
77
+ """Extracts all stems/channels from a multichannel audio file"""
78
 
79
  if audio_file is None:
80
+ return None, "❌ Please upload an audio file"
81
 
82
  converted_wav = None
83
 
84
  try:
85
+ # ── 1. Convert to WAV if needed ──────────────────────────
86
  ext = Path(audio_file).suffix.lower()
87
 
88
  if ext not in [".wav", ".flac", ".aiff", ".aif"]:
89
+ info_text = "⏳ Converting format... please wait\n\n"
90
  converted_wav = convert_to_wav(audio_file)
91
  read_path = converted_wav
92
  else:
93
  read_path = audio_file
94
 
95
+ # ── 2. Read the file ────────────────────────────────────────
96
  audio_data, sample_rate = sf.read(read_path)
97
 
 
98
  if len(audio_data.shape) == 1:
99
  num_channels = 1
100
  audio_data = audio_data.reshape(-1, 1)
 
104
  duration = len(audio_data) / sample_rate
105
  file_name = Path(audio_file).stem
106
 
107
+ # ── 3. File info ───────────────────────────────────────
108
+ info_text = f"""## πŸ“Š File Information
109
+ | Field | Value |
 
110
  |-------|-------|
111
+ | **Name** | {Path(audio_file).name} |
112
+ | **Detected Channels** | {num_channels} |
113
  | **Sample Rate** | {sample_rate} Hz |
114
+ | **Duration** | {int(duration//60)}:{int(duration%60):02d} min |
115
+ | **Bit Depth** | {audio_data.dtype} |
116
+ | **Output Format** | {output_format.upper()} |
117
+ ## 🎚️ Extracted Stems:\n\n"""
 
118
 
119
+ # ── 4. Extract each channel ─────────────────────────────────────
120
  temp_dir = tempfile.mkdtemp()
121
  stem_files = []
122
 
 
124
  channel_name = get_channel_name(i, num_channels)
125
  channel_data = audio_data[:, i].copy().astype(np.float32)
126
 
 
127
  if normalize:
128
  max_val = np.max(np.abs(channel_data))
129
  if max_val > 0:
130
  channel_data = channel_data / max_val * 0.95
131
 
 
132
  rms = np.sqrt(np.mean(channel_data**2))
133
  rms_db = 20 * np.log10(rms + 1e-10)
134
 
 
138
  sf.write(stem_path, channel_data, sample_rate)
139
  stem_files.append(stem_path)
140
 
 
141
  emoji = "πŸ”Š"
142
  if "LFE" in channel_name or "Sub" in channel_name:
143
  emoji = "πŸ’₯"
 
154
 
155
  info_text += f"{emoji} **{channel_name}** β†’ `{stem_filename}` | RMS: `{rms_db:.1f} dBFS`\n\n"
156
 
157
+ # ── 5. Create ZIP ──────────────────────────────────────────────
158
  zip_filename = f"{file_name}_stems.zip"
159
  zip_path = os.path.join(temp_dir, zip_filename)
160
 
 
163
  zipf.write(stem_file, os.path.basename(stem_file))
164
 
165
  zip_size = os.path.getsize(zip_path) / (1024 * 1024)
166
+ info_text += f"\n---\n## πŸ“¦ ZIP Ready\n`{zip_filename}` β€” **{zip_size:.1f} MB** with {num_channels} stems"
167
 
168
  return zip_path, info_text
169
 
 
172
  return None, f"❌ Error: {str(e)}\n\n```\n{traceback.format_exc()}\n```"
173
 
174
  finally:
 
175
  if converted_wav and os.path.exists(converted_wav):
176
  os.remove(converted_wav)
177
 
178
 
179
  def create_demo_51():
180
+ """Creates a 5.1 Surround demo file"""
181
  temp_dir = tempfile.mkdtemp()
182
  demo_path = os.path.join(temp_dir, "demo_5.1_surround.wav")
183
 
 
185
  t = np.linspace(0, 4, sr * 4)
186
 
187
  channels = [
188
+ np.sin(2 * np.pi * 440 * t) * 0.6,
189
+ np.sin(2 * np.pi * 554 * t) * 0.6,
190
+ np.sin(2 * np.pi * 330 * t) * 0.7,
191
+ np.sin(2 * np.pi * 55 * t) * 0.9,
192
+ np.sin(2 * np.pi * 392 * t) * 0.4,
193
+ np.sin(2 * np.pi * 494 * t) * 0.4,
194
  ]
195
 
196
  sf.write(demo_path, np.column_stack(channels).astype(np.float32), sr)
197
  return demo_path
198
 
199
 
 
200
  with gr.Blocks(
201
  title="🎡 Atmos Stem Extractor",
202
  theme=gr.themes.Soft(primary_hue="purple", secondary_hue="blue"),
 
208
 
209
  gr.Markdown("""
210
  # 🎡 Dolby Atmos Β· Stem Extractor
211
+ **Extract each channel from your multichannel files** β€” Atmos, 5.1, 7.1, 7.1.4 and more
212
  """)
213
 
214
  with gr.Row():
 
215
  with gr.Column(scale=1):
216
  audio_input = gr.File(
217
+ label="πŸ“ Upload your audio file",
218
  file_types=[".wav", ".flac", ".aiff", ".aif",
219
  ".m4a", ".mp3", ".aac", ".ogg",
220
  ".mp4", ".wma"],
 
225
  output_format = gr.Radio(
226
  choices=["wav", "flac", "ogg"],
227
  value="wav",
228
+ label="🎚️ Output Format"
229
  )
230
  normalize = gr.Checkbox(
231
  value=False,
232
+ label="πŸ“Ά Normalize Channels"
233
  )
234
 
235
+ extract_btn = gr.Button("πŸš€ Extract Stems", variant="primary", size="lg")
236
+ demo_btn = gr.Button("🎹 Generate 5.1 Demo", variant="secondary")
237
 
238
  gr.Markdown("""
239
+ ### πŸ“‹ Supported Formats
240
+ | Input | Output |
241
  |---------|--------|
242
  | WAV, FLAC, AIFF | WAV |
243
  | **M4A, MP3, AAC** | FLAC |
244
  | OGG, MP4, WMA | OGG |
245
+ ### πŸŽ›οΈ Detected Configurations
246
+ | Config | Channels |
 
247
  |--------|---------|
248
  | Stereo | 2 |
249
  | 5.1 Surround | 6 |
 
252
  | 9.1.6 Atmos | 16 |
253
  """)
254
 
 
255
  with gr.Column(scale=1):
256
+ output_file = gr.File(label="πŸ“¦ Download ZIP with all stems")
257
+ info_output = gr.Markdown(value="*Upload a file to get started...*")
258
 
 
259
  extract_btn.click(
260
  fn=extract_stems,
261
  inputs=[audio_input, output_format, normalize],