SUNO-API-V5 / app.py
MySafeCode's picture
Update app.py
6ccce86 verified
raw
history blame
21.6 kB
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)