MySafeCode commited on
Commit
5afb0f7
·
verified ·
1 Parent(s): 1ff9b8a

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +300 -142
app.py CHANGED
@@ -9,8 +9,64 @@ SUNO_KEY = os.environ.get("SunoKey", "")
9
  if not SUNO_KEY:
10
  print("⚠️ SunoKey not set!")
11
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
  def submit_separation_task(task_id, audio_id, separation_type):
13
- """Submit stem separation task - SIMPLE VERSION"""
14
  try:
15
  resp = requests.post(
16
  "https://api.sunoapi.org/api/v1/vocal-removal/generate",
@@ -30,239 +86,341 @@ def submit_separation_task(task_id, audio_id, separation_type):
30
  if resp.status_code == 200:
31
  data = resp.json()
32
 
33
- # Try different response formats
 
34
  if "taskId" in data:
35
- return data["taskId"], None # New format
36
  elif data.get("code") == 200 and "data" in data and "taskId" in data["data"]:
37
- return data["data"]["taskId"], None # Nested format
 
 
 
 
 
 
 
 
 
38
  else:
39
- return None, f"Unexpected response format: {data}"
40
  else:
41
- return None, f"HTTP Error: {resp.status_code}"
42
 
43
  except Exception as e:
44
- return None, f"Error: {str(e)}"
45
 
46
- def poll_task_status(task_id):
47
- """Poll separation task status - SIMPLE VERSION"""
48
  try:
49
  resp = requests.get(
50
  "https://api.sunoapi.org/api/v1/vocal-removal/record-info",
51
  headers={"Authorization": f"Bearer {SUNO_KEY}"},
52
- params={"taskId": task_id},
53
  timeout=30
54
  )
55
 
56
  if resp.status_code == 200:
57
  data = resp.json()
58
 
59
- # Simple status extraction
60
  status = "UNKNOWN"
61
  if "status" in data:
62
  status = data["status"]
63
  elif "data" in data and "status" in data["data"]:
64
  status = data["data"]["status"]
65
  elif data.get("code") == 200:
66
- status = "SUCCESS" # Assume success if code is 200
67
 
68
- # Simple result extraction
69
  results = {}
70
  if "vocal_removal_info" in data:
71
  results = data["vocal_removal_info"]
72
  elif "data" in data and "vocal_removal_info" in data["data"]:
73
  results = data["data"]["vocal_removal_info"]
74
- elif data.get("code") == 200 and "data" in data:
75
- # Maybe results are directly in data
76
- results = {k: v for k, v in data["data"].items() if k.endswith("_url")}
77
 
78
  return status, results, None
79
  else:
80
- return "ERROR", {}, f"HTTP {resp.status_code}"
81
 
82
  except Exception as e:
83
  return "ERROR", {}, str(e)
84
 
85
- def format_results(results):
86
- """Format results as simple markdown"""
87
  if not results:
88
- return "No results found"
89
 
90
  output = "## 🎵 Download Links\n\n"
91
- for key, url in results.items():
92
- if url and "url" in key.lower():
93
- stem_name = key.replace("_url", "").replace("_", " ").title()
94
- output += f"**{stem_name}:** [Download]({url})\n"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
95
 
96
  return output
97
 
98
  # Create the app
99
  with gr.Blocks(title="Suno Stem Separator", theme="soft") as app:
100
  gr.Markdown("# 🎵 Suno Stem Separator")
101
- gr.Markdown("Separate Suno tracks into stems")
102
 
103
- with gr.Tab("Submit New Task"):
104
- with gr.Row():
105
- with gr.Column():
106
- task_id_input = gr.Textbox(
 
 
 
107
  label="Original Task ID",
108
- placeholder="From your Suno generation"
 
109
  )
110
- audio_id_input = gr.Textbox(
111
- label="Audio ID",
112
- placeholder="From your Suno generation"
 
 
 
 
 
 
 
 
113
  )
 
 
 
 
114
  separation_type = gr.Radio(
115
- choices=["separate_vocal", "split_stem"],
116
  label="Separation Type",
117
- value="separate_vocal"
 
 
 
 
 
118
  )
119
- submit_btn = gr.Button("Submit Separation", variant="primary")
120
-
121
- with gr.Column():
122
- task_output = gr.Markdown("## Task Status")
123
-
124
- with gr.Tab("Poll Existing Task"):
125
- with gr.Row():
126
- with gr.Column():
 
 
 
 
 
 
 
 
 
 
 
 
 
127
  poll_task_id = gr.Textbox(
128
  label="Separation Task ID",
129
- placeholder="Enter task ID to poll"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
130
  )
131
- poll_btn = gr.Button("Check Status", variant="secondary")
132
-
133
- with gr.Column():
134
- poll_output = gr.Markdown("## Poll Results")
135
 
136
- with gr.Tab("Instructions"):
137
- gr.Markdown("""
138
- ## 📋 How to Use
139
-
140
- 1. **Get Task ID & Audio ID:**
141
- - Go to your Suno generation history
142
- - Copy the Task ID and Audio ID
143
-
144
- 2. **Submit Separation:**
145
- - Enter Task ID and Audio ID
146
- - Choose separation type
147
- - Click "Submit Separation"
148
- - You'll get a new Separation Task ID
149
 
150
- 3. **Get Results:**
151
- - Wait 1-3 minutes
152
- - Use the Poll tab to check status
153
- - OR wait for callback to https://1hit.no/callback.php
154
 
155
- 4. **View Results:**
156
- - Visit: https://1hit.no/viewer.php?task_id=YOUR_TASK_ID
157
 
158
- ## ⚠️ Notes
159
- - Processing takes 1-3 minutes
160
- - Each separation uses credits
161
- - Links expire after some time
162
- """)
 
 
163
 
164
- # Submit task function
165
  def on_submit(task_id, audio_id, sep_type):
166
  if not task_id or not audio_id:
167
- return "❌ Please enter both Task ID and Audio ID"
168
 
169
- result = submit_separation_task(task_id, audio_id, sep_type)
170
 
171
- if result[1]: # Error
172
- return f"❌ {result[1]}"
173
 
174
- separation_task_id = result[0]
 
 
 
 
 
 
 
 
 
 
 
175
 
176
- output = f"""
177
- ## ✅ Task Submitted!
178
 
179
- **Separation Task ID:** `{separation_task_id}`
 
180
 
181
- **Next Steps:**
182
- 1. Wait 1-3 minutes for processing
183
- 2. Check status in the **Poll** tab using this Task ID
184
- 3. Results will also be sent to callback endpoint
185
 
186
- **Poll this task:** Use ID: `{separation_task_id}`
187
- """
 
 
 
 
 
 
 
 
 
 
188
 
189
- return output
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
190
 
191
- # Poll task function
192
- def on_poll(task_id):
193
  if not task_id:
194
- return "❌ Please enter a Task ID"
195
 
196
- status, results, error = poll_task_status(task_id)
197
 
198
  if error:
199
  return f"❌ Error: {error}"
200
 
201
  if status == "SUCCESS":
202
  if results:
203
- return f"""
204
- ## Separation Complete!
205
-
206
- **Status:** {status}
207
- **Task ID:** `{task_id}`
208
-
209
- {format_results(results)}
210
-
211
- **Also check:** https://1hit.no/viewer.php?task_id={task_id}
212
- """
213
  else:
214
- return f"""
215
- ## Separation Complete!
216
-
217
- **Status:** {status}
218
- **Task ID:** `{task_id}`
219
-
220
- No direct links found in response.
221
- Check callback results: https://1hit.no/viewer.php?task_id={task_id}
222
- """
223
  elif status in ["PENDING", "PROCESSING", "RUNNING"]:
224
- return f"""
225
- ## Still Processing
226
-
227
- **Status:** {status}
228
- **Task ID:** `{task_id}`
229
-
230
- Processing usually takes 1-3 minutes.
231
- Check again in a minute.
232
- """
233
  elif status == "FAILED":
234
- return f"""
235
- ## Separation Failed
236
-
237
- **Status:** {status}
238
- **Task ID:** `{task_id}`
239
-
240
- The separation task failed.
241
- Please check your inputs and try again.
242
- """
243
  else:
244
- return f"""
245
- ## 🔄 Status Check
246
-
247
- **Status:** {status}
248
- **Task ID:** `{task_id}`
249
-
250
- Current status: {status}
251
- Keep checking every 30 seconds.
252
- """
 
 
253
 
254
- # Connect buttons
255
  submit_btn.click(
256
  fn=on_submit,
257
- inputs=[task_id_input, audio_id_input, separation_type],
258
- outputs=[task_output]
 
 
 
 
 
259
  )
260
 
261
  poll_btn.click(
262
- fn=on_poll,
263
  inputs=[poll_task_id],
264
  outputs=[poll_output]
265
  )
 
 
 
 
 
 
 
 
 
 
 
 
266
 
267
  if __name__ == "__main__":
268
  print("🚀 Starting Suno Stem Separator")
 
9
  if not SUNO_KEY:
10
  print("⚠️ SunoKey not set!")
11
 
12
+ # Store task info for auto-fill
13
+ current_task_info = {}
14
+
15
+ def get_audio_files(task_id):
16
+ """Get audio files from Suno task ID"""
17
+ if not SUNO_KEY:
18
+ return "❌ Error: SunoKey not configured", []
19
+
20
+ if not task_id.strip():
21
+ return "❌ Please enter a Task ID", []
22
+
23
+ try:
24
+ resp = requests.get(
25
+ "https://api.sunoapi.org/api/v1/generate/record-info",
26
+ headers={"Authorization": f"Bearer {SUNO_KEY}"},
27
+ params={"taskId": task_id.strip()},
28
+ timeout=30
29
+ )
30
+
31
+ if resp.status_code != 200:
32
+ return f"❌ Request failed: HTTP {resp.status_code}", []
33
+
34
+ data = resp.json()
35
+
36
+ if data.get("code") != 200:
37
+ return f"❌ API error: {data.get('msg', 'Unknown')}", []
38
+
39
+ # Check status
40
+ status = data.get("data", {}).get("status", "UNKNOWN")
41
+ if status != "SUCCESS":
42
+ return f"⏳ Task status: {status}. Wait for generation to complete.", []
43
+
44
+ # Get audio files
45
+ suno_data = data.get("data", {}).get("response", {}).get("sunoData", [])
46
+ if not suno_data:
47
+ return "❌ No audio files found", []
48
+
49
+ # Create dropdown options
50
+ audio_options = []
51
+ for i, audio in enumerate(suno_data):
52
+ audio_id = audio.get("id", f"audio_{i}")
53
+ title = audio.get("title", f"Track {i+1}")
54
+ duration = audio.get("duration", 0)
55
+ prompt = audio.get("prompt", "")[:50]
56
+
57
+ display = f"{i+1}. {title} ({duration:.1f}s)"
58
+ if prompt:
59
+ display += f" - {prompt}..."
60
+
61
+ audio_options.append((display, audio_id))
62
+
63
+ return f"✅ Found {len(audio_options)} audio file(s)", audio_options
64
+
65
+ except Exception as e:
66
+ return f"❌ Error: {str(e)}", []
67
+
68
  def submit_separation_task(task_id, audio_id, separation_type):
69
+ """Submit separation task"""
70
  try:
71
  resp = requests.post(
72
  "https://api.sunoapi.org/api/v1/vocal-removal/generate",
 
86
  if resp.status_code == 200:
87
  data = resp.json()
88
 
89
+ # Get separation task ID
90
+ separation_task_id = None
91
  if "taskId" in data:
92
+ separation_task_id = data["taskId"]
93
  elif data.get("code") == 200 and "data" in data and "taskId" in data["data"]:
94
+ separation_task_id = data["data"]["taskId"]
95
+
96
+ if separation_task_id:
97
+ # Store for auto-fill
98
+ current_task_info["separation_task_id"] = separation_task_id
99
+ current_task_info["original_task_id"] = task_id
100
+ current_task_info["audio_id"] = audio_id
101
+ current_task_info["separation_type"] = separation_type
102
+
103
+ return f"✅ Task submitted!\n\n**Separation Task ID:** `{separation_task_id}`\n\n⏳ Starting auto-polling...", separation_task_id
104
  else:
105
+ return f" No task ID in response:\n{json.dumps(data, indent=2)}", None
106
  else:
107
+ return f"HTTP Error {resp.status_code}:\n{resp.text}", None
108
 
109
  except Exception as e:
110
+ return f"Error: {str(e)}", None
111
 
112
+ def poll_separation_status(separation_task_id):
113
+ """Poll separation task status"""
114
  try:
115
  resp = requests.get(
116
  "https://api.sunoapi.org/api/v1/vocal-removal/record-info",
117
  headers={"Authorization": f"Bearer {SUNO_KEY}"},
118
+ params={"taskId": separation_task_id},
119
  timeout=30
120
  )
121
 
122
  if resp.status_code == 200:
123
  data = resp.json()
124
 
125
+ # Get status
126
  status = "UNKNOWN"
127
  if "status" in data:
128
  status = data["status"]
129
  elif "data" in data and "status" in data["data"]:
130
  status = data["data"]["status"]
131
  elif data.get("code") == 200:
132
+ status = "SUCCESS"
133
 
134
+ # Get results
135
  results = {}
136
  if "vocal_removal_info" in data:
137
  results = data["vocal_removal_info"]
138
  elif "data" in data and "vocal_removal_info" in data["data"]:
139
  results = data["data"]["vocal_removal_info"]
 
 
 
140
 
141
  return status, results, None
142
  else:
143
+ return "ERROR", {}, f"HTTP {resp.status_code}: {resp.text}"
144
 
145
  except Exception as e:
146
  return "ERROR", {}, str(e)
147
 
148
+ def format_download_links(results, separation_task_id):
149
+ """Format download links from results"""
150
  if not results:
151
+ return "No download links found"
152
 
153
  output = "## 🎵 Download Links\n\n"
154
+
155
+ # 2-stem separation
156
+ if "vocal_url" in results or "instrumental_url" in results:
157
+ if results.get("vocal_url"):
158
+ output += f"**🎤 Vocals:** [Download MP3]({results['vocal_url']})\n"
159
+ if results.get("instrumental_url"):
160
+ output += f"**🎵 Instrumental:** [Download MP3]({results['instrumental_url']})\n"
161
+
162
+ # 12-stem separation
163
+ stem_fields = [
164
+ ("backing_vocals_url", "🎤 Backing Vocals"),
165
+ ("bass_url", "🎸 Bass"),
166
+ ("brass_url", "🎺 Brass"),
167
+ ("drums_url", "🥁 Drums"),
168
+ ("fx_url", "🎛️ FX/Other"),
169
+ ("guitar_url", "🎸 Guitar"),
170
+ ("keyboard_url", "🎹 Keyboard"),
171
+ ("percussion_url", "🪘 Percussion"),
172
+ ("strings_url", "🎻 Strings"),
173
+ ("synth_url", "🎹 Synth"),
174
+ ("woodwinds_url", "🎷 Woodwinds"),
175
+ ("vocal_url", "🎤 Vocals"),
176
+ ("instrumental_url", "🎵 Instrumental"),
177
+ ]
178
+
179
+ for field, name in stem_fields:
180
+ if results.get(field):
181
+ output += f"**{name}:** [Download MP3]({results[field]})\n"
182
+
183
+ output += f"\n**🔗 Viewer:** [Open in Viewer](https://1hit.no/viewer.php?task_id={separation_task_id})"
184
 
185
  return output
186
 
187
  # Create the app
188
  with gr.Blocks(title="Suno Stem Separator", theme="soft") as app:
189
  gr.Markdown("# 🎵 Suno Stem Separator")
 
190
 
191
+ with gr.Row():
192
+ # Left column: Input and control
193
+ with gr.Column(scale=1):
194
+ # Step 1: Get audio files
195
+ with gr.Group():
196
+ gr.Markdown("### 1. Get Audio Files")
197
+ original_task_id = gr.Textbox(
198
  label="Original Task ID",
199
+ placeholder="Enter Suno generation task ID",
200
+ info="From your Suno history"
201
  )
202
+ get_audio_btn = gr.Button("📥 Get Audio Files", variant="secondary")
203
+ audio_status = gr.Markdown("Enter Task ID above")
204
+
205
+ # Step 2: Select audio file
206
+ with gr.Group():
207
+ gr.Markdown("### 2. Select Audio File")
208
+ audio_dropdown = gr.Dropdown(
209
+ label="Select Audio",
210
+ choices=[],
211
+ interactive=True,
212
+ visible=False
213
  )
214
+
215
+ # Step 3: Start separation
216
+ with gr.Group():
217
+ gr.Markdown("### 3. Start Separation")
218
  separation_type = gr.Radio(
 
219
  label="Separation Type",
220
+ choices=[
221
+ ("🎤 Vocals Only (1 credit)", "separate_vocal"),
222
+ ("🎛️ Full Stems (5 credits)", "split_stem")
223
+ ],
224
+ value="separate_vocal",
225
+ visible=False
226
  )
227
+ submit_btn = gr.Button("🚀 Start Separation", variant="primary", visible=False)
228
+ submission_output = gr.Markdown("Select audio file first", visible=False)
229
+
230
+ # Right column: Results and polling
231
+ with gr.Column(scale=2):
232
+ # Auto-polling section
233
+ with gr.Group():
234
+ gr.Markdown("### 4. Auto-Polling Status")
235
+ auto_poll_status = gr.Markdown("Waiting for task submission...")
236
+ auto_poll_progress = gr.Slider(
237
+ minimum=0,
238
+ maximum=60,
239
+ value=0,
240
+ label="Polling attempts",
241
+ interactive=False,
242
+ visible=False
243
+ )
244
+
245
+ # Manual polling section
246
+ with gr.Group():
247
+ gr.Markdown("### 5. Manual Polling")
248
  poll_task_id = gr.Textbox(
249
  label="Separation Task ID",
250
+ placeholder="Will auto-fill from current task"
251
+ )
252
+ poll_btn = gr.Button("🔍 Check Status", variant="secondary")
253
+ poll_output = gr.Markdown("Enter Task ID to check")
254
+
255
+ # Results section
256
+ with gr.Group():
257
+ gr.Markdown("### 6. Download Links")
258
+ download_output = gr.Markdown("Results will appear here")
259
+
260
+ # Viewer link
261
+ with gr.Group():
262
+ gr.Markdown("### 7. Viewer")
263
+ viewer_link = gr.Markdown(
264
+ "[Open Viewer](https://1hit.no/viewer.php)",
265
+ elem_id="viewer_link"
266
  )
 
 
 
 
267
 
268
+ # Store current separation task ID
269
+ current_separation_task_id = gr.State(value="")
270
+
271
+ # Step 1: Get audio files
272
+ def on_get_audio(task_id):
273
+ if not task_id:
274
+ return "❌ Enter Task ID", gr.Dropdown(choices=[], visible=False), gr.Radio(visible=False), gr.Button(visible=False), gr.Markdown(visible=False)
 
 
 
 
 
 
275
 
276
+ status, options = get_audio_files(task_id)
 
 
 
277
 
278
+ if not options:
279
+ return status, gr.Dropdown(choices=[], visible=False), gr.Radio(visible=False), gr.Button(visible=False), gr.Markdown(visible=False)
280
 
281
+ return (
282
+ status,
283
+ gr.Dropdown(choices=options, value=options[0][1] if options else None, visible=True),
284
+ gr.Radio(visible=True),
285
+ gr.Button(visible=True),
286
+ gr.Markdown("Ready to separate!", visible=True)
287
+ )
288
 
289
+ # Step 2-3: Submit separation task
290
  def on_submit(task_id, audio_id, sep_type):
291
  if not task_id or not audio_id:
292
+ return "❌ Missing Task ID or Audio ID", None, gr.Markdown(), gr.Slider(visible=False)
293
 
294
+ status, separation_task_id = submit_separation_task(task_id, audio_id, sep_type)
295
 
296
+ if not separation_task_id:
297
+ return status, None, gr.Markdown(), gr.Slider(visible=False)
298
 
299
+ # Start auto-polling
300
+ return (
301
+ status,
302
+ separation_task_id,
303
+ gr.Markdown("⏳ Polling started..."),
304
+ gr.Slider(visible=True, value=0)
305
+ )
306
+
307
+ # Auto-polling function
308
+ def auto_poll(separation_task_id, attempt):
309
+ if not separation_task_id:
310
+ return "⏳ Waiting for task...", 0, gr.Markdown(), False
311
 
312
+ attempt += 1
 
313
 
314
+ # Poll for status
315
+ status, results, error = poll_separation_status(separation_task_id)
316
 
317
+ if error:
318
+ return f"❌ Poll error: {error}", attempt, gr.Markdown(), attempt >= 60
 
 
319
 
320
+ if status == "SUCCESS":
321
+ if results:
322
+ output = f"✅ **Separation Complete!**\n\n"
323
+ output += f"**Task ID:** `{separation_task_id}`\n\n"
324
+ output += format_download_links(results, separation_task_id)
325
+ return output, attempt, gr.Markdown(), True
326
+ else:
327
+ output = f"✅ **Processing Complete**\n\n"
328
+ output += f"**Task ID:** `{separation_task_id}`\n\n"
329
+ output += "Check callback results: "
330
+ output += f"[Viewer](https://1hit.no/viewer.php?task_id={separation_task_id})"
331
+ return output, attempt, gr.Markdown(), True
332
 
333
+ elif status in ["PENDING", "PROCESSING", "RUNNING"]:
334
+ output = f"⏳ **Status:** {status}\n\n"
335
+ output += f"**Task ID:** `{separation_task_id}`\n"
336
+ output += f"**Attempt:** {attempt}/60\n"
337
+ output += f"**Estimated wait:** {attempt * 5} seconds\n\n"
338
+ output += "Polling every 5 seconds..."
339
+ return output, attempt, gr.Markdown(), attempt >= 60
340
+
341
+ elif status == "FAILED":
342
+ output = f"❌ **Separation Failed**\n\n"
343
+ output += f"**Task ID:** `{separation_task_id}`\n"
344
+ output += f"**Status:** {status}\n"
345
+ return output, attempt, gr.Markdown(), True
346
+
347
+ else:
348
+ output = f"🔄 **Status:** {status}\n\n"
349
+ output += f"**Task ID:** `{separation_task_id}`\n"
350
+ output += f"**Attempt:** {attempt}/60\n"
351
+ return output, attempt, gr.Markdown(), attempt >= 60
352
 
353
+ # Manual polling function
354
+ def manual_poll(task_id):
355
  if not task_id:
356
+ return "❌ Enter Task ID"
357
 
358
+ status, results, error = poll_separation_status(task_id)
359
 
360
  if error:
361
  return f"❌ Error: {error}"
362
 
363
  if status == "SUCCESS":
364
  if results:
365
+ output = f"✅ **Complete!**\n\n"
366
+ output += f"**Task ID:** `{task_id}`\n\n"
367
+ output += format_download_links(results, task_id)
 
 
 
 
 
 
 
368
  else:
369
+ output = f"✅ **Complete** (no direct links)\n\n"
370
+ output += f"**Task ID:** `{task_id}`\n\n"
371
+ output += f"Check: [Viewer](https://1hit.no/viewer.php?task_id={task_id})"
372
+
 
 
 
 
 
373
  elif status in ["PENDING", "PROCESSING", "RUNNING"]:
374
+ output = f"⏳ **Status:** {status}\n\n"
375
+ output += f"**Task ID:** `{task_id}`\n\n"
376
+ output += "Still processing. Try again in 30 seconds."
377
+
 
 
 
 
 
378
  elif status == "FAILED":
379
+ output = f"❌ **Failed**\n\n"
380
+ output += f"**Task ID:** `{task_id}`\n"
381
+ output += f"**Status:** {status}"
382
+
 
 
 
 
 
383
  else:
384
+ output = f"🔄 **Status:** {status}\n\n"
385
+ output += f"**Task ID:** `{task_id}`"
386
+
387
+ return output
388
+
389
+ # Connect events
390
+ get_audio_btn.click(
391
+ fn=on_get_audio,
392
+ inputs=[original_task_id],
393
+ outputs=[audio_status, audio_dropdown, separation_type, submit_btn, submission_output]
394
+ )
395
 
 
396
  submit_btn.click(
397
  fn=on_submit,
398
+ inputs=[original_task_id, audio_dropdown, separation_type],
399
+ outputs=[submission_output, current_separation_task_id, auto_poll_status, auto_poll_progress]
400
+ ).then(
401
+ fn=auto_poll,
402
+ inputs=[current_separation_task_id, auto_poll_progress],
403
+ outputs=[auto_poll_status, auto_poll_progress, download_output, auto_poll_progress],
404
+ every=5 # Poll every 5 seconds
405
  )
406
 
407
  poll_btn.click(
408
+ fn=manual_poll,
409
  inputs=[poll_task_id],
410
  outputs=[poll_output]
411
  )
412
+
413
+ # Auto-fill poll field when separation task is submitted
414
+ def update_poll_field(separation_task_id):
415
+ if separation_task_id:
416
+ return separation_task_id
417
+ return ""
418
+
419
+ current_separation_task_id.change(
420
+ fn=update_poll_field,
421
+ inputs=[current_separation_task_id],
422
+ outputs=[poll_task_id]
423
+ )
424
 
425
  if __name__ == "__main__":
426
  print("🚀 Starting Suno Stem Separator")