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_lyrics(prompt): | |
| """Final working lyrics generator""" | |
| if not SUNO_KEY: | |
| yield "❌ Error: SunoKey not configured in environment variables" | |
| return | |
| if not prompt.strip(): | |
| yield "❌ Error: Please enter a prompt" | |
| return | |
| # Submit task | |
| try: | |
| resp = requests.post( | |
| "https://api.sunoapi.org/api/v1/lyrics", | |
| json={ | |
| "prompt": prompt, | |
| "callBackUrl": "http://dummy.com/callback" # Required but not used | |
| }, | |
| 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"✅ **Submitted!**\nTask ID: `{task_id}`\n\n⏳ Waiting for lyrics...\n" | |
| # Poll for results | |
| for attempt in range(30): # 30 attempts * 5 seconds = 150 seconds max | |
| time.sleep(5) | |
| try: | |
| check = requests.get( | |
| "https://api.sunoapi.org/api/v1/lyrics/record-info", | |
| headers={"Authorization": f"Bearer {SUNO_KEY}"}, | |
| params={"taskId": 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 lyrics | |
| response_data = check_data["data"].get("response", {}) | |
| if isinstance(response_data, str): | |
| # Sometimes response is a JSON string | |
| try: | |
| response_data = json.loads(response_data.replace('null', 'None')) | |
| except: | |
| response_data = {"data": []} | |
| lyrics_list = response_data.get("data", []) | |
| if lyrics_list: | |
| output = "🎵 **Lyrics Generated Successfully!**\n\n" | |
| for i, lyric in enumerate(lyrics_list, 1): | |
| title = lyric.get('title', f'Variant {i}') | |
| text = lyric.get('text', 'No lyrics') | |
| output += f"## Variant {i}: {title}\n" | |
| output += "```\n" | |
| output += text | |
| output += "\n```\n" | |
| output += "---\n\n" | |
| output += f"⏱️ Generated in about {(attempt + 1) * 5} seconds\n\n" | |
| output += "📝 **Now you can:**\n" | |
| output += "1. Select a variant below\n" | |
| output += "2. Edit the text if needed\n" | |
| output += "3. Generate a song!" | |
| # Prepare data for state | |
| lyrics_data = { | |
| "variants": lyrics_list, | |
| "original_prompt": prompt | |
| } | |
| # Update dropdown with variants | |
| variant_choices = [f"{i+1}: {lyric.get('title', f'Variant {i+1}')}" | |
| for i, lyric in enumerate(lyrics_list)] | |
| # Get first variant text for editing | |
| first_text = lyrics_list[0].get('text', '') if lyrics_list else "" | |
| yield output, lyrics_data, variant_choices, first_text | |
| else: | |
| yield "✅ Completed but no lyrics found in response", None, [], "" | |
| return | |
| elif status == "FAILED": | |
| error = check_data["data"].get("errorMessage", "Unknown error") | |
| yield f"❌ Task failed: {error}", None, [], "" | |
| return | |
| else: | |
| # PENDING or PROCESSING | |
| yield (f"⏳ Status: {status}\n" | |
| f"Attempt: {attempt + 1}/30\n" | |
| f"Task ID: `{task_id}`\n\n" | |
| f"Still processing... (Usually takes 30-90 seconds)"), None, [], "" | |
| else: | |
| yield f"⚠️ Check error: HTTP {check.status_code}", None, [], "" | |
| except Exception as e: | |
| yield f"⚠️ Error checking status: {str(e)}", None, [], "" | |
| yield "⏰ Timeout after 150 seconds. Try checking again later.", None, [], "" | |
| except Exception as e: | |
| yield f"❌ Error: {str(e)}", None, [], [] | |
| def generate_song_from_text(lyrics_text, style, title, instrumental, model, custom_mode): | |
| """Generate a song from text""" | |
| if not SUNO_KEY: | |
| return "❌ Error: SunoKey not configured in environment variables" | |
| if not lyrics_text.strip(): | |
| return "❌ Error: Please provide lyrics text" | |
| try: | |
| # Prepare request data based on custom mode | |
| request_data = { | |
| "customMode": custom_mode, | |
| "instrumental": instrumental, | |
| "model": model, | |
| "callBackUrl": "http://dummy.com/callback", # Required but not used | |
| } | |
| if custom_mode: | |
| # Custom mode requires style and title | |
| if not style.strip(): | |
| return "❌ Error: Style is required in Custom Mode" | |
| if not title.strip(): | |
| return "❌ Error: Title is required in Custom Mode" | |
| request_data["style"] = style | |
| request_data["title"] = title | |
| if not instrumental: | |
| # Non-instrumental requires lyrics as prompt | |
| if len(lyrics_text) > 5000 and model in ["V4_5", "V4_5PLUS", "V4_5ALL", "V5"]: | |
| lyrics_text = lyrics_text[:5000] | |
| elif len(lyrics_text) > 3000 and model == "V4": | |
| lyrics_text = lyrics_text[:3000] | |
| request_data["prompt"] = lyrics_text | |
| else: | |
| # Non-custom mode only requires prompt (max 500 chars) | |
| if len(lyrics_text) > 500: | |
| lyrics_text = lyrics_text[:497] + "..." | |
| request_data["prompt"] = lyrics_text | |
| # Clear other fields for non-custom mode | |
| request_data["style"] = "" | |
| request_data["title"] = "" | |
| # 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: | |
| return f"❌ Submission failed: HTTP {resp.status_code}" | |
| data = resp.json() | |
| if data.get("code") != 200: | |
| return f"❌ API error: {data.get('msg', 'Unknown')}" | |
| task_id = data["data"]["taskId"] | |
| # Poll for results | |
| for attempt in range(60): # 60 attempts * 5 seconds = 300 seconds max (5 minutes) | |
| time.sleep(5) | |
| try: | |
| check = requests.get( | |
| "https://api.sunoapi.org/api/v1/music-info", | |
| headers={"Authorization": f"Bearer {SUNO_KEY}"}, | |
| params={"taskId": task_id}, | |
| timeout=30 | |
| ) | |
| if check.status_code == 200: | |
| check_data = check.json() | |
| status = check_data["data"].get("status", "PENDING") | |
| if status == "COMPLETE": | |
| # Success! Extract song URLs | |
| songs = check_data["data"].get("songs", []) | |
| if songs: | |
| output = "🎶 **Song Generation Complete!**\n\n" | |
| for i, song in enumerate(songs, 1): | |
| song_title = song.get('title', f'Song {i}') | |
| stream_url = song.get('streamUrl', 'No URL') | |
| download_url = song.get('downloadUrl', 'No URL') | |
| output += f"## Song {i}: {song_title}\n" | |
| output += f"**Stream URL:** {stream_url}\n\n" | |
| output += f"**Download URL:** {download_url}\n\n" | |
| output += f"**Listen Now:** [Click to Stream]({stream_url})\n\n" | |
| output += "---\n\n" | |
| output += f"⏱️ Generated in about {(attempt + 1) * 5} seconds\n\n" | |
| output += "⚠️ **Note:** Files are retained for 15 days" | |
| return output | |
| else: | |
| return "✅ Completed but no songs found in response" | |
| elif status == "FAILED": | |
| error = check_data["data"].get("errorMessage", "Unknown error") | |
| return f"❌ Task failed: {error}" | |
| else: | |
| # Still processing | |
| if attempt % 6 == 0: # Update every 30 seconds | |
| yield f"⏳ Status: {status}\nAttempt: {attempt + 1}/60\nTask ID: `{task_id}`\n\nStill processing... (Usually takes 30-180 seconds)" | |
| else: | |
| yield f"⚠️ Check error: HTTP {check.status_code}" | |
| except Exception as e: | |
| yield f"⚠️ Error checking status: {str(e)}" | |
| return "⏰ Timeout after 300 seconds. Try checking again later." | |
| except Exception as e: | |
| return f"❌ Error: {str(e)}" | |
| def update_text_from_variant(lyrics_data, variant_choice): | |
| """Update the text area when a variant is selected""" | |
| if lyrics_data and lyrics_data.get("variants") and variant_choice: | |
| try: | |
| # Extract variant index from choice (e.g., "1: Title" -> 0) | |
| variant_idx = int(variant_choice.split(":")[0].strip()) - 1 | |
| variants = lyrics_data["variants"] | |
| if 0 <= variant_idx < len(variants): | |
| text = variants[variant_idx].get('text', '') | |
| return text | |
| except: | |
| pass | |
| return "" | |
| def download_text_file(text): | |
| """Create a downloadable text file""" | |
| if not text.strip(): | |
| return None | |
| # Create a temporary file | |
| with tempfile.NamedTemporaryFile(mode='w', suffix='.txt', delete=False) 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)}" | |
| # Create the app | |
| with gr.Blocks(title="Suno Lyrics Generator", theme="soft") as app: | |
| gr.Markdown("# 🎵 Suno Lyrics Generator") | |
| gr.Markdown("Generate song lyrics and turn them into full songs using Suno AI") | |
| # Store lyrics data between steps | |
| lyrics_state = gr.State() | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| # Lyrics Generation Section | |
| gr.Markdown("### Step 1: Generate or Upload Lyrics") | |
| with gr.Tab("Generate Lyrics"): | |
| prompt = gr.Textbox( | |
| label="Lyrics Prompt", | |
| placeholder="Example: A happy song about sunshine and rainbows", | |
| lines=3 | |
| ) | |
| lyrics_btn = gr.Button("🎵 Generate Lyrics", variant="primary") | |
| with gr.Tab("Upload Lyrics"): | |
| file_upload = gr.File( | |
| label="Upload Text File", | |
| file_types=[".txt"], | |
| type="filepath" | |
| ) | |
| upload_btn = gr.Button("📁 Load from File", variant="secondary") | |
| upload_text = gr.Textbox( | |
| label="Uploaded Text", | |
| lines=10, | |
| interactive=False | |
| ) | |
| # Lyrics Selection & Editing Section | |
| gr.Markdown("### Step 2: Select & Edit Lyrics") | |
| variant_choice = gr.Dropdown( | |
| label="Select Lyrics Variant", | |
| choices=[], | |
| interactive=True | |
| ) | |
| lyrics_text = gr.Textbox( | |
| label="Lyrics Text (Editable)", | |
| placeholder="Your lyrics will appear here...", | |
| lines=15, | |
| interactive=True | |
| ) | |
| with gr.Row(): | |
| download_btn = gr.Button("💾 Download Text", variant="secondary") | |
| clear_btn = gr.Button("🗑️ Clear", variant="secondary") | |
| # Song Generation Section | |
| gr.Markdown("### Step 3: Create Song") | |
| with gr.Row(): | |
| custom_mode = gr.Checkbox( | |
| label="Custom Mode", | |
| value=False, | |
| interactive=True | |
| ) | |
| instrumental = gr.Checkbox( | |
| label="Instrumental", | |
| value=False, | |
| interactive=True | |
| ) | |
| with gr.Row(): | |
| style = gr.Textbox( | |
| label="Music Style", | |
| placeholder="Pop, Rock, Jazz, Classical", | |
| interactive=True, | |
| value="Pop" | |
| ) | |
| title = gr.Textbox( | |
| label="Song Title", | |
| placeholder="My Awesome Song", | |
| interactive=True, | |
| value="Generated Song" | |
| ) | |
| model = gr.Dropdown( | |
| label="Model Version", | |
| choices=["V5", "V4_5PLUS", "V4_5ALL", "V4_5", "V4"], | |
| value="V4_5ALL", | |
| interactive=True | |
| ) | |
| song_btn = gr.Button("🎶 Generate Song", variant="primary") | |
| with gr.Column(scale=2): | |
| output = gr.Markdown( | |
| label="Results", | |
| value="Your generated lyrics and songs will appear here..." | |
| ) | |
| # Hidden file 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> • | |
| <a href="https://github.com" target="_blank">GitHub</a></p> | |
| <p><small>This app uses the Suno API to generate AI-powered lyrics and music.</small></p> | |
| </div> | |
| """, | |
| elem_id="footer" | |
| ) | |
| # Event handlers | |
| # Generate lyrics from prompt | |
| lyrics_btn.click( | |
| generate_lyrics, | |
| inputs=prompt, | |
| outputs=[output, lyrics_state, variant_choice, lyrics_text] | |
| ) | |
| # Upload text from file | |
| upload_btn.click( | |
| upload_text_file, | |
| inputs=file_upload, | |
| outputs=lyrics_text | |
| ).then( | |
| lambda: "📁 **Text loaded from file!**\n\nYou can now edit the text above and generate a song.", | |
| outputs=output | |
| ) | |
| # Update text when variant is selected | |
| variant_choice.change( | |
| update_text_from_variant, | |
| inputs=[lyrics_state, variant_choice], | |
| outputs=lyrics_text | |
| ) | |
| # Download text as file | |
| download_btn.click( | |
| download_text_file, | |
| inputs=lyrics_text, | |
| outputs=file_output | |
| ).then( | |
| lambda: "💾 **Text ready for download!**\n\nCheck the download button below.", | |
| outputs=output | |
| ) | |
| # Clear text | |
| clear_btn.click( | |
| lambda: ("", ""), | |
| outputs=[lyrics_text, output] | |
| ) | |
| # Generate song from text | |
| song_btn.click( | |
| generate_song_from_text, | |
| inputs=[lyrics_text, style, title, instrumental, model, custom_mode], | |
| outputs=output | |
| ) | |
| if __name__ == "__main__": | |
| print("🚀 Starting Suno Lyrics Generator") | |
| print(f"🔑 SunoKey: {'✅ Set' if SUNO_KEY else '❌ Not set'}") | |
| app.launch(server_name="0.0.0.0", server_port=7860, share=False) |