SUNO-API-V5 / app.py
MySafeCode's picture
Update app.py
f18900e verified
raw
history blame
17.7 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, 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)