MySafeCode commited on
Commit
cc45c3f
·
verified ·
1 Parent(s): 3f8d06c

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +268 -142
app.py CHANGED
@@ -3,12 +3,18 @@ import requests
3
  import os
4
  import time
5
  import json
 
 
6
 
7
  # Suno API key
8
  SUNO_KEY = os.environ.get("SunoKey", "")
9
  if not SUNO_KEY:
10
  print("⚠️ SunoKey not set!")
11
 
 
 
 
 
12
  def get_audio_info(task_id):
13
  """Get audio information from Suno task ID"""
14
  if not SUNO_KEY:
@@ -83,30 +89,20 @@ def get_audio_info(task_id):
83
  except Exception as e:
84
  return f"❌ Error: {str(e)}", []
85
 
86
- def separate_vocals(task_id, audio_id, separation_type, audio_data=None):
87
- """Separate vocals and instruments from Suno tracks"""
88
- if not SUNO_KEY:
89
- yield "❌ Error: SunoKey not configured in environment variables"
90
- return
91
-
92
- if not task_id.strip() or not audio_id.strip():
93
- yield "❌ Error: Please enter both Task ID and Audio ID"
94
- return
95
-
96
- # Validate separation type
97
- if separation_type not in ["separate_vocal", "split_stem"]:
98
- yield "❌ Error: Invalid separation type"
99
- return
100
-
101
- # Submit separation task
102
  try:
 
 
 
 
103
  resp = requests.post(
104
  "https://api.sunoapi.org/api/v1/vocal-removal/generate",
105
  json={
106
  "taskId": task_id,
107
  "audioId": audio_id,
108
  "type": separation_type,
109
- "callBackUrl": "https://1hit.no/callback.php" # Required but not used for polling
110
  },
111
  headers={
112
  "Authorization": f"Bearer {SUNO_KEY}",
@@ -115,103 +111,236 @@ def separate_vocals(task_id, audio_id, separation_type, audio_data=None):
115
  timeout=30
116
  )
117
 
 
 
 
118
  if resp.status_code != 200:
119
- yield f"❌ Submission failed: HTTP {resp.status_code}"
120
- return
121
 
122
  data = resp.json()
123
- if data.get("code") != 200:
124
- yield f"❌ API error: {data.get('msg', 'Unknown')}"
125
- return
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
126
 
127
- separation_task_id = data["data"]["taskId"]
128
- yield f"✅ **Submitted!**\nSeparation Task ID: `{separation_task_id}`\n\n⏳ Processing separation...\n"
129
 
130
- # Poll for results
131
- for attempt in range(40): # 40 attempts * 5 seconds = 200 seconds max
132
- time.sleep(5)
 
 
 
 
133
 
134
- try:
135
- check = requests.get(
136
- "https://api.sunoapi.org/api/v1/vocal-removal/record-info",
137
- headers={"Authorization": f"Bearer {SUNO_KEY}"},
138
- params={"taskId": separation_task_id},
139
- timeout=30
140
- )
 
 
 
 
 
 
141
 
142
- if check.status_code == 200:
143
- check_data = check.json()
144
- status = check_data["data"].get("status", "PENDING")
145
-
146
- if status == "SUCCESS":
147
- # Success! Extract separation results
148
- separation_info = check_data["data"].get("vocal_removal_info", {})
149
-
150
- if not separation_info:
151
- yield "✅ Completed but no separation results found"
152
- return
153
-
154
- # Format output based on separation type
155
- output = "🎵 **Separation Complete!**\n\n"
156
-
157
- if separation_type == "separate_vocal":
158
- output += "## 2-Stem Separation\n\n"
159
- output += f"**Vocals:** [Download]({separation_info.get('vocal_url', 'N/A')})\n"
160
- output += f"**Instrumental:** [Download]({separation_info.get('instrumental_url', 'N/A')})\n"
161
- if separation_info.get('origin_url'):
162
- output += f"**Original:** [Download]({separation_info.get('origin_url')})\n"
163
-
164
- elif separation_type == "split_stem":
165
- output += "## 12-Stem Separation\n\n"
166
-
167
- stems = [
168
- ("Vocals", separation_info.get('vocal_url')),
169
- ("Backing Vocals", separation_info.get('backing_vocals_url')),
170
- ("Drums", separation_info.get('drums_url')),
171
- ("Bass", separation_info.get('bass_url')),
172
- ("Guitar", separation_info.get('guitar_url')),
173
- ("Keyboard", separation_info.get('keyboard_url')),
174
- ("Strings", separation_info.get('strings_url')),
175
- ("Brass", separation_info.get('brass_url')),
176
- ("Woodwinds", separation_info.get('woodwinds_url')),
177
- ("Percussion", separation_info.get('percussion_url')),
178
- ("Synth", separation_info.get('synth_url')),
179
- ("FX/Other", separation_info.get('fx_url')),
180
- ("Instrumental", separation_info.get('instrumental_url')),
181
- ("Original", separation_info.get('origin_url'))
182
- ]
183
-
184
- for stem_name, stem_url in stems:
185
- if stem_url:
186
- output += f"**{stem_name}:** [Download]({stem_url})\n"
187
-
188
- output += f"\n⏱️ Processed in about {(attempt + 1) * 5} seconds\n"
189
- output += f"⚠️ **Note:** Download links expire in 14 days"
190
-
191
- yield output
192
- return
193
-
194
- elif status == "FAILED":
195
- error = check_data["data"].get("errorMessage", "Unknown error")
196
- yield f"❌ Separation failed: {error}"
197
- return
198
 
199
- else:
200
- # PENDING or PROCESSING
201
- yield (f" Status: {status}\n"
202
- f"Attempt: {attempt + 1}/40\n"
203
- f"Separation Task ID: `{separation_task_id}`\n\n"
204
- f"Processing... (Usually takes 30-120 seconds)")
205
- else:
206
- yield f"⚠️ Check error: HTTP {check.status_code}"
207
 
208
- except Exception as e:
209
- yield f"⚠️ Error checking status: {str(e)}"
210
-
211
- yield "⏰ Timeout after 200 seconds. Try checking again later."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
212
 
213
- except Exception as e:
214
- yield f" Error: {str(e)}"
 
 
 
 
215
 
216
  # Create the app
217
  with gr.Blocks(title="Suno Stem Separator", theme="soft") as app:
@@ -222,7 +351,7 @@ with gr.Blocks(title="Suno Stem Separator", theme="soft") as app:
222
  with gr.Column(scale=1):
223
  # Step 1: Enter Task ID
224
  task_id_input = gr.Textbox(
225
- label="Task ID",
226
  placeholder="Example: 5c79****be8e",
227
  info="Enter your Suno generation task ID",
228
  elem_id="task_id_input"
@@ -239,9 +368,6 @@ with gr.Blocks(title="Suno Stem Separator", theme="soft") as app:
239
  interactive=True
240
  )
241
 
242
- # Hidden store for audio data
243
- audio_data_store = gr.State(value=None)
244
-
245
  separation_type = gr.Radio(
246
  label="Separation Type",
247
  choices=[
@@ -255,27 +381,32 @@ with gr.Blocks(title="Suno Stem Separator", theme="soft") as app:
255
 
256
  separate_btn = gr.Button("🎵 Separate Stems", variant="primary", visible=False)
257
 
258
- gr.Markdown("""**Workflow:**
259
- 1. Enter Task ID → Click "Find Audio Tracks"
260
- 2. Select which audio track to separate
261
- 3. Choose separation type
262
- 4. Click "Separate Stems"
263
- 5. Wait 30-120 seconds
264
- 6. Get download links for each stem
 
265
 
266
- **Separation types:**
267
- - 🎤 **separate_vocal**: Vocals + Instrumental (2 stems, 1 credit)
268
- - 🎛️ **split_stem**: 12 detailed stems (5 credits)
 
 
269
 
270
- ⚠️ **Important:**
271
- - Each request consumes credits
272
- - Links expire in 14 days
 
 
273
  """)
274
 
275
  with gr.Column(scale=2):
276
  status_output = gr.Markdown(
277
  label="Status",
278
- value="Enter a Task ID and click 'Find Audio Tracks'..."
279
  )
280
 
281
  results_output = gr.Markdown(
@@ -290,7 +421,7 @@ with gr.Blocks(title="Suno Stem Separator", theme="soft") as app:
290
  <p>Powered by <a href="https://suno.ai" target="_blank">Suno AI</a> •
291
  <a href="https://sunoapi.org" target="_blank">Suno API Docs</a> •
292
  <a href="https://docs.sunoapi.org/separate-vocals" target="_blank">Stem Separation Guide</a></p>
293
- <p><small>This app uses the Suno API to separate tracks into individual stems.</small></p>
294
  </div>
295
  """,
296
  elem_id="footer"
@@ -300,12 +431,11 @@ with gr.Blocks(title="Suno Stem Separator", theme="soft") as app:
300
  def on_get_audio(task_id):
301
  if not task_id:
302
  return (
303
- "❌ Please enter a Task ID",
304
  gr.Dropdown(choices=[], visible=False),
305
  gr.Radio(visible=False),
306
  gr.Button(visible=False),
307
- gr.Markdown(visible=False),
308
- None
309
  )
310
 
311
  output, audio_options = get_audio_info(task_id)
@@ -317,43 +447,39 @@ with gr.Blocks(title="Suno Stem Separator", theme="soft") as app:
317
  gr.Dropdown(choices=[], visible=False),
318
  gr.Radio(visible=False),
319
  gr.Button(visible=False),
320
- gr.Markdown(visible=False),
321
- None
322
  )
323
 
324
  # Create choices for dropdown
325
  choices = [(display_text, audio_id) for display_text, audio_id, _ in audio_options]
326
 
327
- # Store full audio data for later use
328
- audio_data_map = {audio_id: audio for _, audio_id, audio in audio_options}
329
-
330
  return (
331
  output,
332
  gr.Dropdown(choices=choices, value=choices[0][1] if choices else None, visible=True),
333
  gr.Radio(visible=True),
334
  gr.Button(visible=True),
335
- gr.Markdown(visible=False),
336
- audio_data_map
337
  )
338
 
339
  # Step 2: Separate stems when button is clicked
340
- def on_separate(task_id, audio_id, separation_type, audio_data_store):
341
- # Use the stored task_id from the first step
342
- yield from separate_vocals(task_id, audio_id, separation_type)
 
 
 
 
343
 
344
  # Connect events
345
  get_audio_btn.click(
346
  fn=on_get_audio,
347
  inputs=[task_id_input],
348
- outputs=[status_output, audio_dropdown, separation_type, separate_btn, results_output, audio_data_store]
349
  )
350
 
351
  separate_btn.click(
352
- fn=on_separate,
353
- inputs=[task_id_input, audio_dropdown, separation_type, audio_data_store],
354
- outputs=[results_output]
355
- ).then(
356
- lambda: gr.Markdown(visible=True),
357
  outputs=[results_output]
358
  )
359
 
 
3
  import os
4
  import time
5
  import json
6
+ import uuid
7
+ from datetime import datetime
8
 
9
  # Suno API key
10
  SUNO_KEY = os.environ.get("SunoKey", "")
11
  if not SUNO_KEY:
12
  print("⚠️ SunoKey not set!")
13
 
14
+ # Store ongoing separation tasks (in-memory for demo)
15
+ # In production, use a database or Redis
16
+ separation_tasks = {}
17
+
18
  def get_audio_info(task_id):
19
  """Get audio information from Suno task ID"""
20
  if not SUNO_KEY:
 
89
  except Exception as e:
90
  return f"❌ Error: {str(e)}", []
91
 
92
+ def submit_separation_task(task_id, audio_id, separation_type):
93
+ """Submit stem separation task and return task ID"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
94
  try:
95
+ # Generate a unique callback URL for this request
96
+ callback_id = str(uuid.uuid4())
97
+ callback_url = f"https://1hit.no/callback.php?callback_id={callback_id}"
98
+
99
  resp = requests.post(
100
  "https://api.sunoapi.org/api/v1/vocal-removal/generate",
101
  json={
102
  "taskId": task_id,
103
  "audioId": audio_id,
104
  "type": separation_type,
105
+ "callBackUrl": callback_url # Using your callback endpoint
106
  },
107
  headers={
108
  "Authorization": f"Bearer {SUNO_KEY}",
 
111
  timeout=30
112
  )
113
 
114
+ print(f"Separation request response: {resp.status_code}")
115
+ print(f"Response text: {resp.text}")
116
+
117
  if resp.status_code != 200:
118
+ return None, f"❌ Submission failed: HTTP {resp.status_code}"
 
119
 
120
  data = resp.json()
121
+ print(f"Parsed response: {data}")
122
+
123
+ # Check for different response formats
124
+ if data.get("code") == 200:
125
+ # New format with data object
126
+ separation_task_id = data.get("data", {}).get("taskId")
127
+ music_id = data.get("data", {}).get("musicId")
128
+
129
+ if separation_task_id:
130
+ # Store task info for polling
131
+ separation_tasks[separation_task_id] = {
132
+ "task_id": task_id,
133
+ "audio_id": audio_id,
134
+ "separation_type": separation_type,
135
+ "submitted_at": datetime.now().isoformat(),
136
+ "callback_id": callback_id,
137
+ "music_id": music_id
138
+ }
139
+
140
+ return separation_task_id, None
141
+ else:
142
+ return None, "❌ No taskId in response"
143
+
144
+ # Try alternative response format
145
+ elif "taskId" in data:
146
+ separation_task_id = data.get("taskId")
147
+ music_id = data.get("musicId")
148
+
149
+ separation_tasks[separation_task_id] = {
150
+ "task_id": task_id,
151
+ "audio_id": audio_id,
152
+ "separation_type": separation_type,
153
+ "submitted_at": datetime.now().isoformat(),
154
+ "callback_id": callback_id,
155
+ "music_id": music_id
156
+ }
157
+
158
+ return separation_task_id, None
159
+
160
+ else:
161
+ error_msg = data.get("msg", data.get("error", "Unknown error"))
162
+ return None, f"❌ API error: {error_msg}"
163
+
164
+ except Exception as e:
165
+ return None, f"❌ Error submitting task: {str(e)}"
166
+
167
+ def poll_separation_result(separation_task_id):
168
+ """Poll for separation results using the vocal-removal/record-info endpoint"""
169
+ try:
170
+ check = requests.get(
171
+ "https://api.sunoapi.org/api/v1/vocal-removal/record-info",
172
+ headers={"Authorization": f"Bearer {SUNO_KEY}"},
173
+ params={"taskId": separation_task_id},
174
+ timeout=30
175
+ )
176
+
177
+ if check.status_code == 200:
178
+ data = check.json()
179
+ print(f"Polling response: {data}")
180
+
181
+ if data.get("code") == 200:
182
+ status = data.get("data", {}).get("status", "PENDING")
183
+ return status, data.get("data", {})
184
+ else:
185
+ return "ERROR", {"error": data.get("msg", "Unknown error")}
186
+ else:
187
+ return "ERROR", {"error": f"HTTP {check.status_code}"}
188
+
189
+ except Exception as e:
190
+ return "ERROR", {"error": str(e)}
191
+
192
+ def separate_vocals(task_id, audio_id, separation_type):
193
+ """Separate vocals and instruments from Suno tracks"""
194
+ if not SUNO_KEY:
195
+ yield "❌ Error: SunoKey not configured in environment variables"
196
+ return
197
+
198
+ if not task_id.strip() or not audio_id.strip():
199
+ yield "❌ Error: Please enter both Task ID and Audio ID"
200
+ return
201
+
202
+ # Validate separation type
203
+ if separation_type not in ["separate_vocal", "split_stem"]:
204
+ yield "❌ Error: Invalid separation type"
205
+ return
206
+
207
+ # Step 1: Submit separation task
208
+ yield "⏳ **Submitting separation request...**\n"
209
+
210
+ separation_task_id, error = submit_separation_task(task_id, audio_id, separation_type)
211
+
212
+ if error:
213
+ yield error
214
+ return
215
+
216
+ if not separation_task_id:
217
+ yield "❌ Failed to get separation task ID"
218
+ return
219
+
220
+ # Store the separation task ID
221
+ separation_task_info = separation_tasks.get(separation_task_id, {})
222
+
223
+ yield f"✅ **Separation Task Submitted!**\n\n"
224
+ yield f"**Separation Task ID:** `{separation_task_id}`\n"
225
+ if separation_task_info.get("music_id"):
226
+ yield f"**Music ID:** `{separation_task_info.get('music_id')}`\n"
227
+ yield f"**Callback URL:** `{separation_task_info.get('callback_url', 'https://1hit.no/callback.php')}`\n\n"
228
+ yield "⏳ **Processing separation (this may take 1-3 minutes)...**\n"
229
+ yield "_Polling API for results..._\n"
230
+
231
+ # Step 2: Poll for results
232
+ for attempt in range(60): # 60 attempts * 5 seconds = 5 minutes max
233
+ time.sleep(5)
234
 
235
+ status, result_data = poll_separation_result(separation_task_id)
 
236
 
237
+ if status == "SUCCESS":
238
+ # Parse successful result
239
+ separation_info = result_data.get("vocal_removal_info", {})
240
+
241
+ if not separation_info:
242
+ # Try alternative field names
243
+ separation_info = result_data
244
 
245
+ if not separation_info:
246
+ yield "✅ Separation completed but no download links found in response\n"
247
+ yield f"Check your callback endpoint: {separation_task_info.get('callback_url')}"
248
+ return
249
+
250
+ # Format output based on separation type
251
+ output = "🎵 **Separation Complete!** 🎵\n\n"
252
+ output += f"**Separation Task ID:** `{separation_task_id}`\n"
253
+ output += f"**Original Task ID:** `{task_id}`\n"
254
+ output += f"**Audio ID:** `{audio_id}`\n\n"
255
+
256
+ if separation_type == "separate_vocal":
257
+ output += "## 🎤 2-Stem Separation Results\n\n"
258
 
259
+ # Try different possible field names
260
+ vocal_url = (separation_info.get('vocal_url') or
261
+ separation_info.get('vocal') or
262
+ separation_info.get('vocals_url'))
263
+ instrumental_url = (separation_info.get('instrumental_url') or
264
+ separation_info.get('instrumental') or
265
+ separation_info.get('accompaniment_url'))
266
+ origin_url = separation_info.get('origin_url') or separation_info.get('original_url')
267
+
268
+ if vocal_url:
269
+ output += f"**🎤 Vocals:** [Download MP3]({vocal_url})\n"
270
+ if instrumental_url:
271
+ output += f"**🎵 Instrumental:** [Download MP3]({instrumental_url})\n"
272
+ if origin_url:
273
+ output += f"**📁 Original:** [Download MP3]({origin_url})\n"
274
+
275
+ elif separation_type == "split_stem":
276
+ output += "## 🎛️ 12-Stem Separation Results\n\n"
277
+
278
+ # Map of possible stem field names
279
+ stem_fields = {
280
+ "Vocals": ["vocal_url", "vocals", "vocal", "vocals_url"],
281
+ "Backing Vocals": ["backing_vocals_url", "backing_vocals", "backing"],
282
+ "Drums": ["drums_url", "drums", "drum"],
283
+ "Bass": ["bass_url", "bass"],
284
+ "Guitar": ["guitar_url", "guitar"],
285
+ "Keyboard": ["keyboard_url", "keyboard", "piano"],
286
+ "Strings": ["strings_url", "strings"],
287
+ "Brass": ["brass_url", "brass"],
288
+ "Woodwinds": ["woodwinds_url", "woodwinds"],
289
+ "Percussion": ["percussion_url", "percussion"],
290
+ "Synth": ["synth_url", "synth", "synthesizer"],
291
+ "FX/Other": ["fx_url", "fx", "other", "effects"],
292
+ "Instrumental": ["instrumental_url", "instrumental", "accompaniment_url"],
293
+ "Original": ["origin_url", "original_url", "original"]
294
+ }
295
+
296
+ found_stems = 0
297
+ for stem_name, possible_fields in stem_fields.items():
298
+ url = None
299
+ for field in possible_fields:
300
+ if field in separation_info and separation_info[field]:
301
+ url = separation_info[field]
302
+ break
 
 
 
 
 
 
 
 
 
 
 
 
303
 
304
+ if url:
305
+ found_stems += 1
306
+ output += f"**{stem_name}:** [Download MP3]({url})\n"
307
+
308
+ if found_stems == 0:
309
+ # If no stems found, show raw data for debugging
310
+ output += "⚠️ **No stem URLs found in response. Raw data:**\n"
311
+ output += f"```json\n{json.dumps(separation_info, indent=2)}\n```\n"
312
 
313
+ output += f"\n⏱️ **Processing time:** {(attempt + 1) * 5} seconds\n"
314
+ output += "⚠️ **Note:** Download links may expire after some time\n"
315
+ output += f"📋 **Callback ID:** `{separation_task_info.get('callback_id', 'N/A')}`\n"
316
+
317
+ yield output
318
+ return
319
+
320
+ elif status == "FAILED":
321
+ error_msg = result_data.get("error", result_data.get("errorMessage", "Unknown error"))
322
+ yield f"❌ **Separation failed:** {error_msg}\n"
323
+ yield f"**Task ID:** `{separation_task_id}`\n"
324
+ return
325
+
326
+ elif status == "ERROR":
327
+ error_msg = result_data.get("error", "Unknown error")
328
+ yield f"⚠️ **Polling error:** {error_msg}\n"
329
+ yield f"**Task ID:** `{separation_task_id}`\n"
330
+ # Continue polling despite error
331
+ else:
332
+ # PENDING or PROCESSING
333
+ yield (f"⏳ **Status:** {status}\n"
334
+ f"**Attempt:** {attempt + 1}/60\n"
335
+ f"**Task ID:** `{separation_task_id}`\n\n"
336
+ f"_Still processing... (usually takes 1-3 minutes)_\n")
337
 
338
+ yield "⏰ **Timeout after 5 minutes.**\n"
339
+ yield f"The separation task is still processing.\n"
340
+ yield f"**Task ID:** `{separation_task_id}`\n"
341
+ yield f"**Music ID:** `{separation_task_info.get('music_id', 'N/A')}`\n"
342
+ yield "You can check the status later using this Task ID.\n"
343
+ yield f"Or check your callback endpoint: {separation_task_info.get('callback_url')}"
344
 
345
  # Create the app
346
  with gr.Blocks(title="Suno Stem Separator", theme="soft") as app:
 
351
  with gr.Column(scale=1):
352
  # Step 1: Enter Task ID
353
  task_id_input = gr.Textbox(
354
+ label="Original Task ID",
355
  placeholder="Example: 5c79****be8e",
356
  info="Enter your Suno generation task ID",
357
  elem_id="task_id_input"
 
368
  interactive=True
369
  )
370
 
 
 
 
371
  separation_type = gr.Radio(
372
  label="Separation Type",
373
  choices=[
 
381
 
382
  separate_btn = gr.Button("🎵 Separate Stems", variant="primary", visible=False)
383
 
384
+ gr.Markdown("""
385
+ ### 📋 Workflow:
386
+ 1. **Enter Task ID** Click "Find Audio Tracks"
387
+ 2. **Select audio track** from dropdown
388
+ 3. **Choose separation type**
389
+ 4. **Click Separate Stems**
390
+ 5. **Wait 1-3 minutes** for processing
391
+ 6. **Get download links** for each stem
392
 
393
+ ### 💡 Tips:
394
+ - Task IDs are from your Suno generation history
395
+ - Each separation consumes API credits
396
+ - Links may expire after some time
397
+ - Processing time varies based on audio length
398
 
399
+ ### 🔍 Check Status:
400
+ You'll receive a Separation Task ID that you can use to:
401
+ - Check status via API
402
+ - Receive callback on your endpoint
403
+ - Retry if needed
404
  """)
405
 
406
  with gr.Column(scale=2):
407
  status_output = gr.Markdown(
408
  label="Status",
409
+ value="### 👋 Welcome!\nEnter a Task ID above to get started..."
410
  )
411
 
412
  results_output = gr.Markdown(
 
421
  <p>Powered by <a href="https://suno.ai" target="_blank">Suno AI</a> •
422
  <a href="https://sunoapi.org" target="_blank">Suno API Docs</a> •
423
  <a href="https://docs.sunoapi.org/separate-vocals" target="_blank">Stem Separation Guide</a></p>
424
+ <p><small>Track separation powered by Suno API. Processing may take 1-3 minutes.</small></p>
425
  </div>
426
  """,
427
  elem_id="footer"
 
431
  def on_get_audio(task_id):
432
  if not task_id:
433
  return (
434
+ "❌ **Please enter a Task ID**",
435
  gr.Dropdown(choices=[], visible=False),
436
  gr.Radio(visible=False),
437
  gr.Button(visible=False),
438
+ gr.Markdown(visible=False)
 
439
  )
440
 
441
  output, audio_options = get_audio_info(task_id)
 
447
  gr.Dropdown(choices=[], visible=False),
448
  gr.Radio(visible=False),
449
  gr.Button(visible=False),
450
+ gr.Markdown(visible=False)
 
451
  )
452
 
453
  # Create choices for dropdown
454
  choices = [(display_text, audio_id) for display_text, audio_id, _ in audio_options]
455
 
 
 
 
456
  return (
457
  output,
458
  gr.Dropdown(choices=choices, value=choices[0][1] if choices else None, visible=True),
459
  gr.Radio(visible=True),
460
  gr.Button(visible=True),
461
+ gr.Markdown(visible=False)
 
462
  )
463
 
464
  # Step 2: Separate stems when button is clicked
465
+ def on_separate_click(task_id, audio_id, separation_type):
466
+ # Clear previous results
467
+ yield gr.Markdown(value="", visible=False)
468
+
469
+ # Start separation process
470
+ for update in separate_vocals(task_id, audio_id, separation_type):
471
+ yield gr.Markdown(value=update, visible=True)
472
 
473
  # Connect events
474
  get_audio_btn.click(
475
  fn=on_get_audio,
476
  inputs=[task_id_input],
477
+ outputs=[status_output, audio_dropdown, separation_type, separate_btn, results_output]
478
  )
479
 
480
  separate_btn.click(
481
+ fn=on_separate_click,
482
+ inputs=[task_id_input, audio_dropdown, separation_type],
 
 
 
483
  outputs=[results_output]
484
  )
485