import gradio as gr import requests import os import json # Suno API key SUNO_KEY = os.environ.get("SunoKey", "") if not SUNO_KEY: print("⚠️ SunoKey not set!") def get_audio_files(task_id): """Get audio files from Suno task ID""" if not SUNO_KEY: return "❌ Error: SunoKey not configured", [] if not task_id.strip(): return "❌ Please enter a Task ID", [] try: resp = requests.get( "https://api.sunoapi.org/api/v1/generate/record-info", headers={"Authorization": f"Bearer {SUNO_KEY}"}, params={"taskId": task_id.strip()}, timeout=30 ) if resp.status_code != 200: return f"❌ Request failed: HTTP {resp.status_code}", [] data = resp.json() if data.get("code") != 200: return f"❌ API error: {data.get('msg', 'Unknown')}", [] # Check status status = data.get("data", {}).get("status", "UNKNOWN") if status != "SUCCESS": return f"⏳ Task status: {status}. Wait for generation to complete.", [] # Get audio files suno_data = data.get("data", {}).get("response", {}).get("sunoData", []) if not suno_data: return "❌ No audio files found", [] # Create dropdown options audio_options = [] for i, audio in enumerate(suno_data): audio_id = audio.get("id", f"audio_{i}") title = audio.get("title", f"Track {i+1}") duration = audio.get("duration", 0) display = f"{i+1}. {title} ({duration:.1f}s)" audio_options.append((display, audio_id)) return f"✅ Found {len(audio_options)} audio file(s)", audio_options except Exception as e: return f"❌ Error: {str(e)}", [] def submit_vocal_separation(task_id, audio_id): """Submit vocal separation task (2-stem only)""" try: resp = requests.post( "https://api.sunoapi.org/api/v1/vocal-removal/generate", json={ "taskId": task_id, "audioId": audio_id, "type": "separate_vocal", # Always 2-stem "callBackUrl": "https://1hit.no/callback.php" }, headers={ "Authorization": f"Bearer {SUNO_KEY}", "Content-Type": "application/json" }, timeout=30 ) print(f"Submission response: {resp.status_code}") print(f"Response: {resp.text}") if resp.status_code == 200: data = resp.json() print(f"Parsed data: {data}") # Get separation task ID from response separation_task_id = None # Try different response formats if data.get("taskId"): separation_task_id = data.get("taskId") elif data.get("data", {}).get("taskId"): separation_task_id = data["data"]["taskId"] elif data.get("code") == 200 and data.get("data", {}).get("taskId"): separation_task_id = data["data"]["taskId"] if separation_task_id: # Also get musicId if available music_id = data.get("musicId") or data.get("data", {}).get("musicId", "N/A") output = f"✅ **Task submitted!**\n\n" output += f"**Separation Task ID:** `{separation_task_id}`\n" output += f"**Music ID:** `{music_id}`\n\n" output += "Check status below using the Separation Task ID." return output, separation_task_id else: error_msg = data.get("msg", data.get("error", "Unknown error")) return f"❌ No task ID in response: {error_msg}\n\nRaw response:\n```json\n{json.dumps(data, indent=2)}\n```", None else: return f"❌ HTTP Error {resp.status_code}:\n{resp.text}", None except Exception as e: return f"❌ Error: {str(e)}", None def check_separation_status(task_id): """Check vocal separation task status""" if not task_id: return "❌ Enter a Task ID to check" try: resp = requests.get( "https://api.sunoapi.org/api/v1/vocal-removal/record-info", headers={"Authorization": f"Bearer {SUNO_KEY}"}, params={"taskId": task_id}, timeout=30 ) print(f"Status check for {task_id}: {resp.status_code}") if resp.status_code == 200: data = resp.json() print(f"Status data: {json.dumps(data, indent=2)}") # Check API response code if data.get("code") != 200: error_msg = data.get("msg", "Unknown error") return f"❌ API Error: {error_msg}\n\n**Task ID:** `{task_id}`" # Get the main data object response_data = data.get("data", {}) # Check success flag success_flag = response_data.get("successFlag", "UNKNOWN") if success_flag == "SUCCESS": # Get response with download URLs result_response = response_data.get("response", {}) if result_response: # Format results output = "✅ **Vocal Separation Complete!**\n\n" output += f"**Task ID:** `{task_id}`\n" output += f"**Music ID:** `{response_data.get('musicId', 'N/A')}`\n\n" output += "## 🎵 Download Links\n\n" # Check for URLs (case-insensitive) urls_found = False # Vocal URL vocal_url = (result_response.get("vocalUrl") or result_response.get("vocal_url") or result_response.get("vocalURL")) if vocal_url: output += f"**🎤 Vocals:** [Download MP3]({vocal_url})\n" urls_found = True # Instrumental URL instrumental_url = (result_response.get("instrumentalUrl") or result_response.get("instrumental_url") or result_response.get("instrumentalURL")) if instrumental_url: output += f"**🎵 Instrumental:** [Download MP3]({instrumental_url})\n" urls_found = True # Original URL origin_url = (result_response.get("originUrl") or result_response.get("origin_url") or result_response.get("originURL")) if origin_url: output += f"**📁 Original:** [Download MP3]({origin_url})\n" urls_found = True if not urls_found: output += "No download URLs found in response.\n" output += f"**Raw response:**\n```json\n{json.dumps(result_response, indent=2)}\n```\n" output += f"\n**🔗 Viewer:** [Open in Viewer](https://1hit.no/viewer.php?task_id={task_id})" # Add other fields if they exist other_fields = [] for key, value in result_response.items(): if value and key.endswith("Url") and key not in ["vocalUrl", "instrumentalUrl", "originUrl"]: field_name = key.replace("Url", "").replace("_", " ").title() other_fields.append(f"**{field_name}:** {value}") if other_fields: output += "\n\n**Other URLs:**\n" + "\n".join(other_fields) return output else: return f"✅ **Processing complete!**\n\n**Task ID:** `{task_id}`\n\nNo download URLs in response yet. Check your callback endpoint or try again in a moment." elif success_flag in ["PENDING", "PROCESSING", "RUNNING"]: return f"⏳ **Status:** {success_flag}\n\n**Task ID:** `{task_id}`\n\nStill processing. Check again in 30 seconds." elif success_flag == "FAILED": error_msg = response_data.get("errorMessage", "Unknown error") return f"❌ **Failed:** {error_msg}\n\n**Task ID:** `{task_id}`" else: return f"🔄 **Status:** {success_flag}\n\n**Task ID:** `{task_id}`\n\n**Full response:**\n```json\n{json.dumps(data, indent=2)}\n```" elif resp.status_code == 404: return f"❌ Task not found: `{task_id}`\n\nMake sure the Task ID is correct and the separation has started." else: return f"❌ HTTP Error {resp.status_code}:\n{resp.text}" except Exception as e: return f"❌ Error checking status: {str(e)}" # Create the app with gr.Blocks() as app: gr.Markdown("# 🎵 Suno Vocal Separator") gr.Markdown("Separate vocals from Suno tracks (2-stem only)") with gr.Row(): # Left column: Input and submit with gr.Column(scale=1): # Step 1: Get audio files gr.Markdown("### 1. Get Audio Files") original_task_id = gr.Textbox( label="Original Task ID", placeholder="Enter Suno generation task ID", info="From your Suno history" ) get_audio_btn = gr.Button("📥 Get Audio Files", variant="secondary") audio_status = gr.Markdown("Enter Task ID above") # Step 2: Select audio file gr.Markdown("### 2. Select Audio File") audio_dropdown = gr.Dropdown( label="Select Audio", choices=[], interactive=True, visible=False ) # Step 3: Start separation gr.Markdown("### 3. Start Vocal Separation") submit_btn = gr.Button("🚀 Separate Vocals", variant="primary", visible=False) submission_output = gr.Markdown("Select audio file first", visible=False) # Right column: Check status and results with gr.Column(scale=1): # Step 4: Check status gr.Markdown("### 4. Check Separation Status") separation_task_id = gr.Textbox( label="Separation Task ID", placeholder="Will auto-fill after submission" ) check_btn = gr.Button("🔍 Check Status", variant="secondary") status_output = gr.Markdown("Enter Task ID to check") # Step 5: Results gr.Markdown("### 5. Download Links") results_output = gr.Markdown("Results will appear here") # Step 6: Viewer link gr.Markdown("### 6. Viewer") gr.Markdown("[Open Viewer](https://1hit.no/viewer.php)") # Step 1: Get audio files def on_get_audio(task_id): if not task_id: return "❌ Enter Task ID", gr.Dropdown(choices=[], visible=False), gr.Button(visible=False), gr.Markdown(visible=False) status, options = get_audio_files(task_id) if not options: return status, gr.Dropdown(choices=[], visible=False), gr.Button(visible=False), gr.Markdown(visible=False) return ( status, gr.Dropdown(choices=options, value=options[0][1] if options else None, visible=True), gr.Button(visible=True), gr.Markdown("Ready for vocal separation!", visible=True) ) # Step 2-3: Submit vocal separation def on_submit(task_id, audio_id): if not task_id or not audio_id: return "❌ Missing Task ID or Audio ID", "", "⏳ Waiting..." status, sep_task_id = submit_vocal_separation(task_id, audio_id) if sep_task_id: return status, sep_task_id, "✅ Task submitted! Check status above." else: return status, "", "❌ Failed to submit task" # Step 4: Check status def on_check_status(task_id): return check_separation_status(task_id) # Connect events get_audio_btn.click( fn=on_get_audio, inputs=[original_task_id], outputs=[audio_status, audio_dropdown, submit_btn, submission_output] ) submit_btn.click( fn=on_submit, inputs=[original_task_id, audio_dropdown], outputs=[submission_output, separation_task_id, status_output] ) check_btn.click( fn=on_check_status, inputs=[separation_task_id], outputs=[results_output] ) # Auto-fill separation task ID field after submission def auto_fill_sep_id(sep_task_id): return sep_task_id separation_task_id.change( fn=auto_fill_sep_id, inputs=[separation_task_id], outputs=[separation_task_id] ) # Launch the app if __name__ == "__main__": print("🚀 Starting Suno Vocal Separator") print(f"🔑 SunoKey: {'✅ Set' if SUNO_KEY else '❌ Not set'}") print("🌐 Open your browser to: http://localhost:7860") app.launch(server_name="0.0.0.0", server_port=7860, share=False)