MySafeCode commited on
Commit
24d3797
·
verified ·
1 Parent(s): 788ddeb

Upload aaa.py

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