import gradio as gr import requests import os import time import json import tempfile # Suno API key SUNO_KEY = os.environ.get("SunoKey", "") if not SUNO_KEY: print("⚠️ SunoKey not set!") def generate_song_from_text(lyrics_text, style, title, instrumental, model): """Generate a song from lyrics text""" if not SUNO_KEY: yield "❌ Error: SunoKey not configured in environment variables" return if not lyrics_text.strip(): yield "❌ Error: Please provide lyrics" return if not style.strip(): yield "❌ Error: Please provide a music style" return if not title.strip(): yield "❌ Error: Please provide a song title" return try: # Always use custom mode for full control request_data = { "customMode": True, "instrumental": instrumental, "model": model, "callBackUrl": "", # Empty callback URL - we'll poll instead "style": style, "title": title, } if not instrumental: # Apply character limits if model == "V4" and len(lyrics_text) > 3000: lyrics_text = lyrics_text[:3000] yield f"⚠️ Lyrics truncated to 3000 characters for V4 model\n\n" elif model in ["V4_5", "V4_5PLUS", "V4_5ALL", "V5"] and len(lyrics_text) > 5000: lyrics_text = lyrics_text[:5000] yield f"⚠️ Lyrics truncated to 5000 characters for {model} model\n\n" request_data["prompt"] = lyrics_text else: request_data["prompt"] = "" # Apply style length limits if model == "V4" and len(style) > 200: style = style[:200] yield f"⚠️ Style truncated to 200 characters for V4 model\n\n" elif model in ["V4_5", "V4_5PLUS", "V4_5ALL", "V5"] and len(style) > 1000: style = style[:1000] yield f"⚠️ Style truncated to 1000 characters for {model} model\n\n" # Apply title length limits if model in ["V4", "V4_5ALL"] and len(title) > 80: title = title[:80] yield f"⚠️ Title truncated to 80 characters for {model} model\n\n" elif model in ["V4_5", "V4_5PLUS", "V5"] and len(title) > 100: title = title[:100] yield f"⚠️ Title truncated to 100 characters for {model} model\n\n" request_data["style"] = style request_data["title"] = title yield f"✅ **Submitting song request...**\n\n" yield f"**Title:** {title}\n" yield f"**Style:** {style}\n" yield f"**Model:** {model}\n" yield f"**Instrumental:** {'Yes' if instrumental else 'No'}\n" if not instrumental: yield f"**Lyrics length:** {len(lyrics_text)} characters\n\n" # Submit generation request try: resp = requests.post( "https://api.sunoapi.org/api/v1/generate", json=request_data, 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 task_id = data["data"]["taskId"] yield f"✅ **Request submitted successfully!**\n" yield f"**Task ID:** `{task_id}`\n\n" yield f"⏳ Song generation started...\n\n" yield "**What to do next:**\n" yield "1. Keep this window open while the song generates\n" yield "2. We'll automatically check the status every 10 seconds\n" yield "3. Generation typically takes 1-3 minutes\n" yield "4. When complete, you'll get streaming and download links\n\n" except Exception as e: yield f"❌ Error submitting request: {str(e)}" return # Poll for completion - with better error handling max_attempts = 60 # 60 attempts * 10 seconds = 10 minutes last_status = "" for attempt in range(max_attempts): time.sleep(10) try: # Check task status check_resp = requests.get( f"https://api.sunoapi.org/api/v1/generate/record-info?taskId={task_id}", headers={"Authorization": f"Bearer {SUNO_KEY}"}, timeout=30 ) if check_resp.status_code == 200: check_data = check_resp.json() if check_data.get("code") == 200: data_info = check_data.get("data", {}) current_status = data_info.get("status", "UNKNOWN") # Only show status update if it changed if current_status != last_status: last_status = current_status if current_status == "COMPLETE": # Try to parse the response response_data = data_info.get("response", {}) # Response might be a JSON string if isinstance(response_data, str): try: response_data = json.loads(response_data) except json.JSONDecodeError: # If it's not JSON, it might be a simple string yield f"✅ **Generation Complete!**\n\n" yield f"**Status:** {current_status}\n" yield f"**Task ID:** `{task_id}`\n\n" yield "**To access your song:**\n" yield "1. Visit https://sunoapi.org\n" yield "2. Log in to your account\n" yield "3. Go to 'Generation History'\n" yield "4. Find your song by Task ID\n" return # Look for songs in different possible locations songs = [] # Try different possible structures if isinstance(response_data, dict): songs = response_data.get("data", []) if not songs: songs = response_data.get("songs", []) if not songs: # Check if response_data itself is a song list if isinstance(response_data.get("0"), dict): songs = [response_data.get("0")] if isinstance(response_data.get("1"), dict): songs.append(response_data.get("1")) elif isinstance(response_data, list): songs = response_data if songs: yield "🎶 **SONG GENERATION COMPLETE!** 🎶\n\n" yield f"Generated {len(songs)} song(s)\n\n" for i, song in enumerate(songs, 1): if isinstance(song, dict): song_title = song.get('title', f'Song {i}') stream_url = song.get('streamUrl') or song.get('stream_url') download_url = song.get('downloadUrl') or song.get('download_url') yield f"## 🎵 Song {i}: {song_title}\n" if stream_url: yield f"**Stream URL:** {stream_url}\n" yield f"**Listen Now:** [Click to Stream]({stream_url})\n\n" # Audio player yield f"""\n\n""" else: yield "⏳ Stream URL not ready yet (check back in 30 seconds)\n\n" if download_url: yield f"**Download URL:** {download_url}\n" yield f"**Download:** [Click to Download]({download_url})\n\n" else: yield "⏳ Download URL not ready yet (usually takes 2-3 minutes)\n\n" yield "---\n\n" yield f"⏱️ Total generation time: {(attempt + 1) * 10} seconds\n\n" yield "**Important:**\n" yield "- Stream links work immediately\n" yield "- Download links may take 2-3 minutes\n" yield "- Files are kept for 15 days\n" yield f"- Task ID: `{task_id}` (save this for reference)\n" return else: # No songs found in response yield f"✅ **Generation Complete!**\n\n" yield f"**Status:** {current_status}\n" yield f"**Task ID:** `{task_id}`\n\n" yield "**Response received but no song data found.**\n\n" yield "**To access your song:**\n" yield "1. Visit https://sunoapi.org\n" yield "2. Log in to your account\n" yield "3. Check 'Generation History'\n" yield "4. Look for this Task ID\n" return elif current_status == "FAILED": error_msg = data_info.get("errorMessage", "Unknown error") yield f"❌ **Generation Failed**\n\n" yield f"**Status:** {current_status}\n" yield f"**Error:** {error_msg}\n" yield f"**Task ID:** `{task_id}`\n\n" yield "Please try again with different parameters." return elif current_status in ["PENDING", "PROCESSING"]: yield f"⏳ **Status Update**\n\n" yield f"**Current Status:** {current_status}\n" yield f"**Task ID:** `{task_id}`\n" yield f"**Check:** {attempt + 1}/{max_attempts}\n\n" yield "**Estimated time remaining:**\n" yield "- Stream URL: 30-60 seconds\n" yield "- Download URL: 2-3 minutes\n" yield "\nWe'll check again in 10 seconds...\n" else: yield f"📊 **Status:** {current_status}\n" yield f"**Task ID:** `{task_id}`\n" yield f"**Check:** {attempt + 1}/{max_attempts}\n\n" yield "Still processing...\n" else: # API returned error code error_msg = check_data.get("msg", "Unknown error") yield f"⚠️ **API Error**\n\n" yield f"**Code:** {check_data.get('code')}\n" yield f"**Message:** {error_msg}\n" yield f"**Task ID:** `{task_id}`\n\n" yield "Will continue checking...\n" else: yield f"⚠️ **HTTP Error {check_resp.status_code}**\n\n" yield f"Failed to check status. Will retry in 10 seconds...\n" yield f"Check: {attempt + 1}/{max_attempts}\n" except requests.exceptions.Timeout: yield f"⏱️ **Timeout checking status**\n\n" yield "The status check timed out. Will try again in 10 seconds...\n" yield f"Check: {attempt + 1}/{max_attempts}\n" except Exception as e: yield f"⚠️ **Error checking status:** {str(e)}\n\n" yield "Will retry in 10 seconds...\n" yield f"Check: {attempt + 1}/{max_attempts}\n" # If we get here, we timed out yield "⏰ **Timeout after 10 minutes**\n\n" yield f"**Task ID:** `{task_id}`\n\n" yield "**What to do:**\n" yield "1. The song may still be processing\n" yield "2. Visit https://sunoapi.org\n" yield "3. Log in and check 'Generation History'\n" yield "4. Look for this Task ID\n" yield "\nSongs can sometimes take longer than expected, especially for longer tracks." except Exception as e: yield f"❌ **Unexpected Error:** {str(e)}" def download_text_file(text): """Create downloadable text file""" if not text.strip(): return None with tempfile.NamedTemporaryFile(mode='w', suffix='.txt', delete=False, encoding='utf-8') as f: f.write(text) temp_path = f.name return temp_path def upload_text_file(file): """Read text from uploaded file""" if file is None: return "" try: with open(file.name, 'r', encoding='utf-8') as f: content = f.read() return content except: try: with open(file.name, 'r', encoding='latin-1') as f: content = f.read() return content except Exception as e: return f"❌ Error reading file: {str(e)}" def clear_all(): """Clear all input fields""" return "", "", "Generated Song", False, "V4_5ALL", "### Ready to generate!\n\n1. Enter lyrics\n2. Set style and title\n3. Click 'Generate Song'" # Create the app with gr.Blocks(title="Suno Song Generator", theme="soft") as app: gr.Markdown("# 🎵 Suno Song Generator") gr.Markdown("Create songs from lyrics and style using Suno AI") with gr.Row(): with gr.Column(scale=1): # Lyrics Input gr.Markdown("### Step 1: Enter Lyrics") with gr.Tab("Paste Lyrics"): lyrics_text = gr.Textbox( label="Lyrics", placeholder="Paste your lyrics here...\n\nExample:\n(Verse 1)\nSun is shining, sky is blue\nBirds are singing, just for you...", lines=15, interactive=True ) with gr.Tab("Upload Lyrics"): file_upload = gr.File( label="Upload Text File (.txt)", file_types=[".txt"], type="filepath" ) upload_btn = gr.Button("📁 Load from File", variant="secondary") # Song Settings gr.Markdown("### Step 2: Song Settings") with gr.Row(): style = gr.Textbox( label="Music Style", placeholder="Example: Pop, Rock, Jazz, Classical, Electronic, Hip Hop, Country", value="Pop", interactive=True, scale=2 ) with gr.Row(): title = gr.Textbox( label="Song Title", placeholder="My Awesome Song", value="Generated Song", interactive=True, scale=2 ) with gr.Row(): instrumental = gr.Checkbox( label="Instrumental (No Vocals)", value=False, interactive=True ) model = gr.Dropdown( label="Model", choices=["V5", "V4_5PLUS", "V4_5ALL", "V4_5", "V4"], value="V4_5ALL", interactive=True ) # Action Buttons with gr.Row(): generate_btn = gr.Button("🎶 Generate Song", variant="primary", scale=2) clear_btn = gr.Button("🗑️ Clear All", variant="secondary", scale=1) with gr.Row(): download_btn = gr.Button("💾 Download Lyrics", variant="secondary") # Instructions gr.Markdown(""" **How to use:** 1. Paste lyrics or upload a .txt file 2. Set music style (genre) 3. Enter song title 4. Choose model (V4_5ALL recommended) 5. Click Generate Song! **Tips:** - Use structured lyrics with verses/chorus for best results - Style examples: "Pop", "Rock guitar", "Jazz piano", "Electronic" - V5: Latest model, best quality - V4_5ALL: Good balance, up to 8 minutes - Instrumental: Check for music only (no vocals) **Generation time:** - 30-60s: Stream URL ready - 2-3 min: Download URL ready - Up to 5 min for longer songs """) with gr.Column(scale=2): # Output Area output = gr.Markdown( label="Generation Status", value="### Ready to generate!\n\n1. Enter lyrics\n2. Set style and title\n3. Click 'Generate Song'" ) # Hidden download component file_output = gr.File(label="Download Lyrics", visible=False) gr.Markdown("---") gr.Markdown( """

Powered by Suno AISuno API Docs

Create custom songs by providing lyrics and music style

""", elem_id="footer" ) # Event handlers # Upload text from file upload_btn.click( upload_text_file, inputs=file_upload, outputs=lyrics_text ).then( lambda: "📁 **Lyrics loaded from file!**\n\nReady to generate song.", outputs=output ) # Download lyrics download_btn.click( download_text_file, inputs=lyrics_text, outputs=file_output ).then( lambda: "💾 **Lyrics ready for download!**\n\nCheck the download button below.", outputs=output ) # Clear all clear_btn.click( clear_all, outputs=[lyrics_text, style, title, instrumental, model, output] ) # Generate song generate_btn.click( generate_song_from_text, inputs=[lyrics_text, style, title, instrumental, model], outputs=output ) if __name__ == "__main__": print("🚀 Starting Suno Song Generator") print(f"🔑 SunoKey: {'✅ Set' if SUNO_KEY else '❌ Not set'}") app.launch(server_name="0.0.0.0", server_port=7860, share=False)