MySafeCode commited on
Commit
0146a46
·
verified ·
1 Parent(s): 8fe8876

Upload aaa.py

Browse files
Files changed (1) hide show
  1. aaa.py +659 -0
aaa.py ADDED
@@ -0,0 +1,659 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ import tempfile
11
+
12
+ # Suno API key
13
+ SUNO_KEY = os.environ.get("SunoKey", "")
14
+ # Secret key for ownership proofs
15
+ SECRET_SALT = "Salt" # You might want to make this configurable
16
+
17
+ if not SUNO_KEY:
18
+ print("⚠️ SunoKey not set!")
19
+
20
+ def generate_ownership_proof(task_id, title, music_id=None):
21
+ """
22
+ Generate a SHA256 hash proof using task_id as seed
23
+ """
24
+ proof_data = {
25
+ "task_id": task_id,
26
+ "title": title,
27
+ "music_id": music_id,
28
+ "timestamp": datetime.utcnow().isoformat(),
29
+ }
30
+
31
+ proof_string = json.dumps(proof_data, sort_keys=True)
32
+
33
+ signature = hmac.new(
34
+ SECRET_SALT.encode('utf-8'),
35
+ proof_string.encode('utf-8'),
36
+ hashlib.sha256
37
+ ).hexdigest()
38
+
39
+ return {
40
+ "proof": signature,
41
+ "data": proof_data,
42
+ "version": "1.0"
43
+ }
44
+
45
+ def create_simple_receipt(task_id, title):
46
+ """
47
+ Create a simple JSON receipt with task ID and title
48
+ """
49
+ proof = generate_ownership_proof(task_id, title)
50
+
51
+ receipt = {
52
+ "receipt_type": "song_ownership_proof",
53
+ "generated": datetime.now().isoformat(),
54
+ "task_id": task_id,
55
+ "title": title,
56
+ "proof": proof,
57
+ "check_url": f"https://1hit.no/gen/view.php?taskid={task_id}"
58
+ }
59
+
60
+ return receipt
61
+
62
+ def create_html_receipt(receipt):
63
+ """
64
+ Create a simple HTML viewer for the receipt
65
+ """
66
+ task_id = receipt['task_id']
67
+ title = receipt['title']
68
+ proof = receipt['proof']
69
+
70
+ html = f"""<!DOCTYPE html>
71
+ <html>
72
+ <head>
73
+ <title>Song Receipt - {title}</title>
74
+ <style>
75
+ body {{ font-family: Arial; max-width: 800px; margin: 40px auto; padding: 20px; }}
76
+ .receipt {{ border: 2px solid #333; padding: 20px; border-radius: 10px; }}
77
+ .proof {{ background: #f0f0f0; padding: 10px; word-break: break-all; font-family: monospace; }}
78
+ h1 {{ color: #2c3e50; }}
79
+ .button {{ background: #3498db; color: white; padding: 10px 20px; text-decoration: none; border-radius: 5px; }}
80
+ </style>
81
+ </head>
82
+ <body>
83
+ <div class="receipt">
84
+ <h1>🎵 Song Ownership Receipt</h1>
85
+ <p><strong>Generated:</strong> {receipt['generated']}</p>
86
+ <p><strong>Title:</strong> {title}</p>
87
+ <p><strong>Task ID:</strong> <code>{task_id}</code></p>
88
+
89
+ <h3>🔐 Proof</h3>
90
+ <div class="proof">
91
+ <strong>Hash:</strong> {proof['proof']}<br>
92
+ <strong>Signed Data:</strong> {json.dumps(proof['data'])}
93
+ </div>
94
+
95
+ <p><strong>Check Status:</strong> <a href="{receipt['check_url']}">{receipt['check_url']}</a></p>
96
+
97
+ <p><small>Save this receipt to prove you created this song request.</small></p>
98
+ </div>
99
+ </body>
100
+ </html>"""
101
+
102
+ return html
103
+
104
+ def get_task_info(task_id):
105
+ """Manually check any Suno task status"""
106
+ if not task_id:
107
+ return "❌ Please enter a Task ID"
108
+
109
+ try:
110
+ resp = requests.get(
111
+ "https://api.sunoapi.org/api/v1/generate/record-info",
112
+ headers={"Authorization": f"Bearer {SUNO_KEY}"},
113
+ params={"taskId": task_id},
114
+ timeout=30
115
+ )
116
+
117
+ if resp.status_code != 200:
118
+ return f"❌ HTTP Error {resp.status_code}\n\n{resp.text}"
119
+
120
+ data = resp.json()
121
+
122
+ # Format the response for display
123
+ output = f"## 🔍 Task Status: `{task_id}`\n\n"
124
+
125
+ if data.get("code") == 200:
126
+ task_data = data.get("data", {})
127
+ status = task_data.get("status", "UNKNOWN")
128
+
129
+ output += f"**Status:** {status}\n"
130
+ output += f"**Task ID:** `{task_data.get('taskId', 'N/A')}`\n"
131
+ output += f"**Music ID:** `{task_data.get('musicId', 'N/A')}`\n"
132
+ output += f"**Created:** {task_data.get('createTime', 'N/A')}\n"
133
+
134
+ if status == "SUCCESS" or status == "TEXT_SUCCESS":
135
+ response_data = task_data.get("response", {})
136
+
137
+ # Try to parse response (could be string or dict)
138
+ if isinstance(response_data, str):
139
+ try:
140
+ response_data = json.loads(response_data)
141
+ except:
142
+ output += f"\n**Raw Response:**\n```\n{response_data}\n```\n"
143
+ response_data = {}
144
+
145
+ # Check for song data
146
+ songs = []
147
+ if isinstance(response_data, dict):
148
+ songs = response_data.get("sunoData", [])
149
+ if not songs:
150
+ songs = response_data.get("data", [])
151
+ elif isinstance(response_data, list):
152
+ songs = response_data
153
+
154
+ if songs:
155
+ output += f"\n## 🎵 Generated Songs ({len(songs)})\n\n"
156
+
157
+ for i, song in enumerate(songs, 1):
158
+ if isinstance(song, dict):
159
+ output += f"### Song {i}\n"
160
+ output += f"**Title:** {song.get('title', 'Untitled')}\n"
161
+ output += f"**ID:** `{song.get('id', 'N/A')}`\n"
162
+
163
+ # Audio URLs
164
+ audio_url = song.get('audioUrl') or song.get('audio_url')
165
+ stream_url = song.get('streamUrl') or song.get('stream_url')
166
+ download_url = song.get('downloadUrl') or song.get('download_url')
167
+
168
+ if audio_url:
169
+ output += f"**Audio:** [Play]({audio_url}) | [Download]({audio_url})\n"
170
+ elif stream_url:
171
+ output += f"**Stream:** [Play]({stream_url})\n"
172
+
173
+ if download_url:
174
+ output += f"**Download:** [MP3]({download_url})\n"
175
+
176
+ # Audio player
177
+ play_url = audio_url or stream_url
178
+ if play_url:
179
+ output += f"""\n<audio controls style="width: 100%; margin: 10px 0;">
180
+ <source src="{play_url}" type="audio/mpeg">
181
+ Your browser does not support audio.
182
+ </audio>\n"""
183
+
184
+ output += f"**Prompt:** {song.get('prompt', 'N/A')[:100]}...\n"
185
+ output += f"**Duration:** {song.get('duration', 'N/A')}s\n"
186
+ output += f"**Created:** {song.get('createTime', 'N/A')}\n\n"
187
+ output += "---\n\n"
188
+ else:
189
+ output += "\n**No song data found in response.**\n"
190
+
191
+ elif status == "FAILED":
192
+ error_msg = task_data.get("errorMessage", "Unknown error")
193
+ output += f"\n**Error:** {error_msg}\n"
194
+
195
+ elif status in ["PENDING", "PROCESSING", "RUNNING"]:
196
+ output += f"\n**Task is still processing...**\n"
197
+ output += f"Check again in 30 seconds.\n"
198
+
199
+ else:
200
+ output += f"\n**Unknown status:** {status}\n"
201
+
202
+ else:
203
+ output += f"**API Error:** {data.get('msg', 'Unknown')}\n"
204
+
205
+ # Show raw JSON for debugging
206
+ output += "\n## 📋 Raw Response\n"
207
+ output += f"```json\n{json.dumps(data, indent=2)}\n```"
208
+
209
+ return output
210
+
211
+ except Exception as e:
212
+ return f"❌ Error checking task: {str(e)}"
213
+
214
+ def generate_song_from_text(lyrics_text, style, title, instrumental, model):
215
+ """Generate a song from lyrics text"""
216
+ if not SUNO_KEY:
217
+ yield "❌ Error: SunoKey not configured in environment variables"
218
+ return
219
+
220
+ if not lyrics_text.strip() and not instrumental:
221
+ yield "❌ Error: Please provide lyrics or select instrumental"
222
+ return
223
+
224
+ if not style.strip():
225
+ yield "❌ Error: Please provide a music style"
226
+ return
227
+
228
+ if not title.strip():
229
+ yield "❌ Error: Please provide a song title"
230
+ return
231
+
232
+ # Store receipt files for download buttons
233
+ receipt_files = []
234
+
235
+ try:
236
+ # Prepare request data
237
+ request_data = {
238
+ "customMode": True,
239
+ "instrumental": instrumental,
240
+ "model": model,
241
+ "callBackUrl": "https://1hit.no/gen/cb.php",
242
+ "style": style,
243
+ "title": title,
244
+ }
245
+
246
+ if not instrumental:
247
+ # Apply character limits
248
+ if model == "V4" and len(lyrics_text) > 3000:
249
+ lyrics_text = lyrics_text[:3000]
250
+ yield f"⚠️ Lyrics truncated to 3000 characters for V4 model\n\n"
251
+ elif model in ["V4_5", "V4_5PLUS", "V4_5ALL", "V5"] and len(lyrics_text) > 5000:
252
+ lyrics_text = lyrics_text[:5000]
253
+ yield f"⚠️ Lyrics truncated to 5000 characters for {model} model\n\n"
254
+
255
+ request_data["prompt"] = lyrics_text
256
+ else:
257
+ request_data["prompt"] = ""
258
+
259
+ # Apply style length limits
260
+ if model == "V4" and len(style) > 200:
261
+ style = style[:200]
262
+ yield f"⚠️ Style truncated to 200 characters for V4 model\n\n"
263
+ elif model in ["V4_5", "V4_5PLUS", "V4_5ALL", "V5"] and len(style) > 1000:
264
+ style = style[:1000]
265
+ yield f"⚠️ Style truncated to 1000 characters for {model} model\n\n"
266
+
267
+ # Apply title length limits
268
+ if model in ["V4", "V4_5ALL"] and len(title) > 80:
269
+ title = title[:80]
270
+ yield f"⚠️ Title truncated to 80 characters for {model} model\n\n"
271
+ elif model in ["V4_5", "V4_5PLUS", "V5"] and len(title) > 100:
272
+ title = title[:100]
273
+ yield f"⚠️ Title truncated to 100 characters for {model} model\n\n"
274
+
275
+ request_data["style"] = style
276
+ request_data["title"] = title
277
+
278
+ yield f"## 🚀 Submitting Song Request\n\n"
279
+ yield f"**Title:** {title}\n"
280
+ yield f"**Style:** {style}\n"
281
+ yield f"**Model:** {model}\n"
282
+ yield f"**Instrumental:** {'Yes' if instrumental else 'No'}\n"
283
+ if not instrumental:
284
+ yield f"**Lyrics length:** {len(lyrics_text)} characters\n\n"
285
+ yield f"**Callback URL:** https://1hit.no/callback.php\n\n"
286
+
287
+ # Submit generation request
288
+ try:
289
+ resp = requests.post(
290
+ "https://api.sunoapi.org/api/v1/generate",
291
+ json=request_data,
292
+ headers={
293
+ "Authorization": f"Bearer {SUNO_KEY}",
294
+ "Content-Type": "application/json"
295
+ },
296
+ timeout=30
297
+ )
298
+
299
+ if resp.status_code != 200:
300
+ yield f"❌ Submission failed: HTTP {resp.status_code}"
301
+ yield f"\n**Response:**\n```\n{resp.text}\n```"
302
+ return
303
+
304
+ data = resp.json()
305
+ print(f"Submission response: {json.dumps(data, indent=2)}")
306
+
307
+ if data.get("code") != 200:
308
+ yield f"❌ API error: {data.get('msg', 'Unknown')}"
309
+ return
310
+
311
+ # Extract task ID from response
312
+ task_id = None
313
+ if "taskId" in data:
314
+ task_id = data["taskId"]
315
+ elif "data" in data and "taskId" in data["data"]:
316
+ task_id = data["data"]["taskId"]
317
+ elif data.get("data") and "taskId" in data.get("data", {}):
318
+ task_id = data["data"]["taskId"]
319
+
320
+ if not task_id:
321
+ yield f"❌ Could not extract Task ID from response"
322
+ yield f"\n**Raw Response:**\n```json\n{json.dumps(data, indent=2)}\n```"
323
+ return
324
+
325
+ yield f"## ✅ Request Submitted Successfully!\n\n"
326
+ yield f"**🎯 Task ID:** `{task_id}`\n\n"
327
+
328
+ # Generate receipt immediately for custom titles
329
+ if title not in ["Generated Song", "Untitled", ""]:
330
+ # Create receipt
331
+ receipt = create_simple_receipt(task_id, title)
332
+
333
+ # Save to temp files
334
+ json_path = tempfile.NamedTemporaryFile(mode='w', suffix=f'_{task_id[:8]}.json', delete=False).name
335
+ with open(json_path, 'w') as f:
336
+ json.dump(receipt, f, indent=2)
337
+
338
+ html_path = tempfile.NamedTemporaryFile(mode='w', suffix=f'_{task_id[:8]}.html', delete=False).name
339
+ with open(html_path, 'w') as f:
340
+ f.write(create_html_receipt(receipt))
341
+
342
+ receipt_files = [json_path, html_path]
343
+
344
+ yield f"""
345
+ ### 📥 YOUR RECEIPT IS READY!
346
+
347
+ <div style="display: flex; gap: 10px; margin: 20px 0;">
348
+ <a href="{json_path}" download="receipt_{task_id[:8]}.json" style="background: #3498db; color: white; padding: 12px 24px; text-decoration: none; border-radius: 5px; font-weight: bold; display: inline-block;">
349
+ 📄 Download JSON Receipt
350
+ </a>
351
+ <a href="{html_path}" download="receipt_{task_id[:8]}.html" style="background: #2ecc71; color: white; padding: 12px 24px; text-decoration: none; border-radius: 5px; font-weight: bold; display: inline-block;">
352
+ 🌐 Download HTML Receipt
353
+ </a>
354
+ </div>
355
+
356
+ **Task ID:** `{task_id}`
357
+ **Title:** {title}
358
+ **Time:** {receipt['generated']}
359
+ **Proof:** `{receipt['proof']['proof'][:32]}...`
360
+
361
+ > 💾 **Save these receipts!** They prove you created this song request.
362
+
363
+ ---
364
+ """
365
+ else:
366
+ yield "⚠️ No receipt generated - use a custom title for ownership proof\n\n"
367
+
368
+ yield f"**⏳ Status:** Generation started\n"
369
+ yield f"**📞 Callback:** https://1hit.no/callback.php\n\n"
370
+ yield "---\n\n"
371
+ yield f"## 🔍 Check Status Manually\n\n"
372
+ yield f"Use this Task ID: `{task_id}` in the Check tab\n\n"
373
+
374
+ # Simple one-time check after 30 seconds
375
+ yield "\n**⏰ Will check once in 30 seconds...**\n"
376
+ time.sleep(30)
377
+
378
+ # Single status check
379
+ status_result = get_task_info(task_id)
380
+ yield "\n## 📊 Status Check (30s)\n\n"
381
+ yield status_result
382
+
383
+ except Exception as e:
384
+ yield f"❌ Error submitting request: {str(e)}"
385
+ return
386
+
387
+ except Exception as e:
388
+ yield f"❌ **Unexpected Error:** {str(e)}"
389
+
390
+ # Function to handle URL parameters
391
+ def parse_url_params(request: gr.Request):
392
+ """Parse taskid from URL parameters"""
393
+ task_id = None
394
+ if request:
395
+ try:
396
+ query_params = parse_qs(urlparse(request.request.url).query)
397
+ if 'taskid' in query_params:
398
+ task_id = query_params['taskid'][0]
399
+ # Remove any whitespace
400
+ task_id = task_id.strip()
401
+ except Exception as e:
402
+ print(f"Error parsing URL params: {e}")
403
+
404
+ return task_id
405
+
406
+ # Create the app
407
+ with gr.Blocks(theme=gr.themes.Soft()) as app:
408
+ gr.Markdown("# 🎵 Suno Song Generator with Receipts")
409
+ gr.Markdown("Create songs from lyrics and style using Suno AI - now with instant receipt downloads!")
410
+
411
+ # Define state variables
412
+ initial_load_done = gr.State(value=False)
413
+
414
+ with gr.TabItem("Audio Link"):
415
+ gr.HTML("""
416
+ <p>Hey gangster kids, plis clean up the site for me, you are making a mess!</p>
417
+ <a href=" https://1hit.no/gen/audio/images/patchfix.php" target="_blank">Open 1hit Image Cleanup</a>
418
+
419
+ <p>Click below to open the audio page:</p>
420
+ <a href="https://1hit.no/gen/audio/mp3/" target="_blank">Open 1hit Audio</a>
421
+ <p>11 feb 2026 - New feature - Minimal m3u file download.</p>
422
+ <a href="https://1hit.no/gen/xm3u.php" target="_blank">Get complete m3u.file of music lib - with titles and duration</a>
423
+ <p>11 feb 2026 - New feature - Minimal m3u file download, better version comes up later?</p>
424
+ <a href="https://1hit.no/gen/sm3u.php" target="_blank">Get complete m3u.file of music lib - with taskid</a>
425
+ <p>Tested with VLC</p>
426
+ <a href=" https://www.videolan.org/vlc/" target="_blank">Download VLC media player</a>
427
+ <p>13 feb 2026 - Making a backup of dataset available, but made to many commits. :)</p>
428
+ <a href="https://huggingface.co/datasets/MySafeCode/1hit.no-Music-Images/" target="_blank">https://huggingface.co/datasets/MySafeCode/1hit.no-Music-Images/</a>
429
+
430
+ """)
431
+
432
+
433
+ with gr.Tab("🎶 Generate Song", id="generate_tab") as tab_generate:
434
+ with gr.Row():
435
+ with gr.Column(scale=1):
436
+ # Lyrics Input
437
+ gr.Markdown("### Step 1: Enter Lyrics")
438
+
439
+ lyrics_text = gr.Textbox(
440
+ label="Lyrics",
441
+ placeholder="Paste your lyrics here...\n\nExample:\n(Verse 1)\nSun is shining, sky is blue\nBirds are singing, just for you...",
442
+ lines=10,
443
+ interactive=True
444
+ )
445
+
446
+ # Song Settings
447
+ gr.Markdown("### Step 2: Song Settings")
448
+
449
+ style = gr.Textbox(
450
+ label="Music Style",
451
+ placeholder="Example: Pop, Rock, Jazz, Classical, Electronic, Hip Hop, Country",
452
+ value="Folk soul flamenco glam rock goa trance fusion",
453
+ interactive=True
454
+ )
455
+
456
+ title = gr.Textbox(
457
+ label="Song Title (use custom title for receipt)",
458
+ placeholder="My Awesome Song",
459
+ value="Generated Song",
460
+ info="✅ Custom title = you get an ownership receipt with download buttons!",
461
+ interactive=True
462
+ )
463
+
464
+ with gr.Row():
465
+ instrumental = gr.Checkbox(
466
+ label="Instrumental (No Vocals)",
467
+ value=False,
468
+ interactive=True
469
+ )
470
+ model = gr.Dropdown(
471
+ label="Model",
472
+ choices=["V5", "V4_5PLUS", "V4_5ALL", "V4_5", "V4"],
473
+ value="V4_5ALL",
474
+ interactive=True
475
+ )
476
+
477
+ # Action Buttons
478
+ generate_btn = gr.Button("🚀 Generate Song", variant="primary", size="lg")
479
+ clear_btn = gr.Button("🗑️ Clear All", variant="secondary")
480
+
481
+ # Instructions
482
+ gr.Markdown("""
483
+ **📋 How to use:**
484
+ 1. Paste lyrics (or leave empty for instrumental)
485
+ 2. Set music style
486
+ 3. Enter song title
487
+ 4. Choose model
488
+ 5. Click Generate!
489
+
490
+ **🔐 NEW: Ownership Receipts with Download Buttons**
491
+ - Use a **custom title** to get instant receipt download buttons
492
+ - Receipt contains cryptographic proof of your request
493
+ - Click buttons to download JSON or HTML receipt
494
+ - Save them to prove you created this song
495
+ """)
496
+
497
+ with gr.Column(scale=2):
498
+ # Output Area
499
+ output = gr.Markdown(
500
+ value="### Ready to generate!\n\nEnter lyrics and settings, then click 'Generate Song' to get your receipt download buttons."
501
+ )
502
+
503
+ with gr.Tab("🔍 Check Any Task", id="check_tab") as tab_check:
504
+ with gr.Row():
505
+ with gr.Column(scale=1):
506
+ gr.Markdown("### Check Task Status")
507
+ gr.Markdown("Enter any Suno Task ID to check its status")
508
+
509
+ check_task_id = gr.Textbox(
510
+ label="Task ID",
511
+ placeholder="Enter Task ID from generation or separation",
512
+ info="From Song Generator or Vocal Separator"
513
+ )
514
+
515
+ check_btn = gr.Button("🔍 Check Status", variant="primary")
516
+ check_clear_btn = gr.Button("🗑️ Clear", variant="secondary")
517
+
518
+ # URL parameter info
519
+ gr.Markdown("""
520
+ **Quick access via URL:**
521
+ Add `?taskid=YOUR_TASK_ID` to the URL
522
+
523
+ Example:
524
+ `https://1hit.no/gen/view.php?taskid=fa3529d5cbaa93427ee4451976ed5c4b`
525
+ """)
526
+
527
+ with gr.Column(scale=2):
528
+ check_output = gr.Markdown(
529
+ value="### Enter a Task ID above\n\nPaste any Suno Task ID to check its current status and results."
530
+ )
531
+
532
+ with gr.Tab("📚 Instructions", id="instructions_tab"):
533
+ gr.Markdown("""
534
+ ## 📖 How to Use This App
535
+
536
+ ### 🎶 Generate Song Tab
537
+ 1. **Enter Lyrics** (or leave empty for instrumental)
538
+ 2. **Set Music Style** (e.g., "Pop", "Rock", "Jazz")
539
+ 3. **Enter Song Title** (use custom title for receipt)
540
+ 4. **Choose Model** (V4_5ALL recommended)
541
+ 5. **Click "Generate Song"**
542
+
543
+ ### 🔐 New: Ownership Receipts with Download Buttons
544
+ - When you use a **custom title**, you get instant receipt download buttons
545
+ - **JSON Receipt** - Machine-readable proof
546
+ - **HTML Receipt** - Human-readable viewer
547
+ - Receipt contains cryptographic proof (HMAC-SHA256)
548
+ - Click the buttons to download and save your proof of ownership
549
+
550
+ ### 🔍 Check Any Task Tab
551
+ 1. **Paste any Suno Task ID**
552
+ 2. **Click "Check Status"**
553
+ 3. **View results and download links**
554
+
555
+ **Quick URL Access:**
556
+ - Visit with `?taskid=YOUR_TASK_ID` in the URL
557
+ - Automatically switches to Check tab
558
+ - Shows task status immediately
559
+ """)
560
+
561
+ with gr.Tab("📚 Less Instructions", id="less_instructions_tab"):
562
+ gr.Markdown("""
563
+ ## 📖 Quick Guide
564
+
565
+ ### 🎶 Generate Song
566
+ 1. Enter lyrics
567
+ 2. Set music style
568
+ 3. Enter song title (custom = receipt with download buttons)
569
+ 4. Click Generate
570
+ 5. Click the receipt download buttons!
571
+
572
+ ### 🔍 Check Task
573
+ Add `?taskid=YOUR_TASK_ID` to URL
574
+
575
+ ### 📞 Callback Status
576
+ https://1hit.no/gen/view.php
577
+ """)
578
+
579
+ gr.Markdown("---")
580
+ gr.Markdown(
581
+ """
582
+ <div style="text-align: center; padding: 20px;">
583
+ <p>Powered by <a href="https://suno.ai" target="_blank">Suno AI</a> •
584
+ <a href="https://sunoapi.org" target="_blank">Suno API Docs</a></p>
585
+ <p><small>Create custom songs with ownership receipts - click buttons to download!</small></p>
586
+ </div>
587
+ """,
588
+ elem_id="footer"
589
+ )
590
+
591
+ # Event handlers for Generate Song tab
592
+ def clear_all():
593
+ 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' to get your receipt download buttons."
594
+
595
+ clear_btn.click(
596
+ clear_all,
597
+ outputs=[lyrics_text, style, title, instrumental, model, output]
598
+ )
599
+
600
+ generate_btn.click(
601
+ generate_song_from_text,
602
+ inputs=[lyrics_text, style, title, instrumental, model],
603
+ outputs=output
604
+ )
605
+
606
+ # Event handlers for Check Any Task tab
607
+ def clear_check():
608
+ return "", "### Enter a Task ID above\n\nPaste any Suno Task ID to check its current status and results."
609
+
610
+ check_clear_btn.click(
611
+ clear_check,
612
+ outputs=[check_task_id, check_output]
613
+ )
614
+
615
+ check_btn.click(
616
+ get_task_info,
617
+ inputs=[check_task_id],
618
+ outputs=check_output
619
+ )
620
+
621
+ # Function to handle URL parameter on load
622
+ def on_page_load(request: gr.Request):
623
+ """Handle URL parameters when page loads"""
624
+ task_id = parse_url_params(request)
625
+
626
+ if task_id:
627
+ # We have a task ID from URL, return it and fetch results
628
+ task_result = get_task_info(task_id)
629
+ return (
630
+ task_id, # For check_task_id
631
+ task_result, # For check_output
632
+ gr.Tabs(selected="check_tab"), # Switch to check tab
633
+ True # Mark as loaded
634
+ )
635
+ else:
636
+ # No task ID in URL, stay on first tab
637
+ return (
638
+ "", # Empty check_task_id
639
+ "### Enter a Task ID above\n\nPaste any Suno Task ID to check its current status and results.", # Default message
640
+ gr.Tabs(selected="generate_tab"), # Stay on generate tab
641
+ True # Mark as loaded
642
+ )
643
+
644
+ # Load URL parameters when the app starts
645
+ app.load(
646
+ fn=on_page_load,
647
+ inputs=[],
648
+ outputs=[check_task_id, check_output, gr.Tabs(), initial_load_done],
649
+ queue=False
650
+ )
651
+
652
+ # Launch the app
653
+ if __name__ == "__main__":
654
+ print("🚀 Starting Suno Song Generator with Receipt Download Buttons")
655
+ print(f"🔑 SunoKey: {'✅ Set' if SUNO_KEY else '❌ Not set'}")
656
+ print("📦 Receipts: Download buttons appear immediately when you get a Task ID with custom title")
657
+ print("🌐 Open your browser to: http://localhost:7860")
658
+
659
+ app.launch(server_name="0.0.0.0", server_port=7860, share=False)