Spaces:
Paused
Paused
| import gradio as gr | |
| import requests | |
| import os | |
| import json | |
| # Suno API key | |
| SUNO_KEY = os.environ.get("SunoKey", "") | |
| if not SUNO_KEY: | |
| print("⚠️ SunoKey not set!") | |
| def submit_song_generation(lyrics_text, style, title, instrumental, model): | |
| """Submit song generation request - IMMEDIATE RESPONSE WITH TASK ID""" | |
| if not SUNO_KEY: | |
| return "❌ Error: SunoKey not configured", "" | |
| if not lyrics_text.strip() and not instrumental: | |
| return "❌ Error: Please provide lyrics or select instrumental", "" | |
| if not style.strip(): | |
| return "❌ Error: Please provide a music style", "" | |
| if not title.strip(): | |
| return "❌ Error: Please provide a song title", "" | |
| try: | |
| # Prepare request data | |
| request_data = { | |
| "customMode": True, | |
| "instrumental": instrumental, | |
| "model": model, | |
| "callBackUrl": "https://1hit.no/callback.php", | |
| "style": style, | |
| "title": title, | |
| } | |
| if not instrumental: | |
| # Apply character limits | |
| if model == "V4" and len(lyrics_text) > 3000: | |
| lyrics_text = lyrics_text[:3000] | |
| elif model in ["V4_5", "V4_5PLUS", "V4_5ALL", "V5"] and len(lyrics_text) > 5000: | |
| lyrics_text = lyrics_text[:5000] | |
| request_data["prompt"] = lyrics_text | |
| else: | |
| request_data["prompt"] = "" | |
| # 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}\n\n{resp.text}", "" | |
| data = resp.json() | |
| # Extract task ID - IMMEDIATELY | |
| task_id = None | |
| # Try different response formats | |
| if "taskId" in data: | |
| task_id = data["taskId"] | |
| elif "data" in data and "taskId" in data["data"]: | |
| task_id = data["data"]["taskId"] | |
| elif data.get("data") and "taskId" in data.get("data", {}): | |
| task_id = data["data"]["taskId"] | |
| if not task_id: | |
| return f"❌ Could not extract Task ID\n\n**Raw:**\n```json\n{json.dumps(data, indent=2)}\n```", "" | |
| # RETURN TASK ID IMMEDIATELY | |
| output = f"## ✅ TASK SUBMITTED!\n\n" | |
| output += f"**🎯 YOUR TASK ID:** `{task_id}`\n\n" | |
| output += f"**📋 Details:**\n" | |
| output += f"- **Title:** {title}\n" | |
| output += f"- **Style:** {style}\n" | |
| output += f"- **Model:** {model}\n" | |
| output += f"- **Instrumental:** {'Yes' if instrumental else 'No'}\n\n" | |
| output += f"**🚀 Next Steps:**\n" | |
| output += f"1. **Copy this Task ID:** `{task_id}`\n" | |
| output += f"2. **Switch to 'Check Status' tab**\n" | |
| output += f"3. **Paste the Task ID**\n" | |
| output += f"4. **Click 'Check Status'** for results\n\n" | |
| output += f"**⏱️ Processing time:** 1-3 minutes\n" | |
| output += f"**📞 Callback URL:** https://1hit.no/callback.php\n" | |
| return output, task_id | |
| except Exception as e: | |
| return f"❌ Error: {str(e)}", "" | |
| def check_song_status(task_id, show_raw=False): | |
| """Check song generation status - RETURNS DOWNLOAD LINKS""" | |
| if not task_id: | |
| return "❌ Please enter a Task ID", "" | |
| try: | |
| resp = requests.get( | |
| "https://api.sunoapi.org/api/v1/generate/record-info", | |
| headers={"Authorization": f"Bearer {SUNO_KEY}"}, | |
| params={"taskId": task_id}, | |
| timeout=30 | |
| ) | |
| if resp.status_code != 200: | |
| return f"❌ HTTP Error {resp.status_code}\n\n{resp.text}", "" | |
| data = resp.json() | |
| # RAW JSON for debugging (always stored, shown if requested) | |
| raw_json = f"```json\n{json.dumps(data, indent=2)}\n```" | |
| if data.get("code") != 200: | |
| return f"❌ API Error: {data.get('msg', 'Unknown')}", raw_json | |
| task_data = data.get("data", {}) | |
| status = task_data.get("status", "UNKNOWN") | |
| # Format main output | |
| output = f"## 🔍 Status Check: `{task_id}`\n\n" | |
| output += f"**Status:** {status}\n" | |
| if status == "TEXT_SUCCESS": | |
| # This is the key status! Songs are ready! | |
| response_data = task_data.get("response", {}) | |
| songs = response_data.get("sunoData", []) | |
| if not songs: | |
| output += f"\n**⚠️ Status TEXT_SUCCESS but no songs found**\n" | |
| output += f"Raw data available in dropdown below\n" | |
| return output, raw_json | |
| output += f"## 🎵 SONGS READY! ({len(songs)} tracks)\n\n" | |
| for i, song in enumerate(songs, 1): | |
| output += f"### Track {i}\n" | |
| output += f"**Title:** {song.get('title', 'Untitled')}\n" | |
| # Get all possible URLs | |
| audio_url = song.get('audioUrl') | |
| stream_url = song.get('streamAudioUrl') or song.get('streamAudioURL') or song.get('stream_url') | |
| source_stream = song.get('sourceStreamAudioUrl') | |
| source_audio = song.get('sourceAudioUrl') | |
| # Image URL | |
| image_url = song.get('imageUrl') or song.get('sourceImageUrl') | |
| # Best URL for listening | |
| best_url = audio_url or stream_url or source_stream or source_audio | |
| if best_url: | |
| output += f"**🎧 Listen Now:** [Click to Play]({best_url})\n" | |
| # Audio player | |
| output += f"""<audio controls style="width: 100%; margin: 10px 0;"> | |
| <source src="{best_url}" type="audio/mpeg"> | |
| Your browser does not support audio. | |
| </audio>\n""" | |
| # Download button | |
| if audio_url: | |
| output += f"**💾 Download:** [MP3 File]({audio_url})\n" | |
| elif stream_url: | |
| output += f"**💾 Download:** [Stream MP3]({stream_url})\n" | |
| if image_url: | |
| output += f"**🖼️ Cover Art:** [View Image]({image_url})\n" | |
| output += f"**ID:** `{song.get('id', 'N/A')}`\n" | |
| output += f"**Model:** {song.get('modelName', 'N/A')}\n" | |
| output += f"**Tags:** {song.get('tags', 'N/A')}\n" | |
| if song.get('duration'): | |
| output += f"**Duration:** {song.get('duration')}s\n" | |
| # Show first 200 chars of prompt | |
| prompt = song.get('prompt', '') | |
| if prompt: | |
| output += f"**Preview:** {prompt[:200]}...\n" | |
| output += "\n---\n\n" | |
| # Summary | |
| output += f"**✅ Generation complete!**\n\n" | |
| output += "**To save your songs:**\n" | |
| output += "1. Right-click 'Listen Now' links → 'Save link as...'\n" | |
| output += "2. Or use the download links above\n" | |
| output += "3. Note the Track IDs for reference\n" | |
| elif status in ["PENDING", "PROCESSING", "RUNNING"]: | |
| output += f"\n**⏳ Song is still being generated...**\n" | |
| output += "Check again in 30-60 seconds\n" | |
| output += f"This usually takes 1-3 minutes total\n" | |
| elif status == "SUCCESS": | |
| output += f"\n**✅ Generation succeeded!**\n" | |
| output += "Check raw data below for song URLs\n" | |
| elif status == "FAILED": | |
| error_msg = task_data.get("errorMessage", "Unknown error") | |
| output += f"\n**❌ Generation failed:** {error_msg}\n" | |
| else: | |
| output += f"\n**⚠️ Unknown status:** {status}\n" | |
| output += "Check raw data below\n" | |
| # Always show premium info | |
| output += f"\n**💡 Tip:** Songs may take 2-3 minutes to fully process\n" | |
| output += f"**📞 Callback sent to:** https://1hit.no/callback.php\n" | |
| output += f"**🔗 Viewer:** [https://1hit.no/viewer.php?task_id={task_id}](https://1hit.no/viewer.php?task_id={task_id})\n" | |
| return output, raw_json | |
| except Exception as e: | |
| return f"❌ Error checking task: {str(e)}", "" | |
| # Create the app | |
| with gr.Blocks() as app: | |
| gr.Markdown("# 🎵 Suno Song Generator") | |
| # Store current task ID | |
| current_task_id = gr.State(value="") | |
| with gr.Tab("🎶 Generate Song"): | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| # Inputs | |
| lyrics_text = gr.Textbox( | |
| label="Lyrics (leave empty for instrumental)", | |
| placeholder="[Verse 1]\nType your lyrics here...", | |
| lines=8 | |
| ) | |
| style = gr.Textbox( | |
| label="Music Style", | |
| value="Pop", | |
| placeholder="e.g., Rock, Jazz, Electronic" | |
| ) | |
| title = gr.Textbox( | |
| label="Song Title", | |
| value="My Song" | |
| ) | |
| with gr.Row(): | |
| instrumental = gr.Checkbox(label="Instrumental Only") | |
| model = gr.Dropdown( | |
| choices=["V5", "V4_5PLUS", "V4_5ALL", "V4_5", "V4"], | |
| value="V4_5ALL", | |
| label="Model" | |
| ) | |
| submit_btn = gr.Button("🚀 Generate Song", variant="primary") | |
| gr.Markdown(""" | |
| **Instructions:** | |
| 1. Enter lyrics (or blank for instrumental) | |
| 2. Set style & title | |
| 3. Click Generate | |
| 4. **Copy the Task ID** | |
| 5. Check status in next tab | |
| """) | |
| with gr.Column(scale=2): | |
| # Submission output | |
| submission_output = gr.Markdown( | |
| value="### Ready to generate!\n\nFill in the form and click 'Generate Song'" | |
| ) | |
| with gr.Tab("🔍 Check Status"): | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| gr.Markdown("### Check Song Status") | |
| # Task ID input (auto-fills from generation) | |
| status_task_id = gr.Textbox( | |
| label="Task ID", | |
| placeholder="Paste your Task ID here", | |
| info="Will auto-fill from generated task" | |
| ) | |
| check_btn = gr.Button("🔍 Check Status Now", variant="primary") | |
| auto_check = gr.Checkbox(label="Auto-check every 30s", value=False) | |
| gr.Markdown(""" | |
| **What to expect:** | |
| - **TEXT_SUCCESS**: Songs ready! Listen/download | |
| - **PENDING/PROCESSING**: Still generating | |
| - Check every 30s until ready | |
| """) | |
| with gr.Column(scale=2): | |
| # Status output | |
| status_output = gr.Markdown( | |
| value="### Enter a Task ID to check status" | |
| ) | |
| # Raw data in accordion | |
| with gr.Accordion("📊 Raw API Response (Debug)", open=False): | |
| raw_output = gr.Markdown( | |
| value="*Raw JSON will appear here when you check status*" | |
| ) | |
| with gr.Tab("📋 Instructions"): | |
| gr.Markdown(""" | |
| ## 🎵 Complete Workflow | |
| ### 1. Generate Song | |
| - Enter lyrics & settings | |
| - Click **Generate Song** | |
| - **IMMEDIATELY get a Task ID** | |
| - Copy the Task ID | |
| ### 2. Check Status | |
| - Switch to **Check Status** tab | |
| - Task ID auto-fills | |
| - Click **Check Status Now** | |
| - Wait for **TEXT_SUCCESS** status | |
| ### 3. Download Songs | |
| - When **TEXT_SUCCESS** appears: | |
| - **Listen Now** links with audio players | |
| - **Download** links for MP3 files | |
| - Cover art images | |
| - Track IDs for reference | |
| ### 4. Tips | |
| - Processing: **1-3 minutes** | |
| - Check every **30 seconds** | |
| - Use **Auto-check** for convenience | |
| - Raw data available for debugging | |
| ### 5. Status Meanings | |
| - **TEXT_SUCCESS**: ✅ Songs ready! | |
| - **PENDING/PROCESSING**: ⏳ Still working | |
| - **SUCCESS**: Check raw data | |
| - **FAILED**: Try again | |
| ### 6. Callback System | |
| - Results also sent to: https://1hit.no/callback.php | |
| - View all results: https://1hit.no/viewer.php | |
| """) | |
| # Auto-fill status tab with task ID | |
| def update_status_field(task_id): | |
| return task_id | |
| current_task_id.change( | |
| fn=update_status_field, | |
| inputs=[current_task_id], | |
| outputs=[status_task_id] | |
| ) | |
| # Generate song | |
| def on_generate(lyrics, style, title, instrumental, model): | |
| output, task_id = submit_song_generation(lyrics, style, title, instrumental, model) | |
| if task_id: | |
| return output, task_id, task_id # Also updates status_task_id | |
| return output, "", "" # Clear if error | |
| submit_btn.click( | |
| fn=on_generate, | |
| inputs=[lyrics_text, style, title, instrumental, model], | |
| outputs=[submission_output, current_task_id, status_task_id] | |
| ) | |
| # Check status | |
| def on_check(task_id, show_raw): | |
| output, raw = check_song_status(task_id, show_raw) | |
| return output, raw | |
| check_btn.click( | |
| fn=on_check, | |
| inputs=[status_task_id, gr.State(True)], # Always show raw | |
| outputs=[status_output, raw_output] | |
| ) | |
| if __name__ == "__main__": | |
| print("🚀 Starting Suno Song Generator") | |
| print(f"🔑 SunoKey: {'✅ Set' if SUNO_KEY else '❌ Not set'}") | |
| print("🌐 Open your browser to: http://localhost:7860") | |
| app.launch(server_name="0.0.0.0", server_port=7860, share=False) |