import gradio as gr import requests import os import time import json # Suno API key SUNO_KEY = os.environ.get("SunoKey", "") if not SUNO_KEY: print("⚠️ SunoKey not set!") def separate_vocals(task_id, audio_id, separation_type): """Separate vocals and instruments from Suno tracks""" if not SUNO_KEY: yield "❌ Error: SunoKey not configured in environment variables" return if not task_id.strip() or not audio_id.strip(): yield "❌ Error: Please enter both Task ID and Audio ID" return # Validate separation type if separation_type not in ["separate_vocal", "split_stem"]: yield "❌ Error: Invalid separation type" return # Submit separation task try: resp = requests.post( "https://api.sunoapi.org/api/v1/vocal-removal/generate", json={ "taskId": task_id, "audioId": audio_id, "type": separation_type, "callBackUrl": "https://1hit.no/callback.php" # Required but not used for polling }, headers={ "Authorization": f"Bearer {SUNO_KEY}", "Content-Type": "application/json" }, timeout=30 ) if resp.status_code != 200: yield f"❌ Submission failed: HTTP {resp.status_code}" return data = resp.json() if data.get("code") != 200: yield f"❌ API error: {data.get('msg', 'Unknown')}" return separation_task_id = data["data"]["taskId"] yield f"✅ **Submitted!**\nSeparation Task ID: `{separation_task_id}`\n\n⏳ Processing separation...\n" # Poll for results for attempt in range(40): # 40 attempts * 5 seconds = 200 seconds max time.sleep(5) try: check = requests.get( "https://api.sunoapi.org/api/v1/vocal-removal/record-info", headers={"Authorization": f"Bearer {SUNO_KEY}"}, params={"taskId": separation_task_id}, timeout=30 ) if check.status_code == 200: check_data = check.json() status = check_data["data"].get("status", "PENDING") if status == "SUCCESS": # Success! Extract separation results separation_info = check_data["data"].get("vocal_removal_info", {}) if not separation_info: yield "✅ Completed but no separation results found" return # Format output based on separation type output = "🎵 **Separation Complete!**\n\n" if separation_type == "separate_vocal": output += "## 2-Stem Separation\n\n" output += f"**Vocals:** [Download]({separation_info.get('vocal_url', 'N/A')})\n" output += f"**Instrumental:** [Download]({separation_info.get('instrumental_url', 'N/A')})\n" if separation_info.get('origin_url'): output += f"**Original:** [Download]({separation_info.get('origin_url')})\n" elif separation_type == "split_stem": output += "## 12-Stem Separation\n\n" stems = [ ("Vocals", separation_info.get('vocal_url')), ("Backing Vocals", separation_info.get('backing_vocals_url')), ("Drums", separation_info.get('drums_url')), ("Bass", separation_info.get('bass_url')), ("Guitar", separation_info.get('guitar_url')), ("Keyboard", separation_info.get('keyboard_url')), ("Strings", separation_info.get('strings_url')), ("Brass", separation_info.get('brass_url')), ("Woodwinds", separation_info.get('woodwinds_url')), ("Percussion", separation_info.get('percussion_url')), ("Synth", separation_info.get('synth_url')), ("FX/Other", separation_info.get('fx_url')), ("Instrumental", separation_info.get('instrumental_url')), ("Original", separation_info.get('origin_url')) ] for stem_name, stem_url in stems: if stem_url: output += f"**{stem_name}:** [Download]({stem_url})\n" output += f"\n⏱️ Processed in about {(attempt + 1) * 5} seconds\n" output += f"⚠️ **Note:** Download links expire in 14 days" yield output return elif status == "FAILED": error = check_data["data"].get("errorMessage", "Unknown error") yield f"❌ Separation failed: {error}" return else: # PENDING or PROCESSING yield (f"⏳ Status: {status}\n" f"Attempt: {attempt + 1}/40\n" f"Separation Task ID: `{separation_task_id}`\n\n" f"Processing... (Usually takes 30-120 seconds)") else: yield f"⚠️ Check error: HTTP {check.status_code}" except Exception as e: yield f"⚠️ Error checking status: {str(e)}" yield "⏰ Timeout after 200 seconds. Try checking again later." except Exception as e: yield f"❌ Error: {str(e)}" # Create the app with gr.Blocks(title="Suno Stem Separator", theme="soft") as app: gr.Markdown("# 🎵 Suno Stem Separator") gr.Markdown("Separate Suno AI tracks into vocal and instrument stems") with gr.Row(): with gr.Column(scale=1): task_id = gr.Textbox( label="Original Task ID", placeholder="Example: 5c79****be8e", info="The task ID from your Suno music generation" ) audio_id = gr.Textbox( label="Audio ID", placeholder="Example: e231****-****-****-****-****8cadc7dc", info="The specific audio ID to separate" ) separation_type = gr.Radio( label="Separation Type", choices=[ ("separate_vocal (2 stems - 1 credit)", "separate_vocal"), ("split_stem (12 stems - 5 credits)", "split_stem") ], value="separate_vocal", info="Choose separation mode" ) btn = gr.Button("🎵 Separate Stems", variant="primary", scale=1) gr.Markdown(""" **How it works:** 1. Enter Task ID and Audio ID from your Suno track 2. Choose separation type 3. Click Separate Stems 4. Wait 30-120 seconds 5. Get download links for each stem **Separation types:** - 🎤 **separate_vocal**: Vocals + Instrumental (2 stems, 1 credit) - 🎛️ **split_stem**: 12 detailed stems (5 credits) **Stem Types in split_stem:** - Vocals, Backing Vocals, Drums, Bass - Guitar, Keyboard, Strings, Brass - Woodwinds, Percussion, Synth, FX/Other ⚠️ **Important:** - Each request consumes credits - Links expire in 14 days """) with gr.Column(scale=2): output = gr.Markdown( label="Separation Results", value="Your separated stems will appear here..." ) gr.Markdown("---") gr.Markdown( """

Powered by Suno AISuno API DocsStem Separation Guide

This app uses the Suno API to separate tracks into individual stems.

💡 Tip: Find your Task ID and Audio ID in your Suno generation history or API responses.

""", elem_id="footer" ) btn.click(separate_vocals, [task_id, audio_id, separation_type], output) if __name__ == "__main__": print("🚀 Starting Suno Stem 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)