Spaces:
Running
Running
| import gradio as gr | |
| import requests | |
| import os | |
| import json | |
| import uuid | |
| from datetime import datetime | |
| # Load Suno API key from environment variable | |
| SUNO_KEY = os.environ.get("SUNO_KEY", os.environ.get("SUNOKEY", "")) | |
| # In production, use your actual public URL | |
| # For Spaces, get from environment or use ngrok for local testing | |
| SPACE_URL = os.environ.get("SPACE_URL", "https://your-username.hf.space") | |
| # Store tasks with timestamps | |
| tasks_db = {} | |
| def generate_lyrics(prompt, callBackUrl=""): | |
| """Submit lyrics generation task with callback URL""" | |
| if not SUNO_KEY: | |
| return "β Error: SUNO_KEY environment variable not set" | |
| # Generate unique task ID | |
| task_id = str(uuid.uuid4())[:8] | |
| # Use provided callback URL or default to your Space URL | |
| callback_url = callBackUrl or f"{SPACE_URL}/callback" | |
| url = "https://api.sunoapi.org/api/v1/lyrics" | |
| headers = { | |
| "Authorization": f"Bearer {SUNO_KEY}", | |
| "Content-Type": "application/json" | |
| } | |
| payload = { | |
| "prompt": prompt, | |
| "callBackUrl": callback_url, | |
| "customTaskId": task_id # Optional: track your own ID | |
| } | |
| try: | |
| resp = requests.post(url, headers=headers, json=payload, timeout=30) | |
| data = resp.json() | |
| if resp.status_code == 200 and data.get("code") == 200: | |
| api_task_id = data["data"]["taskId"] | |
| # Store task info | |
| tasks_db[task_id] = { | |
| "api_task_id": api_task_id, | |
| "prompt": prompt, | |
| "status": "submitted", | |
| "submitted_at": datetime.now().isoformat(), | |
| "callback_received": False, | |
| "lyrics": None | |
| } | |
| return f"""β Task Submitted! | |
| Your Task ID: {task_id} | |
| API Task ID: {api_task_id} | |
| Callback URL: {callback_url} | |
| π Prompt: {prompt} | |
| β³ Processing... The results will be sent to the callback URL. | |
| You can also poll manually using your Task ID above.""" | |
| else: | |
| error_msg = data.get("msg", "Unknown error") | |
| return f"β API Error: {error_msg} (Code: {data.get('code')})" | |
| except Exception as e: | |
| return f"β Error: {str(e)}" | |
| def poll_task(task_id): | |
| """Manual polling as fallback""" | |
| if not SUNO_KEY: | |
| return "β Error: SUNO_KEY not configured" | |
| if task_id not in tasks_db: | |
| return "β Task ID not found. Please submit a task first." | |
| task_info = tasks_db[task_id] | |
| api_task_id = task_info["api_task_id"] | |
| url = f"https://api.sunoapi.org/api/v1/lyrics/details?taskId={api_task_id}" | |
| headers = {"Authorization": f"Bearer {SUNO_KEY}"} | |
| try: | |
| resp = requests.get(url, headers=headers, timeout=30) | |
| data = resp.json() | |
| if resp.status_code == 200 and data.get("code") == 200: | |
| task_data = data["data"] | |
| status = task_data.get("status", "unknown") | |
| tasks_db[task_id]["status"] = status | |
| if status == "completed" and "data" in task_data: | |
| lyrics_data = task_data["data"] | |
| tasks_db[task_id]["lyrics"] = lyrics_data | |
| tasks_db[task_id]["callback_received"] = True | |
| return format_lyrics(lyrics_data, task_id) | |
| elif status == "failed": | |
| return f"β Task failed: {task_data.get('error', 'Unknown error')}" | |
| else: | |
| return f"β³ Status: {status}\nLast checked: {datetime.now().strftime('%H:%M:%S')}" | |
| else: | |
| return f"β Polling error: {data.get('msg', 'Unknown error')}" | |
| except Exception as e: | |
| return f"β Error: {str(e)}" | |
| def format_lyrics(lyrics_data, task_id): | |
| """Format lyrics for display""" | |
| output = [f"π΅ **Task {task_id} - Generated Lyrics**", ""] | |
| for i, item in enumerate(lyrics_data, 1): | |
| title = item.get('title', f'Variant {i}') | |
| text = item.get('text', 'No lyrics generated') | |
| output.append(f"**Variant {i}: {title}**") | |
| output.append("```") | |
| output.append(text) | |
| output.append("```") | |
| output.append("---") | |
| output.append(f"\nβ Generated at: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") | |
| return "\n".join(output) | |
| def list_tasks(): | |
| """Show all submitted tasks""" | |
| if not tasks_db: | |
| return "No tasks submitted yet." | |
| output = ["π **Submitted Tasks:**", ""] | |
| for task_id, info in tasks_db.items(): | |
| status_icon = "β " if info["callback_received"] else "β³" | |
| output.append(f"{status_icon} **{task_id}** - {info['status']}") | |
| output.append(f" Prompt: {info['prompt'][:50]}...") | |
| output.append(f" Submitted: {info['submitted_at']}") | |
| output.append("") | |
| return "\n".join(output) | |
| # WEBHOOK ENDPOINT (for receiving callbacks) | |
| def webhook_callback(request: gr.Request): | |
| """Handle incoming webhook from Suno API""" | |
| try: | |
| # Try to get JSON data | |
| data = request.json() | |
| if not data: | |
| # Try form data | |
| data = dict(request.form) | |
| print(f"π₯ Received webhook: {json.dumps(data, indent=2)}") | |
| # Extract task info from webhook | |
| # Suno API format might vary - adjust based on actual response | |
| if "data" in data and "taskId" in data["data"]: | |
| api_task_id = data["data"]["taskId"] | |
| status = data["data"].get("status", "unknown") | |
| # Find our task by API task ID | |
| for task_id, task_info in tasks_db.items(): | |
| if task_info["api_task_id"] == api_task_id: | |
| task_info["status"] = status | |
| task_info["callback_received"] = True | |
| if status == "completed" and "data" in data["data"]: | |
| task_info["lyrics"] = data["data"]["data"] | |
| print(f"β Lyrics received for task {task_id}") | |
| return {"status": "success", "message": f"Updated task {task_id}"} | |
| return {"status": "error", "message": "Task not found"} | |
| except Exception as e: | |
| print(f"β Webhook error: {e}") | |
| return {"status": "error", "message": str(e)} | |
| # Gradio Interface | |
| with gr.Blocks(theme=gr.themes.Soft(), title="Suno Lyrics Generator") as app: | |
| gr.Markdown("# π΅ Suno AI Lyrics Generator") | |
| gr.Markdown("Generate song lyrics with webhook support") | |
| with gr.Tabs(): | |
| with gr.TabItem("π€ Generate"): | |
| with gr.Row(): | |
| with gr.Column(): | |
| prompt = gr.Textbox( | |
| label="Lyrics Prompt", | |
| placeholder="A romantic ballad about stargazing...", | |
| lines=3 | |
| ) | |
| # Optional: Custom callback URL | |
| callback_url = gr.Textbox( | |
| label="Callback URL (Optional)", | |
| value=f"{SPACE_URL}/callback", | |
| info="Where Suno should send results. Leave as default for Spaces." | |
| ) | |
| submit_btn = gr.Button("β¨ Generate Lyrics", variant="primary") | |
| gr.Markdown("### βΉοΈ Instructions:") | |
| gr.Markdown(""" | |
| 1. Enter your lyrics prompt | |
| 2. Click Generate | |
| 3. Save your Task ID | |
| 4. Check status in the "Poll Tasks" tab | |
| 5. Results will arrive via webhook automatically | |
| """) | |
| with gr.Column(): | |
| output = gr.Textbox( | |
| label="Submission Result", | |
| lines=10, | |
| interactive=False | |
| ) | |
| submit_btn.click( | |
| generate_lyrics, | |
| inputs=[prompt, callback_url], | |
| outputs=output | |
| ) | |
| with gr.TabItem("π Poll Tasks"): | |
| with gr.Row(): | |
| with gr.Column(): | |
| task_id_input = gr.Textbox( | |
| label="Your Task ID", | |
| placeholder="Enter the Task ID from generation step" | |
| ) | |
| poll_btn = gr.Button("π Check Status", variant="primary") | |
| gr.Markdown("---") | |
| refresh_btn = gr.Button("π List All Tasks") | |
| tasks_list = gr.Textbox(label="All Tasks", lines=10) | |
| with gr.Column(): | |
| poll_result = gr.Textbox( | |
| label="Task Status", | |
| lines=15, | |
| interactive=False | |
| ) | |
| poll_btn.click( | |
| poll_task, | |
| inputs=task_id_input, | |
| outputs=poll_result | |
| ) | |
| refresh_btn.click( | |
| list_tasks, | |
| inputs=None, | |
| outputs=tasks_list | |
| ) | |
| with gr.TabItem("βοΈ Webhook Info"): | |
| gr.Markdown("### π Webhook Configuration") | |
| gr.Markdown(f""" | |
| **Your Webhook URL:** `{SPACE_URL}/callback` | |
| **For Local Development:** | |
| 1. Use [ngrok](https://ngrok.com/): `ngrok http 7860` | |
| 2. Update callback URL: `https://your-ngrok-url.ngrok.io/callback` | |
| **For Hugging Face Spaces:** | |
| - The URL above should work automatically | |
| - Make sure your Space is public or has network access | |
| """) | |
| webhook_status = gr.Textbox( | |
| label="Last Webhook Status", | |
| value="No webhooks received yet", | |
| lines=5 | |
| ) | |
| # Register webhook endpoint | |
| # Note: In production, you'd set up proper route handling | |
| # For Gradio, we can simulate with a POST endpoint | |
| app.post("/callback")(webhook_callback) | |
| # Launch configuration | |
| if __name__ == "__main__": | |
| # For local testing with webhooks | |
| import socket | |
| # Get local IP for ngrok compatibility | |
| try: | |
| s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) | |
| s.connect(("8.8.8.8", 80)) | |
| local_ip = s.getsockname()[0] | |
| s.close() | |
| print(f"π Local IP: {local_ip}") | |
| print(f"π Webhook URL: http://{local_ip}:7860/callback") | |
| except: | |
| pass | |
| app.launch( | |
| server_name="0.0.0.0", | |
| server_port=7860, | |
| share=False, | |
| show_error=True | |
| ) |