Spaces:
Running
Running
| 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"""<audio controls style="width: 100%; margin: 10px 0; padding: 10px; background: #f0f0f0; border-radius: 5px;"> | |
| <source src="{stream_url}" type="audio/mpeg"> | |
| Your browser does not support audio playback. | |
| </audio>\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( | |
| """ | |
| <div style="text-align: center; padding: 20px;"> | |
| <p>Powered by <a href="https://suno.ai" target="_blank">Suno AI</a> • | |
| <a href="https://sunoapi.org" target="_blank">Suno API Docs</a></p> | |
| <p><small>Create custom songs by providing lyrics and music style</small></p> | |
| </div> | |
| """, | |
| 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) |