MySafeCode commited on
Commit
6ccce86
·
verified ·
1 Parent(s): f18900e

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +208 -152
app.py CHANGED
@@ -10,62 +10,64 @@ SUNO_KEY = os.environ.get("SunoKey", "")
10
  if not SUNO_KEY:
11
  print("⚠️ SunoKey not set!")
12
 
13
- def generate_song_from_text(lyrics_text, style, title, instrumental, model, custom_mode=True):
14
  """Generate a song from lyrics text"""
15
  if not SUNO_KEY:
16
  yield "❌ Error: SunoKey not configured in environment variables"
17
  return
18
 
19
  if not lyrics_text.strip():
20
- return "❌ Error: Please provide lyrics"
 
21
 
22
  if not style.strip():
23
- return "❌ Error: Please provide a music style"
 
24
 
25
  if not title.strip():
26
- return "❌ Error: Please provide a song title"
 
27
 
28
  try:
29
  # Always use custom mode for full control
30
  request_data = {
31
- "customMode": True, # Always True for our use case
32
  "instrumental": instrumental,
33
  "model": model,
34
- "callBackUrl": "http://dummy.com/callback",
35
  "style": style,
36
  "title": title,
37
  }
38
 
39
  if not instrumental:
40
- # Non-instrumental requires lyrics as prompt
41
- # Apply character limits based on model
42
- if len(lyrics_text) > 5000 and model in ["V4_5", "V4_5PLUS", "V4_5ALL", "V5"]:
43
- lyrics_text = lyrics_text[:5000]
44
- yield f"⚠️ Lyrics truncated to 5000 characters for {model} model\n\n"
45
- elif len(lyrics_text) > 3000 and model == "V4":
46
  lyrics_text = lyrics_text[:3000]
47
  yield f"⚠️ Lyrics truncated to 3000 characters for V4 model\n\n"
 
 
 
48
 
49
  request_data["prompt"] = lyrics_text
50
  else:
51
- # For instrumental, clear the prompt
52
  request_data["prompt"] = ""
53
 
54
  # Apply style length limits
55
- if len(style) > 1000 and model in ["V4_5", "V4_5PLUS", "V4_5ALL", "V5"]:
56
- style = style[:1000]
57
- elif len(style) > 200 and model == "V4":
58
  style = style[:200]
59
  yield f"⚠️ Style truncated to 200 characters for V4 model\n\n"
 
 
 
60
 
61
  # Apply title length limits
62
- if len(title) > 100 and model in ["V4_5", "V4_5PLUS", "V5"]:
63
- title = title[:100]
64
- elif len(title) > 80 and model in ["V4", "V4_5ALL"]:
65
  title = title[:80]
66
  yield f"⚠️ Title truncated to 80 characters for {model} model\n\n"
 
 
 
67
 
68
- # Update with possibly truncated values
69
  request_data["style"] = style
70
  request_data["title"] = title
71
 
@@ -76,165 +78,219 @@ def generate_song_from_text(lyrics_text, style, title, instrumental, model, cust
76
  yield f"**Instrumental:** {'Yes' if instrumental else 'No'}\n"
77
  if not instrumental:
78
  yield f"**Lyrics length:** {len(lyrics_text)} characters\n\n"
79
- yield f"⏳ Processing...\n"
80
 
81
  # Submit generation request
82
- resp = requests.post(
83
- "https://api.sunoapi.org/api/v1/generate",
84
- json=request_data,
85
- headers={
86
- "Authorization": f"Bearer {SUNO_KEY}",
87
- "Content-Type": "application/json"
88
- },
89
- timeout=30
90
- )
91
-
92
- if resp.status_code != 200:
93
- yield f"❌ Submission failed: HTTP {resp.status_code}"
94
- return
95
-
96
- data = resp.json()
97
- if data.get("code") != 200:
98
- yield f"❌ API error: {data.get('msg', 'Unknown')}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
99
  return
100
 
101
- task_id = data["data"]["taskId"]
102
- yield f"✅ **Request submitted!**\nTask ID: `{task_id}`\n\n⏳ Waiting for song generation...\n"
 
103
 
104
- # Poll for results
105
- max_attempts = 90 # 90 attempts * 10 seconds = 900 seconds (15 minutes)
106
  for attempt in range(max_attempts):
107
- time.sleep(10) # Check every 10 seconds
108
 
109
  try:
110
  # Check task status
111
- check = requests.get(
112
  f"https://api.sunoapi.org/api/v1/generate/record-info?taskId={task_id}",
113
  headers={"Authorization": f"Bearer {SUNO_KEY}"},
114
  timeout=30
115
  )
116
 
117
- if check.status_code == 200:
118
- check_data = check.json()
119
-
120
- if check_data.get("code") != 200:
121
- # If we get an error, check if task might still be processing
122
- if attempt < max_attempts - 1:
123
- continue
124
- else:
125
- yield f"⚠️ API returned error: {check_data.get('msg', 'Unknown')}"
126
- continue
127
-
128
- data_info = check_data.get("data", {})
129
- status = data_info.get("status", "PENDING")
130
 
131
- if status == "COMPLETE":
132
- # Try to get the response data
133
- response_data = data_info.get("response", {})
134
-
135
- # The response might be a JSON string or already parsed
136
- if isinstance(response_data, str):
137
- try:
138
- response_data = json.loads(response_data)
139
- except:
140
- # Try to extract URLs directly if it's not JSON
141
- yield f"🎵 **Generation Complete!**\n\n"
142
- yield f"Task ID: `{task_id}`\nStatus: {status}\n\n"
143
- yield "However, we couldn't parse the song URLs from the response.\n\n"
144
- yield "**Try this:**\n"
145
- yield "1. Go to https://sunoapi.org\n"
146
- yield "2. Log in to your account\n"
147
- yield f"3. Check your generation history for task ID: {task_id}\n"
148
- yield "4. You should find your songs there with download links\n"
149
- return
150
 
151
- # Extract songs from response
152
- songs = response_data.get("data", [])
153
-
154
- if not songs:
155
- # Try alternative structure
156
- songs = response_data.get("songs", [])
157
-
158
- if songs:
159
- output = "🎶 **Song Generation Complete!**\n\n"
160
- output += f"Generated {len(songs)} song(s)\n\n"
161
 
162
- for i, song in enumerate(songs, 1):
163
- song_title = song.get('title', f'Song {i}')
164
- stream_url = song.get('streamUrl') or song.get('stream_url')
165
- download_url = song.get('downloadUrl') or song.get('download_url')
166
 
167
- output += f"## Song {i}: {song_title}\n"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
168
 
169
- if stream_url:
170
- output += f"**Stream URL:** {stream_url}\n\n"
171
- output += f"**Listen Now:** [Click to Stream]({stream_url})\n\n"
172
-
173
- # Add audio player for streaming
174
- output += f"""<audio controls style="width: 100%; margin: 10px 0;">
175
- <source src="{stream_url}" type="audio/mpeg">
176
- Your browser does not support the audio element.
177
- </audio>\n\n"""
178
- else:
179
- output += "⚠️ Stream URL not available yet\n\n"
180
 
181
- if download_url:
182
- output += f"**Download URL:** {download_url}\n\n"
183
- output += f"**Download:** [Click to Download]({download_url})\n\n"
184
- else:
185
- output += "⚠️ Download URL not available yet (check back in 2-3 minutes)\n\n"
 
 
 
 
 
 
186
 
187
- output += "---\n\n"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
188
 
189
- output += f"⏱️ Generated in about {(attempt + 1) * 10} seconds\n\n"
190
- output += "**Tips:**\n"
191
- output += "- Stream URLs work immediately\n"
192
- output += "- Download URLs may take 2-3 minutes to become available\n"
193
- output += "- Files are retained for 15 days\n\n"
194
- output += "**If URLs don't work:**\n"
195
- output += f"1. Visit https://sunoapi.org\n"
196
- output += f"2. Log in and check your generation history\n"
197
- output += f"3. Look for task ID: `{task_id}`\n"
198
 
199
- yield output
200
- else:
201
- # No songs found in response
202
- yield f"🎵 **Generation Complete!**\n\n"
203
- yield f"Task ID: `{task_id}`\nStatus: {status}\n\n"
204
- yield "**To access your songs:**\n"
205
- yield "1. Go to https://sunoapi.org\n"
206
- yield "2. Log in to your account\n"
207
- yield "3. Check your generation history\n"
208
- yield f"4. Look for task ID: `{task_id}`\n"
209
- yield "\nThe songs should be available there with download links."
210
- return
211
-
212
- elif status == "FAILED":
213
- error = data_info.get("errorMessage", "Unknown error")
214
- yield f"❌ Task failed: {error}"
215
- return
216
 
217
  else:
218
- # Still processing (PENDING or PROCESSING)
219
- if attempt % 3 == 0: # Update every 30 seconds
220
- yield f" Status: {status}\n"
221
- yield f"Attempt: {attempt + 1}/{max_attempts}\n"
222
- yield f"Task ID: `{task_id}`\n\n"
223
- yield "Still processing...\n\n"
224
- yield "**Typical timing:**\n"
225
- yield "- 30-40 seconds: Stream URL ready\n"
226
- yield "- 2-3 minutes: Download URL ready\n"
227
- yield "- Up to 5 minutes for longer songs\n"
228
  else:
229
- yield f"⚠️ Check error: HTTP {check.status_code} - Task might still be processing"
 
 
 
 
 
 
 
230
 
231
  except Exception as e:
232
- yield f"⚠️ Error checking status: {str(e)} - Will try again..."
 
 
233
 
234
- yield "⏰ Timeout after 15 minutes. Try checking your Suno API dashboard for results."
 
 
 
 
 
 
 
 
235
 
236
  except Exception as e:
237
- yield f"❌ Error: {str(e)}"
238
 
239
  def download_text_file(text):
240
  """Create downloadable text file"""
@@ -346,14 +402,14 @@ with gr.Blocks(title="Suno Song Generator", theme="soft") as app:
346
  5. Click Generate Song!
347
 
348
  **Tips:**
349
- - For best results, use structured lyrics with verses/chorus
350
- - Style examples: "Pop", "Rock guitar solo", "Jazz piano", "Electronic dance"
351
  - V5: Latest model, best quality
352
  - V4_5ALL: Good balance, up to 8 minutes
353
- - Instrumental: Check for music only
354
 
355
  **Generation time:**
356
- - 30-40s: Stream URL ready
357
  - 2-3 min: Download URL ready
358
  - Up to 5 min for longer songs
359
  """)
 
10
  if not SUNO_KEY:
11
  print("⚠️ SunoKey not set!")
12
 
13
+ def generate_song_from_text(lyrics_text, style, title, instrumental, model):
14
  """Generate a song from lyrics text"""
15
  if not SUNO_KEY:
16
  yield "❌ Error: SunoKey not configured in environment variables"
17
  return
18
 
19
  if not lyrics_text.strip():
20
+ yield "❌ Error: Please provide lyrics"
21
+ return
22
 
23
  if not style.strip():
24
+ yield "❌ Error: Please provide a music style"
25
+ return
26
 
27
  if not title.strip():
28
+ yield "❌ Error: Please provide a song title"
29
+ return
30
 
31
  try:
32
  # Always use custom mode for full control
33
  request_data = {
34
+ "customMode": True,
35
  "instrumental": instrumental,
36
  "model": model,
37
+ "callBackUrl": "", # Empty callback URL - we'll poll instead
38
  "style": style,
39
  "title": title,
40
  }
41
 
42
  if not instrumental:
43
+ # Apply character limits
44
+ if model == "V4" and len(lyrics_text) > 3000:
 
 
 
 
45
  lyrics_text = lyrics_text[:3000]
46
  yield f"⚠️ Lyrics truncated to 3000 characters for V4 model\n\n"
47
+ elif model in ["V4_5", "V4_5PLUS", "V4_5ALL", "V5"] and len(lyrics_text) > 5000:
48
+ lyrics_text = lyrics_text[:5000]
49
+ yield f"⚠️ Lyrics truncated to 5000 characters for {model} model\n\n"
50
 
51
  request_data["prompt"] = lyrics_text
52
  else:
 
53
  request_data["prompt"] = ""
54
 
55
  # Apply style length limits
56
+ if model == "V4" and len(style) > 200:
 
 
57
  style = style[:200]
58
  yield f"⚠️ Style truncated to 200 characters for V4 model\n\n"
59
+ elif model in ["V4_5", "V4_5PLUS", "V4_5ALL", "V5"] and len(style) > 1000:
60
+ style = style[:1000]
61
+ yield f"⚠️ Style truncated to 1000 characters for {model} model\n\n"
62
 
63
  # Apply title length limits
64
+ if model in ["V4", "V4_5ALL"] and len(title) > 80:
 
 
65
  title = title[:80]
66
  yield f"⚠️ Title truncated to 80 characters for {model} model\n\n"
67
+ elif model in ["V4_5", "V4_5PLUS", "V5"] and len(title) > 100:
68
+ title = title[:100]
69
+ yield f"⚠️ Title truncated to 100 characters for {model} model\n\n"
70
 
 
71
  request_data["style"] = style
72
  request_data["title"] = title
73
 
 
78
  yield f"**Instrumental:** {'Yes' if instrumental else 'No'}\n"
79
  if not instrumental:
80
  yield f"**Lyrics length:** {len(lyrics_text)} characters\n\n"
 
81
 
82
  # Submit generation request
83
+ try:
84
+ resp = requests.post(
85
+ "https://api.sunoapi.org/api/v1/generate",
86
+ json=request_data,
87
+ headers={
88
+ "Authorization": f"Bearer {SUNO_KEY}",
89
+ "Content-Type": "application/json"
90
+ },
91
+ timeout=30
92
+ )
93
+
94
+ if resp.status_code != 200:
95
+ yield f"❌ Submission failed: HTTP {resp.status_code}"
96
+ return
97
+
98
+ data = resp.json()
99
+ if data.get("code") != 200:
100
+ yield f"❌ API error: {data.get('msg', 'Unknown')}"
101
+ return
102
+
103
+ task_id = data["data"]["taskId"]
104
+ yield f"✅ **Request submitted successfully!**\n"
105
+ yield f"**Task ID:** `{task_id}`\n\n"
106
+ yield f"⏳ Song generation started...\n\n"
107
+ yield "**What to do next:**\n"
108
+ yield "1. Keep this window open while the song generates\n"
109
+ yield "2. We'll automatically check the status every 10 seconds\n"
110
+ yield "3. Generation typically takes 1-3 minutes\n"
111
+ yield "4. When complete, you'll get streaming and download links\n\n"
112
+
113
+ except Exception as e:
114
+ yield f"❌ Error submitting request: {str(e)}"
115
  return
116
 
117
+ # Poll for completion - with better error handling
118
+ max_attempts = 60 # 60 attempts * 10 seconds = 10 minutes
119
+ last_status = ""
120
 
 
 
121
  for attempt in range(max_attempts):
122
+ time.sleep(10)
123
 
124
  try:
125
  # Check task status
126
+ check_resp = requests.get(
127
  f"https://api.sunoapi.org/api/v1/generate/record-info?taskId={task_id}",
128
  headers={"Authorization": f"Bearer {SUNO_KEY}"},
129
  timeout=30
130
  )
131
 
132
+ if check_resp.status_code == 200:
133
+ check_data = check_resp.json()
 
 
 
 
 
 
 
 
 
 
 
134
 
135
+ if check_data.get("code") == 200:
136
+ data_info = check_data.get("data", {})
137
+ current_status = data_info.get("status", "UNKNOWN")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
138
 
139
+ # Only show status update if it changed
140
+ if current_status != last_status:
141
+ last_status = current_status
 
 
 
 
 
 
 
142
 
143
+ if current_status == "COMPLETE":
144
+ # Try to parse the response
145
+ response_data = data_info.get("response", {})
 
146
 
147
+ # Response might be a JSON string
148
+ if isinstance(response_data, str):
149
+ try:
150
+ response_data = json.loads(response_data)
151
+ except json.JSONDecodeError:
152
+ # If it's not JSON, it might be a simple string
153
+ yield f"✅ **Generation Complete!**\n\n"
154
+ yield f"**Status:** {current_status}\n"
155
+ yield f"**Task ID:** `{task_id}`\n\n"
156
+ yield "**To access your song:**\n"
157
+ yield "1. Visit https://sunoapi.org\n"
158
+ yield "2. Log in to your account\n"
159
+ yield "3. Go to 'Generation History'\n"
160
+ yield "4. Find your song by Task ID\n"
161
+ return
162
 
163
+ # Look for songs in different possible locations
164
+ songs = []
 
 
 
 
 
 
 
 
 
165
 
166
+ # Try different possible structures
167
+ if isinstance(response_data, dict):
168
+ songs = response_data.get("data", [])
169
+ if not songs:
170
+ songs = response_data.get("songs", [])
171
+ if not songs:
172
+ # Check if response_data itself is a song list
173
+ if isinstance(response_data.get("0"), dict):
174
+ songs = [response_data.get("0")]
175
+ if isinstance(response_data.get("1"), dict):
176
+ songs.append(response_data.get("1"))
177
 
178
+ elif isinstance(response_data, list):
179
+ songs = response_data
180
+
181
+ if songs:
182
+ yield "🎶 **SONG GENERATION COMPLETE!** 🎶\n\n"
183
+ yield f"Generated {len(songs)} song(s)\n\n"
184
+
185
+ for i, song in enumerate(songs, 1):
186
+ if isinstance(song, dict):
187
+ song_title = song.get('title', f'Song {i}')
188
+ stream_url = song.get('streamUrl') or song.get('stream_url')
189
+ download_url = song.get('downloadUrl') or song.get('download_url')
190
+
191
+ yield f"## 🎵 Song {i}: {song_title}\n"
192
+
193
+ if stream_url:
194
+ yield f"**Stream URL:** {stream_url}\n"
195
+ yield f"**Listen Now:** [Click to Stream]({stream_url})\n\n"
196
+
197
+ # Audio player
198
+ yield f"""<audio controls style="width: 100%; margin: 10px 0; padding: 10px; background: #f0f0f0; border-radius: 5px;">
199
+ <source src="{stream_url}" type="audio/mpeg">
200
+ Your browser does not support audio playback.
201
+ </audio>\n\n"""
202
+ else:
203
+ yield "⏳ Stream URL not ready yet (check back in 30 seconds)\n\n"
204
+
205
+ if download_url:
206
+ yield f"**Download URL:** {download_url}\n"
207
+ yield f"**Download:** [Click to Download]({download_url})\n\n"
208
+ else:
209
+ yield "⏳ Download URL not ready yet (usually takes 2-3 minutes)\n\n"
210
+
211
+ yield "---\n\n"
212
+
213
+ yield f"⏱️ Total generation time: {(attempt + 1) * 10} seconds\n\n"
214
+ yield "**Important:**\n"
215
+ yield "- Stream links work immediately\n"
216
+ yield "- Download links may take 2-3 minutes\n"
217
+ yield "- Files are kept for 15 days\n"
218
+ yield f"- Task ID: `{task_id}` (save this for reference)\n"
219
+ return
220
+ else:
221
+ # No songs found in response
222
+ yield f"✅ **Generation Complete!**\n\n"
223
+ yield f"**Status:** {current_status}\n"
224
+ yield f"**Task ID:** `{task_id}`\n\n"
225
+ yield "**Response received but no song data found.**\n\n"
226
+ yield "**To access your song:**\n"
227
+ yield "1. Visit https://sunoapi.org\n"
228
+ yield "2. Log in to your account\n"
229
+ yield "3. Check 'Generation History'\n"
230
+ yield "4. Look for this Task ID\n"
231
+ return
232
 
233
+ elif current_status == "FAILED":
234
+ error_msg = data_info.get("errorMessage", "Unknown error")
235
+ yield f" **Generation Failed**\n\n"
236
+ yield f"**Status:** {current_status}\n"
237
+ yield f"**Error:** {error_msg}\n"
238
+ yield f"**Task ID:** `{task_id}`\n\n"
239
+ yield "Please try again with different parameters."
240
+ return
 
241
 
242
+ elif current_status in ["PENDING", "PROCESSING"]:
243
+ yield f"⏳ **Status Update**\n\n"
244
+ yield f"**Current Status:** {current_status}\n"
245
+ yield f"**Task ID:** `{task_id}`\n"
246
+ yield f"**Check:** {attempt + 1}/{max_attempts}\n\n"
247
+ yield "**Estimated time remaining:**\n"
248
+ yield "- Stream URL: 30-60 seconds\n"
249
+ yield "- Download URL: 2-3 minutes\n"
250
+ yield "\nWe'll check again in 10 seconds...\n"
251
+
252
+ else:
253
+ yield f"📊 **Status:** {current_status}\n"
254
+ yield f"**Task ID:** `{task_id}`\n"
255
+ yield f"**Check:** {attempt + 1}/{max_attempts}\n\n"
256
+ yield "Still processing...\n"
 
 
257
 
258
  else:
259
+ # API returned error code
260
+ error_msg = check_data.get("msg", "Unknown error")
261
+ yield f"⚠️ **API Error**\n\n"
262
+ yield f"**Code:** {check_data.get('code')}\n"
263
+ yield f"**Message:** {error_msg}\n"
264
+ yield f"**Task ID:** `{task_id}`\n\n"
265
+ yield "Will continue checking...\n"
266
+
 
 
267
  else:
268
+ yield f"⚠️ **HTTP Error {check_resp.status_code}**\n\n"
269
+ yield f"Failed to check status. Will retry in 10 seconds...\n"
270
+ yield f"Check: {attempt + 1}/{max_attempts}\n"
271
+
272
+ except requests.exceptions.Timeout:
273
+ yield f"⏱️ **Timeout checking status**\n\n"
274
+ yield "The status check timed out. Will try again in 10 seconds...\n"
275
+ yield f"Check: {attempt + 1}/{max_attempts}\n"
276
 
277
  except Exception as e:
278
+ yield f"⚠️ **Error checking status:** {str(e)}\n\n"
279
+ yield "Will retry in 10 seconds...\n"
280
+ yield f"Check: {attempt + 1}/{max_attempts}\n"
281
 
282
+ # If we get here, we timed out
283
+ yield "⏰ **Timeout after 10 minutes**\n\n"
284
+ yield f"**Task ID:** `{task_id}`\n\n"
285
+ yield "**What to do:**\n"
286
+ yield "1. The song may still be processing\n"
287
+ yield "2. Visit https://sunoapi.org\n"
288
+ yield "3. Log in and check 'Generation History'\n"
289
+ yield "4. Look for this Task ID\n"
290
+ yield "\nSongs can sometimes take longer than expected, especially for longer tracks."
291
 
292
  except Exception as e:
293
+ yield f"❌ **Unexpected Error:** {str(e)}"
294
 
295
  def download_text_file(text):
296
  """Create downloadable text file"""
 
402
  5. Click Generate Song!
403
 
404
  **Tips:**
405
+ - Use structured lyrics with verses/chorus for best results
406
+ - Style examples: "Pop", "Rock guitar", "Jazz piano", "Electronic"
407
  - V5: Latest model, best quality
408
  - V4_5ALL: Good balance, up to 8 minutes
409
+ - Instrumental: Check for music only (no vocals)
410
 
411
  **Generation time:**
412
+ - 30-60s: Stream URL ready
413
  - 2-3 min: Download URL ready
414
  - Up to 5 min for longer songs
415
  """)