194130157a commited on
Commit
6b44ecd
·
verified ·
1 Parent(s): 9cd9efb

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +207 -0
app.py ADDED
@@ -0,0 +1,207 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import requests
3
+ import os
4
+ import time
5
+ import zipfile
6
+ import shutil
7
+ from concurrent.futures import ThreadPoolExecutor, as_completed
8
+
9
+ # ==========================================
10
+ # ⚙️ 核心配置
11
+ # ==========================================
12
+ SUNO_API_KEY = "2335a91def6e8c7b851c286ffd80f0df"
13
+ UPLOAD_URL = "https://kieai.redpandaai.co/api/file-stream-upload"
14
+ COVER_URL = "https://api.kie.ai/api/v1/generate/upload-cover"
15
+ QUERY_URL = "https://api.kie.ai/api/v1/generate/record-info"
16
+ DUMMY_CALLBACK = "https://example.com/callback"
17
+
18
+ # 最大并发数
19
+ MAX_WORKERS = 5
20
+
21
+ def log(msg):
22
+ return f"[{time.strftime('%H:%M:%S')}] {msg}"
23
+
24
+ # ==========================================
25
+ # 🛠️ 基础功能
26
+ # ==========================================
27
+ def upload_file(file_obj):
28
+ try:
29
+ file_path = file_obj.name
30
+ file_name = os.path.basename(file_path)
31
+ headers = {"Authorization": f"Bearer {SUNO_API_KEY}"}
32
+ files = {'file': (file_name, open(file_path, 'rb'))}
33
+ data = {'uploadPath': 'suno-inst-copy'}
34
+
35
+ res = requests.post(UPLOAD_URL, headers=headers, files=files, data=data)
36
+ res_json = res.json()
37
+
38
+ url = res_json.get('data', {}).get('downloadUrl') or res_json.get('data', {}).get('fileUrl')
39
+ if url: return {"status": "success", "name": file_name, "url": url}
40
+ return {"status": "failed", "name": file_name, "msg": "No URL returned"}
41
+ except Exception as e:
42
+ return {"status": "error", "name": os.path.basename(file_obj.name), "msg": str(e)}
43
+
44
+ # ==========================================
45
+ # 🎹 核心:复刻任务提交 (接收相似度参数)
46
+ # ==========================================
47
+ def submit_copy_task(upload_data, similarity_val):
48
+ if upload_data['status'] != 'success': return None
49
+
50
+ # 万能高保真 Prompt
51
+ universal_prompt = "Instrumental, High Fidelity, Cinematic, Clear Production, Masterpiece, Original Vibe"
52
+
53
+ payload = {
54
+ "uploadUrl": upload_data['url'],
55
+ "callBackUrl": DUMMY_CALLBACK,
56
+ "model": "V5",
57
+
58
+ "instrumental": True, # 必须:纯音乐
59
+ "prompt": universal_prompt, # 通用高保真提示词
60
+ "lyrics": "", # 无歌词
61
+ "customMode": True,
62
+ "title": f"{upload_data['name']} (Sim_{int(similarity_val*100)}%)",
63
+
64
+ # ⚡️ 核心修改点:动态相似度 ⚡️
65
+ "styleWeight": 0.95, # 风格权重保持高位,确保流派不偏
66
+ "audioWeight": similarity_val # 音频权重由滑块控制
67
+ }
68
+
69
+ try:
70
+ res = requests.post(COVER_URL, headers={"Authorization": f"Bearer {SUNO_API_KEY}", "Content-Type": "application/json"}, json=payload)
71
+ data = res.json()
72
+ if data.get('code') == 200:
73
+ return {"id": data['data']['taskId'], "name": upload_data['name']}
74
+ return {"failed": True, "name": upload_data['name'], "msg": data.get('msg')}
75
+ except Exception as e:
76
+ return {"failed": True, "name": upload_data['name'], "msg": str(e)}
77
+
78
+ # ==========================================
79
+ # 🔄 流程控制
80
+ # ==========================================
81
+ def run_one_click_copy(files, similarity):
82
+ logs = [log(f"🚀 启动复刻流程 | 目标相似度: {similarity}")]
83
+ if not files: return "\n".join(logs), None
84
+
85
+ # 1. 上传
86
+ logs.append(log(f"⬆️ 正在上传 {len(files)} 个音频..."))
87
+ with ThreadPoolExecutor(max_workers=MAX_WORKERS) as pool:
88
+ uploads = list(pool.map(upload_file, files))
89
+
90
+ # 2. 提交
91
+ logs.append(log(f"⚙️ 提交任务..."))
92
+ tasks = []
93
+ with ThreadPoolExecutor(max_workers=MAX_WORKERS) as pool:
94
+ # 将 similarity 参数传递给 submit 函数
95
+ futures = [pool.submit(submit_copy_task, up, similarity) for up in uploads]
96
+ for f in as_completed(futures):
97
+ res = f.result()
98
+ if res and not res.get('failed'):
99
+ tasks.append(res)
100
+ logs.append(log(f" ✅ 任务建立: {res['name']}"))
101
+ else:
102
+ logs.append(log(f" ❌ 建立失败: {res.get('name')} - {res.get('msg')}"))
103
+
104
+ if not tasks:
105
+ logs.append(log("❌ 无有效任务,流程结束"))
106
+ return "\n".join(logs), None
107
+
108
+ # 3. 监控
109
+ logs.append(log(f"⏳ 等待处理 (共 {len(tasks)} 个)..."))
110
+ yield "\n".join(logs), None
111
+
112
+ completed_urls = []
113
+ headers = {"Authorization": f"Bearer {SUNO_API_KEY}"}
114
+ start_time = time.time()
115
+
116
+ while len(completed_urls) < len(tasks):
117
+ if time.time() - start_time > 1200: break
118
+
119
+ pending = False
120
+ for task in tasks:
121
+ if any(c['id'] == task['id'] for c in completed_urls): continue
122
+ if task.get('failed_final'): continue
123
+
124
+ try:
125
+ r = requests.get(QUERY_URL, headers=headers, params={"taskId": task['id']}).json()
126
+ status = r.get('data', {}).get('status')
127
+
128
+ if status == 'SUCCESS':
129
+ data = r['data']['response'].get('sunoData', [])
130
+ if data:
131
+ info = {"id": task['id'], "url": data[0]['audioUrl'], "name": task['name']}
132
+ completed_urls.append(info)
133
+ logs.append(log(f" 🎉 完成: {task['name']}"))
134
+ yield "\n".join(logs), None
135
+ elif status in ['GENERATE_AUDIO_FAILED', 'SENSITIVE_WORD_ERROR']:
136
+ task['failed_final'] = True
137
+ logs.append(log(f" ❌ 平台生成失败: {task['name']}"))
138
+ yield "\n".join(logs), None
139
+ else:
140
+ pending = True
141
+ except: pending = True
142
+
143
+ if pending: time.sleep(5)
144
+ else: break
145
+
146
+ # 4. 打包
147
+ logs.append(log("📦 打包文件中..."))
148
+ temp_dir = f"Inst_Copy_{int(time.time())}"
149
+ os.makedirs(temp_dir, exist_ok=True)
150
+
151
+ cnt = 0
152
+ for item in completed_urls:
153
+ try:
154
+ with requests.get(item['url'], stream=True) as r:
155
+ fname = f"{os.path.splitext(item['name'])[0]}_Remix.mp3"
156
+ with open(os.path.join(temp_dir, fname), 'wb') as f:
157
+ f.write(r.content)
158
+ cnt += 1
159
+ except: pass
160
+
161
+ if cnt > 0:
162
+ zip_path = f"{temp_dir}.zip"
163
+ with zipfile.ZipFile(zip_path, 'w') as z:
164
+ for root, _, fs in os.walk(temp_dir):
165
+ for f in fs: z.write(os.path.join(root, f), f)
166
+ shutil.rmtree(temp_dir)
167
+ logs.append(log(f"✅ 完成!成功下载 {cnt} 个文件"))
168
+ yield "\n".join(logs), zip_path
169
+ else:
170
+ shutil.rmtree(temp_dir)
171
+ yield "\n".join(logs), None
172
+
173
+ # ==========================================
174
+ # 🖥️ 界面构建
175
+ # ==========================================
176
+ with gr.Blocks(title="Suno 纯音乐复刻专家") as demo:
177
+ gr.Markdown("## 🎹 Suno 纯音乐复刻专家 (Instrumental Copy)")
178
+ gr.Markdown("上传音频 -> 调节相似度 -> 一键生成。")
179
+
180
+ with gr.Row():
181
+ with gr.Column(scale=1):
182
+ file_in = gr.File(label="拖入音频文件 (支持批量)", file_count="multiple", height=200)
183
+
184
+ # 🔥 新增:相似度调节滑块 🔥
185
+ similarity_in = gr.Slider(
186
+ minimum=0.1,
187
+ maximum=0.99,
188
+ value=0.95,
189
+ step=0.01,
190
+ label="🎚️ 复刻相似度 (Audio Weight)",
191
+ info="⚠️ 调节说明:\n"
192
+ "【0.90 - 0.99】:高保真复刻,几乎和原曲一样,仅音质/混音微调。\n"
193
+ "【0.60 - 0.80】:保留旋律框架,但在乐器和氛围上有明显变化(二创)。\n"
194
+ "【0.30 - 0.50】:仅保留一点原曲的影子,大幅度重写(魔改)。"
195
+ )
196
+
197
+ btn = gr.Button("🚀 开始制作", variant="primary", size="lg")
198
+
199
+ with gr.Column(scale=1):
200
+ log_box = gr.Textbox(label="运行日志", lines=12)
201
+ out_file = gr.File(label="下载结果 (ZIP)")
202
+
203
+ # 将 similarity_in 加入输入列表
204
+ btn.click(run_one_click_copy, inputs=[file_in, similarity_in], outputs=[log_box, out_file])
205
+
206
+ if __name__ == "__main__":
207
+ demo.launch()