dmmmmm commited on
Commit
ea2857b
·
verified ·
1 Parent(s): c886c1f

Upload suno_app.py

Browse files
Files changed (1) hide show
  1. suno_app.py +599 -0
suno_app.py ADDED
@@ -0,0 +1,599 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import datetime
2
+
3
+ import gradio as gr
4
+ import json
5
+ import requests
6
+ import time
7
+
8
+
9
+ def create_task(prompt: str, style: str, title: str, api_key: str):
10
+ headers = {
11
+ "Content-Type": "application/json",
12
+ "Authorization": f"Bearer {api_key}"
13
+ }
14
+ url = "https://api.kie.ai/api/v1/generate"
15
+ payload = {
16
+ "prompt": prompt,
17
+ "style": style,
18
+ "title": title,
19
+ "customMode": False,
20
+ "instrumental": True,
21
+ "model": "V3_5",
22
+ "callBackUrl": "https://your-app.com/callback"
23
+ }
24
+ try:
25
+ response = requests.post(url, headers=headers, data=json.dumps(payload), timeout=60)
26
+ result = response.json()
27
+
28
+ if response.ok and result.get('code') == 200:
29
+ print(f"任务ID: {result['data']['taskId']}")
30
+ return True, result['data']['taskId']
31
+ else:
32
+ print(f"请求失败: {result.get('msg', '未知错误')}")
33
+ return False, result.get('msg', "Internal Server Error")
34
+ except Exception:
35
+ pass
36
+ return False, "Internal Server Error"
37
+
38
+
39
+ def check_task_status(task_id, api_key):
40
+ url = f"https://api.kie.ai/api/v1/generate/record-info?taskId={task_id}"
41
+ headers = {"Authorization": f"Bearer {api_key}"}
42
+ for _ in range(3):
43
+ try:
44
+ response = requests.get(url, headers=headers)
45
+ result = response.json()
46
+
47
+ if response.ok and result.get('code') == 200:
48
+ task_data = result['data']
49
+ status = task_data['status']
50
+
51
+ response_data = task_data['response']
52
+
53
+ if status == 'SUCCESS':
54
+ return "SUCCESS", response_data["sunoData"]
55
+ elif status == 'FIRST_SUCCESS':
56
+ return "FIRST_SUCCESS", response_data["sunoData"]
57
+ elif status == 'TEXT_SUCCESS':
58
+ return "TEXT_SUCCESS", task_data
59
+ elif status == 'PENDING':
60
+ return "PENDING", None
61
+ else:
62
+ return "FAILED", response_data.get('errorMessage', 'Internal Server Error')
63
+ else:
64
+ return "FAILED", result.get('msg', 'Internal Server Error')
65
+ except Exception:
66
+ pass
67
+ time.sleep(5)
68
+ return "FAILED", 'Internal Server Error'
69
+
70
+
71
+ def format_duration(seconds):
72
+ """格式化时长显示"""
73
+ minutes = int(seconds // 60)
74
+ seconds = int(seconds % 60)
75
+ return f"{minutes:02d}:{seconds:02d}"
76
+
77
+
78
+ def create_music_card(music_data, index):
79
+ """创建音乐卡片的HTML"""
80
+ if music_data is None:
81
+ return f"""
82
+ <div class="music-card empty-card">
83
+ <div class="card-content">
84
+ <div class="empty-icon">🎵</div>
85
+ <div class="empty-text">Waiting for music #{index} to generate...</div>
86
+ </div>
87
+ </div>
88
+ """
89
+
90
+ # 格式化时长
91
+ duration = format_duration(music_data.get('duration', 0))
92
+
93
+ # 格式化创建时间
94
+ create_time = music_data.get('createTime', '')
95
+ if create_time:
96
+ # 如果是毫秒级时间戳,先转成秒
97
+ if create_time > 1e12:
98
+ create_time = create_time / 1000
99
+
100
+ # 转换为 datetime 对象
101
+ dt = datetime.datetime.fromtimestamp(create_time)
102
+ # 格式化输出
103
+ formatted_time = dt.strftime("%Y-%m-%d %H:%M:%S")
104
+ else:
105
+ formatted_time = ""
106
+
107
+ print(formatted_time)
108
+ return f"""
109
+ <div class="music-card">
110
+ <div class="card-header">
111
+ <img src="{music_data.get('imageUrl', '')}" alt="Album cover" class="album-cover">
112
+ <div class="track-number">#{index}</div>
113
+ </div>
114
+ <div class="card-content">
115
+ <h3 class="music-title">{music_data.get('title', 'Untitled')}</h3>
116
+ <div class="music-tags">
117
+ <span class="tag-icon">🏷️</span>
118
+ <span class="tag-text">{music_data.get('tags', 'No tags')}</span>
119
+ </div>
120
+ <div class="music-duration">
121
+ <span class="duration-icon">⏱️</span>
122
+ <span class="duration-text">{duration}</span>
123
+ </div>
124
+ <audio controls class="audio-player">
125
+ <source src="{music_data.get('audioUrl', '')}" type="audio/mpeg">
126
+ Your browser does not support audio playback
127
+ </audio>
128
+ <div class="create-time">Generated: {formatted_time}</div>
129
+ </div>
130
+ </div>
131
+ """
132
+
133
+
134
+ def process_music_generation(prompt, style, title, api_key, progress=gr.Progress()):
135
+ """处理音乐生成的主函数"""
136
+ if not api_key or not api_key.strip():
137
+ return create_music_card(None, 1), create_music_card(None, 2), "❌ Please enter API Key"
138
+
139
+ if not prompt or not prompt.strip():
140
+ return create_music_card(None, 1), create_music_card(None, 2), "❌ Please enter music description"
141
+
142
+ try:
143
+ # 步骤1:创建任务
144
+ progress(0.1, desc="🚀 Creating music generation task...")
145
+ success, task_id = create_task(prompt.strip(), style.strip(), title.strip(), api_key.strip())
146
+
147
+ if not success:
148
+ return create_music_card(None, 1), create_music_card(None, 2), f"❌ Failed to create task: {task_id}"
149
+
150
+ progress(0.2, desc=f"📋 Starting music generation")
151
+
152
+ # 步骤2:轮询检查状态
153
+ start_time = time.time()
154
+ first_music_shown = False
155
+ music_data_1 = None
156
+ music_data_2 = None
157
+
158
+ while time.time() - start_time < 600: # 最多等待10分钟
159
+ elapsed = int(time.time() - start_time)
160
+ progress_value = min(0.2 + (elapsed / 600) * 0.7, 0.9)
161
+
162
+ # 检查任务状态
163
+ status, data = check_task_status(task_id, api_key.strip())
164
+ if status == 'FIRST_SUCCESS' and not first_music_shown:
165
+ # 第一首音乐生成完成
166
+ progress(progress_value, desc="🎵 First music generated!")
167
+ if data and len(data) > 0:
168
+ music_data_1 = data[0]
169
+ first_music_shown = True
170
+ # 立即更新第一首音乐的显示
171
+ yield (
172
+ create_music_card(music_data_1, 1),
173
+ create_music_card(None, 2),
174
+ f"✅ First music generated, generating second..."
175
+ )
176
+
177
+ elif status == 'SUCCESS':
178
+ # 两首音乐都生成完成
179
+ progress(0.95, desc="🎉 All music generated!")
180
+ if data and len(data) > 0:
181
+ music_data_1 = data[0] if len(data) > 0 else music_data_1
182
+ music_data_2 = data[1] if len(data) > 1 else None
183
+
184
+ yield (
185
+ create_music_card(music_data_1, 1),
186
+ create_music_card(music_data_2, 2) if music_data_2 else create_music_card(None, 2),
187
+ f"✅ Successfully generated {len(data)} music tracks!"
188
+ )
189
+ break
190
+
191
+ elif status == 'FAILED':
192
+ yield (
193
+ create_music_card(music_data_1, 1) if music_data_1 else create_music_card(None, 1),
194
+ create_music_card(music_data_2, 2) if music_data_2 else create_music_card(None, 2),
195
+ f"❌ Generation failed: {data}"
196
+ )
197
+ break
198
+
199
+ elif status == 'PENDING':
200
+ progress(progress_value, desc=f"⏳ Generating music... ({elapsed}s)")
201
+
202
+ # 实时更新状态 - 只有在第一首音乐还没生成时才显示等待状态
203
+ if not first_music_shown:
204
+ yield (
205
+ create_music_card(None, 1),
206
+ create_music_card(None, 2),
207
+ f"⏳ Generating music... (waited {elapsed}s)"
208
+ )
209
+ elif first_music_shown and music_data_1:
210
+ # 第一首音乐已生成,继续等待第二首
211
+ yield (
212
+ create_music_card(music_data_1, 1),
213
+ create_music_card(None, 2),
214
+ f"✅ First music generated, generating second... (waited {elapsed}s)"
215
+ )
216
+
217
+ time.sleep(5) # 每5秒检查一次
218
+
219
+ # 超时
220
+ if time.time() - start_time >= 600:
221
+ yield (
222
+ create_music_card(music_data_1, 1) if music_data_1 else create_music_card(None, 1),
223
+ create_music_card(music_data_2, 2) if music_data_2 else create_music_card(None, 2),
224
+ "⚠️ Generation timeout, please try again later"
225
+ )
226
+
227
+ except Exception as e:
228
+ yield create_music_card(None, 1), create_music_card(None, 2), f"❌ Error occurred: {str(e)}"
229
+
230
+
231
+ # CSS样式
232
+ css = """
233
+ .gradio-container {
234
+ background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
235
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
236
+ min-height: 100vh;
237
+ }
238
+ .header-container {
239
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
240
+ padding: 2.5rem;
241
+ border-radius: 24px;
242
+ margin-bottom: 2.5rem;
243
+ box-shadow: 0 20px 60px rgba(102, 126, 234, 0.25);
244
+ }
245
+ .logo-text {
246
+ font-size: 3.5rem;
247
+ font-weight: 900;
248
+ color: white;
249
+ text-align: center;
250
+ margin: 0;
251
+ letter-spacing: -2px;
252
+ text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3);
253
+ }
254
+ .subtitle {
255
+ color: rgba(255, 255, 255, 0.9);
256
+ text-align: center;
257
+ font-size: 1.2rem;
258
+ margin-top: 0.5rem;
259
+ }
260
+ .main-content {
261
+ background: rgba(255, 255, 255, 0.95);
262
+ backdrop-filter: blur(20px);
263
+ border-radius: 24px;
264
+ padding: 2.5rem;
265
+ box-shadow: 0 10px 40px rgba(0, 0, 0, 0.08);
266
+ }
267
+ .mode-indicator {
268
+ background: rgba(255, 255, 255, 0.3);
269
+ backdrop-filter: blur(10px);
270
+ border-radius: 12px;
271
+ padding: 0.5rem 1rem;
272
+ margin-top: 1rem;
273
+ text-align: center;
274
+ font-weight: 600;
275
+ color: white;
276
+ }
277
+
278
+ .info-box {
279
+ background: linear-gradient(135deg, #e3f2fd 0%, #bbdefb 100%);
280
+ border-radius: 12px;
281
+ padding: 1.5rem;
282
+ margin-bottom: 1.5rem;
283
+ border-left: 4px solid #2196f3;
284
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
285
+ }
286
+
287
+ .info-box strong {
288
+ color: #1976d2;
289
+ font-size: 1.1rem;
290
+ }
291
+
292
+ .gr-button-primary {
293
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
294
+ border: none !important;
295
+ color: white !important;
296
+ font-weight: 700 !important;
297
+ font-size: 1.1rem !important;
298
+ padding: 1.2rem 2rem !important;
299
+ border-radius: 14px !important;
300
+ text-transform: uppercase;
301
+ letter-spacing: 1px;
302
+ width: 100%;
303
+ margin-top: 1rem !important;
304
+ transition: all 0.3s ease !important;
305
+ }
306
+
307
+ .gr-button-primary:hover {
308
+ background: linear-gradient(135deg, #5a67d8 0%, #6b46c1 100%) !important;
309
+ transform: translateY(-2px);
310
+ box-shadow: 0 6px 20px rgba(0, 0, 0, 0.15) !important;
311
+ }
312
+
313
+ /* 音乐卡片样式 */
314
+ .music-cards-container {
315
+ display: flex;
316
+ gap: 1.5rem;
317
+ margin-top: 1rem;
318
+ flex-wrap: wrap;
319
+ }
320
+
321
+ .music-card {
322
+ background: white;
323
+ border-radius: 16px;
324
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
325
+ overflow: hidden;
326
+ transition: all 0.3s ease;
327
+ flex: 1;
328
+ min-width: 300px;
329
+ animation: slideIn 0.5s ease-out;
330
+ }
331
+
332
+ @keyframes slideIn {
333
+ from {
334
+ opacity: 0;
335
+ transform: translateY(20px);
336
+ }
337
+ to {
338
+ opacity: 1;
339
+ transform: translateY(0);
340
+ }
341
+ }
342
+
343
+ .music-card:hover {
344
+ transform: translateY(-5px);
345
+ box-shadow: 0 8px 30px rgba(0, 0, 0, 0.15);
346
+ }
347
+
348
+ .empty-card {
349
+ border: 2px dashed #e0e0e0;
350
+ background: #fafafa;
351
+ min-height: 400px;
352
+ display: flex;
353
+ align-items: center;
354
+ justify-content: center;
355
+ }
356
+
357
+ .empty-icon {
358
+ font-size: 3rem;
359
+ margin-bottom: 1rem;
360
+ opacity: 0.3;
361
+ }
362
+
363
+ .empty-text {
364
+ color: #9e9e9e;
365
+ font-size: 1rem;
366
+ }
367
+
368
+ .card-header {
369
+ position: relative;
370
+ height: 200px;
371
+ overflow: hidden;
372
+ }
373
+
374
+ .album-cover {
375
+ width: 100%;
376
+ height: 100%;
377
+ object-fit: cover;
378
+ }
379
+
380
+ .track-number {
381
+ position: absolute;
382
+ top: 10px;
383
+ right: 10px;
384
+ background: rgba(0, 0, 0, 0.7);
385
+ color: white;
386
+ padding: 5px 12px;
387
+ border-radius: 20px;
388
+ font-weight: bold;
389
+ font-size: 0.9rem;
390
+ }
391
+
392
+ .card-content {
393
+ padding: 1.5rem;
394
+ }
395
+
396
+ .music-title {
397
+ font-size: 1.4rem;
398
+ font-weight: 700;
399
+ color: #2c3e50;
400
+ margin: 0 0 1rem 0;
401
+ }
402
+
403
+ .music-tags {
404
+ display: flex;
405
+ align-items: center;
406
+ margin-bottom: 0.8rem;
407
+ color: #7f8c8d;
408
+ }
409
+
410
+ .tag-icon, .duration-icon {
411
+ margin-right: 0.5rem;
412
+ font-size: 1rem;
413
+ }
414
+
415
+ .tag-text {
416
+ font-size: 0.9rem;
417
+ font-style: italic;
418
+ }
419
+
420
+ .music-duration {
421
+ display: flex;
422
+ align-items: center;
423
+ margin-bottom: 1rem;
424
+ color: #7f8c8d;
425
+ }
426
+
427
+ .duration-text {
428
+ font-weight: 600;
429
+ }
430
+
431
+ .audio-player {
432
+ width: 100%;
433
+ margin: 1rem 0;
434
+ border-radius: 8px;
435
+ }
436
+
437
+ .create-time {
438
+ font-size: 0.8rem;
439
+ color: #95a5a6;
440
+ text-align: right;
441
+ margin-top: 1rem;
442
+ }
443
+
444
+ /* 输入框样式 */
445
+ .gr-input, .gr-textarea {
446
+ background: #ffffff !important;
447
+ border: 2px solid #e1e8ed !important;
448
+ border-radius: 10px !important;
449
+ color: #374151 !important;
450
+ font-size: 1rem !important;
451
+ padding: 0.75rem 1rem !important;
452
+ transition: all 0.3s ease !important;
453
+ }
454
+
455
+ .gr-input:focus, .gr-textarea:focus {
456
+ border-color: #667eea !important;
457
+ box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1) !important;
458
+ }
459
+
460
+ /* 下拉框样式 */
461
+ .gr-dropdown {
462
+ background: #ffffff !important;
463
+ border: 2px solid #e1e8ed !important;
464
+ border-radius: 10px !important;
465
+ }
466
+
467
+ label {
468
+ color: #4a5568 !important;
469
+ font-weight: 600 !important;
470
+ font-size: 0.9rem !important;
471
+ margin-bottom: 0.5rem !important;
472
+ }
473
+
474
+ /* 示例样式 */
475
+ .examples-holder {
476
+ margin-top: 2rem;
477
+ padding: 1.5rem;
478
+ background: #f8f9fa;
479
+ border-radius: 12px;
480
+ }
481
+
482
+ /* 隐藏页脚 */
483
+ footer {
484
+ display: none !important;
485
+ }
486
+ """
487
+
488
+ # 创建Gradio界面
489
+ with gr.Blocks(css=css, theme=gr.themes.Base()) as demo:
490
+ with gr.Column(elem_classes="header-container"):
491
+ gr.HTML("""
492
+ <h1 class="logo-text">🎵 Suno AI Music API Free Online Test</h1>
493
+ <p class="subtitle">Supported by the latest Suno v4.5 and Suno v4.5+ models</p>
494
+ <div class="mode-indicator">
495
+ 💡 Works with or without images - Just describe what you want!
496
+ </div>
497
+ """)
498
+
499
+ with gr.Column(elem_classes="main-content"):
500
+ # 信息提示框
501
+ gr.HTML("""
502
+ <div class="info-box">
503
+ <strong>Usage Instructions: </strong><br>
504
+ • Get your Suno API Key <a href="https://kie.ai/playground/suno" target="_blank" style="color: #1976d2;">👉 here 👈</a><br>
505
+ • Select your model: Choose from Suno v4.5+, Suno v4.5, Suno v4, or Suno v3.5.<br>
506
+ • Choose Custom Mode (optional), specify if you want to generate instrumental music only, and enter your music style description or lyrics.<br>
507
+ • Click Generate and wait ~1–2 minutes for processing.<br>
508
+ </div>
509
+ """)
510
+
511
+ with gr.Row():
512
+ # 左侧输入区域
513
+ with gr.Column(scale=1):
514
+ # 第一块:API Key
515
+ with gr.Group():
516
+ gr.Markdown("### 🔑 API Configuration")
517
+ api_key = gr.Textbox(
518
+ label="API Key",
519
+ placeholder="Please enter your API Key",
520
+ type="password"
521
+ )
522
+
523
+ # 第二块:音乐信息
524
+ with gr.Group():
525
+ gr.Markdown("### 🎵 Music Information")
526
+ title = gr.Textbox(
527
+ label="Music Title",
528
+ placeholder="Give your music a name",
529
+ value=""
530
+ )
531
+
532
+ style = gr.Textbox(
533
+ label="Music Style",
534
+ placeholder="Enter your desired music style",
535
+ interactive=True
536
+ )
537
+
538
+ prompt = gr.Textbox(
539
+ label="Music Description",
540
+ placeholder="Describe the music you want, e.g.: An energetic electronic dance track with futuristic synthesizer sounds...",
541
+ lines=3,
542
+ )
543
+
544
+ generate_btn = gr.Button(
545
+ "🎼 Generate Music",
546
+ variant="primary",
547
+ size="lg"
548
+ )
549
+
550
+ # 右侧输出区域
551
+ with gr.Column(scale=2):
552
+ gr.Markdown("### 🎵 Generation Results")
553
+
554
+ # 两个音乐展示区域
555
+ with gr.Row(elem_classes="music-cards-container"):
556
+ music_display_1 = gr.HTML(
557
+ value=create_music_card(None, 1),
558
+ elem_id="music-1"
559
+ )
560
+ music_display_2 = gr.HTML(
561
+ value=create_music_card(None, 2),
562
+ elem_id="music-2"
563
+ )
564
+
565
+ # 状态显示
566
+ status_text = gr.Textbox(
567
+ label="Generation Status",
568
+ interactive=False,
569
+ value="Ready! Please enter music description and click generate button...",
570
+ lines=2
571
+ )
572
+
573
+ # 音乐创意示例 - 放在页面最下面
574
+ gr.Examples(
575
+ examples=[
576
+ ["Summer Beach", "Pop", "A light and cheerful summer beach music with fresh guitar and upbeat rhythm"],
577
+ ["Burning Heart", "Rock", "Passionate rock music with powerful electric guitar and drums"],
578
+ ["Neon Night", "Electronic", "Futuristic electronic music with cyberpunk style"],
579
+ ["Afternoon Time", "Jazz", "Gentle jazz piano piece, perfect for café ambiance"],
580
+ ["Heroic Epic", "Classical", "Epic orchestral music, grand and majestic, perfect for film soundtrack"],
581
+ ],
582
+ inputs=[title, style, prompt],
583
+ label="🎵 Music Creative Examples"
584
+ )
585
+
586
+ # 绑定生成事件
587
+ generate_btn.click(
588
+ fn=process_music_generation,
589
+ inputs=[prompt, style, title, api_key],
590
+ outputs=[music_display_1, music_display_2, status_text]
591
+ )
592
+
593
+ # 启动应用
594
+ if __name__ == "__main__":
595
+ demo.launch(
596
+ share=True,
597
+ server_name="0.0.0.0",
598
+ server_port=7861 # 使用不同的端口
599
+ )