haraberget commited on
Commit
abec1cb
·
verified ·
1 Parent(s): 72b4aa9

Update aaa.py

Browse files
Files changed (1) hide show
  1. aaa.py +692 -692
aaa.py CHANGED
@@ -1,693 +1,693 @@
1
- import gradio as gr
2
- import os
3
- import tempfile
4
- import shutil
5
- import requests
6
- import json
7
- import uuid
8
- import time
9
- from datetime import datetime
10
- from dotenv import load_dotenv
11
- from urllib.parse import urlparse, parse_qs
12
-
13
- # Load environment variables
14
- load_dotenv()
15
-
16
- # ============================================
17
- # CONFIGURATION
18
- # ============================================
19
-
20
- SUNO_API_KEY = os.environ.get("SunoKey", "")
21
- FIXED_CALLBACK_URL = "https://1hit.no/cover/cb.php"
22
- SUNO_API_URL = "https://api.sunoapi.org/api/v1/generate/upload-cover"
23
- LOGS_URL = "https://www.1hit.no/cover/compu.php"
24
- BACK_URL = "https://www.1hit.no/cover/ib.php"
25
-
26
- # Create uploads directory
27
- UPLOADS_FOLDER = "uploads"
28
- os.makedirs(UPLOADS_FOLDER, exist_ok=True)
29
-
30
- # ============================================
31
- # PAGE 1: FILE UPLOAD
32
- # ============================================
33
-
34
- def upload_file(file_obj, custom_name):
35
- """Simple file upload - returns file for download"""
36
- if not file_obj:
37
- return None, "❌ Please upload a file first"
38
-
39
- try:
40
- if isinstance(file_obj, dict):
41
- file_path = file_obj["path"]
42
- original_name = file_obj["name"]
43
- else:
44
- file_path = file_obj.name
45
- original_name = os.path.basename(file_path)
46
-
47
- temp_dir = tempfile.mkdtemp()
48
-
49
- if custom_name and custom_name.strip():
50
- ext = os.path.splitext(original_name)[1]
51
- base_name = custom_name.strip()
52
- if not base_name.endswith(ext):
53
- final_name = base_name + ext
54
- else:
55
- final_name = base_name
56
- else:
57
- final_name = original_name
58
-
59
- final_path = os.path.join(temp_dir, final_name)
60
- shutil.copy2(file_path, final_path)
61
-
62
- return final_path, f"✅ Ready: {final_name}"
63
-
64
- except Exception as e:
65
- return None, f"❌ Error: {str(e)}"
66
-
67
- def get_file_info(file_obj):
68
- """Display file information"""
69
- if not file_obj:
70
- return "📁 No file selected"
71
-
72
- try:
73
- if isinstance(file_obj, dict):
74
- file_path = file_obj["path"]
75
- file_name = file_obj["name"]
76
- else:
77
- file_path = file_obj.name
78
- file_name = os.path.basename(file_path)
79
-
80
- size = os.path.getsize(file_path)
81
- return f"📄 **{file_name}**\n• Size: {size/1024:.1f} KB\n• Type: {os.path.splitext(file_name)[1]}"
82
- except:
83
- return "📁 File selected"
84
-
85
- # ============================================
86
- # PAGE 2: COVER CREATION WITH PREVIEW
87
- # ============================================
88
-
89
- def get_file_path(file_obj):
90
- """Extract file path from uploaded file object"""
91
- if not file_obj:
92
- return None
93
- if isinstance(file_obj, dict):
94
- return file_obj.get("path", "")
95
- return getattr(file_obj, "name", "")
96
-
97
- def generate_cover(
98
- prompt,
99
- title,
100
- style,
101
- use_custom_url,
102
- custom_url_input,
103
- uploaded_file,
104
- instrumental=True,
105
- model="V4_5ALL",
106
- persona_id="",
107
- negative_tags="",
108
- vocal_gender="m",
109
- style_weight=0.65,
110
- weirdness_constraint=0.65,
111
- audio_weight=0.65,
112
- custom_mode=True
113
- ):
114
- """Generate cover using Suno API"""
115
-
116
- if not SUNO_API_KEY:
117
- return "❌ Suno API key not found"
118
-
119
- # Determine which URL to use
120
- if use_custom_url:
121
- upload_url = custom_url_input.strip()
122
- if not upload_url:
123
- return "❌ Please enter a custom URL"
124
- url_source = f"Custom URL: {upload_url}"
125
- else:
126
- if not uploaded_file:
127
- return "❌ Please upload a file first"
128
- #upload_url = get_file_path(uploaded_file)
129
- #upload_url = f"/file={get_file_path(uploaded_file)}"
130
- upload_url = f"https://1hit-upload-cover.hf.space/gradio_api/file={get_file_path(uploaded_file)}"
131
- if not upload_url:
132
- return "❌ Could not get file path from uploaded file"
133
-
134
- url_source = f"Uploaded file: {upload_url}"
135
-
136
- # Prepare payload
137
- payload = {
138
- "uploadUrl": upload_url,
139
- "customMode": custom_mode,
140
- "instrumental": instrumental,
141
- "model": model,
142
- "callBackUrl": FIXED_CALLBACK_URL,
143
- "prompt": prompt,
144
- "style": style,
145
- "title": title,
146
- "personaId": persona_id,
147
- "negativeTags": negative_tags,
148
- "vocalGender": vocal_gender,
149
- "styleWeight": style_weight,
150
- "weirdnessConstraint": weirdness_constraint,
151
- "audioWeight": audio_weight
152
- }
153
-
154
- payload = {k: v for k, v in payload.items() if v not in ["", None]}
155
-
156
- headers = {
157
- "Authorization": f"Bearer {SUNO_API_KEY}",
158
- "Content-Type": "application/json"
159
- }
160
-
161
- try:
162
- response = requests.post(SUNO_API_URL, json=payload, headers=headers)
163
-
164
- if response.status_code == 200:
165
- result = response.json()
166
- if result.get("code") == 200:
167
- task_id = result.get("data", {}).get("taskId", "Unknown")
168
- return f"""✅ **Generation Started!**
169
-
170
- **Task ID:** `{task_id}`
171
-
172
- **URL Source:** {url_source}
173
-
174
- 📋 **Full Response:**
175
- ```json
176
- {json.dumps(result, indent=2)}
177
- ```"""
178
- else:
179
- return f"""❌ **API Error:** {result.get('msg', 'Unknown error')}
180
- 📋 **Response:**
181
- ```json
182
- {json.dumps(result, indent=2)}
183
- ```"""
184
- else:
185
- return f"""❌ **HTTP Error {response.status_code}**
186
- {response.text}"""
187
-
188
- except Exception as e:
189
- return f"❌ **Error:** {str(e)}"
190
-
191
- def update_preview(use_custom_url, custom_url_input, uploaded_file):
192
- """Update audio preview based on selected source"""
193
- if use_custom_url:
194
- url = custom_url_input.strip()
195
- if url:
196
- return gr.update(value=url, visible=True, label="Preview URL")
197
- else:
198
- return gr.update(value=None, visible=False, label="Preview URL")
199
- else:
200
- if uploaded_file:
201
- file_path = get_file_path(uploaded_file)
202
- if file_path:
203
- return gr.update(value=file_path, visible=True, label="Preview from uploaded file")
204
- return gr.update(value=None, visible=False, label="Preview URL")
205
-
206
- # ============================================
207
- # PAGE 3: CHECK TASK
208
- # ============================================
209
-
210
- def get_task_info(task_id):
211
- """Check Suno task status"""
212
- if not task_id:
213
- return "❌ Please enter a Task ID ❌"
214
-
215
- if not SUNO_API_KEY:
216
- return "❌ Suno API key not configured"
217
-
218
- try:
219
- resp = requests.get(
220
- "https://api.sunoapi.org/api/v1/generate/record-info",
221
- headers={"Authorization": f"Bearer {SUNO_API_KEY}"},
222
- params={"taskId": task_id},
223
- timeout=30
224
- )
225
-
226
- if resp.status_code == 401:
227
- return "❌ Authentication failed - Invalid API key"
228
- elif resp.status_code == 404:
229
- return f"❌ Task ID '{task_id}' not found"
230
- elif resp.status_code != 200:
231
- return f"❌ HTTP Error {resp.status_code}\n\n{resp.text}"
232
-
233
- data = resp.json()
234
-
235
- # Format the response
236
- output = f"## 🔍 Task Status: `{task_id}`\n\n"
237
-
238
- if data.get("code") == 200:
239
- task_data = data.get("data", {})
240
- status = task_data.get("status", "UNKNOWN")
241
-
242
- output += f"**Status:** {status}\n"
243
- output += f"**Task ID:** `{task_data.get('taskId', 'N/A')}`\n"
244
- output += f"**Music ID:** `{task_data.get('musicId', 'N/A')}`\n"
245
- output += f"**Created:** {task_data.get('createTime', 'N/A')}\n"
246
-
247
- if status == "SUCCESS":
248
- response_data = task_data.get("response", {})
249
-
250
- if isinstance(response_data, str):
251
- try:
252
- response_data = json.loads(response_data)
253
- except:
254
- response_data = {}
255
-
256
- songs = []
257
- if isinstance(response_data, dict):
258
- songs = response_data.get("sunoData", []) or response_data.get("data", [])
259
- elif isinstance(response_data, list):
260
- songs = response_data
261
-
262
- if songs:
263
- output += f"\n## 🎵 Generated Songs ({len(songs)})\n\n"
264
-
265
- for i, song in enumerate(songs, 1):
266
- if isinstance(song, dict):
267
- output += f"### Song {i}\n"
268
- output += f"**Title:** {song.get('title', 'Untitled')}\n"
269
- output += f"**ID:** `{song.get('id', 'N/A')}`\n"
270
-
271
- audio_url = song.get('audioUrl') or song.get('audio_url')
272
- stream_url = song.get('streamUrl') or song.get('stream_url')
273
-
274
- if audio_url:
275
- output += f"**Audio:** [Play]({audio_url}) | [Download]({audio_url})\n"
276
- output += f"""\n<audio controls style="width: 100%; margin: 10px 0;">
277
- <source src="{audio_url}" type="audio/mpeg">
278
- Your browser does not support audio.
279
- </audio>\n"""
280
- elif stream_url:
281
- output += f"**Stream:** [Play]({stream_url})\n"
282
- output += f"""\n<audio controls style="width: 100%; margin: 10px 0;">
283
- <source src="{stream_url}" type="audio/mpeg">
284
- Your browser does not support audio.
285
- </audio>\n"""
286
-
287
- output += f"**Prompt:** {song.get('prompt', 'N/A')[:100]}...\n"
288
- output += f"**Duration:** {song.get('duration', 'N/A')}s\n\n"
289
- output += "---\n\n"
290
- else:
291
- output += "\n**No song data found in response.**\n"
292
- else:
293
- output += f"**API Error:** {data.get('msg', 'Unknown')}\n"
294
-
295
- output += f"\n## 📋 Raw Response\n```json\n{json.dumps(data, indent=2)}\n```"
296
- return output
297
-
298
- except Exception as e:
299
- return f"❌ Error checking task: {str(e)}"
300
-
301
- # ============================================
302
- # URL PARAMETER HANDLING
303
- # ============================================
304
-
305
- def parse_url_params(request: gr.Request):
306
- """Parse taskid from URL parameters"""
307
- if request:
308
- try:
309
- query_params = parse_qs(urlparse(request.request.url).query)
310
- if 'taskid' in query_params:
311
- return query_params['taskid'][0].strip()
312
- except Exception as e:
313
- print(f"Error parsing URL params: {e}")
314
- return None
315
-
316
- def on_page_load(request: gr.Request):
317
- """Handle URL parameters when page loads"""
318
- task_id = parse_url_params(request)
319
-
320
- if task_id:
321
- task_result = get_task_info(task_id)
322
- return (
323
- task_id,
324
- task_result,
325
- gr.Tabs(selected="tab_check"),
326
- True
327
- )
328
- else:
329
- return (
330
- "",
331
- "### Enter a Task ID above\n\nPaste any Suno Task ID to check its current status and results.",
332
- gr.Tabs(selected="tab_upload"),
333
- True
334
- )
335
-
336
- # ============================================
337
- # BUILD THE APP
338
- # ============================================
339
-
340
- css = """
341
- .status-badge { padding: 5px 10px; border-radius: 20px; font-weight: bold; }
342
- .api-status-ok { background-color: #d4edda; color: #155724; }
343
- .api-status-missing { background-color: #f8d7da; color: #721c24; }
344
- audio { width: 100%; margin: 10px 0; }
345
- .logs-iframe { width: 100%; height: 600px; border: 1px solid #ddd; border-radius: 5px; }
346
- .preview-box {
347
- border: 1px solid #ddd;
348
- border-radius: 5px;
349
- padding: 15px;
350
- margin: 10px 0;
351
- background-color: #f9f9f9;
352
- }
353
- """
354
-
355
- with gr.Blocks(title="Suno Cover Creator", theme=gr.themes.Soft(), css=css) as app:
356
-
357
- gr.Markdown("""
358
- <div style="text-align: center; margin-bottom: 20px;">
359
- <h1>🎵 Suno Cover Creator</h1>
360
- <p>Upload → Preview Audio → Create Cover → Check Status → View Logs</p>
361
- <p>Friendly advice: Dont cover originals, cover covers, tv-jingles, weird fringe stuff.</p>
362
- </div>
363
- """)
364
-
365
- # API Status Banner
366
- api_status_color = "✅ API Key: Loaded" if SUNO_API_KEY else "❌ API Key: Not Found"
367
- api_status_class = "api-status-ok" if SUNO_API_KEY else "api-status-missing"
368
-
369
- with gr.Row():
370
- gr.Markdown(f"""
371
- <div class="{api_status_class}" style="padding: 10px; border-radius: 5px; text-align: center;">
372
- {api_status_color}
373
- </div>
374
- """)
375
-
376
- initial_load_done = gr.State(value=False)
377
- uploaded_file_state = gr.State()
378
-
379
- with gr.Tabs() as tabs:
380
- # ========== PAGE 1: FILE UPLOAD ==========
381
- with gr.TabItem("📤 1. Upload File", id="tab_upload"):
382
- with gr.Row():
383
- with gr.Column():
384
- file_input = gr.File(
385
- label="Upload Your Audio File",
386
- file_count="single",
387
- file_types=["audio", ".mp3", ".wav", ".m4a", ".flac", ".ogg"]
388
- )
389
-
390
- file_info = gr.Markdown("📁 No file selected")
391
-
392
- custom_filename = gr.Textbox(
393
- label="Custom Filename (optional)",
394
- placeholder="my-audio-file",
395
- info="Extension preserved automatically"
396
- )
397
-
398
- upload_btn = gr.Button("⚡ Prepare File", variant="primary", size="lg")
399
-
400
- upload_status = gr.Textbox(label="Status", interactive=False)
401
- upload_download = gr.File(label="Download File", interactive=False)
402
-
403
- # ========== PAGE 2: COVER CREATION WITH PREVIEW ==========
404
- with gr.TabItem("🎨 2. Create Cover", id="tab_create"):
405
- with gr.Row():
406
- with gr.Column(scale=1):
407
- gr.Markdown("### 🎵 Cover Details")
408
-
409
- cover_prompt = gr.Textbox(
410
- label="Prompt/Lyrics",
411
- value="",
412
- lines=5
413
- )
414
-
415
- cover_title = gr.Textbox(
416
- label="Title",
417
- value=""
418
- )
419
-
420
- cover_style = gr.Textbox(
421
- label="Style",
422
- value=""
423
- )
424
-
425
- gr.Markdown("### 🔗 Audio Source")
426
-
427
- # Radio button to choose source
428
- use_custom_url = gr.Radio(
429
- label="Select source type",
430
- choices=[
431
- ("Use uploaded file", False),
432
- ("Use custom URL", True)
433
- ],
434
- value=False,
435
- info="Choose where your audio comes from"
436
- )
437
-
438
- # Custom URL input (visible when custom is selected)
439
- custom_url_input = gr.Textbox(
440
- label="Custom URL",
441
- placeholder="https://example.com/audio.mp3 or /file=/path/to/file",
442
- visible=False
443
- )
444
-
445
- # Preview section
446
- gr.Markdown("### 🎧 Audio Preview")
447
- gr.Markdown("Listen to verify your audio source works:")
448
-
449
- preview_audio = gr.Audio(
450
- label="Preview",
451
- type="filepath",
452
- interactive=False,
453
- visible=False
454
- )
455
-
456
- # Store uploaded file reference
457
- uploaded_file_ref = gr.State()
458
-
459
- with gr.Column(scale=1):
460
- gr.Markdown("### ⚙️ Advanced Settings")
461
-
462
- cover_model = gr.Dropdown(
463
- label="Model",
464
- choices=["V4_5ALL", "V5", "V4"],
465
- value="V4_5ALL"
466
- )
467
-
468
- cover_instrumental = gr.Checkbox(
469
- label="Instrumental",
470
- value=True
471
- )
472
-
473
- cover_vocal = gr.Dropdown(
474
- label="Vocal Gender",
475
- choices=["m", "f", "none"],
476
- value="m"
477
- )
478
-
479
- cover_negative = gr.Textbox(
480
- label="Negative Tags",
481
- value="Distorted, Low Quality",
482
- placeholder="What to avoid"
483
- )
484
-
485
- # Update visibility of custom URL input
486
- def toggle_custom_url(choice):
487
- return gr.update(visible=choice)
488
-
489
- use_custom_url.change(
490
- toggle_custom_url,
491
- inputs=use_custom_url,
492
- outputs=custom_url_input
493
- )
494
-
495
- # Update preview when source changes
496
- def update_preview_with_source(use_custom, custom_url, uploaded):
497
- if use_custom and custom_url.strip():
498
- return gr.update(value=custom_url.strip(), visible=True)
499
- elif not use_custom and uploaded:
500
- file_path = get_file_path(uploaded)
501
- if file_path:
502
- return gr.update(value=file_path, visible=True)
503
- return gr.update(value=None, visible=False)
504
-
505
- # Trigger preview updates
506
- use_custom_url.change(
507
- fn=update_preview_with_source,
508
- inputs=[use_custom_url, custom_url_input, uploaded_file_ref],
509
- outputs=preview_audio
510
- )
511
-
512
- custom_url_input.change(
513
- fn=update_preview_with_source,
514
- inputs=[use_custom_url, custom_url_input, uploaded_file_ref],
515
- outputs=preview_audio
516
- )
517
-
518
- # Generate button
519
- generate_btn = gr.Button("🎬 Generate Cover", variant="primary", size="lg")
520
- generate_output = gr.Markdown("Ready to generate...")
521
-
522
- # ========== PAGE 3: CHECK TASK ==========
523
- with gr.TabItem("🔍 3. Check Any Task", id="tab_check"):
524
- with gr.Row():
525
- with gr.Column(scale=1):
526
- gr.Markdown("### Check Task Status")
527
-
528
- check_task_id = gr.Textbox(
529
- label="Task ID",
530
- placeholder="Enter Task ID from generation"
531
- )
532
-
533
- check_btn = gr.Button("🔍 Check Status", variant="primary")
534
- check_clear_btn = gr.Button("🗑️ Clear", variant="secondary")
535
-
536
- gr.Markdown("""
537
- **Quick access via URL:**
538
- Add `?taskid=YOUR_TASK_ID` to the URL
539
-
540
- Example:
541
- `https://1hit.no/gen/view.php?task_id=fa3529d5cbaa93427ee4451976ed5c4b`
542
- """)
543
-
544
- with gr.Column(scale=2):
545
- check_output = gr.Markdown(
546
- value="### Enter a Task ID above\n\nPaste any Suno Task ID to check its current status and results."
547
- )
548
-
549
- # ========== PAGE 4: CALLBACK LOGS ==========
550
- with gr.TabItem("📋 4. Callback Logs", id="tab_logs"):
551
- gr.Markdown(f"""
552
- ### 📋 Callback Logs
553
- View all callback data from Suno API at: [`{LOGS_URL}`]({LOGS_URL})
554
- """)
555
-
556
- logs_iframe = gr.HTML(f"""
557
- <iframe src="{LOGS_URL}" class="logs-iframe" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
558
- """)
559
-
560
- with gr.Row():
561
- refresh_logs_btn = gr.Button("🔄 Refresh Logs", size="sm")
562
-
563
- def refresh_logs():
564
- return f"""
565
- <iframe src="{LOGS_URL}?t={int(time.time())}" class="logs-iframe" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
566
- """
567
-
568
- refresh_logs_btn.click(
569
- fn=refresh_logs,
570
- outputs=logs_iframe
571
- )
572
-
573
- # ========== PAGE 4: CALLBACK LOGS ==========
574
- with gr.TabItem("📋 5. Backup/Download", id="tab_backup"):
575
- gr.Markdown(f"""
576
- ### 📋 Callback Logs
577
- View all callback data from Suno API at: [`{LOGS_URL}`]({LOGS_URL})
578
- """)
579
-
580
- logs_iframe = gr.HTML(f"""
581
- <iframe src="{BACK_URL}" class="logs-iframe" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
582
- """)
583
-
584
- with gr.Row():
585
- refresh_logs_btn = gr.Button("🔄 Refresh Logs", size="sm")
586
-
587
- def refresh_logs():
588
- return f"""
589
- <iframe src="{BACK_URL}?t={int(time.time())}" class="logs-iframe" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
590
- """
591
-
592
- refresh_logs_btn.click(
593
- fn=refresh_logs,
594
- outputs=logs_iframe
595
- )
596
-
597
- # ============================================
598
- # EVENT HANDLERS
599
- # ============================================
600
-
601
- # Page 1 handlers
602
- file_input.change(
603
- fn=get_file_info,
604
- inputs=[file_input],
605
- outputs=file_info
606
- )
607
-
608
- upload_btn.click(
609
- fn=upload_file,
610
- inputs=[file_input, custom_filename],
611
- outputs=[upload_download, upload_status]
612
- )
613
-
614
- def store_file_ref(file_obj):
615
- return file_obj
616
-
617
- file_input.change(
618
- fn=store_file_ref,
619
- inputs=[file_input],
620
- outputs=uploaded_file_ref
621
- )
622
-
623
- # Also update preview when new file is uploaded
624
- file_input.change(
625
- fn=lambda f, use_custom, custom: update_preview_with_source(use_custom, custom, f),
626
- inputs=[file_input, use_custom_url, custom_url_input],
627
- outputs=preview_audio
628
- )
629
-
630
- # Store uploaded file for Page 2
631
- file_input.change(
632
- fn=store_file_ref,
633
- inputs=[file_input],
634
- outputs=uploaded_file_state
635
- )
636
-
637
- # Page 2 generate handler
638
- generate_btn.click(
639
- fn=generate_cover,
640
- inputs=[
641
- cover_prompt, cover_title, cover_style,
642
- use_custom_url, custom_url_input, uploaded_file_state,
643
- cover_instrumental, cover_model,
644
- gr.State(""), cover_negative, cover_vocal,
645
- gr.State(0.65), gr.State(0.65), gr.State(0.65),
646
- gr.State(True)
647
- ],
648
- outputs=generate_output
649
- )
650
-
651
- # Page 3 handlers
652
- def clear_check():
653
- return "", "### Enter a Task ID above\n\nPaste any Suno Task ID to check its current status and results."
654
-
655
- check_clear_btn.click(
656
- clear_check,
657
- outputs=[check_task_id, check_output]
658
- )
659
-
660
- check_btn.click(
661
- get_task_info,
662
- inputs=[check_task_id],
663
- outputs=check_output
664
- )
665
-
666
- # URL parameter handling
667
- app.load(
668
- fn=on_page_load,
669
- inputs=[],
670
- outputs=[check_task_id, check_output, tabs, initial_load_done],
671
- queue=False
672
- )
673
-
674
- # Footer
675
- gr.Markdown("---")
676
- gr.Markdown(f"""
677
- <div style="text-align: center; padding: 20px;">
678
- <p>🔗 <b>Quick URL Access:</b> Add <code>?taskid=YOUR_TASK_ID</code> to auto-load task</p>
679
- <p>📋 <b>Logs:</b> <a href="{LOGS_URL}" target="_blank">{LOGS_URL}</a></p>
680
- <p><b>💡 Preview Feature:</b> Listen to your audio before generating to verify the URL works!</p>
681
- </div>
682
- """)
683
-
684
- if __name__ == "__main__":
685
- print(f"🚀 Starting Suno Cover Creator with Audio Preview")
686
- print(f"🔑 API Key: {'✅ Set' if SUNO_API_KEY else '❌ Not set'}")
687
- print(f"📋 Logs: {LOGS_URL}")
688
-
689
- app.launch(
690
- server_name="0.0.0.0",
691
- server_port=7860,
692
- share=False
693
  )
 
1
+ import gradio as gr
2
+ import os
3
+ import tempfile
4
+ import shutil
5
+ import requests
6
+ import json
7
+ import uuid
8
+ import time
9
+ from datetime import datetime
10
+ from dotenv import load_dotenv
11
+ from urllib.parse import urlparse, parse_qs
12
+
13
+ # Load environment variables
14
+ load_dotenv()
15
+
16
+ # ============================================
17
+ # CONFIGURATION
18
+ # ============================================
19
+
20
+ SUNO_API_KEY = os.environ.get("SunoKey", "")
21
+ FIXED_CALLBACK_URL = "https://1hit.no/cover/cb.php"
22
+ SUNO_API_URL = "https://api.sunoapi.org/api/v1/generate/upload-cover"
23
+ LOGS_URL = "https://www.1hit.no/cover/compu.php"
24
+ BACK_URL = "https://www.1hit.no/cover/ib.php"
25
+
26
+ # Create uploads directory
27
+ UPLOADS_FOLDER = "uploads"
28
+ os.makedirs(UPLOADS_FOLDER, exist_ok=True)
29
+
30
+ # ============================================
31
+ # PAGE 1: FILE UPLOAD
32
+ # ============================================
33
+
34
+ def upload_file(file_obj, custom_name):
35
+ """Simple file upload - returns file for download"""
36
+ if not file_obj:
37
+ return None, "❌ Please upload a file first"
38
+
39
+ try:
40
+ if isinstance(file_obj, dict):
41
+ file_path = file_obj["path"]
42
+ original_name = file_obj["name"]
43
+ else:
44
+ file_path = file_obj.name
45
+ original_name = os.path.basename(file_path)
46
+
47
+ temp_dir = tempfile.mkdtemp()
48
+
49
+ if custom_name and custom_name.strip():
50
+ ext = os.path.splitext(original_name)[1]
51
+ base_name = custom_name.strip()
52
+ if not base_name.endswith(ext):
53
+ final_name = base_name + ext
54
+ else:
55
+ final_name = base_name
56
+ else:
57
+ final_name = original_name
58
+
59
+ final_path = os.path.join(temp_dir, final_name)
60
+ shutil.copy2(file_path, final_path)
61
+
62
+ return final_path, f"✅ Ready: {final_name}"
63
+
64
+ except Exception as e:
65
+ return None, f"❌ Error: {str(e)}"
66
+
67
+ def get_file_info(file_obj):
68
+ """Display file information"""
69
+ if not file_obj:
70
+ return "📁 No file selected"
71
+
72
+ try:
73
+ if isinstance(file_obj, dict):
74
+ file_path = file_obj["path"]
75
+ file_name = file_obj["name"]
76
+ else:
77
+ file_path = file_obj.name
78
+ file_name = os.path.basename(file_path)
79
+
80
+ size = os.path.getsize(file_path)
81
+ return f"📄 **{file_name}**\n• Size: {size/1024:.1f} KB\n• Type: {os.path.splitext(file_name)[1]}"
82
+ except:
83
+ return "📁 File selected"
84
+
85
+ # ============================================
86
+ # PAGE 2: COVER CREATION WITH PREVIEW
87
+ # ============================================
88
+
89
+ def get_file_path(file_obj):
90
+ """Extract file path from uploaded file object"""
91
+ if not file_obj:
92
+ return None
93
+ if isinstance(file_obj, dict):
94
+ return file_obj.get("path", "")
95
+ return getattr(file_obj, "name", "")
96
+
97
+ def generate_cover(
98
+ prompt,
99
+ title,
100
+ style,
101
+ use_custom_url,
102
+ custom_url_input,
103
+ uploaded_file,
104
+ instrumental=True,
105
+ model="V5_5",
106
+ persona_id="",
107
+ negative_tags="",
108
+ vocal_gender="m",
109
+ style_weight=0.65,
110
+ weirdness_constraint=0.65,
111
+ audio_weight=0.65,
112
+ custom_mode=True
113
+ ):
114
+ """Generate cover using Suno API"""
115
+
116
+ if not SUNO_API_KEY:
117
+ return "❌ Suno API key not found"
118
+
119
+ # Determine which URL to use
120
+ if use_custom_url:
121
+ upload_url = custom_url_input.strip()
122
+ if not upload_url:
123
+ return "❌ Please enter a custom URL"
124
+ url_source = f"Custom URL: {upload_url}"
125
+ else:
126
+ if not uploaded_file:
127
+ return "❌ Please upload a file first"
128
+ #upload_url = get_file_path(uploaded_file)
129
+ #upload_url = f"/file={get_file_path(uploaded_file)}"
130
+ upload_url = f"https://1hit-upload-cover.hf.space/gradio_api/file={get_file_path(uploaded_file)}"
131
+ if not upload_url:
132
+ return "❌ Could not get file path from uploaded file"
133
+
134
+ url_source = f"Uploaded file: {upload_url}"
135
+
136
+ # Prepare payload
137
+ payload = {
138
+ "uploadUrl": upload_url,
139
+ "customMode": custom_mode,
140
+ "instrumental": instrumental,
141
+ "model": model,
142
+ "callBackUrl": FIXED_CALLBACK_URL,
143
+ "prompt": prompt,
144
+ "style": style,
145
+ "title": title,
146
+ "personaId": persona_id,
147
+ "negativeTags": negative_tags,
148
+ "vocalGender": vocal_gender,
149
+ "styleWeight": style_weight,
150
+ "weirdnessConstraint": weirdness_constraint,
151
+ "audioWeight": audio_weight
152
+ }
153
+
154
+ payload = {k: v for k, v in payload.items() if v not in ["", None]}
155
+
156
+ headers = {
157
+ "Authorization": f"Bearer {SUNO_API_KEY}",
158
+ "Content-Type": "application/json"
159
+ }
160
+
161
+ try:
162
+ response = requests.post(SUNO_API_URL, json=payload, headers=headers)
163
+
164
+ if response.status_code == 200:
165
+ result = response.json()
166
+ if result.get("code") == 200:
167
+ task_id = result.get("data", {}).get("taskId", "Unknown")
168
+ return f"""✅ **Generation Started!**
169
+
170
+ **Task ID:** `{task_id}`
171
+
172
+ **URL Source:** {url_source}
173
+
174
+ 📋 **Full Response:**
175
+ ```json
176
+ {json.dumps(result, indent=2)}
177
+ ```"""
178
+ else:
179
+ return f"""❌ **API Error:** {result.get('msg', 'Unknown error')}
180
+ 📋 **Response:**
181
+ ```json
182
+ {json.dumps(result, indent=2)}
183
+ ```"""
184
+ else:
185
+ return f"""❌ **HTTP Error {response.status_code}**
186
+ {response.text}"""
187
+
188
+ except Exception as e:
189
+ return f"❌ **Error:** {str(e)}"
190
+
191
+ def update_preview(use_custom_url, custom_url_input, uploaded_file):
192
+ """Update audio preview based on selected source"""
193
+ if use_custom_url:
194
+ url = custom_url_input.strip()
195
+ if url:
196
+ return gr.update(value=url, visible=True, label="Preview URL")
197
+ else:
198
+ return gr.update(value=None, visible=False, label="Preview URL")
199
+ else:
200
+ if uploaded_file:
201
+ file_path = get_file_path(uploaded_file)
202
+ if file_path:
203
+ return gr.update(value=file_path, visible=True, label="Preview from uploaded file")
204
+ return gr.update(value=None, visible=False, label="Preview URL")
205
+
206
+ # ============================================
207
+ # PAGE 3: CHECK TASK
208
+ # ============================================
209
+
210
+ def get_task_info(task_id):
211
+ """Check Suno task status"""
212
+ if not task_id:
213
+ return "❌ Please enter a Task ID ❌"
214
+
215
+ if not SUNO_API_KEY:
216
+ return "❌ Suno API key not configured"
217
+
218
+ try:
219
+ resp = requests.get(
220
+ "https://api.sunoapi.org/api/v1/generate/record-info",
221
+ headers={"Authorization": f"Bearer {SUNO_API_KEY}"},
222
+ params={"taskId": task_id},
223
+ timeout=30
224
+ )
225
+
226
+ if resp.status_code == 401:
227
+ return "❌ Authentication failed - Invalid API key"
228
+ elif resp.status_code == 404:
229
+ return f"❌ Task ID '{task_id}' not found"
230
+ elif resp.status_code != 200:
231
+ return f"❌ HTTP Error {resp.status_code}\n\n{resp.text}"
232
+
233
+ data = resp.json()
234
+
235
+ # Format the response
236
+ output = f"## 🔍 Task Status: `{task_id}`\n\n"
237
+
238
+ if data.get("code") == 200:
239
+ task_data = data.get("data", {})
240
+ status = task_data.get("status", "UNKNOWN")
241
+
242
+ output += f"**Status:** {status}\n"
243
+ output += f"**Task ID:** `{task_data.get('taskId', 'N/A')}`\n"
244
+ output += f"**Music ID:** `{task_data.get('musicId', 'N/A')}`\n"
245
+ output += f"**Created:** {task_data.get('createTime', 'N/A')}\n"
246
+
247
+ if status == "SUCCESS":
248
+ response_data = task_data.get("response", {})
249
+
250
+ if isinstance(response_data, str):
251
+ try:
252
+ response_data = json.loads(response_data)
253
+ except:
254
+ response_data = {}
255
+
256
+ songs = []
257
+ if isinstance(response_data, dict):
258
+ songs = response_data.get("sunoData", []) or response_data.get("data", [])
259
+ elif isinstance(response_data, list):
260
+ songs = response_data
261
+
262
+ if songs:
263
+ output += f"\n## 🎵 Generated Songs ({len(songs)})\n\n"
264
+
265
+ for i, song in enumerate(songs, 1):
266
+ if isinstance(song, dict):
267
+ output += f"### Song {i}\n"
268
+ output += f"**Title:** {song.get('title', 'Untitled')}\n"
269
+ output += f"**ID:** `{song.get('id', 'N/A')}`\n"
270
+
271
+ audio_url = song.get('audioUrl') or song.get('audio_url')
272
+ stream_url = song.get('streamUrl') or song.get('stream_url')
273
+
274
+ if audio_url:
275
+ output += f"**Audio:** [Play]({audio_url}) | [Download]({audio_url})\n"
276
+ output += f"""\n<audio controls style="width: 100%; margin: 10px 0;">
277
+ <source src="{audio_url}" type="audio/mpeg">
278
+ Your browser does not support audio.
279
+ </audio>\n"""
280
+ elif stream_url:
281
+ output += f"**Stream:** [Play]({stream_url})\n"
282
+ output += f"""\n<audio controls style="width: 100%; margin: 10px 0;">
283
+ <source src="{stream_url}" type="audio/mpeg">
284
+ Your browser does not support audio.
285
+ </audio>\n"""
286
+
287
+ output += f"**Prompt:** {song.get('prompt', 'N/A')[:100]}...\n"
288
+ output += f"**Duration:** {song.get('duration', 'N/A')}s\n\n"
289
+ output += "---\n\n"
290
+ else:
291
+ output += "\n**No song data found in response.**\n"
292
+ else:
293
+ output += f"**API Error:** {data.get('msg', 'Unknown')}\n"
294
+
295
+ output += f"\n## 📋 Raw Response\n```json\n{json.dumps(data, indent=2)}\n```"
296
+ return output
297
+
298
+ except Exception as e:
299
+ return f"❌ Error checking task: {str(e)}"
300
+
301
+ # ============================================
302
+ # URL PARAMETER HANDLING
303
+ # ============================================
304
+
305
+ def parse_url_params(request: gr.Request):
306
+ """Parse taskid from URL parameters"""
307
+ if request:
308
+ try:
309
+ query_params = parse_qs(urlparse(request.request.url).query)
310
+ if 'taskid' in query_params:
311
+ return query_params['taskid'][0].strip()
312
+ except Exception as e:
313
+ print(f"Error parsing URL params: {e}")
314
+ return None
315
+
316
+ def on_page_load(request: gr.Request):
317
+ """Handle URL parameters when page loads"""
318
+ task_id = parse_url_params(request)
319
+
320
+ if task_id:
321
+ task_result = get_task_info(task_id)
322
+ return (
323
+ task_id,
324
+ task_result,
325
+ gr.Tabs(selected="tab_check"),
326
+ True
327
+ )
328
+ else:
329
+ return (
330
+ "",
331
+ "### Enter a Task ID above\n\nPaste any Suno Task ID to check its current status and results.",
332
+ gr.Tabs(selected="tab_upload"),
333
+ True
334
+ )
335
+
336
+ # ============================================
337
+ # BUILD THE APP
338
+ # ============================================
339
+
340
+ css = """
341
+ .status-badge { padding: 5px 10px; border-radius: 20px; font-weight: bold; }
342
+ .api-status-ok { background-color: #d4edda; color: #155724; }
343
+ .api-status-missing { background-color: #f8d7da; color: #721c24; }
344
+ audio { width: 100%; margin: 10px 0; }
345
+ .logs-iframe { width: 100%; height: 600px; border: 1px solid #ddd; border-radius: 5px; }
346
+ .preview-box {
347
+ border: 1px solid #ddd;
348
+ border-radius: 5px;
349
+ padding: 15px;
350
+ margin: 10px 0;
351
+ background-color: #f9f9f9;
352
+ }
353
+ """
354
+
355
+ with gr.Blocks(title="Suno Cover Creator", theme=gr.themes.Soft(), css=css) as app:
356
+
357
+ gr.Markdown("""
358
+ <div style="text-align: center; margin-bottom: 20px;">
359
+ <h1>🎵 Suno Cover Creator</h1>
360
+ <p>Upload → Preview Audio → Create Cover → Check Status → View Logs</p>
361
+ <p>Friendly advice: Dont cover originals, cover covers, tv-jingles, weird fringe stuff.</p>
362
+ </div>
363
+ """)
364
+
365
+ # API Status Banner
366
+ api_status_color = "✅ API Key: Loaded" if SUNO_API_KEY else "❌ API Key: Not Found"
367
+ api_status_class = "api-status-ok" if SUNO_API_KEY else "api-status-missing"
368
+
369
+ with gr.Row():
370
+ gr.Markdown(f"""
371
+ <div class="{api_status_class}" style="padding: 10px; border-radius: 5px; text-align: center;">
372
+ {api_status_color}
373
+ </div>
374
+ """)
375
+
376
+ initial_load_done = gr.State(value=False)
377
+ uploaded_file_state = gr.State()
378
+
379
+ with gr.Tabs() as tabs:
380
+ # ========== PAGE 1: FILE UPLOAD ==========
381
+ with gr.TabItem("📤 1. Upload File", id="tab_upload"):
382
+ with gr.Row():
383
+ with gr.Column():
384
+ file_input = gr.File(
385
+ label="Upload Your Audio File",
386
+ file_count="single",
387
+ file_types=["audio", ".mp3", ".wav", ".m4a", ".flac", ".ogg"]
388
+ )
389
+
390
+ file_info = gr.Markdown("📁 No file selected")
391
+
392
+ custom_filename = gr.Textbox(
393
+ label="Custom Filename (optional)",
394
+ placeholder="my-audio-file",
395
+ info="Extension preserved automatically"
396
+ )
397
+
398
+ upload_btn = gr.Button("⚡ Prepare File", variant="primary", size="lg")
399
+
400
+ upload_status = gr.Textbox(label="Status", interactive=False)
401
+ upload_download = gr.File(label="Download File", interactive=False)
402
+
403
+ # ========== PAGE 2: COVER CREATION WITH PREVIEW ==========
404
+ with gr.TabItem("🎨 2. Create Cover", id="tab_create"):
405
+ with gr.Row():
406
+ with gr.Column(scale=1):
407
+ gr.Markdown("### 🎵 Cover Details")
408
+
409
+ cover_prompt = gr.Textbox(
410
+ label="Prompt/Lyrics",
411
+ value="",
412
+ lines=5
413
+ )
414
+
415
+ cover_title = gr.Textbox(
416
+ label="Title",
417
+ value=""
418
+ )
419
+
420
+ cover_style = gr.Textbox(
421
+ label="Style",
422
+ value=""
423
+ )
424
+
425
+ gr.Markdown("### 🔗 Audio Source")
426
+
427
+ # Radio button to choose source
428
+ use_custom_url = gr.Radio(
429
+ label="Select source type",
430
+ choices=[
431
+ ("Use uploaded file", False),
432
+ ("Use custom URL", True)
433
+ ],
434
+ value=False,
435
+ info="Choose where your audio comes from"
436
+ )
437
+
438
+ # Custom URL input (visible when custom is selected)
439
+ custom_url_input = gr.Textbox(
440
+ label="Custom URL",
441
+ placeholder="https://example.com/audio.mp3 or /file=/path/to/file",
442
+ visible=False
443
+ )
444
+
445
+ # Preview section
446
+ gr.Markdown("### 🎧 Audio Preview")
447
+ gr.Markdown("Listen to verify your audio source works:")
448
+
449
+ preview_audio = gr.Audio(
450
+ label="Preview",
451
+ type="filepath",
452
+ interactive=False,
453
+ visible=False
454
+ )
455
+
456
+ # Store uploaded file reference
457
+ uploaded_file_ref = gr.State()
458
+
459
+ with gr.Column(scale=1):
460
+ gr.Markdown("### ⚙️ Advanced Settings")
461
+
462
+ cover_model = gr.Dropdown(
463
+ label="Model",
464
+ choices=["V5_5,""V4_5ALL", "V5", "V4"],
465
+ value="V5_5"
466
+ )
467
+
468
+ cover_instrumental = gr.Checkbox(
469
+ label="Instrumental",
470
+ value=True
471
+ )
472
+
473
+ cover_vocal = gr.Dropdown(
474
+ label="Vocal Gender",
475
+ choices=["m", "f", "none"],
476
+ value="none"
477
+ )
478
+
479
+ cover_negative = gr.Textbox(
480
+ label="Negative Tags",
481
+ value="Distorted, Low Quality",
482
+ placeholder="What to avoid"
483
+ )
484
+
485
+ # Update visibility of custom URL input
486
+ def toggle_custom_url(choice):
487
+ return gr.update(visible=choice)
488
+
489
+ use_custom_url.change(
490
+ toggle_custom_url,
491
+ inputs=use_custom_url,
492
+ outputs=custom_url_input
493
+ )
494
+
495
+ # Update preview when source changes
496
+ def update_preview_with_source(use_custom, custom_url, uploaded):
497
+ if use_custom and custom_url.strip():
498
+ return gr.update(value=custom_url.strip(), visible=True)
499
+ elif not use_custom and uploaded:
500
+ file_path = get_file_path(uploaded)
501
+ if file_path:
502
+ return gr.update(value=file_path, visible=True)
503
+ return gr.update(value=None, visible=False)
504
+
505
+ # Trigger preview updates
506
+ use_custom_url.change(
507
+ fn=update_preview_with_source,
508
+ inputs=[use_custom_url, custom_url_input, uploaded_file_ref],
509
+ outputs=preview_audio
510
+ )
511
+
512
+ custom_url_input.change(
513
+ fn=update_preview_with_source,
514
+ inputs=[use_custom_url, custom_url_input, uploaded_file_ref],
515
+ outputs=preview_audio
516
+ )
517
+
518
+ # Generate button
519
+ generate_btn = gr.Button("🎬 Generate Cover", variant="primary", size="lg")
520
+ generate_output = gr.Markdown("Ready to generate...")
521
+
522
+ # ========== PAGE 3: CHECK TASK ==========
523
+ with gr.TabItem("🔍 3. Check Any Task", id="tab_check"):
524
+ with gr.Row():
525
+ with gr.Column(scale=1):
526
+ gr.Markdown("### Check Task Status")
527
+
528
+ check_task_id = gr.Textbox(
529
+ label="Task ID",
530
+ placeholder="Enter Task ID from generation"
531
+ )
532
+
533
+ check_btn = gr.Button("🔍 Check Status", variant="primary")
534
+ check_clear_btn = gr.Button("🗑️ Clear", variant="secondary")
535
+
536
+ gr.Markdown("""
537
+ **Quick access via URL:**
538
+ Add `?taskid=YOUR_TASK_ID` to the URL
539
+
540
+ Example:
541
+ `https://1hit.no/gen/view.php?task_id=fa3529d5cbaa93427ee4451976ed5c4b`
542
+ """)
543
+
544
+ with gr.Column(scale=2):
545
+ check_output = gr.Markdown(
546
+ value="### Enter a Task ID above\n\nPaste any Suno Task ID to check its current status and results."
547
+ )
548
+
549
+ # ========== PAGE 4: CALLBACK LOGS ==========
550
+ with gr.TabItem("📋 4. Callback Logs", id="tab_logs"):
551
+ gr.Markdown(f"""
552
+ ### 📋 Callback Logs
553
+ View all callback data from Suno API at: [`{LOGS_URL}`]({LOGS_URL})
554
+ """)
555
+
556
+ logs_iframe = gr.HTML(f"""
557
+ <iframe src="{LOGS_URL}" class="logs-iframe" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
558
+ """)
559
+
560
+ with gr.Row():
561
+ refresh_logs_btn = gr.Button("🔄 Refresh Logs", size="sm")
562
+
563
+ def refresh_logs():
564
+ return f"""
565
+ <iframe src="{LOGS_URL}?t={int(time.time())}" class="logs-iframe" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
566
+ """
567
+
568
+ refresh_logs_btn.click(
569
+ fn=refresh_logs,
570
+ outputs=logs_iframe
571
+ )
572
+
573
+ # ========== PAGE 4: CALLBACK LOGS ==========
574
+ with gr.TabItem("📋 5. Backup/Download", id="tab_backup"):
575
+ gr.Markdown(f"""
576
+ ### 📋 Callback Logs
577
+ View all callback data from Suno API at: [`{LOGS_URL}`]({LOGS_URL})
578
+ """)
579
+
580
+ logs_iframe = gr.HTML(f"""
581
+ <iframe src="{BACK_URL}" class="logs-iframe" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
582
+ """)
583
+
584
+ with gr.Row():
585
+ refresh_logs_btn = gr.Button("🔄 Refresh Logs", size="sm")
586
+
587
+ def refresh_logs():
588
+ return f"""
589
+ <iframe src="{BACK_URL}?t={int(time.time())}" class="logs-iframe" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
590
+ """
591
+
592
+ refresh_logs_btn.click(
593
+ fn=refresh_logs,
594
+ outputs=logs_iframe
595
+ )
596
+
597
+ # ============================================
598
+ # EVENT HANDLERS
599
+ # ============================================
600
+
601
+ # Page 1 handlers
602
+ file_input.change(
603
+ fn=get_file_info,
604
+ inputs=[file_input],
605
+ outputs=file_info
606
+ )
607
+
608
+ upload_btn.click(
609
+ fn=upload_file,
610
+ inputs=[file_input, custom_filename],
611
+ outputs=[upload_download, upload_status]
612
+ )
613
+
614
+ def store_file_ref(file_obj):
615
+ return file_obj
616
+
617
+ file_input.change(
618
+ fn=store_file_ref,
619
+ inputs=[file_input],
620
+ outputs=uploaded_file_ref
621
+ )
622
+
623
+ # Also update preview when new file is uploaded
624
+ file_input.change(
625
+ fn=lambda f, use_custom, custom: update_preview_with_source(use_custom, custom, f),
626
+ inputs=[file_input, use_custom_url, custom_url_input],
627
+ outputs=preview_audio
628
+ )
629
+
630
+ # Store uploaded file for Page 2
631
+ file_input.change(
632
+ fn=store_file_ref,
633
+ inputs=[file_input],
634
+ outputs=uploaded_file_state
635
+ )
636
+
637
+ # Page 2 generate handler
638
+ generate_btn.click(
639
+ fn=generate_cover,
640
+ inputs=[
641
+ cover_prompt, cover_title, cover_style,
642
+ use_custom_url, custom_url_input, uploaded_file_state,
643
+ cover_instrumental, cover_model,
644
+ gr.State(""), cover_negative, cover_vocal,
645
+ gr.State(0.65), gr.State(0.65), gr.State(0.65),
646
+ gr.State(True)
647
+ ],
648
+ outputs=generate_output
649
+ )
650
+
651
+ # Page 3 handlers
652
+ def clear_check():
653
+ return "", "### Enter a Task ID above\n\nPaste any Suno Task ID to check its current status and results."
654
+
655
+ check_clear_btn.click(
656
+ clear_check,
657
+ outputs=[check_task_id, check_output]
658
+ )
659
+
660
+ check_btn.click(
661
+ get_task_info,
662
+ inputs=[check_task_id],
663
+ outputs=check_output
664
+ )
665
+
666
+ # URL parameter handling
667
+ app.load(
668
+ fn=on_page_load,
669
+ inputs=[],
670
+ outputs=[check_task_id, check_output, tabs, initial_load_done],
671
+ queue=False
672
+ )
673
+
674
+ # Footer
675
+ gr.Markdown("---")
676
+ gr.Markdown(f"""
677
+ <div style="text-align: center; padding: 20px;">
678
+ <p>🔗 <b>Quick URL Access:</b> Add <code>?taskid=YOUR_TASK_ID</code> to auto-load task</p>
679
+ <p>📋 <b>Logs:</b> <a href="{LOGS_URL}" target="_blank">{LOGS_URL}</a></p>
680
+ <p><b>💡 Preview Feature:</b> Listen to your audio before generating to verify the URL works!</p>
681
+ </div>
682
+ """)
683
+
684
+ if __name__ == "__main__":
685
+ print(f"🚀 Starting Suno Cover Creator with Audio Preview")
686
+ print(f"🔑 API Key: {'✅ Set' if SUNO_API_KEY else '❌ Not set'}")
687
+ print(f"📋 Logs: {LOGS_URL}")
688
+
689
+ app.launch(
690
+ server_name="0.0.0.0",
691
+ server_port=7860,
692
+ share=False
693
  )