MySafeCode commited on
Commit
e1500b9
·
verified ·
1 Parent(s): ea4a4f4

Create a.py

Browse files
Files changed (1) hide show
  1. a.py +618 -0
a.py ADDED
@@ -0,0 +1,618 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ # Configuration
17
+ SUNO_API_KEY = os.environ.get("SunoKey", "")
18
+ FIXED_CALLBACK_URL = "https://1hit.no/cover/cb.php"
19
+ SUNO_API_URL = "https://api.sunoapi.org/api/v1/generate/upload-cover"
20
+
21
+ # Create uploads directory
22
+ UPLOADS_FOLDER = "uploads"
23
+ os.makedirs(UPLOADS_FOLDER, exist_ok=True)
24
+
25
+ # ============================================
26
+ # PAGE 1: FILE UPLOAD
27
+ # ============================================
28
+
29
+ def upload_file(file_obj, custom_name):
30
+ """Simple file upload - returns file for download"""
31
+ if not file_obj:
32
+ return None, "❌ Please upload a file first"
33
+
34
+ try:
35
+ # Handle Gradio 6+ file object
36
+ if isinstance(file_obj, dict):
37
+ file_path = file_obj["path"]
38
+ original_name = file_obj["name"]
39
+ else:
40
+ file_path = file_obj.name
41
+ original_name = os.path.basename(file_path)
42
+
43
+ # Create temp directory
44
+ temp_dir = tempfile.mkdtemp()
45
+
46
+ # Determine filename
47
+ if custom_name and custom_name.strip():
48
+ ext = os.path.splitext(original_name)[1]
49
+ base_name = custom_name.strip()
50
+ if not base_name.endswith(ext):
51
+ final_name = base_name + ext
52
+ else:
53
+ final_name = base_name
54
+ else:
55
+ final_name = original_name
56
+
57
+ # Copy file
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
86
+ # ============================================
87
+
88
+ def generate_cover(
89
+ prompt,
90
+ title,
91
+ style,
92
+ upload_url_type,
93
+ custom_upload_url,
94
+ uploaded_file,
95
+ instrumental=True,
96
+ model="V4_5ALL",
97
+ persona_id="",
98
+ negative_tags="",
99
+ vocal_gender="m",
100
+ style_weight=0.65,
101
+ weirdness_constraint=0.65,
102
+ audio_weight=0.65,
103
+ custom_mode=True
104
+ ):
105
+ """Generate cover using Suno API"""
106
+
107
+ # Check API key
108
+ if not SUNO_API_KEY:
109
+ return "❌ Suno API key not found. Please set 'SunoKey' environment variable."
110
+
111
+ # Determine upload URL
112
+ if upload_url_type == "uploaded":
113
+ # Use uploaded file - in real implementation, you'd upload to storage and get URL
114
+ if not uploaded_file:
115
+ return "❌ Please upload a file first or select another URL type"
116
+
117
+ # For demo - simulate URL from uploaded file
118
+ if isinstance(uploaded_file, dict):
119
+ file_name = uploaded_file.get("name", "uploaded_file.mp3")
120
+ else:
121
+ file_name = getattr(uploaded_file, "name", "uploaded_file.mp3")
122
+
123
+ upload_url = f"https://storage.temp.example.com/uploads/{int(time.time())}_{file_name}"
124
+
125
+ elif upload_url_type == "custom":
126
+ upload_url = custom_upload_url.strip()
127
+ if not upload_url:
128
+ return "❌ Please provide a custom upload URL"
129
+ else: # auto
130
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
131
+ unique_id = str(uuid.uuid4())[:8]
132
+ upload_url = f"https://storage.temp.example.com/uploads/{timestamp}_{unique_id}.mp3"
133
+
134
+ # Prepare payload
135
+ payload = {
136
+ "uploadUrl": upload_url,
137
+ "customMode": custom_mode,
138
+ "instrumental": instrumental,
139
+ "model": model,
140
+ "callBackUrl": FIXED_CALLBACK_URL,
141
+ "prompt": prompt,
142
+ "style": style,
143
+ "title": title,
144
+ "personaId": persona_id,
145
+ "negativeTags": negative_tags,
146
+ "vocalGender": vocal_gender,
147
+ "styleWeight": style_weight,
148
+ "weirdnessConstraint": weirdness_constraint,
149
+ "audioWeight": audio_weight
150
+ }
151
+
152
+ # Remove empty fields
153
+ payload = {k: v for k, v in payload.items() if v not in ["", None]}
154
+
155
+ # Headers
156
+ headers = {
157
+ "Authorization": f"Bearer {SUNO_API_KEY}",
158
+ "Content-Type": "application/json"
159
+ }
160
+
161
+ try:
162
+ # Make API request
163
+ response = requests.post(SUNO_API_URL, json=payload, headers=headers)
164
+
165
+ if response.status_code == 200:
166
+ result = response.json()
167
+ if result.get("code") == 200:
168
+ task_id = result.get("data", {}).get("taskId", "Unknown")
169
+ return f"""✅ **Generation Started!**
170
+
171
+ **Task ID:** `{task_id}`
172
+
173
+ **Upload URL:** {upload_url}
174
+ **Callback URL:** {FIXED_CALLBACK_URL}
175
+
176
+ 📋 **Full Response:**
177
+ ```json
178
+ {json.dumps(result, indent=2)}
179
+ ```"""
180
+ else:
181
+ return f"""❌ **API Error:** {result.get('msg', 'Unknown error')}
182
+
183
+ 📋 **Response:**
184
+ ```json
185
+ {json.dumps(result, indent=2)}
186
+ ```"""
187
+ else:
188
+ return f"""❌ **HTTP Error {response.status_code}**
189
+
190
+ {response.text}"""
191
+
192
+ except Exception as e:
193
+ return f"❌ **Error:** {str(e)}"
194
+
195
+ # ============================================
196
+ # PAGE 3: CHECK TASK - ORIGINAL CODE
197
+ # ============================================
198
+
199
+ def get_task_info(task_id):
200
+ """Manually check any Suno task status - ORIGINAL CODE"""
201
+ if not task_id:
202
+ return "❌ Please enter a Task ID ❌"
203
+
204
+ try:
205
+ resp = requests.get(
206
+ "https://api.sunoapi.org/api/v1/generate/record-info",
207
+ headers={"Authorization": f"Bearer {SUNO_API_KEY}"},
208
+ params={"taskId": task_id},
209
+ timeout=30
210
+ )
211
+
212
+ if resp.status_code != 200:
213
+ return f"❌ HTTP Error {resp.status_code}\n\n{resp.text}"
214
+
215
+ data = resp.json()
216
+
217
+ # Format the response for display
218
+ output = f"## 🔍 Task Status: `{task_id}`\n\n"
219
+
220
+ if data.get("code") == 200:
221
+ task_data = data.get("data", {})
222
+ status = task_data.get("status", "UNKNOWN")
223
+
224
+ output += f"**Status:** {status}\n"
225
+ output += f"**Task ID:** `{task_data.get('taskId', 'N/A')}`\n"
226
+ output += f"**Music ID:** `{task_data.get('musicId', 'N/A')}`\n"
227
+ output += f"**Created:** {task_data.get('createTime', 'N/A')}\n"
228
+
229
+ if status == "SUCCESS":
230
+ response_data = task_data.get("response", {})
231
+
232
+ # Try to parse response (could be string or dict)
233
+ if isinstance(response_data, str):
234
+ try:
235
+ response_data = json.loads(response_data)
236
+ except:
237
+ output += f"\n**Raw Response:**\n```\n{response_data}\n```\n"
238
+ response_data = {}
239
+
240
+ # Check for song data
241
+ songs = []
242
+ if isinstance(response_data, dict):
243
+ songs = response_data.get("sunoData", [])
244
+ if not songs:
245
+ songs = response_data.get("data", [])
246
+ elif isinstance(response_data, list):
247
+ songs = response_data
248
+
249
+ if songs:
250
+ output += f"\n## 🎵 Generated Songs ({len(songs)})\n\n"
251
+
252
+ for i, song in enumerate(songs, 1):
253
+ if isinstance(song, dict):
254
+ output += f"### Song {i}\n"
255
+ output += f"**Title:** {song.get('title', 'Untitled')}\n"
256
+ output += f"**ID:** `{song.get('id', 'N/A')}`\n"
257
+
258
+ # Audio URLs
259
+ audio_url = song.get('audioUrl') or song.get('audio_url')
260
+ stream_url = song.get('streamUrl') or song.get('stream_url')
261
+ download_url = song.get('downloadUrl') or song.get('download_url')
262
+
263
+ if audio_url:
264
+ output += f"**Audio:** [Play]({audio_url}) | [Download]({audio_url})\n"
265
+ elif stream_url:
266
+ output += f"**Stream:** [Play]({stream_url})\n"
267
+
268
+ if download_url:
269
+ output += f"**Download:** [MP3]({download_url})\n"
270
+
271
+ # Audio player
272
+ play_url = audio_url or stream_url
273
+ if play_url:
274
+ output += f"""\n<audio controls style="width: 100%; margin: 10px 0;">
275
+ <source src="{play_url}" type="audio/mpeg">
276
+ Your browser does not support audio.
277
+ </audio>\n"""
278
+
279
+ output += f"**Prompt:** {song.get('prompt', 'N/A')[:100]}...\n"
280
+ output += f"**Duration:** {song.get('duration', 'N/A')}s\n"
281
+ output += f"**Created:** {song.get('createTime', 'N/A')}\n\n"
282
+ output += "---\n\n"
283
+ else:
284
+ output += "\n**No song data found in response.**\n"
285
+
286
+ elif status == "FAILED":
287
+ error_msg = task_data.get("errorMessage", "Unknown error")
288
+ output += f"\n**Error:** {error_msg}\n"
289
+
290
+ elif status in ["PENDING", "PROCESSING", "RUNNING"]:
291
+ output += f"\n**Task is still processing...**\n"
292
+ output += f"Check again in 30 seconds.\n"
293
+
294
+ else:
295
+ output += f"\n**Unknown status:** {status}\n"
296
+
297
+ else:
298
+ output += f"**API Error:** {data.get('msg', 'Unknown')}\n"
299
+
300
+ # Show raw JSON for debugging
301
+ output += "\n## 📋 Raw Response\n"
302
+ output += f"```json\n{json.dumps(data, indent=2)}\n```"
303
+
304
+ return output
305
+
306
+ except Exception as e:
307
+ return f"❌ Error checking task: {str(e)}"
308
+
309
+ # ============================================
310
+ # URL PARAMETER HANDLING
311
+ # ============================================
312
+
313
+ def parse_url_params(request: gr.Request):
314
+ """Parse taskid from URL parameters"""
315
+ task_id = None
316
+ if request:
317
+ try:
318
+ query_params = parse_qs(urlparse(request.request.url).query)
319
+ if 'taskid' in query_params:
320
+ task_id = query_params['taskid'][0]
321
+ # Remove any whitespace
322
+ task_id = task_id.strip()
323
+ except Exception as e:
324
+ print(f"Error parsing URL params: {e}")
325
+
326
+ return task_id
327
+
328
+ def on_page_load(request: gr.Request):
329
+ """Handle URL parameters when page loads"""
330
+ task_id = parse_url_params(request)
331
+
332
+ if task_id:
333
+ # We have a task ID from URL, return it and fetch results
334
+ task_result = get_task_info(task_id)
335
+ return (
336
+ task_id, # For check_task_id
337
+ task_result, # For check_output
338
+ gr.Tabs(selected="tab_check"), # Switch to check tab
339
+ True # Mark as loaded
340
+ )
341
+ else:
342
+ # No task ID in URL, stay on first tab
343
+ return (
344
+ "", # Empty check_task_id
345
+ "### Enter a Task ID above\n\nPaste any Suno Task ID to check its current status and results.", # Default message
346
+ gr.Tabs(selected="tab_upload"), # Stay on upload tab
347
+ True # Mark as loaded
348
+ )
349
+
350
+ # ============================================
351
+ # BUILD THE APP
352
+ # ============================================
353
+
354
+ # Custom CSS
355
+ css = """
356
+ .status-badge {
357
+ padding: 5px 10px;
358
+ border-radius: 20px;
359
+ font-weight: bold;
360
+ }
361
+ .api-status-ok { background-color: #d4edda; color: #155724; }
362
+ .api-status-missing { background-color: #f8d7da; color: #721c24; }
363
+ audio { width: 100%; margin: 10px 0; }
364
+ """
365
+
366
+ # Build interface
367
+ with gr.Blocks(title="Suno Cover Creator", theme=gr.themes.Soft(), css=css) as app:
368
+
369
+ gr.Markdown("""
370
+ <div style="text-align: center; margin-bottom: 20px;">
371
+ <h1>🎵 Suno Cover Creator</h1>
372
+ <p>Upload → Create Cover → Check Status</p>
373
+ </div>
374
+ """)
375
+
376
+ # API Status Banner
377
+ api_status_color = "✅ API Key: Loaded" if SUNO_API_KEY else "❌ API Key: Not Found"
378
+ api_status_class = "api-status-ok" if SUNO_API_KEY else "api-status-missing"
379
+
380
+ with gr.Row():
381
+ gr.Markdown(f"""
382
+ <div class="{api_status_class}" style="padding: 10px; border-radius: 5px; text-align: center;">
383
+ {api_status_color} | Callback URL: <code>{FIXED_CALLBACK_URL}</code>
384
+ </div>
385
+ """)
386
+
387
+ # State for initial load tracking
388
+ initial_load_done = gr.State(value=False)
389
+
390
+ # Main tabs
391
+ with gr.Tabs() as tabs:
392
+ # ========== PAGE 1: FILE UPLOAD ==========
393
+ with gr.TabItem("📤 1. Upload File", id="tab_upload"):
394
+ with gr.Row():
395
+ with gr.Column():
396
+ file_input = gr.File(
397
+ label="Upload Your Audio File",
398
+ file_count="single",
399
+ file_types=["audio", ".mp3", ".wav", ".m4a", ".flac", ".ogg"]
400
+ )
401
+
402
+ file_info = gr.Markdown("📁 No file selected")
403
+
404
+ custom_filename = gr.Textbox(
405
+ label="Custom Filename (optional)",
406
+ placeholder="my-audio-file",
407
+ info="Extension preserved automatically"
408
+ )
409
+
410
+ upload_btn = gr.Button("⚡ Prepare File", variant="primary", size="lg")
411
+
412
+ upload_status = gr.Textbox(label="Status", interactive=False)
413
+ upload_download = gr.File(label="Download File", interactive=False)
414
+
415
+ # ========== PAGE 2: COVER CREATION ==========
416
+ with gr.TabItem("🎨 2. Create Cover", id="tab_create"):
417
+ with gr.Row():
418
+ with gr.Column(scale=1):
419
+ gr.Markdown("### 🎵 Cover Details")
420
+
421
+ cover_prompt = gr.Textbox(
422
+ label="Prompt",
423
+ value="A dramatic orchestral cover with dark undertones",
424
+ lines=2
425
+ )
426
+
427
+ cover_title = gr.Textbox(
428
+ label="Title",
429
+ value="The Fool's Ascension"
430
+ )
431
+
432
+ cover_style = gr.Textbox(
433
+ label="Style",
434
+ value="Epic Orchestral, Dark Cinematic"
435
+ )
436
+
437
+ # URL Selection
438
+ gr.Markdown("### 🔗 Upload URL")
439
+
440
+ url_type = gr.Radio(
441
+ label="Source",
442
+ choices=[
443
+ ("Use uploaded file", "uploaded"),
444
+ ("Use custom URL", "custom"),
445
+ ("Auto-generate", "auto")
446
+ ],
447
+ value="uploaded",
448
+ info="Choose where the audio comes from"
449
+ )
450
+
451
+ custom_url = gr.Textbox(
452
+ label="Custom URL",
453
+ placeholder="https://example.com/audio.mp3",
454
+ visible=False
455
+ )
456
+
457
+ # Reference to uploaded file from Page 1
458
+ uploaded_file_ref = gr.State()
459
+
460
+ with gr.Column(scale=1):
461
+ gr.Markdown("### ⚙️ Advanced Settings")
462
+
463
+ cover_model = gr.Dropdown(
464
+ label="Model",
465
+ choices=["V4_5ALL", "V5", "V4"],
466
+ value="V4_5ALL"
467
+ )
468
+
469
+ cover_instrumental = gr.Checkbox(
470
+ label="Instrumental",
471
+ value=True
472
+ )
473
+
474
+ cover_vocal = gr.Dropdown(
475
+ label="Vocal Gender",
476
+ choices=["m", "f", "none"],
477
+ value="m"
478
+ )
479
+
480
+ cover_negative = gr.Textbox(
481
+ label="Negative Tags",
482
+ value="Distorted, Low Quality",
483
+ placeholder="What to avoid"
484
+ )
485
+
486
+ # Show/hide custom URL
487
+ def toggle_custom_url(choice):
488
+ return gr.update(visible=choice == "custom")
489
+
490
+ url_type.change(
491
+ toggle_custom_url,
492
+ inputs=url_type,
493
+ outputs=custom_url
494
+ )
495
+
496
+ # Generate button
497
+ generate_btn = gr.Button("🎬 Generate Cover", variant="primary", size="lg")
498
+ generate_output = gr.Markdown("Ready to generate...")
499
+
500
+ # ========== PAGE 3: CHECK TASK - ORIGINAL ==========
501
+ with gr.TabItem("🔍 3. Check Any Task", id="tab_check"):
502
+ with gr.Row():
503
+ with gr.Column(scale=1):
504
+ gr.Markdown("### Check Task Status")
505
+ gr.Markdown("Enter any Suno Task ID to check its status")
506
+
507
+ check_task_id = gr.Textbox(
508
+ label="Task ID",
509
+ placeholder="Enter Task ID from generation",
510
+ info="From Cover Creator or any Suno API request"
511
+ )
512
+
513
+ check_btn = gr.Button("🔍 Check Status", variant="primary")
514
+ check_clear_btn = gr.Button("🗑️ Clear", variant="secondary")
515
+
516
+ # URL parameter info
517
+ gr.Markdown("""
518
+ **Quick access via URL:**
519
+ Add `?taskid=YOUR_TASK_ID` to the URL
520
+
521
+ Example:
522
+ `https://your-space.space/?taskid=fa3529d5cbaa93427ee4451976ed5c4b`
523
+ """)
524
+
525
+ with gr.Column(scale=2):
526
+ check_output = gr.Markdown(
527
+ value="### Enter a Task ID above\n\nPaste any Suno Task ID to check its current status and results."
528
+ )
529
+
530
+ # ============================================
531
+ # EVENT HANDLERS
532
+ # ============================================
533
+
534
+ # Page 1: File Upload
535
+ file_input.change(
536
+ fn=get_file_info,
537
+ inputs=[file_input],
538
+ outputs=file_info
539
+ )
540
+
541
+ upload_btn.click(
542
+ fn=upload_file,
543
+ inputs=[file_input, custom_filename],
544
+ outputs=[upload_download, upload_status]
545
+ )
546
+
547
+ # Store uploaded file reference for Page 2
548
+ def store_file_ref(file_obj):
549
+ return file_obj
550
+
551
+ file_input.change(
552
+ fn=store_file_ref,
553
+ inputs=[file_input],
554
+ outputs=uploaded_file_ref
555
+ )
556
+
557
+ # Page 2: Generate Cover
558
+ generate_btn.click(
559
+ fn=generate_cover,
560
+ inputs=[
561
+ cover_prompt, cover_title, cover_style,
562
+ url_type, custom_url, uploaded_file_ref,
563
+ cover_instrumental, cover_model,
564
+ gr.State(""), # persona_id placeholder
565
+ cover_negative, cover_vocal,
566
+ gr.State(0.65), gr.State(0.65), gr.State(0.65), # weights
567
+ gr.State(True) # custom_mode
568
+ ],
569
+ outputs=generate_output
570
+ )
571
+
572
+ # Page 3: Check Task - Original handlers
573
+ def clear_check():
574
+ return "", "### Enter a Task ID above\n\nPaste any Suno Task ID to check its current status and results."
575
+
576
+ check_clear_btn.click(
577
+ clear_check,
578
+ outputs=[check_task_id, check_output]
579
+ )
580
+
581
+ check_btn.click(
582
+ get_task_info,
583
+ inputs=[check_task_id],
584
+ outputs=check_output
585
+ )
586
+
587
+ # Load URL parameters when the app starts
588
+ app.load(
589
+ fn=on_page_load,
590
+ inputs=[],
591
+ outputs=[check_task_id, check_output, tabs, initial_load_done],
592
+ queue=False
593
+ )
594
+
595
+ # Footer
596
+ gr.Markdown("---")
597
+ gr.Markdown("""
598
+ <div style="text-align: center; padding: 20px;">
599
+ <p>🔗 <b>Quick URL Access:</b> Add <code>?taskid=YOUR_TASK_ID</code> to auto-load task</p>
600
+ <p>📞 <b>Callback URL:</b> <code>https://1hit.no/cover/cb.php</code></p>
601
+ </div>
602
+ """)
603
+
604
+ # Launch
605
+ if __name__ == "__main__":
606
+ if not SUNO_API_KEY:
607
+ print("⚠️ Warning: Suno API key not found")
608
+ print("Set 'SunoKey' environment variable or add to .env file")
609
+
610
+ print("🚀 Starting Suno Cover Creator")
611
+ print(f"🔑 SunoKey: {'✅ Set' if SUNO_API_KEY else '❌ Not set'}")
612
+ print("🔗 Use URL parameter: http://localhost:7860/?taskid=YOUR_TASK_ID")
613
+
614
+ app.launch(
615
+ server_name="0.0.0.0",
616
+ server_port=7860,
617
+ share=False
618
+ )