MySafeCode commited on
Commit
d4a8061
·
verified ·
1 Parent(s): 8c59959

Upload aa.py

Browse files
Files changed (1) hide show
  1. aa.py +905 -0
aa.py ADDED
@@ -0,0 +1,905 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import requests
3
+ import os
4
+ import time
5
+ import json
6
+ import hashlib
7
+ import hmac
8
+ from datetime import datetime
9
+ from urllib.parse import urlparse, parse_qs
10
+
11
+ # Suno API key
12
+ SUNO_KEY = os.environ.get("SunoKey", "")
13
+ # Secret key for ownership proofs
14
+ SECRET_SALT = "Salt" # You might want to make this configurable
15
+
16
+ if not SUNO_KEY:
17
+ print("⚠️ SunoKey not set!")
18
+
19
+ def generate_ownership_proof(task_id, title, music_id=None, custom_data=None):
20
+ """
21
+ Generate a SHA256 hash proof of ownership using the task_id as seed
22
+ and "Salt" as the secret key.
23
+ """
24
+ # Create a unique string to hash
25
+ proof_data = {
26
+ "task_id": task_id,
27
+ "title": title,
28
+ "music_id": music_id,
29
+ "timestamp": datetime.utcnow().isoformat(),
30
+ "custom": custom_data or {}
31
+ }
32
+
33
+ # Convert to string for hashing
34
+ proof_string = json.dumps(proof_data, sort_keys=True)
35
+
36
+ # Create HMAC-SHA256 using the secret salt
37
+ signature = hmac.new(
38
+ SECRET_SALT.encode('utf-8'),
39
+ proof_string.encode('utf-8'),
40
+ hashlib.sha256
41
+ ).hexdigest()
42
+
43
+ return {
44
+ "proof": signature,
45
+ "data": proof_data,
46
+ "version": "1.0"
47
+ }
48
+
49
+ def verify_ownership_proof(proof_data, task_id, title):
50
+ """
51
+ Verify an ownership proof
52
+ """
53
+ stored_signature = proof_data.get("proof")
54
+ stored_data = proof_data.get("data", {})
55
+
56
+ # Recreate the proof string
57
+ proof_string = json.dumps(stored_data, sort_keys=True)
58
+
59
+ # Recalculate signature
60
+ calculated_signature = hmac.new(
61
+ SECRET_SALT.encode('utf-8'),
62
+ proof_string.encode('utf-8'),
63
+ hashlib.sha256
64
+ ).hexdigest()
65
+
66
+ # Verify
67
+ return hmac.compare_digest(calculated_signature, stored_signature)
68
+
69
+ def create_html_receipt(song_data, task_id, ownership_proof):
70
+ """
71
+ Create an HTML receipt for download
72
+ """
73
+ timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
74
+
75
+ # Determine if title is custom (not default)
76
+ title = song_data.get('title', 'Untitled')
77
+ is_custom_title = title not in ["Generated Song", "Untitled"]
78
+
79
+ html = f"""<!DOCTYPE html>
80
+ <html lang="en">
81
+ <head>
82
+ <meta charset="UTF-8">
83
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
84
+ <title>🎵 Song Ownership Receipt - {title}</title>
85
+ <style>
86
+ body {{
87
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
88
+ line-height: 1.6;
89
+ color: #333;
90
+ max-width: 800px;
91
+ margin: 0 auto;
92
+ padding: 20px;
93
+ background: #f5f5f5;
94
+ }}
95
+ .receipt {{
96
+ background: white;
97
+ border-radius: 15px;
98
+ padding: 30px;
99
+ box-shadow: 0 4px 6px rgba(0,0,0,0.1);
100
+ }}
101
+ .header {{
102
+ text-align: center;
103
+ margin-bottom: 30px;
104
+ padding-bottom: 20px;
105
+ border-bottom: 2px solid #eee;
106
+ }}
107
+ .header h1 {{
108
+ color: #2c3e50;
109
+ margin: 0;
110
+ font-size: 2.5em;
111
+ }}
112
+ .proof-badge {{
113
+ background: {('#27ae60' if is_custom_title else '#95a5a6')};
114
+ color: white;
115
+ padding: 8px 15px;
116
+ border-radius: 20px;
117
+ display: inline-block;
118
+ margin: 10px 0;
119
+ font-weight: bold;
120
+ }}
121
+ .song-info {{
122
+ background: #f8f9fa;
123
+ border-radius: 10px;
124
+ padding: 20px;
125
+ margin: 20px 0;
126
+ }}
127
+ .song-info h2 {{
128
+ margin-top: 0;
129
+ color: #2c3e50;
130
+ }}
131
+ .info-grid {{
132
+ display: grid;
133
+ grid-template-columns: 120px 1fr;
134
+ gap: 10px;
135
+ margin: 15px 0;
136
+ }}
137
+ .info-label {{
138
+ font-weight: bold;
139
+ color: #7f8c8d;
140
+ }}
141
+ .proof-section {{
142
+ background: #fff3e0;
143
+ border-radius: 10px;
144
+ padding: 20px;
145
+ margin: 20px 0;
146
+ font-family: monospace;
147
+ word-break: break-all;
148
+ }}
149
+ .proof-section h3 {{
150
+ margin-top: 0;
151
+ color: #e67e22;
152
+ }}
153
+ .proof-hash {{
154
+ background: #2c3e50;
155
+ color: #ecf0f1;
156
+ padding: 15px;
157
+ border-radius: 5px;
158
+ overflow-x: auto;
159
+ font-size: 0.9em;
160
+ }}
161
+ .verification-note {{
162
+ background: #e8f4fd;
163
+ border-left: 4px solid #3498db;
164
+ padding: 15px;
165
+ margin: 20px 0;
166
+ }}
167
+ .footer {{
168
+ text-align: center;
169
+ margin-top: 30px;
170
+ padding-top: 20px;
171
+ border-top: 1px solid #eee;
172
+ color: #7f8c8d;
173
+ font-size: 0.9em;
174
+ }}
175
+ .download-links {{
176
+ margin: 20px 0;
177
+ }}
178
+ .download-link {{
179
+ display: inline-block;
180
+ background: #3498db;
181
+ color: white;
182
+ text-decoration: none;
183
+ padding: 10px 20px;
184
+ border-radius: 5px;
185
+ margin-right: 10px;
186
+ }}
187
+ .download-link:hover {{
188
+ background: #2980b9;
189
+ }}
190
+ .qr-placeholder {{
191
+ text-align: center;
192
+ margin: 20px 0;
193
+ }}
194
+ .qr-placeholder img {{
195
+ max-width: 150px;
196
+ border: 1px solid #ddd;
197
+ border-radius: 5px;
198
+ }}
199
+ </style>
200
+ </head>
201
+ <body>
202
+ <div class="receipt">
203
+ <div class="header">
204
+ <h1>🎵 Song Ownership Receipt</h1>
205
+ <div class="proof-badge">
206
+ {('🔒 Proof Valid - Custom Title' if is_custom_title else '⚠️ Default Title - No Proof')}
207
+ </div>
208
+ <p>Generated: {timestamp}</p>
209
+ </div>
210
+
211
+ <div class="song-info">
212
+ <h2>Song Information</h2>
213
+ <div class="info-grid">
214
+ <span class="info-label">Title:</span>
215
+ <span>{song_data.get('title', 'N/A')}</span>
216
+
217
+ <span class="info-label">Music ID:</span>
218
+ <span><code>{song_data.get('id', 'N/A')}</code></span>
219
+
220
+ <span class="info-label">Task ID:</span>
221
+ <span><code>{task_id}</code></span>
222
+
223
+ <span class="info-label">Duration:</span>
224
+ <span>{song_data.get('duration', 'N/A')}s</span>
225
+
226
+ <span class="info-label">Created:</span>
227
+ <span>{song_data.get('createTime', 'N/A')}</span>
228
+
229
+ <span class="info-label">Style:</span>
230
+ <span>{song_data.get('style', 'N/A')}</span>
231
+ </div>
232
+
233
+ <div class="download-links">
234
+ {f'<a href="{song_data.get("audioUrl", "")}" class="download-link" target="_blank">🎧 Stream Audio</a>' if song_data.get("audioUrl") else ''}
235
+ {f'<a href="{song_data.get("downloadUrl", "")}" class="download-link" target="_blank">📥 Download MP3</a>' if song_data.get("downloadUrl") else ''}
236
+ </div>
237
+ </div>
238
+
239
+ {f'''
240
+ <div class="proof-section">
241
+ <h3>🔐 Ownership Proof</h3>
242
+ <p><strong>Seed (Task ID):</strong> <code>{task_id}</code></p>
243
+ <p><strong>Signed with:</strong> Secret Salt (SHA256-HMAC)</p>
244
+ <div class="proof-hash">
245
+ <strong>Proof Hash:</strong><br>
246
+ {ownership_proof["proof"]}
247
+ </div>
248
+ <p style="margin-top: 10px; font-size: 0.9em;">
249
+ <strong>Signed Data:</strong><br>
250
+ <pre>{json.dumps(ownership_proof["data"], indent=2)}</pre>
251
+ </p>
252
+ </div>
253
+
254
+ <div class="verification-note">
255
+ <strong>🔍 How to Verify Ownership:</strong>
256
+ <ol style="margin-bottom: 0;">
257
+ <li>Keep this receipt safe - it proves you generated this song</li>
258
+ <li>To claim ownership, provide this proof hash</li>
259
+ <li>The system will verify using the same secret salt</li>
260
+ <li>Only you (with this receipt) can prove ownership</li>
261
+ </ol>
262
+ </div>
263
+ ''' if is_custom_title else '''
264
+ <div class="verification-note" style="background: #fef9e7; border-left-color: #f39c12;">
265
+ <strong>⚠️ No Ownership Proof Generated</strong>
266
+ <p>This song uses the default title. To generate an ownership proof, rename your song to a custom title.</p>
267
+ </div>
268
+ '''}
269
+
270
+ <div class="footer">
271
+ <p>📱 Save this receipt to prove ownership of your song</p>
272
+ <p>🔗 Verification URL: https://1hit.no/verify?task_id={task_id}</p>
273
+ <p><small>Receipt Version 1.0 • Anonymous Ownership System</small></p>
274
+ </div>
275
+ </div>
276
+
277
+ <script>
278
+ // Add print functionality
279
+ window.onload = function() {{
280
+ console.log('Receipt loaded - you can print or save as PDF');
281
+ }}
282
+ </script>
283
+ </body>
284
+ </html>"""
285
+
286
+ return html
287
+
288
+ def download_receipt(song_data, task_id, ownership_proof):
289
+ """
290
+ Create a downloadable receipt file
291
+ """
292
+ html_content = create_html_receipt(song_data, task_id, ownership_proof)
293
+ title = song_data.get('title', 'song').replace(' ', '_').replace('/', '_')
294
+ filename = f"receipt_{title}_{task_id[:8]}.html"
295
+
296
+ return html_content, filename
297
+
298
+ def get_task_info(task_id):
299
+ """Manually check any Suno task status"""
300
+ if not task_id:
301
+ return "❌ Please enter a Task ID"
302
+
303
+ try:
304
+ resp = requests.get(
305
+ "https://api.sunoapi.org/api/v1/generate/record-info",
306
+ headers={"Authorization": f"Bearer {SUNO_KEY}"},
307
+ params={"taskId": task_id},
308
+ timeout=30
309
+ )
310
+
311
+ if resp.status_code != 200:
312
+ return f"❌ HTTP Error {resp.status_code}\n\n{resp.text}"
313
+
314
+ data = resp.json()
315
+
316
+ # Format the response for display
317
+ output = f"## 🔍 Task Status: `{task_id}`\n\n"
318
+
319
+ if data.get("code") == 200:
320
+ task_data = data.get("data", {})
321
+ status = task_data.get("status", "UNKNOWN")
322
+
323
+ output += f"**Status:** {status}\n"
324
+ output += f"**Task ID:** `{task_data.get('taskId', 'N/A')}`\n"
325
+ output += f"**Music ID:** `{task_data.get('musicId', 'N/A')}`\n"
326
+ output += f"**Created:** {task_data.get('createTime', 'N/A')}\n"
327
+
328
+ if status == "SUCCESS":
329
+ response_data = task_data.get("response", {})
330
+
331
+ # Try to parse response (could be string or dict)
332
+ if isinstance(response_data, str):
333
+ try:
334
+ response_data = json.loads(response_data)
335
+ except:
336
+ output += f"\n**Raw Response:**\n```\n{response_data}\n```\n"
337
+ response_data = {}
338
+
339
+ # Check for song data
340
+ songs = []
341
+ if isinstance(response_data, dict):
342
+ songs = response_data.get("sunoData", [])
343
+ if not songs:
344
+ songs = response_data.get("data", [])
345
+ elif isinstance(response_data, list):
346
+ songs = response_data
347
+
348
+ if songs:
349
+ output += f"\n## 🎵 Generated Songs ({len(songs)})\n\n"
350
+
351
+ for i, song in enumerate(songs, 1):
352
+ if isinstance(song, dict):
353
+ # Check if title is custom
354
+ title = song.get('title', 'Untitled')
355
+ is_custom = title not in ["Generated Song", "Untitled"]
356
+
357
+ output += f"### Song {i} {('🔒' if is_custom else '⚠️')}\n"
358
+ output += f"**Title:** {title}\n"
359
+ output += f"**ID:** `{song.get('id', 'N/A')}`\n"
360
+
361
+ # Generate ownership proof if title is custom
362
+ if is_custom:
363
+ proof = generate_ownership_proof(task_id, title, song.get('id'))
364
+ output += f"**🔐 Proof Generated:** [Download Receipt](#)\n"
365
+ output += f"**Proof Hash:** `{proof['proof'][:16]}...`\n"
366
+
367
+ # Audio URLs
368
+ audio_url = song.get('audioUrl') or song.get('audio_url')
369
+ stream_url = song.get('streamUrl') or song.get('stream_url')
370
+ download_url = song.get('downloadUrl') or song.get('download_url')
371
+
372
+ if audio_url:
373
+ output += f"**Audio:** [Play]({audio_url}) | [Download]({audio_url})\n"
374
+ elif stream_url:
375
+ output += f"**Stream:** [Play]({stream_url})\n"
376
+
377
+ if download_url:
378
+ output += f"**Download:** [MP3]({download_url})\n"
379
+
380
+ # Audio player
381
+ play_url = audio_url or stream_url
382
+ if play_url:
383
+ output += f"""\n<audio controls style="width: 100%; margin: 10px 0;">
384
+ <source src="{play_url}" type="audio/mpeg">
385
+ Your browser does not support audio.
386
+ </audio>\n"""
387
+
388
+ # Add receipt download button if custom title
389
+ if is_custom:
390
+ # Create a unique ID for this song's receipt
391
+ receipt_id = f"receipt_{task_id}_{i}"
392
+ output += f"""\n<div style="margin: 10px 0;">
393
+ <button onclick="downloadReceipt('{task_id}', {i})"
394
+ style="background: #27ae60; color: white; border: none;
395
+ padding: 10px 20px; border-radius: 5px; cursor: pointer;">
396
+ 📥 Download Ownership Receipt
397
+ </button>
398
+ </div>\n"""
399
+
400
+ output += f"**Prompt:** {song.get('prompt', 'N/A')[:100]}...\n"
401
+ output += f"**Duration:** {song.get('duration', 'N/A')}s\n"
402
+ output += f"**Created:** {song.get('createTime', 'N/A')}\n\n"
403
+ output += "---\n\n"
404
+ else:
405
+ output += "\n**No song data found in response.**\n"
406
+
407
+ elif status == "FAILED":
408
+ error_msg = task_data.get("errorMessage", "Unknown error")
409
+ output += f"\n**Error:** {error_msg}\n"
410
+
411
+ elif status in ["PENDING", "PROCESSING", "RUNNING"]:
412
+ output += f"\n**Task is still processing...**\n"
413
+ output += f"Check again in 30 seconds.\n"
414
+
415
+ else:
416
+ output += f"\n**Unknown status:** {status}\n"
417
+
418
+ else:
419
+ output += f"**API Error:** {data.get('msg', 'Unknown')}\n"
420
+
421
+ # Show raw JSON for debugging
422
+ output += "\n## 📋 Raw Response\n"
423
+ output += f"```json\n{json.dumps(data, indent=2)}\n```"
424
+
425
+ return output
426
+
427
+ except Exception as e:
428
+ return f"❌ Error checking task: {str(e)}"
429
+
430
+ def generate_song_from_text(lyrics_text, style, title, instrumental, model):
431
+ """Generate a song from lyrics text"""
432
+ if not SUNO_KEY:
433
+ yield "❌ Error: SunoKey not configured in environment variables"
434
+ return
435
+
436
+ if not lyrics_text.strip() and not instrumental:
437
+ yield "❌ Error: Please provide lyrics or select instrumental"
438
+ return
439
+
440
+ if not style.strip():
441
+ yield "❌ Error: Please provide a music style"
442
+ return
443
+
444
+ if not title.strip():
445
+ yield "❌ Error: Please provide a song title"
446
+ return
447
+
448
+ try:
449
+ # Prepare request data
450
+ request_data = {
451
+ "customMode": True,
452
+ "instrumental": instrumental,
453
+ "model": model,
454
+ "callBackUrl": "https://1hit.no/gen/cb.php",
455
+ "style": style,
456
+ "title": title,
457
+ }
458
+
459
+ if not instrumental:
460
+ # Apply character limits
461
+ if model == "V4" and len(lyrics_text) > 3000:
462
+ lyrics_text = lyrics_text[:3000]
463
+ yield f"⚠️ Lyrics truncated to 3000 characters for V4 model\n\n"
464
+ elif model in ["V4_5", "V4_5PLUS", "V4_5ALL", "V5"] and len(lyrics_text) > 5000:
465
+ lyrics_text = lyrics_text[:5000]
466
+ yield f"⚠️ Lyrics truncated to 5000 characters for {model} model\n\n"
467
+
468
+ request_data["prompt"] = lyrics_text
469
+ else:
470
+ request_data["prompt"] = ""
471
+
472
+ # Apply style length limits
473
+ if model == "V4" and len(style) > 200:
474
+ style = style[:200]
475
+ yield f"⚠️ Style truncated to 200 characters for V4 model\n\n"
476
+ elif model in ["V4_5", "V4_5PLUS", "V4_5ALL", "V5"] and len(style) > 1000:
477
+ style = style[:1000]
478
+ yield f"⚠️ Style truncated to 1000 characters for {model} model\n\n"
479
+
480
+ # Apply title length limits
481
+ if model in ["V4", "V4_5ALL"] and len(title) > 80:
482
+ title = title[:80]
483
+ yield f"⚠️ Title truncated to 80 characters for {model} model\n\n"
484
+ elif model in ["V4_5", "V4_5PLUS", "V5"] and len(title) > 100:
485
+ title = title[:100]
486
+ yield f"⚠️ Title truncated to 100 characters for {model} model\n\n"
487
+
488
+ request_data["style"] = style
489
+ request_data["title"] = title
490
+
491
+ yield f"## 🚀 Submitting Song Request\n\n"
492
+ yield f"**Title:** {title}\n"
493
+ yield f"**Style:** {style}\n"
494
+ yield f"**Model:** {model}\n"
495
+ yield f"**Instrumental:** {'Yes' if instrumental else 'No'}\n"
496
+ if not instrumental:
497
+ yield f"**Lyrics length:** {len(lyrics_text)} characters\n\n"
498
+ yield f"**Callback URL:** https://1hit.no/callback.php\n\n"
499
+
500
+ # Submit generation request
501
+ try:
502
+ resp = requests.post(
503
+ "https://api.sunoapi.org/api/v1/generate",
504
+ json=request_data,
505
+ headers={
506
+ "Authorization": f"Bearer {SUNO_KEY}",
507
+ "Content-Type": "application/json"
508
+ },
509
+ timeout=30
510
+ )
511
+
512
+ if resp.status_code != 200:
513
+ yield f"❌ Submission failed: HTTP {resp.status_code}"
514
+ yield f"\n**Response:**\n```\n{resp.text}\n```"
515
+ return
516
+
517
+ data = resp.json()
518
+ print(f"Submission response: {json.dumps(data, indent=2)}")
519
+
520
+ if data.get("code") != 200:
521
+ yield f"❌ API error: {data.get('msg', 'Unknown')}"
522
+ return
523
+
524
+ # Extract task ID from response
525
+ task_id = None
526
+ if "taskId" in data:
527
+ task_id = data["taskId"]
528
+ elif "data" in data and "taskId" in data["data"]:
529
+ task_id = data["data"]["taskId"]
530
+ elif data.get("data") and "taskId" in data.get("data", {}):
531
+ task_id = data["data"]["taskId"]
532
+
533
+ if not task_id:
534
+ yield f"❌ Could not extract Task ID from response"
535
+ yield f"\n**Raw Response:**\n```json\n{json.dumps(data, indent=2)}\n```"
536
+ return
537
+
538
+ yield f"## ✅ Request Submitted Successfully!\n\n"
539
+ yield f"**🎯 Task ID:** `{task_id}`\n\n"
540
+ yield f"**⏳ Status:** Generation started\n"
541
+ yield f"**📞 Callback:** https://1hit.no/callback.php\n\n"
542
+ yield "**What happens now:**\n"
543
+ yield "1. Suno AI generates your song (1-3 minutes)\n"
544
+ yield "2. You'll get a callback notification\n"
545
+ yield "3. Use the Task ID above to check status manually\n\n"
546
+ yield "---\n\n"
547
+ yield f"## 🔍 Check Status Manually\n\n"
548
+ yield f"Use this Task ID: `{task_id}`\n\n"
549
+ yield "**To check status:**\n"
550
+ yield "1. Copy the Task ID above\n"
551
+ yield "2. Go to 'Check Any Task' tab\n"
552
+ yield "3. Paste and click 'Check Status'\n"
553
+ yield "4. Or wait for callback notification\n\n"
554
+ yield "**Generation time:**\n"
555
+ yield "- 30-60 seconds for stream URL\n"
556
+ yield "- 2-3 minutes for download URL\n"
557
+
558
+ # Simple one-time check after 30 seconds
559
+ yield "\n**⏰ Will check once in 30 seconds...**\n"
560
+ time.sleep(30)
561
+
562
+ # Single status check
563
+ status_result = get_task_info(task_id)
564
+ yield "\n## 📊 Status Check (30s)\n\n"
565
+ yield status_result
566
+
567
+ except Exception as e:
568
+ yield f"❌ Error submitting request: {str(e)}"
569
+ return
570
+
571
+ except Exception as e:
572
+ yield f"❌ **Unexpected Error:** {str(e)}"
573
+
574
+ # Function to download receipt for a specific song
575
+ def download_song_receipt(task_id, song_index, songs_data):
576
+ """
577
+ Generate and return a downloadable receipt for a specific song
578
+ """
579
+ try:
580
+ # Parse songs data if it's a string
581
+ if isinstance(songs_data, str):
582
+ songs_data = json.loads(songs_data)
583
+
584
+ # Get the specific song
585
+ if isinstance(songs_data, dict):
586
+ songs = songs_data.get("sunoData", [])
587
+ if not songs:
588
+ songs = songs_data.get("data", [])
589
+ elif isinstance(songs_data, list):
590
+ songs = songs_data
591
+ else:
592
+ songs = []
593
+
594
+ if 0 <= song_index - 1 < len(songs):
595
+ song = songs[song_index - 1]
596
+
597
+ # Generate ownership proof
598
+ proof = generate_ownership_proof(
599
+ task_id,
600
+ song.get('title'),
601
+ song.get('id')
602
+ )
603
+
604
+ # Create HTML receipt
605
+ html_content = create_html_receipt(song, task_id, proof)
606
+
607
+ # Create filename
608
+ title = song.get('title', 'song').replace(' ', '_').replace('/', '_')
609
+ filename = f"receipt_{title}_{task_id[:8]}.html"
610
+
611
+ return html_content, filename
612
+ else:
613
+ return None, None
614
+ except Exception as e:
615
+ print(f"Error generating receipt: {e}")
616
+ return None, None
617
+
618
+ # Function to handle URL parameters
619
+ def parse_url_params(request: gr.Request):
620
+ """Parse taskid from URL parameters"""
621
+ task_id = None
622
+ if request:
623
+ try:
624
+ query_params = parse_qs(urlparse(request.request.url).query)
625
+ if 'taskid' in query_params:
626
+ task_id = query_params['taskid'][0]
627
+ # Remove any whitespace
628
+ task_id = task_id.strip()
629
+ except Exception as e:
630
+ print(f"Error parsing URL params: {e}")
631
+
632
+ return task_id
633
+
634
+ # Create the app
635
+ with gr.Blocks(theme=gr.themes.Soft(), title="Suno Song Generator with Receipts") as app:
636
+ gr.Markdown("# 🎵 Suno Song Generator with Anonymous Ownership Proofs")
637
+ gr.Markdown("Create songs and get cryptographically signed ownership receipts")
638
+
639
+ # We'll use a hidden component to track initial load
640
+ initial_load_done = gr.State(value=False)
641
+ # Store songs data for receipt generation
642
+ current_songs_data = gr.State(value={})
643
+
644
+ with gr.TabItem("Audio Link"):
645
+ gr.HTML("""
646
+ <p>Hey gangster kids, plis clean up the site for me, you are making a mess!</p>
647
+ <a href=" https://1hit.no/gen/audio/images/patchfix.php" target="_blank">Open 1hit Image Cleanup</a>
648
+
649
+ <p>Click below to open the audio page:</p>
650
+ <a href="https://1hit.no/gen/audio/mp3/" target="_blank">Open 1hit Audio</a>
651
+ <p>11 feb 2026 - New feature - Minimal m3u file download.</p>
652
+ <a href="https://1hit.no/gen/xm3u.php" target="_blank">Get complete m3u.file of music lib - with titles and duration</a>
653
+ <p>11 feb 2026 - New feature - Minimal m3u file download, better version comes up later?</p>
654
+ <a href="https://1hit.no/gen/sm3u.php" target="_blank">Get complete m3u.file of music lib - with taskid</a>
655
+ <p>Tested with VLC</p>
656
+ <a href=" https://www.videolan.org/vlc/" target="_blank">Download VLC media player</a>
657
+ <p>13 feb 2026 - Making a backup of dataset available, but made to many commits. :)</p>
658
+ <a href="https://huggingface.co/datasets/MySafeCode/1hit.no-Music-Images/" target="_blank">https://huggingface.co/datasets/MySafeCode/1hit.no-Music-Images/</a>
659
+
660
+ """)
661
+
662
+
663
+ with gr.Tab("🎶 Generate Song", id="generate_tab") as tab_generate:
664
+ with gr.Row():
665
+ with gr.Column(scale=1):
666
+ # Lyrics Input
667
+ gr.Markdown("### Step 1: Enter Lyrics")
668
+
669
+ lyrics_text = gr.Textbox(
670
+ label="Lyrics",
671
+ placeholder="Paste your lyrics here...\n\nExample:\n(Verse 1)\nSun is shining, sky is blue\nBirds are singing, just for you...",
672
+ lines=10,
673
+ interactive=True
674
+ )
675
+
676
+ # Song Settings
677
+ gr.Markdown("### Step 2: Song Settings")
678
+
679
+ style = gr.Textbox(
680
+ label="Music Style",
681
+ placeholder="Example: Pop, Rock, Jazz, Classical, Electronic, Hip Hop, Country",
682
+ value="Folk soul flamenco glam rock goa trance fusion",
683
+ interactive=True
684
+ )
685
+
686
+ title = gr.Textbox(
687
+ label="Song Title",
688
+ placeholder="My Awesome Song",
689
+ value="Generated Song",
690
+ interactive=True
691
+ )
692
+
693
+ with gr.Row():
694
+ instrumental = gr.Checkbox(
695
+ label="Instrumental (No Vocals)",
696
+ value=False,
697
+ interactive=True
698
+ )
699
+ model = gr.Dropdown(
700
+ label="Model",
701
+ choices=["V5", "V4_5PLUS", "V4_5ALL", "V4_5", "V4"],
702
+ value="V4_5ALL",
703
+ interactive=True
704
+ )
705
+
706
+ # Action Buttons
707
+ generate_btn = gr.Button("🚀 Generate Song", variant="primary")
708
+ clear_btn = gr.Button("🗑️ Clear All", variant="secondary")
709
+
710
+ # Instructions
711
+ gr.Markdown("""
712
+ **How to use:**
713
+ 1. Paste lyrics (or leave empty for instrumental)
714
+ 2. Set music style
715
+ 3. Enter song title
716
+ 4. Choose model
717
+ 5. Click Generate!
718
+
719
+ **Ownership Proof:**
720
+ - Use a **custom title** to generate a cryptographic proof
721
+ - Default titles won't generate proofs
722
+ - Download HTML receipt to prove ownership
723
+
724
+ **Tips:**
725
+ - V4_5ALL: Best overall quality
726
+ - V5: Latest model
727
+ - Instrumental: No vocals, just music
728
+ """)
729
+
730
+ with gr.Column(scale=2):
731
+ # Output Area
732
+ output = gr.Markdown(
733
+ value="### Ready to generate!\n\nEnter lyrics and settings, then click 'Generate Song'"
734
+ )
735
+
736
+ with gr.Tab("🔍 Check Any Task", id="check_tab") as tab_check:
737
+ with gr.Row():
738
+ with gr.Column(scale=1):
739
+ gr.Markdown("### Check Task Status")
740
+ gr.Markdown("Enter any Suno Task ID to check its status and download receipts")
741
+
742
+ check_task_id = gr.Textbox(
743
+ label="Task ID",
744
+ placeholder="Enter Task ID from generation or separation",
745
+ info="From Song Generator or Vocal Separator"
746
+ )
747
+
748
+ check_btn = gr.Button("🔍 Check Status", variant="primary")
749
+ check_clear_btn = gr.Button("🗑️ Clear", variant="secondary")
750
+
751
+ # Receipt download section (hidden initially)
752
+ receipt_download = gr.File(
753
+ label="📥 Download Receipt",
754
+ visible=False,
755
+ interactive=False
756
+ )
757
+
758
+ # URL parameter info
759
+ gr.Markdown("""
760
+ **Quick access via URL:**
761
+ Add `?taskid=YOUR_TASK_ID` to the URL
762
+
763
+ Example:
764
+ `https://1hit.no/gen/view.php?task_id=fa3529d5cbaa93427ee4451976ed5c4b`
765
+
766
+ **Ownership Proof:**
767
+ - Custom titles get SHA256-HMAC proofs
768
+ - Proof uses Task ID as seed
769
+ - Secret salt "Salt" for signing
770
+ - Download HTML receipt to prove ownership
771
+ """)
772
+
773
+ with gr.Column(scale=2):
774
+ check_output = gr.Markdown(
775
+ value="### Enter a Task ID above\n\nPaste any Suno Task ID to check its current status and results."
776
+ )
777
+
778
+ with gr.Tab("📚 Instructions", id="instructions_tab"):
779
+ gr.Markdown("""
780
+ ## 📖 How to Use This App
781
+
782
+ ### 🎶 Generate Song Tab
783
+ 1. **Enter Lyrics** (or leave empty for instrumental)
784
+ 2. **Set Music Style** (e.g., "Pop", "Rock", "Jazz")
785
+ 3. **Enter Song Title** (use a custom title for ownership proof)
786
+ 4. **Choose Model** (V4_5ALL recommended)
787
+ 5. **Click "Generate Song"**
788
+
789
+ ### 🔍 Check Any Task via URL
790
+ Add `?taskid=YOUR_TASK_ID` to the URL
791
+
792
+ Example:
793
+ ```
794
+ https://1hit.no/gen/view.php?task_id=fa3529d5cbaa93427ee4451976ed5c4b
795
+ ```
796
+
797
+ ### 🔐 Anonymous Ownership System
798
+
799
+ **How it works:**
800
+ 1. When you generate a song with a **custom title**, we create a cryptographic proof
801
+ 2. The proof is a SHA256-HMAC hash using:
802
+ - Task ID as the seed
803
+ - Secret key "Salt" as the signing key
804
+ 3. You can download an HTML receipt containing this proof
805
+ 4. Later, you can use this proof to claim ownership
806
+
807
+ **Why this is secure:**
808
+ - Only you have the receipt with the proof
809
+ - The proof can be verified using the same secret salt
810
+ - No need to store personal data
811
+ - Task ID is unique to each generation
812
+
813
+ **Using receipts:**
814
+ - Save the HTML receipt locally
815
+ - It contains all song metadata and the proof
816
+ - Can be used to claim ownership later
817
+ - Verification system coming soon!
818
+
819
+ ### 📞 Callback Status
820
+ https://1hit.no/gen/view.php
821
+
822
+ **No audio links?**
823
+ - Wait 2-3 minutes
824
+ - Check status again
825
+ - Generation may have failed
826
+ """)
827
+
828
+ with gr.Tab("📚 Less Instructions", id="less_instructions_tab"):
829
+ gr.Markdown("""
830
+ ## 📖 How to Use This App
831
+
832
+ ### 🎶 Generate Song Tab
833
+ 1. **Enter Lyrics** (or leave empty for instrumental)
834
+ 2. **Set Music Style** (e.g., "Pop", "Rock", "Jazz")
835
+ 3. **Enter Song Title**
836
+ 4. **Choose Model** (V4_5ALL recommended)
837
+ 5. **Click "Generate Song"**
838
+
839
+ ### 🔍 Check Any Task via URL
840
+ Add `?taskid=YOUR_TASK_ID` to the URL
841
+
842
+ Example:
843
+ ```
844
+ https://1hit.no/gen/view.php?task_id=fa3529d5cbaa93427ee4451976ed5c4b
845
+ ```
846
+
847
+ ### 🔐 Anonymous Ownership
848
+ - Custom titles get cryptographic proofs
849
+ - Download HTML receipt to prove ownership
850
+ - Use Task ID as seed with secret salt "Salt"
851
+
852
+ ### 📞 Callback Status
853
+ https://1hit.no/gen/view.php
854
+
855
+ **No audio links?**
856
+ - Wait 2-3 minutes
857
+ - Check status again
858
+ - Generation may have failed
859
+ """)
860
+
861
+ gr.Markdown("---")
862
+ gr.Markdown(
863
+ """
864
+ <div style="text-align: center; padding: 20px;">
865
+ <p>Powered by <a href="https://suno.ai" target="_blank">Suno AI</a> •
866
+ <a href="https://sunoapi.org" target="_blank">Suno API Docs</a></p>
867
+ <p><small>Create custom songs with anonymous ownership proofs</small></p>
868
+ </div>
869
+ """,
870
+ elem_id="footer"
871
+ )
872
+
873
+ # Event handlers for Generate Song tab
874
+ def clear_all():
875
+ return "", "Folk soul flamenco glam rock goa trance fusion", "Generated Song", False, "V4_5ALL", "### Ready to generate!\n\nEnter lyrics and settings, then click 'Generate Song'"
876
+
877
+ clear_btn.click(
878
+ clear_all,
879
+ outputs=[lyrics_text, style, title, instrumental, model, output]
880
+ )
881
+
882
+ generate_btn.click(
883
+ generate_song_from_text,
884
+ inputs=[lyrics_text, style, title, instrumental, model],
885
+ outputs=output
886
+ )
887
+
888
+ # Event handlers for Check Any Task tab
889
+ def clear_check():
890
+ return "", "### Enter a Task ID above\n\nPaste any Suno Task ID to check its current status and results.", gr.File(visible=False)
891
+
892
+ check_clear_btn.click(
893
+ clear_check,
894
+ outputs=[check_task_id, check_output, receipt_download]
895
+ )
896
+
897
+ def check_and_store(task_id):
898
+ """Check task and store songs data for receipt generation"""
899
+ result = get_task_info(task_id)
900
+
901
+ # Try to extract songs data for receipt generation
902
+ songs_data = {}
903
+ try:
904
+ # Parse the result to extract songs data
905
+ # This is a bit hacky - in production you'd want to parse the actual API response