Spaces:
Paused
Paused
| 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, custom_mode=True): | |
| """Generate a song from lyrics text""" | |
| if not SUNO_KEY: | |
| yield "❌ Error: SunoKey not configured in environment variables" | |
| return | |
| if not lyrics_text.strip(): | |
| return "❌ Error: Please provide lyrics" | |
| if not style.strip(): | |
| return "❌ Error: Please provide a music style" | |
| if not title.strip(): | |
| return "❌ Error: Please provide a song title" | |
| try: | |
| # Always use custom mode for full control | |
| request_data = { | |
| "customMode": True, # Always True for our use case | |
| "instrumental": instrumental, | |
| "model": model, | |
| "callBackUrl": "http://dummy.com/callback", | |
| "style": style, | |
| "title": title, | |
| } | |
| if not instrumental: | |
| # Non-instrumental requires lyrics as prompt | |
| # Apply character limits based on model | |
| if len(lyrics_text) > 5000 and model in ["V4_5", "V4_5PLUS", "V4_5ALL", "V5"]: | |
| lyrics_text = lyrics_text[:5000] | |
| yield f"⚠️ Lyrics truncated to 5000 characters for {model} model\n\n" | |
| elif len(lyrics_text) > 3000 and model == "V4": | |
| lyrics_text = lyrics_text[:3000] | |
| yield f"⚠️ Lyrics truncated to 3000 characters for V4 model\n\n" | |
| request_data["prompt"] = lyrics_text | |
| else: | |
| # For instrumental, clear the prompt | |
| request_data["prompt"] = "" | |
| # Apply style length limits | |
| if len(style) > 1000 and model in ["V4_5", "V4_5PLUS", "V4_5ALL", "V5"]: | |
| style = style[:1000] | |
| elif len(style) > 200 and model == "V4": | |
| style = style[:200] | |
| yield f"⚠️ Style truncated to 200 characters for V4 model\n\n" | |
| # Apply title length limits | |
| if len(title) > 100 and model in ["V4_5", "V4_5PLUS", "V5"]: | |
| title = title[:100] | |
| elif len(title) > 80 and model in ["V4", "V4_5ALL"]: | |
| title = title[:80] | |
| yield f"⚠️ Title truncated to 80 characters for {model} model\n\n" | |
| # Update with possibly truncated values | |
| 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" | |
| yield f"⏳ Processing...\n" | |
| # Submit generation request | |
| 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!**\nTask ID: `{task_id}`\n\n⏳ Waiting for song generation...\n" | |
| # Poll for results | |
| max_attempts = 90 # 90 attempts * 10 seconds = 900 seconds (15 minutes) | |
| for attempt in range(max_attempts): | |
| time.sleep(10) # Check every 10 seconds | |
| try: | |
| # Check task status | |
| check = 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.status_code == 200: | |
| check_data = check.json() | |
| if check_data.get("code") != 200: | |
| # If we get an error, check if task might still be processing | |
| if attempt < max_attempts - 1: | |
| continue | |
| else: | |
| yield f"⚠️ API returned error: {check_data.get('msg', 'Unknown')}" | |
| continue | |
| data_info = check_data.get("data", {}) | |
| status = data_info.get("status", "PENDING") | |
| if status == "COMPLETE": | |
| # Try to get the response data | |
| response_data = data_info.get("response", {}) | |
| # The response might be a JSON string or already parsed | |
| if isinstance(response_data, str): | |
| try: | |
| response_data = json.loads(response_data) | |
| except: | |
| # Try to extract URLs directly if it's not JSON | |
| yield f"🎵 **Generation Complete!**\n\n" | |
| yield f"Task ID: `{task_id}`\nStatus: {status}\n\n" | |
| yield "However, we couldn't parse the song URLs from the response.\n\n" | |
| yield "**Try this:**\n" | |
| yield "1. Go to https://sunoapi.org\n" | |
| yield "2. Log in to your account\n" | |
| yield f"3. Check your generation history for task ID: {task_id}\n" | |
| yield "4. You should find your songs there with download links\n" | |
| return | |
| # Extract songs from response | |
| songs = response_data.get("data", []) | |
| if not songs: | |
| # Try alternative structure | |
| songs = response_data.get("songs", []) | |
| if songs: | |
| output = "🎶 **Song Generation Complete!**\n\n" | |
| output += f"Generated {len(songs)} song(s)\n\n" | |
| for i, song in enumerate(songs, 1): | |
| 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') | |
| output += f"## Song {i}: {song_title}\n" | |
| if stream_url: | |
| output += f"**Stream URL:** {stream_url}\n\n" | |
| output += f"**Listen Now:** [Click to Stream]({stream_url})\n\n" | |
| # Add audio player for streaming | |
| output += f"""<audio controls style="width: 100%; margin: 10px 0;"> | |
| <source src="{stream_url}" type="audio/mpeg"> | |
| Your browser does not support the audio element. | |
| </audio>\n\n""" | |
| else: | |
| output += "⚠️ Stream URL not available yet\n\n" | |
| if download_url: | |
| output += f"**Download URL:** {download_url}\n\n" | |
| output += f"**Download:** [Click to Download]({download_url})\n\n" | |
| else: | |
| output += "⚠️ Download URL not available yet (check back in 2-3 minutes)\n\n" | |
| output += "---\n\n" | |
| output += f"⏱️ Generated in about {(attempt + 1) * 10} seconds\n\n" | |
| output += "**Tips:**\n" | |
| output += "- Stream URLs work immediately\n" | |
| output += "- Download URLs may take 2-3 minutes to become available\n" | |
| output += "- Files are retained for 15 days\n\n" | |
| output += "**If URLs don't work:**\n" | |
| output += f"1. Visit https://sunoapi.org\n" | |
| output += f"2. Log in and check your generation history\n" | |
| output += f"3. Look for task ID: `{task_id}`\n" | |
| yield output | |
| else: | |
| # No songs found in response | |
| yield f"🎵 **Generation Complete!**\n\n" | |
| yield f"Task ID: `{task_id}`\nStatus: {status}\n\n" | |
| yield "**To access your songs:**\n" | |
| yield "1. Go to https://sunoapi.org\n" | |
| yield "2. Log in to your account\n" | |
| yield "3. Check your generation history\n" | |
| yield f"4. Look for task ID: `{task_id}`\n" | |
| yield "\nThe songs should be available there with download links." | |
| return | |
| elif status == "FAILED": | |
| error = data_info.get("errorMessage", "Unknown error") | |
| yield f"❌ Task failed: {error}" | |
| return | |
| else: | |
| # Still processing (PENDING or PROCESSING) | |
| if attempt % 3 == 0: # Update every 30 seconds | |
| yield f"⏳ Status: {status}\n" | |
| yield f"Attempt: {attempt + 1}/{max_attempts}\n" | |
| yield f"Task ID: `{task_id}`\n\n" | |
| yield "Still processing...\n\n" | |
| yield "**Typical timing:**\n" | |
| yield "- 30-40 seconds: Stream URL ready\n" | |
| yield "- 2-3 minutes: Download URL ready\n" | |
| yield "- Up to 5 minutes for longer songs\n" | |
| else: | |
| yield f"⚠️ Check error: HTTP {check.status_code} - Task might still be processing" | |
| except Exception as e: | |
| yield f"⚠️ Error checking status: {str(e)} - Will try again..." | |
| yield "⏰ Timeout after 15 minutes. Try checking your Suno API dashboard for results." | |
| except Exception as e: | |
| yield f"❌ 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:** | |
| - For best results, use structured lyrics with verses/chorus | |
| - Style examples: "Pop", "Rock guitar solo", "Jazz piano", "Electronic dance" | |
| - V5: Latest model, best quality | |
| - V4_5ALL: Good balance, up to 8 minutes | |
| - Instrumental: Check for music only | |
| **Generation time:** | |
| - 30-40s: 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) |