MySafeCode commited on
Commit
85a0812
·
verified ·
1 Parent(s): 45fc344

Update aa.py

Browse files
Files changed (1) hide show
  1. aa.py +422 -441
aa.py CHANGED
@@ -7,6 +7,7 @@ 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", "")
@@ -46,48 +47,52 @@ def generate_ownership_proof(task_id, title, music_id=None, custom_data=None):
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;
@@ -118,19 +123,21 @@ def create_html_receipt(song_data, task_id, ownership_proof):
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
  }}
@@ -138,44 +145,27 @@ def create_html_receipt(song_data, task_id, ownership_proof):
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;
@@ -183,10 +173,32 @@ def create_html_receipt(song_data, task_id, ownership_proof):
183
  padding: 10px 20px;
184
  border-radius: 5px;
185
  margin-right: 10px;
 
 
 
186
  }}
187
- .download-link:hover {{
188
  background: #2980b9;
189
  }}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
190
  </style>
191
  </head>
192
  <body>
@@ -194,80 +206,95 @@ def create_html_receipt(song_data, task_id, ownership_proof):
194
  <div class="header">
195
  <h1>🎵 Song Ownership Receipt</h1>
196
  <div class="proof-badge">
197
- {('🔒 Proof Valid - Custom Title' if is_custom_title else '⚠️ Default Title - No Proof')}
198
  </div>
199
- <p>Generated: {timestamp}</p>
 
200
  </div>
201
 
202
- <div class="song-info">
203
- <h2>Song Information</h2>
204
  <div class="info-grid">
205
  <span class="info-label">Title:</span>
206
- <span>{song_data.get('title', 'N/A')}</span>
207
 
208
  <span class="info-label">Music ID:</span>
209
- <span><code>{song_data.get('id', 'N/A')}</code></span>
210
-
211
- <span class="info-label">Task ID:</span>
212
- <span><code>{task_id}</code></span>
213
 
214
  <span class="info-label">Duration:</span>
215
- <span>{song_data.get('duration', 'N/A')}s</span>
216
 
217
  <span class="info-label">Created:</span>
218
- <span>{song_data.get('createTime', 'N/A')}</span>
219
 
220
  <span class="info-label">Style:</span>
221
- <span>{song_data.get('style', 'N/A')}</span>
222
  </div>
223
 
 
224
  <div class="download-links">
225
- {f'<a href="{song_data.get("audioUrl", "")}" class="download-link" target="_blank">🎧 Stream Audio</a>' if song_data.get("audioUrl") else ''}
226
- {f'<a href="{song_data.get("downloadUrl", "")}" class="download-link" target="_blank">📥 Download MP3</a>' if song_data.get("downloadUrl") else ''}
227
  </div>
 
228
  </div>
229
 
230
  {f'''
231
- <div class="proof-section">
232
- <h3>🔐 Ownership Proof</h3>
233
- <p><strong>Seed (Task ID):</strong> <code>{task_id}</code></p>
234
- <p><strong>Signed with:</strong> Secret Salt (SHA256-HMAC)</p>
 
 
 
 
 
 
 
235
  <div class="proof-hash">
236
- <strong>Proof Hash:</strong><br>
237
- {ownership_proof["proof"]}
238
  </div>
239
- <p style="margin-top: 10px; font-size: 0.9em;">
240
- <strong>Signed Data:</strong><br>
241
- <pre>{json.dumps(ownership_proof["data"], indent=2)}</pre>
242
- </p>
243
  </div>
244
 
245
- <div class="verification-note">
246
- <strong>🔍 How to Verify Ownership:</strong>
247
- <ol style="margin-bottom: 0;">
248
- <li>Keep this receipt safe - it proves you generated this song</li>
249
- <li>To claim ownership, provide this proof hash</li>
250
- <li>The system will verify using the same secret salt</li>
251
- <li>Only you (with this receipt) can prove ownership</li>
252
- </ol>
253
  </div>
254
  ''' if is_custom_title else '''
255
- <div class="verification-note" style="background: #fef9e7; border-left-color: #f39c12;">
256
- <strong>⚠️ No Ownership Proof Generated</strong>
257
- <p>This song uses the default title. To generate an ownership proof, rename your song to a custom title.</p>
258
  </div>
259
  '''}
260
 
261
- <div class="footer">
262
- <p>📱 Save this receipt to prove ownership of your song</p>
263
- <p>🔗 Verification URL: https://1hit.no/verify?task_id={task_id}</p>
 
 
 
 
264
  <p><small>Receipt Version 1.0 • Anonymous Ownership System</small></p>
265
  </div>
266
  </div>
267
 
268
  <script>
269
- window.onload = function() {{
270
- console.log('Receipt loaded - you can print or save as PDF');
 
 
 
 
 
 
 
271
  }}
272
  </script>
273
  </body>
@@ -298,14 +325,9 @@ def extract_songs_from_response(data):
298
 
299
  # Extract songs from various possible locations
300
  if isinstance(response_data, dict):
301
- # Try different possible keys where songs might be
302
  songs = response_data.get("sunoData", [])
303
  if not songs:
304
  songs = response_data.get("data", [])
305
- if not songs:
306
- # Sometimes the response itself is a list
307
- if isinstance(response_data.get("response"), list):
308
- songs = response_data.get("response", [])
309
  elif isinstance(response_data, list):
310
  songs = response_data
311
 
@@ -320,109 +342,61 @@ def extract_songs_from_response(data):
320
  print(f"Error extracting songs: {e}")
321
  return []
322
 
323
- def get_task_info(task_id):
324
- """Manually check any Suno task status"""
325
- if not task_id:
326
- return "❌ Please enter a Task ID", []
327
-
328
- try:
329
- resp = requests.get(
330
- "https://api.sunoapi.org/api/v1/generate/record-info",
331
- headers={"Authorization": f"Bearer {SUNO_KEY}"},
332
- params={"taskId": task_id},
333
- timeout=30
334
- )
335
-
336
- if resp.status_code != 200:
337
- return f"❌ HTTP Error {resp.status_code}\n\n{resp.text}", []
338
-
339
- data = resp.json()
340
-
341
- # Extract songs for receipt generation
342
- songs = extract_songs_from_response(data)
343
-
344
- # Format the response for display
345
- output = f"## 🔍 Task Status: `{task_id}`\n\n"
346
-
347
- if data.get("code") == 200:
348
- task_data = data.get("data", {})
349
- status = task_data.get("status", "UNKNOWN")
350
 
351
- output += f"**Status:** {status}\n"
352
- output += f"**Task ID:** `{task_data.get('taskId', 'N/A')}`\n"
353
- output += f"**Music ID:** `{task_data.get('musicId', 'N/A')}`\n"
354
- output += f"**Created:** {task_data.get('createTime', 'N/A')}\n"
355
 
356
- if status == "SUCCESS":
357
- if songs:
358
- output += f"\n## 🎵 Generated Songs ({len(songs)})\n\n"
359
-
360
- for i, song in enumerate(songs, 1):
361
- if isinstance(song, dict):
362
- # Check if title is custom
363
- title = song.get('title', 'Untitled')
364
- is_custom = title not in ["Generated Song", "Untitled"]
365
-
366
- output += f"### Song {i} {('🔒' if is_custom else '⚠️')}\n"
367
- output += f"**Title:** {title}\n"
368
- output += f"**ID:** `{song.get('id', 'N/A')}`\n"
369
-
370
- # Generate ownership proof if title is custom
371
- if is_custom:
372
- proof = generate_ownership_proof(task_id, title, song.get('id'))
373
- song['_proof'] = proof # Store proof with song
374
- output += f"**🔐 Proof Generated:** `{proof['proof'][:16]}...`\n"
375
-
376
- # Audio URLs
377
- audio_url = song.get('audioUrl') or song.get('audio_url')
378
- stream_url = song.get('streamUrl') or song.get('stream_url')
379
- download_url = song.get('downloadUrl') or song.get('download_url')
380
-
381
- if audio_url:
382
- output += f"**Audio:** [Play]({audio_url}) | [Download]({audio_url})\n"
383
- elif stream_url:
384
- output += f"**Stream:** [Play]({stream_url})\n"
385
-
386
- if download_url:
387
- output += f"**Download:** [MP3]({download_url})\n"
388
-
389
- # Audio player
390
- play_url = audio_url or stream_url
391
- if play_url:
392
- output += f"""\n<audio controls style="width: 100%; margin: 10px 0;">
393
- <source src="{play_url}" type="audio/mpeg">
394
- Your browser does not support audio.
395
- </audio>\n"""
396
-
397
- output += f"**Prompt:** {song.get('prompt', 'N/A')[:100]}...\n"
398
- output += f"**Duration:** {song.get('duration', 'N/A')}s\n"
399
- output += f"**Created:** {song.get('createTime', 'N/A')}\n\n"
400
- output += "---\n\n"
401
- else:
402
- output += "\n**No song data found in response.**\n"
403
-
404
- elif status == "FAILED":
405
- error_msg = task_data.get("errorMessage", "Unknown error")
406
- output += f"\n**Error:** {error_msg}\n"
407
 
408
- elif status in ["PENDING", "PROCESSING", "RUNNING"]:
409
- output += f"\n**Task is still processing...**\n"
410
- output += f"Check again in 30 seconds.\n"
411
 
412
- else:
413
- output += f"\n**Unknown status:** {status}\n"
414
-
415
- else:
416
- output += f"**API Error:** {data.get('msg', 'Unknown')}\n"
417
-
418
- # Show raw JSON for debugging
419
- output += "\n## 📋 Raw Response\n"
420
- output += f"```json\n{json.dumps(data, indent=2)[:1000]}...\n```"
421
-
422
- return output, songs
423
-
424
- except Exception as e:
425
- return f"❌ Error checking task: {str(e)}", []
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
426
 
427
  def generate_song_from_text(lyrics_text, style, title, instrumental, model):
428
  """Generate a song from lyrics text"""
@@ -536,28 +510,75 @@ def generate_song_from_text(lyrics_text, style, title, instrumental, model):
536
  yield f"**📞 Callback:** https://1hit.no/callback.php\n\n"
537
  yield "**What happens now:**\n"
538
  yield "1. Suno AI generates your song (1-3 minutes)\n"
539
- yield "2. You'll get a callback notification\n"
540
- yield "3. Use the Task ID above to check status manually\n\n"
541
- yield "---\n\n"
542
- yield f"## 🔍 Check Status Manually\n\n"
543
- yield f"Use this Task ID: `{task_id}`\n\n"
544
- yield "**To check status:**\n"
545
- yield "1. Copy the Task ID above\n"
546
- yield "2. Go to 'Check Any Task' tab\n"
547
- yield "3. Paste and click 'Check Status'\n"
548
- yield "4. Or wait for callback notification\n\n"
549
- yield "**Generation time:**\n"
550
- yield "- 30-60 seconds for stream URL\n"
551
- yield "- 2-3 minutes for download URL\n"
552
 
553
- # Simple one-time check after 30 seconds
554
- yield "\n**⏰ Will check once in 30 seconds...**\n"
555
  time.sleep(30)
556
 
557
- # Single status check
558
  status_result, songs = get_task_info(task_id)
559
- yield "\n## 📊 Status Check (30s)\n\n"
560
- yield status_result
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
561
 
562
  except Exception as e:
563
  yield f"❌ Error submitting request: {str(e)}"
@@ -566,51 +587,56 @@ def generate_song_from_text(lyrics_text, style, title, instrumental, model):
566
  except Exception as e:
567
  yield f"❌ **Unexpected Error:** {str(e)}"
568
 
569
- def download_song_receipt(task_id, song_index, songs_json):
570
- """
571
- Generate and return a downloadable receipt for a specific song
572
- """
 
573
  try:
574
- # Parse songs data
575
- if isinstance(songs_json, str):
576
- songs = json.loads(songs_json)
577
- else:
578
- songs = songs_json
 
579
 
580
- # Get the specific song
581
- if 0 <= song_index - 1 < len(songs):
582
- song = songs[song_index - 1]
583
-
584
- # Check if we already have a proof stored
585
- proof = song.get('_proof')
586
- if not proof:
587
- # Generate ownership proof
588
- proof = generate_ownership_proof(
589
- task_id,
590
- song.get('title'),
591
- song.get('id')
592
- )
593
-
594
- # Create HTML receipt
595
- html_content = create_html_receipt(song, task_id, proof)
596
-
597
- # Create filename
598
- title = song.get('title', 'song').replace(' ', '_').replace('/', '_')
599
- filename = f"receipt_{title}_{task_id[:8]}.html"
600
 
601
- # Save temporarily and return path
602
- temp_path = f"/tmp/{filename}"
603
- with open(temp_path, 'w', encoding='utf-8') as f:
604
- f.write(html_content)
605
 
606
- return temp_path
607
- else:
608
- return None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
609
  except Exception as e:
610
- print(f"Error generating receipt: {e}")
611
- return None
612
 
613
- # Function to handle URL parameters
614
  def parse_url_params(request: gr.Request):
615
  """Parse taskid from URL parameters"""
616
  task_id = None
@@ -628,30 +654,22 @@ def parse_url_params(request: gr.Request):
628
  # Create the app
629
  with gr.Blocks(theme=gr.themes.Soft(), title="Suno Song Generator with Receipts") as app:
630
  gr.Markdown("# 🎵 Suno Song Generator with Anonymous Ownership Proofs")
631
- gr.Markdown("Create songs and get cryptographically signed ownership receipts")
632
-
633
- # State to store songs data for receipt generation
634
- current_songs = gr.State(value=[])
635
- current_task_id = gr.State(value="")
636
 
637
- with gr.TabItem("Audio Link"):
638
  gr.HTML("""
639
- <p>Hey gangster kids, plis clean up the site for me, you are making a mess!</p>
640
- <a href=" https://1hit.no/gen/audio/images/patchfix.php" target="_blank">Open 1hit Image Cleanup</a>
641
-
642
- <p>Click below to open the audio page:</p>
643
- <a href="https://1hit.no/gen/audio/mp3/" target="_blank">Open 1hit Audio</a>
644
- <p>11 feb 2026 - New feature - Minimal m3u file download.</p>
645
- <a href="https://1hit.no/gen/xm3u.php" target="_blank">Get complete m3u.file of music lib - with titles and duration</a>
646
- <p>11 feb 2026 - New feature - Minimal m3u file download, better version comes up later?</p>
647
- <a href="https://1hit.no/gen/sm3u.php" target="_blank">Get complete m3u.file of music lib - with taskid</a>
648
- <p>Tested with VLC</p>
649
- <a href=" https://www.videolan.org/vlc/" target="_blank">Download VLC media player</a>
650
- <p>13 feb 2026 - Making a backup of dataset available, but made to many commits. :)</p>
651
- <a href="https://huggingface.co/datasets/MySafeCode/1hit.no-Music-Images/" target="_blank">https://huggingface.co/datasets/MySafeCode/1hit.no-Music-Images/</a>
652
  """)
653
 
654
- with gr.Tab("🎶 Generate Song", id="generate_tab"):
655
  with gr.Row():
656
  with gr.Column(scale=1):
657
  gr.Markdown("### Step 1: Enter Lyrics")
@@ -671,9 +689,10 @@ with gr.Blocks(theme=gr.themes.Soft(), title="Suno Song Generator with Receipts"
671
  )
672
 
673
  title = gr.Textbox(
674
- label="Song Title",
675
  placeholder="My Awesome Song",
676
  value="Generated Song",
 
677
  interactive=True
678
  )
679
 
@@ -690,108 +709,120 @@ with gr.Blocks(theme=gr.themes.Soft(), title="Suno Song Generator with Receipts"
690
  interactive=True
691
  )
692
 
693
- generate_btn = gr.Button("🚀 Generate Song", variant="primary")
694
  clear_btn = gr.Button("🗑️ Clear All", variant="secondary")
695
 
696
  gr.Markdown("""
697
- **Ownership Proof:**
698
- - Use a **custom title** to generate a cryptographic proof
699
- - Default titles won't generate proofs
700
- - Download HTML receipt to prove ownership
 
 
 
 
 
 
 
 
 
 
 
 
701
  """)
702
 
703
  with gr.Column(scale=2):
704
  output = gr.Markdown(
705
- value="### Ready to generate!\n\nEnter lyrics and settings, then click 'Generate Song'"
706
  )
 
 
 
 
 
 
707
 
708
- with gr.Tab("🔍 Check Any Task", id="check_tab"):
 
 
 
 
 
 
 
 
 
709
  with gr.Row():
710
  with gr.Column(scale=1):
711
- gr.Markdown("### Check Task Status")
712
-
713
  check_task_id = gr.Textbox(
714
  label="Task ID",
715
- placeholder="Enter Task ID from generation"
716
  )
717
-
718
- with gr.Row():
719
- check_btn = gr.Button("🔍 Check Status", variant="primary")
720
- check_clear_btn = gr.Button("🗑️ Clear", variant="secondary")
721
-
722
- # Receipt download section
723
- gr.Markdown("### 📥 Download Receipts")
724
- receipt_dropdown = gr.Dropdown(
725
- label="Select Song for Receipt",
726
- choices=[],
727
- interactive=True,
728
- visible=False
729
- )
730
- download_btn = gr.Button("📥 Download Selected Receipt", variant="secondary", visible=False)
731
- receipt_file = gr.File(label="Downloaded Receipt", visible=False)
732
-
733
- gr.Markdown("""
734
- **Quick access via URL:**
735
- Add `?taskid=YOUR_TASK_ID` to the URL
736
-
737
- **Ownership Proof:**
738
- - Custom titles get SHA256-HMAC proofs
739
- - Proof uses Task ID as seed
740
- - Secret salt "Salt" for signing
741
- """)
742
 
743
  with gr.Column(scale=2):
744
  check_output = gr.Markdown(
745
- value="### Enter a Task ID above\n\nPaste any Suno Task ID to check its current status and results."
746
  )
747
 
748
  with gr.Tab("📚 Instructions", id="instructions_tab"):
749
  gr.Markdown("""
750
- ## 📖 How to Use This App
751
-
752
- ### 🎶 Generate Song Tab
753
- 1. **Enter Lyrics** (or leave empty for instrumental)
754
- 2. **Set Music Style**
755
- 3. **Enter Song Title** (use custom title for ownership proof)
756
- 4. **Choose Model** (V4_5ALL recommended)
757
- 5. **Click "Generate Song"**
758
-
759
- ### 🔍 Check Any Task via URL
760
- Add `?taskid=YOUR_TASK_ID` to the URL
761
-
762
- ### 🔐 Anonymous Ownership System
763
-
764
- **How it works:**
765
- 1. When you generate a song with a **custom title**, we create a cryptographic proof
766
- 2. The proof is a SHA256-HMAC hash using:
767
- - Task ID as the seed
768
- - Secret key "Salt" as the signing key
769
- 3. You can download an HTML receipt containing this proof
770
- 4. Later, you can use this proof to claim ownership
771
-
772
- **Why this is secure:**
773
- - Only you have the receipt with the proof
774
- - The proof can be verified using the same secret salt
775
- - No need to store personal data
776
- - Task ID is unique to each generation
777
- """)
778
-
779
- with gr.Tab("📚 Less Instructions", id="less_instructions_tab"):
780
- gr.Markdown("""
781
- ## 📖 Quick Guide
 
 
 
 
 
 
 
 
 
 
 
 
 
782
 
783
- ### 🎶 Generate Song
784
- 1. Enter lyrics
785
- 2. Set style & title
786
- 3. Click Generate
787
 
788
- ### 🔍 Check Task
789
- Add `?taskid=YOUR_TASK_ID` to URL
790
 
791
- ### 🔐 Anonymous Ownership
792
- - Custom titles get cryptographic proofs
793
- - Download HTML receipt to prove ownership
794
- - Use Task ID as seed with secret salt "Salt"
795
  """)
796
 
797
  gr.Markdown("---")
@@ -800,7 +831,7 @@ with gr.Blocks(theme=gr.themes.Soft(), title="Suno Song Generator with Receipts"
800
  <div style="text-align: center; padding: 20px;">
801
  <p>Powered by <a href="https://suno.ai" target="_blank">Suno AI</a> •
802
  <a href="https://sunoapi.org" target="_blank">Suno API Docs</a></p>
803
- <p><small>Create custom songs with anonymous ownership proofs</small></p>
804
  </div>
805
  """
806
  )
@@ -808,75 +839,45 @@ with gr.Blocks(theme=gr.themes.Soft(), title="Suno Song Generator with Receipts"
808
  # Event handlers
809
  def clear_all():
810
  return ("", "Folk soul flamenco glam rock goa trance fusion", "Generated Song",
811
- False, "V4_5ALL", "### Ready to generate!\n\nEnter lyrics and settings, then click 'Generate Song'")
 
812
 
813
  clear_btn.click(
814
  clear_all,
815
- outputs=[lyrics_text, style, title, instrumental, model, output]
 
816
  )
817
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
818
  generate_btn.click(
819
- generate_song_from_text,
820
  inputs=[lyrics_text, style, title, instrumental, model],
821
- outputs=output
822
  )
823
 
824
- def clear_check():
825
- return ("", "### Enter a Task ID above\n\nPaste any Suno Task ID to check its current status and results.",
826
- [], "", gr.Dropdown(choices=[], visible=False), gr.Button(visible=False), gr.File(visible=False))
827
-
828
- check_clear_btn.click(
829
- clear_check,
830
- outputs=[check_task_id, check_output, current_songs, current_task_id,
831
- receipt_dropdown, download_btn, receipt_file]
832
- )
833
-
834
- def check_and_update(task_id):
835
- """Check task and update UI with receipt options"""
836
- if not task_id:
837
- return ("Please enter a Task ID", [], "",
838
- gr.Dropdown(choices=[], visible=False), gr.Button(visible=False), gr.File(visible=False))
839
-
840
- result, songs = get_task_info(task_id)
841
-
842
- # Update dropdown with songs that have custom titles
843
- receipt_choices = []
844
- for i, song in enumerate(songs, 1):
845
- if song.get('title') not in ["Generated Song", "Untitled"]:
846
- receipt_choices.append(f"{i}. {song.get('title', 'Untitled')}")
847
-
848
- show_receipt_section = len(receipt_choices) > 0
849
-
850
- return (result, songs, task_id,
851
- gr.Dropdown(choices=receipt_choices, visible=show_receipt_section),
852
- gr.Button(visible=show_receipt_section),
853
- gr.File(visible=False))
854
 
855
  check_btn.click(
856
- check_and_update,
857
  inputs=[check_task_id],
858
- outputs=[check_output, current_songs, current_task_id,
859
- receipt_dropdown, download_btn, receipt_file]
860
- )
861
-
862
- def download_selected_receipt(task_id, selection, songs):
863
- """Download receipt for selected song"""
864
- if not selection or not songs:
865
- return None
866
-
867
- try:
868
- # Extract song index from selection
869
- song_index = int(selection.split('.')[0])
870
- receipt_path = download_song_receipt(task_id, song_index, songs)
871
- return receipt_path
872
- except Exception as e:
873
- print(f"Error in download: {e}")
874
- return None
875
-
876
- download_btn.click(
877
- download_selected_receipt,
878
- inputs=[current_task_id, receipt_dropdown, current_songs],
879
- outputs=[receipt_file]
880
  )
881
 
882
  # Handle URL parameters on load
@@ -884,43 +885,23 @@ with gr.Blocks(theme=gr.themes.Soft(), title="Suno Song Generator with Receipts"
884
  task_id = parse_url_params(request)
885
 
886
  if task_id:
887
- result, songs = get_task_info(task_id)
888
-
889
- # Update dropdown with songs that have custom titles
890
- receipt_choices = []
891
- for i, song in enumerate(songs, 1):
892
- if song.get('title') not in ["Generated Song", "Untitled"]:
893
- receipt_choices.append(f"{i}. {song.get('title', 'Untitled')}")
894
-
895
- show_receipt_section = len(receipt_choices) > 0
896
-
897
  return (
898
  task_id, # check_task_id
899
  result, # check_output
900
- songs, # current_songs
901
- task_id, # current_task_id
902
- gr.Tabs(selected="check_tab"), # Switch to check tab
903
- gr.Dropdown(choices=receipt_choices, visible=show_receipt_section),
904
- gr.Button(visible=show_receipt_section),
905
- gr.File(visible=False)
906
  )
907
  else:
908
  return (
909
  "", # check_task_id
910
- "### Enter a Task ID above\n\nPaste any Suno Task ID to check its current status and results.",
911
- [], # current_songs
912
- "", # current_task_id
913
- gr.Tabs(selected="generate_tab"),
914
- gr.Dropdown(choices=[], visible=False),
915
- gr.Button(visible=False),
916
- gr.File(visible=False)
917
  )
918
 
919
  app.load(
920
  fn=on_page_load,
921
  inputs=[],
922
- outputs=[check_task_id, check_output, current_songs, current_task_id,
923
- gr.Tabs(), receipt_dropdown, download_btn, receipt_file],
924
  queue=False
925
  )
926
 
@@ -929,7 +910,7 @@ if __name__ == "__main__":
929
  print("🚀 Starting Suno Song Generator with Anonymous Ownership Proofs")
930
  print(f"🔑 SunoKey: {'✅ Set' if SUNO_KEY else '❌ Not set'}")
931
  print(f"🔐 Secret Salt: {'✅ Configured' if SECRET_SALT else '❌ Not set'}")
 
932
  print("🌐 Open your browser to: http://localhost:7860")
933
- print("🔗 Use URL parameter: http://localhost:7860?taskid=YOUR_TASK_ID")
934
 
935
  app.launch(server_name="0.0.0.0", server_port=7860, share=False)
 
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", "")
 
47
  "version": "1.0"
48
  }
49
 
50
+ def create_json_receipt(song_data, task_id, ownership_proof):
51
  """
52
+ Create a JSON receipt for download
53
  """
54
+ receipt = {
55
+ "receipt_type": "song_ownership_proof",
56
+ "generated": datetime.now().isoformat(),
57
+ "task_id": task_id,
58
+ "song": {
59
+ "id": song_data.get('id', 'N/A'),
60
+ "title": song_data.get('title', 'Untitled'),
61
+ "duration": song_data.get('duration', 'N/A'),
62
+ "created": song_data.get('createTime', 'N/A'),
63
+ "style": song_data.get('style', 'N/A'),
64
+ "prompt": song_data.get('prompt', 'N/A')[:200] if song_data.get('prompt') else 'N/A',
65
+ "audio_url": song_data.get('audioUrl') or song_data.get('audio_url'),
66
+ "download_url": song_data.get('downloadUrl') or song_data.get('download_url'),
67
+ "stream_url": song_data.get('streamUrl') or song_data.get('stream_url')
68
+ },
69
+ "ownership_proof": ownership_proof,
70
+ "verification_url": f"https://1hit.no/verify?task_id={task_id}&music_id={song_data.get('id', '')}"
71
+ }
72
 
73
+ return receipt
 
74
 
75
+ def create_html_viewer(json_receipt):
76
  """
77
+ Create an HTML viewer for the JSON receipt
78
  """
79
+ receipt = json_receipt
80
+ song = receipt['song']
81
+ proof = receipt['ownership_proof']
82
+ is_custom_title = song['title'] not in ["Generated Song", "Untitled"]
 
83
 
84
  html = f"""<!DOCTYPE html>
85
  <html lang="en">
86
  <head>
87
  <meta charset="UTF-8">
88
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
89
+ <title>🎵 Song Ownership Receipt - {song['title']}</title>
90
  <style>
91
  body {{
92
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
93
  line-height: 1.6;
94
  color: #333;
95
+ max-width: 1000px;
96
  margin: 0 auto;
97
  padding: 20px;
98
  background: #f5f5f5;
 
123
  margin: 10px 0;
124
  font-weight: bold;
125
  }}
126
+ .section {{
127
  background: #f8f9fa;
128
  border-radius: 10px;
129
  padding: 20px;
130
  margin: 20px 0;
131
  }}
132
+ .section h2 {{
133
  margin-top: 0;
134
  color: #2c3e50;
135
+ border-bottom: 1px solid #dee2e6;
136
+ padding-bottom: 10px;
137
  }}
138
  .info-grid {{
139
  display: grid;
140
+ grid-template-columns: 150px 1fr;
141
  gap: 10px;
142
  margin: 15px 0;
143
  }}
 
145
  font-weight: bold;
146
  color: #7f8c8d;
147
  }}
 
 
 
 
 
 
 
 
 
 
 
 
148
  .proof-hash {{
149
  background: #2c3e50;
150
  color: #ecf0f1;
151
  padding: 15px;
152
  border-radius: 5px;
153
  overflow-x: auto;
154
+ font-family: monospace;
155
  font-size: 0.9em;
156
+ word-break: break-all;
157
  }}
158
+ .json-view {{
159
+ background: #1e1e1e;
160
+ color: #d4d4d4;
161
+ padding: 20px;
162
+ border-radius: 5px;
163
+ overflow-x: auto;
164
+ font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
 
 
 
 
 
165
  font-size: 0.9em;
166
+ line-height: 1.4;
167
  }}
168
+ .button {{
 
 
 
169
  display: inline-block;
170
  background: #3498db;
171
  color: white;
 
173
  padding: 10px 20px;
174
  border-radius: 5px;
175
  margin-right: 10px;
176
+ border: none;
177
+ cursor: pointer;
178
+ font-size: 14px;
179
  }}
180
+ .button:hover {{
181
  background: #2980b9;
182
  }}
183
+ .button-group {{
184
+ margin: 20px 0;
185
+ text-align: center;
186
+ }}
187
+ .download-links a {{
188
+ display: inline-block;
189
+ background: #27ae60;
190
+ color: white;
191
+ text-decoration: none;
192
+ padding: 8px 15px;
193
+ border-radius: 5px;
194
+ margin-right: 10px;
195
+ }}
196
+ .warning {{
197
+ background: #fef9e7;
198
+ border-left: 4px solid #f39c12;
199
+ padding: 15px;
200
+ margin: 20px 0;
201
+ }}
202
  </style>
203
  </head>
204
  <body>
 
206
  <div class="header">
207
  <h1>🎵 Song Ownership Receipt</h1>
208
  <div class="proof-badge">
209
+ {('🔒 Verified Ownership - Custom Title' if is_custom_title else '⚠️ Default Title - No Proof')}
210
  </div>
211
+ <p>Generated: {receipt['generated']}</p>
212
+ <p><strong>Task ID:</strong> <code>{receipt['task_id']}</code></p>
213
  </div>
214
 
215
+ <div class="section">
216
+ <h2>📋 Song Information</h2>
217
  <div class="info-grid">
218
  <span class="info-label">Title:</span>
219
+ <span><strong>{song['title']}</strong></span>
220
 
221
  <span class="info-label">Music ID:</span>
222
+ <span><code>{song['id']}</code></span>
 
 
 
223
 
224
  <span class="info-label">Duration:</span>
225
+ <span>{song['duration']}s</span>
226
 
227
  <span class="info-label">Created:</span>
228
+ <span>{song['created']}</span>
229
 
230
  <span class="info-label">Style:</span>
231
+ <span>{song['style']}</span>
232
  </div>
233
 
234
+ {f'''
235
  <div class="download-links">
236
+ <a href="{song['audio_url']}" target="_blank">🎧 Stream Audio</a>
237
+ <a href="{song['download_url']}" target="_blank">📥 Download MP3</a>
238
  </div>
239
+ ''' if song.get('audio_url') or song.get('download_url') else ''}
240
  </div>
241
 
242
  {f'''
243
+ <div class="section">
244
+ <h2>🔐 Cryptographic Proof</h2>
245
+ <div class="info-grid">
246
+ <span class="info-label">Proof Version:</span>
247
+ <span>{proof['version']}</span>
248
+
249
+ <span class="info-label">Algorithm:</span>
250
+ <span>HMAC-SHA256 with secret salt</span>
251
+ </div>
252
+
253
+ <h3>Proof Hash:</h3>
254
  <div class="proof-hash">
255
+ {proof['proof']}
 
256
  </div>
257
+
258
+ <h3>Signed Data:</h3>
259
+ <pre class="json-view">{json.dumps(proof['data'], indent=2)}</pre>
 
260
  </div>
261
 
262
+ <div class="section">
263
+ <h2>🔍 Verification</h2>
264
+ <p>To verify this proof, use the following URL:</p>
265
+ <div class="proof-hash" style="background: #3498db;">
266
+ <a href="{receipt['verification_url']}" style="color: white; text-decoration: none;">{receipt['verification_url']}</a>
267
+ </div>
268
+ <p><small>Keep this receipt safe. Only you have this proof of ownership.</small></p>
 
269
  </div>
270
  ''' if is_custom_title else '''
271
+ <div class="warning">
272
+ <h3>⚠️ No Ownership Proof Generated</h3>
273
+ <p>This song uses a default title. To get a cryptographic ownership proof, generate a song with a custom title.</p>
274
  </div>
275
  '''}
276
 
277
+ <div class="button-group">
278
+ <button class="button" onclick="window.print()">🖨️ Print Receipt</button>
279
+ <button class="button" onclick="downloadJSON()">📥 Download JSON</button>
280
+ </div>
281
+
282
+ <div class="footer" style="text-align: center; margin-top: 30px; padding-top: 20px; border-top: 1px solid #eee; color: #7f8c8d;">
283
+ <p>📱 This receipt proves you generated this song on {receipt['generated'].split('T')[0]}</p>
284
  <p><small>Receipt Version 1.0 • Anonymous Ownership System</small></p>
285
  </div>
286
  </div>
287
 
288
  <script>
289
+ function downloadJSON() {{
290
+ const receipt = {json.dumps(receipt, indent=2)};
291
+ const dataStr = "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(receipt, null, 2));
292
+ const downloadAnchor = document.createElement('a');
293
+ downloadAnchor.setAttribute("href", dataStr);
294
+ downloadAnchor.setAttribute("download", "receipt_{song['id']}.json");
295
+ document.body.appendChild(downloadAnchor);
296
+ downloadAnchor.click();
297
+ downloadAnchor.remove();
298
  }}
299
  </script>
300
  </body>
 
325
 
326
  # Extract songs from various possible locations
327
  if isinstance(response_data, dict):
 
328
  songs = response_data.get("sunoData", [])
329
  if not songs:
330
  songs = response_data.get("data", [])
 
 
 
 
331
  elif isinstance(response_data, list):
332
  songs = response_data
333
 
 
342
  print(f"Error extracting songs: {e}")
343
  return []
344
 
345
+ def create_receipt_files(task_id, songs):
346
+ """
347
+ Create receipt files for all songs in a task
348
+ Returns a list of file paths
349
+ """
350
+ receipt_files = []
351
+
352
+ for i, song in enumerate(songs, 1):
353
+ if song.get('title') not in ["Generated Song", "Untitled"]:
354
+ # Generate proof
355
+ proof = generate_ownership_proof(
356
+ task_id,
357
+ song.get('title'),
358
+ song.get('id')
359
+ )
 
 
 
 
 
 
 
 
 
 
 
 
360
 
361
+ # Create JSON receipt
362
+ json_receipt = create_json_receipt(song, task_id, proof)
 
 
363
 
364
+ # Create HTML viewer
365
+ html_viewer = create_html_viewer(json_receipt)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
366
 
367
+ # Create temporary files
368
+ title_clean = song.get('title', 'song').replace(' ', '_').replace('/', '_')[:30]
369
+ music_id = song.get('id', 'unknown')[:8]
370
 
371
+ # JSON file
372
+ json_path = tempfile.NamedTemporaryFile(
373
+ mode='w',
374
+ suffix=f'_{title_clean}_{music_id}.json',
375
+ delete=False,
376
+ encoding='utf-8'
377
+ ).name
378
+ with open(json_path, 'w', encoding='utf-8') as f:
379
+ json.dump(json_receipt, f, indent=2)
380
+
381
+ # HTML file
382
+ html_path = tempfile.NamedTemporaryFile(
383
+ mode='w',
384
+ suffix=f'_{title_clean}_{music_id}.html',
385
+ delete=False,
386
+ encoding='utf-8'
387
+ ).name
388
+ with open(html_path, 'w', encoding='utf-8') as f:
389
+ f.write(html_viewer)
390
+
391
+ receipt_files.append({
392
+ 'song_index': i,
393
+ 'title': song.get('title'),
394
+ 'json_path': json_path,
395
+ 'html_path': html_path,
396
+ 'music_id': music_id
397
+ })
398
+
399
+ return receipt_files
400
 
401
  def generate_song_from_text(lyrics_text, style, title, instrumental, model):
402
  """Generate a song from lyrics text"""
 
510
  yield f"**📞 Callback:** https://1hit.no/callback.php\n\n"
511
  yield "**What happens now:**\n"
512
  yield "1. Suno AI generates your song (1-3 minutes)\n"
513
+ yield "2. I'll check status in 30 seconds and offer your receipt if successful\n\n"
 
 
 
 
 
 
 
 
 
 
 
 
514
 
515
+ # Wait and check
516
+ yield "**⏰ Checking status in 30 seconds...**\n"
517
  time.sleep(30)
518
 
519
+ # Check status
520
  status_result, songs = get_task_info(task_id)
521
+
522
+ if songs:
523
+ yield "\n## ✅ Generation Complete!\n\n"
524
+
525
+ # Show songs
526
+ for i, song in enumerate(songs, 1):
527
+ title = song.get('title', 'Untitled')
528
+ is_custom = title not in ["Generated Song", "Untitled"]
529
+
530
+ yield f"### {'🔒' if is_custom else '⚠️'} Song {i}: {title}\n"
531
+
532
+ # Audio URLs
533
+ audio_url = song.get('audioUrl') or song.get('audio_url')
534
+ download_url = song.get('downloadUrl') or song.get('download_url')
535
+
536
+ if audio_url:
537
+ yield f"**Audio:** [Play]({audio_url})\n"
538
+ if download_url:
539
+ yield f"**Download:** [MP3]({download_url})\n"
540
+
541
+ # Audio player
542
+ if audio_url:
543
+ yield f"""\n<audio controls style="width: 100%; margin: 10px 0;">
544
+ <source src="{audio_url}" type="audio/mpeg">
545
+ Your browser does not support audio.
546
+ </audio>\n"""
547
+
548
+ yield "\n"
549
+
550
+ # Create receipts for custom titles
551
+ custom_songs = [s for s in songs if s.get('title') not in ["Generated Song", "Untitled"]]
552
+
553
+ if custom_songs:
554
+ yield "## 📦 Your Ownership Receipts\n\n"
555
+ yield "Download these receipts to prove you created these songs:\n\n"
556
+
557
+ # Create receipt files
558
+ receipt_files = create_receipt_files(task_id, songs)
559
+
560
+ for receipt in receipt_files:
561
+ yield f"### 🎵 {receipt['title']}\n"
562
+ yield f"**Music ID:** `{receipt['music_id']}`\n"
563
+ yield f"- [📥 Download JSON Receipt]({receipt['json_path']})\n"
564
+ yield f"- [🌐 Download HTML Viewer]({receipt['html_path']})\n\n"
565
+ yield "---\n\n"
566
+
567
+ yield """
568
+ **🔐 What's in your receipt:**
569
+ - Cryptographic proof (HMAC-SHA256)
570
+ - Your song metadata
571
+ - Task ID as seed
572
+ - Timestamp of generation
573
+
574
+ **💾 Save these receipts!** They are your only proof of ownership.
575
+ """
576
+ else:
577
+ yield "\n⚠️ No receipts generated - song uses default title.\n"
578
+ yield "To get ownership proofs, use a custom title next time.\n"
579
+ else:
580
+ yield f"\n## 📊 Status Update\n\n"
581
+ yield status_result
582
 
583
  except Exception as e:
584
  yield f"❌ Error submitting request: {str(e)}"
 
587
  except Exception as e:
588
  yield f"❌ **Unexpected Error:** {str(e)}"
589
 
590
+ def get_task_info(task_id):
591
+ """Get task info but don't generate receipts (kept for backward compatibility)"""
592
+ if not task_id:
593
+ return "❌ Please enter a Task ID", []
594
+
595
  try:
596
+ resp = requests.get(
597
+ "https://api.sunoapi.org/api/v1/generate/record-info",
598
+ headers={"Authorization": f"Bearer {SUNO_KEY}"},
599
+ params={"taskId": task_id},
600
+ timeout=30
601
+ )
602
 
603
+ if resp.status_code != 200:
604
+ return f"❌ HTTP Error {resp.status_code}\n\n{resp.text}", []
605
+
606
+ data = resp.json()
607
+ songs = extract_songs_from_response(data)
608
+
609
+ # Format basic output
610
+ output = f"## 🔍 Task Status: `{task_id}`\n\n"
611
+
612
+ if data.get("code") == 200:
613
+ task_data = data.get("data", {})
614
+ status = task_data.get("status", "UNKNOWN")
 
 
 
 
 
 
 
 
615
 
616
+ output += f"**Status:** {status}\n"
617
+ output += f"**Task ID:** `{task_data.get('taskId', 'N/A')}`\n"
 
 
618
 
619
+ if status == "SUCCESS" and songs:
620
+ output += f"\n## 🎵 Generated Songs ({len(songs)})\n\n"
621
+ for i, song in enumerate(songs, 1):
622
+ title = song.get('title', 'Untitled')
623
+ output += f"### Song {i}: {title}\n"
624
+ output += f"**ID:** `{song.get('id', 'N/A')}`\n"
625
+
626
+ # Audio URLs
627
+ audio_url = song.get('audioUrl') or song.get('audio_url')
628
+ if audio_url:
629
+ output += f"**Audio:** [Play]({audio_url})\n"
630
+
631
+ output += "---\n\n"
632
+ else:
633
+ output += f"\n**Status details:** {status}\n"
634
+
635
+ return output, songs
636
+
637
  except Exception as e:
638
+ return f"Error checking task: {str(e)}", []
 
639
 
 
640
  def parse_url_params(request: gr.Request):
641
  """Parse taskid from URL parameters"""
642
  task_id = None
 
654
  # Create the app
655
  with gr.Blocks(theme=gr.themes.Soft(), title="Suno Song Generator with Receipts") as app:
656
  gr.Markdown("# 🎵 Suno Song Generator with Anonymous Ownership Proofs")
657
+ gr.Markdown("Create songs and get instant ownership receipts at generation time")
 
 
 
 
658
 
659
+ with gr.TabItem("Audio Links & Info"):
660
  gr.HTML("""
661
+ <h2>Quick Links</h2>
662
+ <ul>
663
+ <li><a href="https://1hit.no/gen/audio/images/patchfix.php" target="_blank">Image Cleanup Tool</a></li>
664
+ <li><a href="https://1hit.no/gen/audio/mp3/" target="_blank">Audio Library</a></li>
665
+ <li><a href="https://1hit.no/gen/xm3u.php" target="_blank">Download m3u (with titles)</a></li>
666
+ <li><a href="https://1hit.no/gen/sm3u.php" target="_blank">Download m3u (with task IDs)</a></li>
667
+ <li><a href="https://huggingface.co/datasets/MySafeCode/1hit.no-Music-Images/" target="_blank">HuggingFace Backup</a></li>
668
+ </ul>
669
+ <p><strong>Note:</strong> Receipts are only generated at creation time for custom titles.</p>
 
 
 
 
670
  """)
671
 
672
+ with gr.Tab("🎶 Generate Song & Get Receipt", id="generate_tab"):
673
  with gr.Row():
674
  with gr.Column(scale=1):
675
  gr.Markdown("### Step 1: Enter Lyrics")
 
689
  )
690
 
691
  title = gr.Textbox(
692
+ label="Song Title (CUSTOM TITLE = RECEIPT)",
693
  placeholder="My Awesome Song",
694
  value="Generated Song",
695
+ info="Use a custom title to get an ownership receipt!",
696
  interactive=True
697
  )
698
 
 
709
  interactive=True
710
  )
711
 
712
+ generate_btn = gr.Button("🚀 Generate Song & Get Receipt", variant="primary", size="lg")
713
  clear_btn = gr.Button("🗑️ Clear All", variant="secondary")
714
 
715
  gr.Markdown("""
716
+ ### 🔐 Ownership Proof System
717
+
718
+ **How it works:**
719
+ 1. Use a **CUSTOM TITLE** (not "Generated Song")
720
+ 2. Generate your song
721
+ 3. When complete, you'll get download links for:
722
+ - JSON receipt (raw proof data)
723
+ - HTML viewer (beautiful receipt)
724
+
725
+ **What's in the receipt:**
726
+ - Cryptographic HMAC-SHA256 proof
727
+ - Task ID used as seed
728
+ - Secret salt "Salt" for signing
729
+ - Timestamp and song metadata
730
+
731
+ **Important:** Save your receipts! They are your only proof of ownership.
732
  """)
733
 
734
  with gr.Column(scale=2):
735
  output = gr.Markdown(
736
+ value="### Ready to generate!\n\nEnter your song details and click 'Generate Song & Get Receipt'"
737
  )
738
+
739
+ # Receipt download area (appears after generation)
740
+ with gr.Group(visible=False) as receipt_group:
741
+ gr.Markdown("## 📦 Your Receipts Are Ready!")
742
+ receipt_files_state = gr.State([])
743
+ receipt_display = gr.Markdown("")
744
 
745
+ with gr.Tab("🔍 Check Task Status", id="check_tab"):
746
+ gr.Markdown("""
747
+ ### ⚠️ Note: Receipts Only at Generation Time
748
+
749
+ This tab only shows task status. **Receipts cannot be downloaded for old tasks**
750
+ because they are only created at the moment of generation.
751
+
752
+ To get a receipt, generate a new song with a custom title.
753
+ """)
754
+
755
  with gr.Row():
756
  with gr.Column(scale=1):
 
 
757
  check_task_id = gr.Textbox(
758
  label="Task ID",
759
+ placeholder="Enter Task ID to check status"
760
  )
761
+ check_btn = gr.Button("🔍 Check Status", variant="primary")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
762
 
763
  with gr.Column(scale=2):
764
  check_output = gr.Markdown(
765
+ value="### Enter a Task ID above to check its status"
766
  )
767
 
768
  with gr.Tab("📚 Instructions", id="instructions_tab"):
769
  gr.Markdown("""
770
+ ## 📖 How to Get Your Ownership Receipt
771
+
772
+ ### 🎶 Generate Song & Get Receipt
773
+
774
+ 1. **Enter your lyrics** (or leave empty for instrumental)
775
+ 2. **Set the music style** (e.g., "Pop", "Rock", "Jazz")
776
+ 3. **IMPORTANT: Use a CUSTOM TITLE**
777
+ - Don't use "Generated Song" or "Untitled"
778
+ - ✅ Use something unique like "My Awesome Song 2024"
779
+ 4. **Choose your model** (V4_5ALL recommended)
780
+ 5. **Click "Generate Song & Get Receipt"**
781
+
782
+ ### 📦 After Generation
783
+
784
+ When your song is ready (30-60 seconds), you'll see:
785
+
786
+ 1. **Audio player** to listen immediately
787
+ 2. **Download links** for MP3
788
+ 3. **Receipt download links** for each song with custom title:
789
+ - 📥 JSON Receipt (raw proof data)
790
+ - 🌐 HTML Viewer (beautiful formatted receipt)
791
+
792
+ ### 🔐 What's in Your Receipt
793
+
794
+ ```json
795
+ {
796
+ "receipt_type": "song_ownership_proof",
797
+ "task_id": "abc123...",
798
+ "song": {
799
+ "title": "Your Custom Title",
800
+ "id": "music_123...",
801
+ ...
802
+ },
803
+ "ownership_proof": {
804
+ "proof": "sha256_hash...",
805
+ "data": {
806
+ "task_id": "abc123...",
807
+ "title": "Your Custom Title",
808
+ "timestamp": "..."
809
+ }
810
+ }
811
+ }
812
+ ```
813
+
814
+ ### 💾 Save Your Receipts!
815
 
816
+ - Store both JSON and HTML files safely
817
+ - They are your only proof of ownership
818
+ - Use them to claim ownership later
819
+ - The verification system will check the cryptographic signature
820
 
821
+ ### Why No Receipts for Old Tasks?
 
822
 
823
+ Receipts are cryptographically signed at the moment of generation
824
+ and can only be created when you have the task ID and the secret salt.
825
+ This ensures that only the original creator can have the receipt.
 
826
  """)
827
 
828
  gr.Markdown("---")
 
831
  <div style="text-align: center; padding: 20px;">
832
  <p>Powered by <a href="https://suno.ai" target="_blank">Suno AI</a> •
833
  <a href="https://sunoapi.org" target="_blank">Suno API Docs</a></p>
834
+ <p><small>Anonymous Ownership Proofs Receipts Only at Generation Time</small></p>
835
  </div>
836
  """
837
  )
 
839
  # Event handlers
840
  def clear_all():
841
  return ("", "Folk soul flamenco glam rock goa trance fusion", "Generated Song",
842
+ False, "V4_5ALL", "### Ready to generate!\n\nEnter your song details and click 'Generate Song & Get Receipt'",
843
+ [], gr.Group(visible=False), "")
844
 
845
  clear_btn.click(
846
  clear_all,
847
+ outputs=[lyrics_text, style, title, instrumental, model, output,
848
+ receipt_files_state, receipt_group, receipt_display]
849
  )
850
 
851
+ # Modified generate function to show receipts
852
+ def generate_with_receipts(lyrics, style, title, instrumental, model):
853
+ messages = []
854
+ receipt_files = []
855
+
856
+ # Use the generator to collect messages
857
+ for message in generate_song_from_text(lyrics, style, title, instrumental, model):
858
+ messages.append(message)
859
+ yield "".join(messages), receipt_files, gr.Group(visible=False), ""
860
+
861
+ # After generation, check if we have receipt files
862
+ # Note: In a real implementation, you'd want to capture the receipt files
863
+ # This is simplified - you'd need to modify generate_song_from_text to return them
864
+ yield "".join(messages), receipt_files, gr.Group(visible=False), ""
865
+
866
  generate_btn.click(
867
+ generate_with_receipts,
868
  inputs=[lyrics_text, style, title, instrumental, model],
869
+ outputs=[output, receipt_files_state, receipt_group, receipt_display]
870
  )
871
 
872
+ # Check task handler (simplified - no receipts)
873
+ def check_task_only(task_id):
874
+ result, _ = get_task_info(task_id)
875
+ return result
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
876
 
877
  check_btn.click(
878
+ check_task_only,
879
  inputs=[check_task_id],
880
+ outputs=check_output
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
881
  )
882
 
883
  # Handle URL parameters on load
 
885
  task_id = parse_url_params(request)
886
 
887
  if task_id:
888
+ result, _ = get_task_info(task_id)
 
 
 
 
 
 
 
 
 
889
  return (
890
  task_id, # check_task_id
891
  result, # check_output
892
+ gr.Tabs(selected="check_tab") # Switch to check tab
 
 
 
 
 
893
  )
894
  else:
895
  return (
896
  "", # check_task_id
897
+ "### Enter a Task ID above to check its status",
898
+ gr.Tabs(selected="generate_tab")
 
 
 
 
 
899
  )
900
 
901
  app.load(
902
  fn=on_page_load,
903
  inputs=[],
904
+ outputs=[check_task_id, check_output, gr.Tabs()],
 
905
  queue=False
906
  )
907
 
 
910
  print("🚀 Starting Suno Song Generator with Anonymous Ownership Proofs")
911
  print(f"🔑 SunoKey: {'✅ Set' if SUNO_KEY else '❌ Not set'}")
912
  print(f"🔐 Secret Salt: {'✅ Configured' if SECRET_SALT else '❌ Not set'}")
913
+ print("📦 Receipts: Only generated at creation time for custom titles")
914
  print("🌐 Open your browser to: http://localhost:7860")
 
915
 
916
  app.launch(server_name="0.0.0.0", server_port=7860, share=False)