haraberget commited on
Commit
2eaa647
·
verified ·
1 Parent(s): 5621d80

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +242 -95
app.py CHANGED
@@ -1,160 +1,307 @@
1
  import gradio as gr
2
  import requests
3
  import os
4
- import time
 
 
5
 
6
  # Load Suno API key from environment variable
7
- SUNO_KEY = os.environ.get("SUNO_KEY") # Changed to match typical naming
8
 
9
- # Check if API key exists
10
- if not SUNO_KEY:
11
- print("⚠️ Warning: SUNO_KEY environment variable not set!")
12
 
13
- # Store tasks in a dictionary for multiple users (better than global)
14
- user_tasks = {}
15
 
16
- def generate_lyrics(prompt, username="default"):
17
- """Submit lyrics generation task"""
18
  if not SUNO_KEY:
19
- return "Error: API key not configured. Please set SUNO_KEY environment variable."
 
 
 
 
 
 
20
 
21
  url = "https://api.sunoapi.org/api/v1/lyrics"
22
  headers = {
23
  "Authorization": f"Bearer {SUNO_KEY}",
24
  "Content-Type": "application/json"
25
  }
 
26
  payload = {
27
  "prompt": prompt,
28
- "callBackUrl": ""
 
29
  }
30
-
31
  try:
32
  resp = requests.post(url, headers=headers, json=payload, timeout=30)
33
- resp.raise_for_status()
34
  data = resp.json()
35
 
36
- if data.get("code") == 200 and "data" in data:
37
- task_id = data["data"]["taskId"]
38
- user_tasks[username] = task_id
39
- return f"✅ Submitted!\nTask ID: {task_id}\nPrompt: {prompt[:50]}..."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
40
  else:
41
- return f"❌ API Error: {data.get('message', 'Unknown error')}"
 
42
 
43
- except requests.exceptions.RequestException as e:
44
- return f"❌ Connection Error: {str(e)}"
45
  except Exception as e:
46
- return f"❌ Unexpected Error: {str(e)}"
47
 
48
- def poll_task(username="default"):
49
- """Poll the current user's task"""
50
  if not SUNO_KEY:
51
- return "Error: API key not configured."
 
 
 
52
 
53
- task_id = user_tasks.get(username)
54
- if not task_id:
55
- return "No task submitted yet! Generate lyrics first."
56
 
57
- url = f"https://api.sunoapi.org/api/v1/lyrics/details?taskId={task_id}"
58
  headers = {"Authorization": f"Bearer {SUNO_KEY}"}
59
 
60
  try:
61
  resp = requests.get(url, headers=headers, timeout=30)
62
- resp.raise_for_status()
63
  data = resp.json()
64
 
65
- if data.get("code") == 200 and "data" in data:
66
  task_data = data["data"]
67
  status = task_data.get("status", "unknown")
 
68
 
69
  if status == "completed" and "data" in task_data:
70
  lyrics_data = task_data["data"]
71
- output_lines = []
 
72
 
73
- for i, item in enumerate(lyrics_data, 1):
74
- output_lines.append(f"🎵 Variant {i}: {item.get('title', 'Untitled')}")
75
- output_lines.append(f"{item.get('text', 'No lyrics')}")
76
- output_lines.append("-" * 40)
77
-
78
- return "\n".join(output_lines)
79
  else:
80
- return f"⏳ Status: {status}\nTask ID: {task_id}\n\nCheck back in a few seconds..."
81
  else:
82
- return f"❌ API Error: {data.get('message', 'Unknown error')}"
83
 
84
- except requests.exceptions.RequestException as e:
85
- return f"❌ Connection Error: {str(e)}"
86
 
87
- def auto_poll_task(username="default"):
88
- """Auto-poll with loading animation"""
89
- if username not in user_tasks:
90
- return "No task submitted yet!"
91
 
92
- result = poll_task(username)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
93
 
94
- # If still processing, show animation
95
- if "⏳ Status: processing" in result:
96
- return f"🔄 Processing...\n{result}"
 
 
 
 
97
 
98
- return result
99
 
100
- # Enhanced Gradio interface
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
101
  with gr.Blocks(theme=gr.themes.Soft(), title="Suno Lyrics Generator") as app:
102
  gr.Markdown("# 🎵 Suno AI Lyrics Generator")
103
- gr.Markdown("Generate song lyrics using Suno's AI API")
104
-
105
- with gr.Row():
106
- with gr.Column(scale=2):
107
- prompt_input = gr.Textbox(
108
- label="Lyrics Prompt",
109
- placeholder="Enter your lyrics idea (e.g., 'A love song about Paris in spring')",
110
- lines=3
111
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
112
 
 
 
 
 
 
 
 
113
  with gr.Row():
114
- submit_btn = gr.Button("✨ Generate Lyrics", variant="primary")
115
- poll_btn = gr.Button("🔄 Check Status", variant="secondary")
116
- auto_poll_btn = gr.Button("🔁 Auto Refresh", variant="secondary")
 
 
 
 
 
 
 
117
 
118
- gr.Markdown("### How to use:")
119
- gr.Markdown("""
120
- 1. Enter your lyrics idea
121
- 2. Click **Generate Lyrics** to submit
122
- 3. Wait 10-30 seconds
123
- 4. Click **Check Status** to see results
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
124
  """)
125
 
126
- with gr.Column(scale=3):
127
- output_area = gr.Textbox(
128
- label="Output",
129
- lines=20,
130
- placeholder="Your generated lyrics will appear here..."
131
  )
132
 
133
- # Event handlers
134
- submit_btn.click(
135
- generate_lyrics,
136
- inputs=prompt_input,
137
- outputs=output_area
138
- )
139
-
140
- poll_btn.click(
141
- poll_task,
142
- inputs=None,
143
- outputs=output_area
144
- )
145
-
146
- auto_poll_btn.click(
147
- auto_poll_task,
148
- inputs=None,
149
- outputs=output_area,
150
- every=5 # Auto-refresh every 5 seconds while visible
151
- )
152
 
153
- # Launch with better configuration
154
  if __name__ == "__main__":
 
 
 
 
 
 
 
 
 
 
 
 
 
 
155
  app.launch(
156
- share=False, # Set to True for temporary public link
157
  server_name="0.0.0.0",
158
  server_port=7860,
 
159
  show_error=True
160
  )
 
1
  import gradio as gr
2
  import requests
3
  import os
4
+ import json
5
+ import uuid
6
+ from datetime import datetime
7
 
8
  # Load Suno API key from environment variable
9
+ SUNO_KEY = os.environ.get("SUNO_KEY", os.environ.get("SUNOKEY", ""))
10
 
11
+ # In production, use your actual public URL
12
+ # For Spaces, get from environment or use ngrok for local testing
13
+ SPACE_URL = os.environ.get("SPACE_URL", "https://your-username.hf.space")
14
 
15
+ # Store tasks with timestamps
16
+ tasks_db = {}
17
 
18
+ def generate_lyrics(prompt, callBackUrl=""):
19
+ """Submit lyrics generation task with callback URL"""
20
  if not SUNO_KEY:
21
+ return "Error: SUNO_KEY environment variable not set"
22
+
23
+ # Generate unique task ID
24
+ task_id = str(uuid.uuid4())[:8]
25
+
26
+ # Use provided callback URL or default to your Space URL
27
+ callback_url = callBackUrl or f"{SPACE_URL}/callback"
28
 
29
  url = "https://api.sunoapi.org/api/v1/lyrics"
30
  headers = {
31
  "Authorization": f"Bearer {SUNO_KEY}",
32
  "Content-Type": "application/json"
33
  }
34
+
35
  payload = {
36
  "prompt": prompt,
37
+ "callBackUrl": callback_url,
38
+ "customTaskId": task_id # Optional: track your own ID
39
  }
40
+
41
  try:
42
  resp = requests.post(url, headers=headers, json=payload, timeout=30)
 
43
  data = resp.json()
44
 
45
+ if resp.status_code == 200 and data.get("code") == 200:
46
+ api_task_id = data["data"]["taskId"]
47
+
48
+ # Store task info
49
+ tasks_db[task_id] = {
50
+ "api_task_id": api_task_id,
51
+ "prompt": prompt,
52
+ "status": "submitted",
53
+ "submitted_at": datetime.now().isoformat(),
54
+ "callback_received": False,
55
+ "lyrics": None
56
+ }
57
+
58
+ return f"""✅ Task Submitted!
59
+ Your Task ID: {task_id}
60
+ API Task ID: {api_task_id}
61
+ Callback URL: {callback_url}
62
+
63
+ 📝 Prompt: {prompt}
64
+
65
+ ⏳ Processing... The results will be sent to the callback URL.
66
+ You can also poll manually using your Task ID above."""
67
+
68
  else:
69
+ error_msg = data.get("msg", "Unknown error")
70
+ return f"❌ API Error: {error_msg} (Code: {data.get('code')})"
71
 
 
 
72
  except Exception as e:
73
+ return f"❌ Error: {str(e)}"
74
 
75
+ def poll_task(task_id):
76
+ """Manual polling as fallback"""
77
  if not SUNO_KEY:
78
+ return "Error: SUNO_KEY not configured"
79
+
80
+ if task_id not in tasks_db:
81
+ return "❌ Task ID not found. Please submit a task first."
82
 
83
+ task_info = tasks_db[task_id]
84
+ api_task_id = task_info["api_task_id"]
 
85
 
86
+ url = f"https://api.sunoapi.org/api/v1/lyrics/details?taskId={api_task_id}"
87
  headers = {"Authorization": f"Bearer {SUNO_KEY}"}
88
 
89
  try:
90
  resp = requests.get(url, headers=headers, timeout=30)
 
91
  data = resp.json()
92
 
93
+ if resp.status_code == 200 and data.get("code") == 200:
94
  task_data = data["data"]
95
  status = task_data.get("status", "unknown")
96
+ tasks_db[task_id]["status"] = status
97
 
98
  if status == "completed" and "data" in task_data:
99
  lyrics_data = task_data["data"]
100
+ tasks_db[task_id]["lyrics"] = lyrics_data
101
+ tasks_db[task_id]["callback_received"] = True
102
 
103
+ return format_lyrics(lyrics_data, task_id)
104
+ elif status == "failed":
105
+ return f"❌ Task failed: {task_data.get('error', 'Unknown error')}"
 
 
 
106
  else:
107
+ return f"⏳ Status: {status}\nLast checked: {datetime.now().strftime('%H:%M:%S')}"
108
  else:
109
+ return f"❌ Polling error: {data.get('msg', 'Unknown error')}"
110
 
111
+ except Exception as e:
112
+ return f"❌ Error: {str(e)}"
113
 
114
+ def format_lyrics(lyrics_data, task_id):
115
+ """Format lyrics for display"""
116
+ output = [f"🎵 **Task {task_id} - Generated Lyrics**", ""]
 
117
 
118
+ for i, item in enumerate(lyrics_data, 1):
119
+ title = item.get('title', f'Variant {i}')
120
+ text = item.get('text', 'No lyrics generated')
121
+
122
+ output.append(f"**Variant {i}: {title}**")
123
+ output.append("```")
124
+ output.append(text)
125
+ output.append("```")
126
+ output.append("---")
127
+
128
+ output.append(f"\n✅ Generated at: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
129
+ return "\n".join(output)
130
+
131
+ def list_tasks():
132
+ """Show all submitted tasks"""
133
+ if not tasks_db:
134
+ return "No tasks submitted yet."
135
 
136
+ output = ["📋 **Submitted Tasks:**", ""]
137
+ for task_id, info in tasks_db.items():
138
+ status_icon = "✅" if info["callback_received"] else "⏳"
139
+ output.append(f"{status_icon} **{task_id}** - {info['status']}")
140
+ output.append(f" Prompt: {info['prompt'][:50]}...")
141
+ output.append(f" Submitted: {info['submitted_at']}")
142
+ output.append("")
143
 
144
+ return "\n".join(output)
145
 
146
+ # WEBHOOK ENDPOINT (for receiving callbacks)
147
+ def webhook_callback(request: gr.Request):
148
+ """Handle incoming webhook from Suno API"""
149
+ try:
150
+ # Try to get JSON data
151
+ data = request.json()
152
+
153
+ if not data:
154
+ # Try form data
155
+ data = dict(request.form)
156
+
157
+ print(f"📥 Received webhook: {json.dumps(data, indent=2)}")
158
+
159
+ # Extract task info from webhook
160
+ # Suno API format might vary - adjust based on actual response
161
+ if "data" in data and "taskId" in data["data"]:
162
+ api_task_id = data["data"]["taskId"]
163
+ status = data["data"].get("status", "unknown")
164
+
165
+ # Find our task by API task ID
166
+ for task_id, task_info in tasks_db.items():
167
+ if task_info["api_task_id"] == api_task_id:
168
+ task_info["status"] = status
169
+ task_info["callback_received"] = True
170
+
171
+ if status == "completed" and "data" in data["data"]:
172
+ task_info["lyrics"] = data["data"]["data"]
173
+ print(f"✅ Lyrics received for task {task_id}")
174
+
175
+ return {"status": "success", "message": f"Updated task {task_id}"}
176
+
177
+ return {"status": "error", "message": "Task not found"}
178
+
179
+ except Exception as e:
180
+ print(f"❌ Webhook error: {e}")
181
+ return {"status": "error", "message": str(e)}
182
+
183
+ # Gradio Interface
184
  with gr.Blocks(theme=gr.themes.Soft(), title="Suno Lyrics Generator") as app:
185
  gr.Markdown("# 🎵 Suno AI Lyrics Generator")
186
+ gr.Markdown("Generate song lyrics with webhook support")
187
+
188
+ with gr.Tabs():
189
+ with gr.TabItem("🎤 Generate"):
190
+ with gr.Row():
191
+ with gr.Column():
192
+ prompt = gr.Textbox(
193
+ label="Lyrics Prompt",
194
+ placeholder="A romantic ballad about stargazing...",
195
+ lines=3
196
+ )
197
+
198
+ # Optional: Custom callback URL
199
+ callback_url = gr.Textbox(
200
+ label="Callback URL (Optional)",
201
+ value=f"{SPACE_URL}/callback",
202
+ info="Where Suno should send results. Leave as default for Spaces."
203
+ )
204
+
205
+ submit_btn = gr.Button("✨ Generate Lyrics", variant="primary")
206
+
207
+ gr.Markdown("### ℹ️ Instructions:")
208
+ gr.Markdown("""
209
+ 1. Enter your lyrics prompt
210
+ 2. Click Generate
211
+ 3. Save your Task ID
212
+ 4. Check status in the "Poll Tasks" tab
213
+ 5. Results will arrive via webhook automatically
214
+ """)
215
+
216
+ with gr.Column():
217
+ output = gr.Textbox(
218
+ label="Submission Result",
219
+ lines=10,
220
+ interactive=False
221
+ )
222
 
223
+ submit_btn.click(
224
+ generate_lyrics,
225
+ inputs=[prompt, callback_url],
226
+ outputs=output
227
+ )
228
+
229
+ with gr.TabItem("🔄 Poll Tasks"):
230
  with gr.Row():
231
+ with gr.Column():
232
+ task_id_input = gr.Textbox(
233
+ label="Your Task ID",
234
+ placeholder="Enter the Task ID from generation step"
235
+ )
236
+ poll_btn = gr.Button("🔍 Check Status", variant="primary")
237
+
238
+ gr.Markdown("---")
239
+ refresh_btn = gr.Button("📋 List All Tasks")
240
+ tasks_list = gr.Textbox(label="All Tasks", lines=10)
241
 
242
+ with gr.Column():
243
+ poll_result = gr.Textbox(
244
+ label="Task Status",
245
+ lines=15,
246
+ interactive=False
247
+ )
248
+
249
+ poll_btn.click(
250
+ poll_task,
251
+ inputs=task_id_input,
252
+ outputs=poll_result
253
+ )
254
+
255
+ refresh_btn.click(
256
+ list_tasks,
257
+ inputs=None,
258
+ outputs=tasks_list
259
+ )
260
+
261
+ with gr.TabItem("⚙️ Webhook Info"):
262
+ gr.Markdown("### 🌐 Webhook Configuration")
263
+ gr.Markdown(f"""
264
+ **Your Webhook URL:** `{SPACE_URL}/callback`
265
+
266
+ **For Local Development:**
267
+ 1. Use [ngrok](https://ngrok.com/): `ngrok http 7860`
268
+ 2. Update callback URL: `https://your-ngrok-url.ngrok.io/callback`
269
+
270
+ **For Hugging Face Spaces:**
271
+ - The URL above should work automatically
272
+ - Make sure your Space is public or has network access
273
  """)
274
 
275
+ webhook_status = gr.Textbox(
276
+ label="Last Webhook Status",
277
+ value="No webhooks received yet",
278
+ lines=5
 
279
  )
280
 
281
+ # Register webhook endpoint
282
+ # Note: In production, you'd set up proper route handling
283
+ # For Gradio, we can simulate with a POST endpoint
284
+ app.post("/callback")(webhook_callback)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
285
 
286
+ # Launch configuration
287
  if __name__ == "__main__":
288
+ # For local testing with webhooks
289
+ import socket
290
+
291
+ # Get local IP for ngrok compatibility
292
+ try:
293
+ s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
294
+ s.connect(("8.8.8.8", 80))
295
+ local_ip = s.getsockname()[0]
296
+ s.close()
297
+ print(f"🌐 Local IP: {local_ip}")
298
+ print(f"🌐 Webhook URL: http://{local_ip}:7860/callback")
299
+ except:
300
+ pass
301
+
302
  app.launch(
 
303
  server_name="0.0.0.0",
304
  server_port=7860,
305
+ share=False,
306
  show_error=True
307
  )