import datetime import gradio as gr import json import requests import time def create_task(prompt: str, style: str, title: str, api_key: str): headers = { "Content-Type": "application/json", "Authorization": f"Bearer {api_key}" } url = "https://api.kie.ai/api/v1/generate" payload = { "prompt": prompt, "style": style, "title": title, "customMode": False, "instrumental": True, "model": "V3_5", "callBackUrl": "https://your-app.com/callback" } try: response = requests.post(url, headers=headers, data=json.dumps(payload), timeout=60) result = response.json() if response.ok and result.get('code') == 200: print(f"任务ID: {result['data']['taskId']}") return True, result['data']['taskId'] else: print(f"请求失败: {result.get('msg', '未知错误')}") return False, result.get('msg', "Internal Server Error") except Exception: pass return False, "Internal Server Error" def check_task_status(task_id, api_key): url = f"https://api.kie.ai/api/v1/generate/record-info?taskId={task_id}" headers = {"Authorization": f"Bearer {api_key}"} for _ in range(3): try: response = requests.get(url, headers=headers) result = response.json() if response.ok and result.get('code') == 200: task_data = result['data'] status = task_data['status'] response_data = task_data['response'] if status == 'SUCCESS': return "SUCCESS", response_data["sunoData"] elif status == 'FIRST_SUCCESS': return "FIRST_SUCCESS", response_data["sunoData"] elif status == 'TEXT_SUCCESS': return "TEXT_SUCCESS", task_data elif status == 'PENDING': return "PENDING", None else: return "FAILED", response_data.get('errorMessage', 'Internal Server Error') else: return "FAILED", result.get('msg', 'Internal Server Error') except Exception: pass time.sleep(5) return "FAILED", 'Internal Server Error' def format_duration(seconds): """格式化时长显示""" minutes = int(seconds // 60) seconds = int(seconds % 60) return f"{minutes:02d}:{seconds:02d}" def create_music_card(music_data, index): """创建音乐卡片的HTML""" if music_data is None: return f"""
🎵
Waiting for music #{index} to generate...
""" # 格式化时长 duration = format_duration(music_data.get('duration', 0)) # 格式化创建时间 create_time = music_data.get('createTime', '') if create_time: # 如果是毫秒级时间戳,先转成秒 if create_time > 1e12: create_time = create_time / 1000 # 转换为 datetime 对象 dt = datetime.datetime.fromtimestamp(create_time) # 格式化输出 formatted_time = dt.strftime("%Y-%m-%d %H:%M:%S") else: formatted_time = "" print(formatted_time) return f"""
Album cover
#{index}

{music_data.get('title', 'Untitled')}

🏷️ {music_data.get('tags', 'No tags')}
⏱️ {duration}
Generated: {formatted_time}
""" def process_music_generation(prompt, style, title, api_key, progress=gr.Progress()): yield ( create_music_card(None, 1), create_music_card(None, 2), f"🚀 Creating music generation task..." ) """处理音乐生成的主函数""" if not api_key or not api_key.strip(): yield (gr.update(), # 保持音乐卡片不变 gr.update(), # 保持音乐卡片不变 f"❌ Please enter API Key") return "❌ Please enter API Key" if not prompt or not prompt.strip(): yield (gr.update(), # 保持音乐卡片不变 gr.update(), # 保持音乐卡片不变 f"❌ Please enter music description") return "❌ Please enter music description" try: # 步骤1:创建任务 success, task_id = create_task(prompt.strip(), style.strip(), title.strip(), api_key.strip()) if not success: yield (gr.update(), # 保持音乐卡片不变 gr.update(), # 保持音乐卡片不变 f"❌ Failed to create task: {task_id}") return f"❌ Failed to create task" yield (gr.update(), # 保持音乐卡片不变 gr.update(), # 保持音乐卡片不变 f"📋 Starting music generation") # 步骤2:轮询检查状态 start_time = time.time() first_music_shown = False music_data_1 = None music_data_2 = None last_api_check = 0 api_check_interval = 8 # API每8秒检查一次 while time.time() - start_time < 600: # 最多等待10分钟 elapsed = int(time.time() - start_time) progress_value = min(0.2 + (elapsed / 600) * 0.7, 0.9) # 每8秒检查一次API状态,避免频繁请求 if time.time() - last_api_check >= api_check_interval: status, data = check_task_status(task_id, api_key.strip()) last_api_check = time.time() if status == 'FIRST_SUCCESS' and not first_music_shown: # 第一首音乐生成完成 progress(progress_value, desc="🎵 First music generated!") if data and len(data) > 0: music_data_1 = data[0] first_music_shown = True # 立即更新第一首音乐的显示 yield ( create_music_card(music_data_1, 1), create_music_card(None, 2), f"✅ First music generated, generating second..." ) continue elif status == 'SUCCESS': # 两首音乐都生成完成 progress(0.95, desc="🎉 All music generated!") if data and len(data) > 0: music_data_1 = data[0] if len(data) > 0 else music_data_1 music_data_2 = data[1] if len(data) > 1 else None yield ( create_music_card(music_data_1, 1), create_music_card(music_data_2, 2) if music_data_2 else create_music_card(None, 2), f"✅ Successfully generated {len(data)} music tracks!" ) break elif status == 'FAILED': yield ( gr.update(), # 保持音乐卡片不变 gr.update(), # 保持音乐卡片不变 f"❌ Generation failed: {data}" ) break if not first_music_shown: yield ( gr.update(), # 保持音乐卡片不变 gr.update(), # 保持音乐卡片不变 f"⏳ Generating music... (waited {elapsed}s)" ) elif first_music_shown: # 第一首音乐已生成,只更新状态文本 yield ( gr.update(), # 保持音乐卡片不变 gr.update(), # 保持音乐卡片不变 f"✅ First music generated, generating second... (waited {elapsed}s)" ) time.sleep(1) # 超时 if time.time() - start_time >= 600: yield ( gr.update(), # 保持音乐卡片不变 gr.update(), # 保持音乐卡片不变 "⚠️ Generation timeout, please try again later" ) except Exception as e: yield ( gr.update(), # 保持音乐卡片不变 gr.update(), # 保持音乐卡片不变 f"❌ Error occurred: {str(e)}" ) return "❌ Error occurred: {str(e)}" # CSS样式 css = """ .gradio-container { background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%); font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; min-height: 100vh; } .header-container { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 2.5rem; border-radius: 24px; margin-bottom: 2.5rem; box-shadow: 0 20px 60px rgba(102, 126, 234, 0.25); } .logo-text { font-size: 3.5rem; font-weight: 900; color: white; text-align: center; margin: 0; letter-spacing: -2px; text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3); } .subtitle { color: rgba(255, 255, 255, 0.9); text-align: center; font-size: 1.2rem; margin-top: 0.5rem; } .main-content { background: rgba(255, 255, 255, 0.95); backdrop-filter: blur(20px); border-radius: 24px; padding: 2.5rem; box-shadow: 0 10px 40px rgba(0, 0, 0, 0.08); } .mode-indicator { background: rgba(255, 255, 255, 0.3); backdrop-filter: blur(10px); border-radius: 12px; padding: 0.5rem 1rem; margin-top: 1rem; text-align: center; font-weight: 600; color: white; } .info-box { background: linear-gradient(135deg, #e3f2fd 0%, #bbdefb 100%); border-radius: 12px; padding: 1.5rem; margin-bottom: 1.5rem; border-left: 4px solid #2196f3; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); } .info-box strong { color: #1976d2; font-size: 1.1rem; } .gr-button-primary { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important; border: none !important; color: white !important; font-weight: 700 !important; font-size: 1.1rem !important; padding: 1.2rem 2rem !important; border-radius: 14px !important; text-transform: uppercase; letter-spacing: 1px; width: 100%; margin-top: 1rem !important; transition: all 0.3s ease !important; } .gr-button-primary:hover { background: linear-gradient(135deg, #5a67d8 0%, #6b46c1 100%) !important; transform: translateY(-2px); box-shadow: 0 6px 20px rgba(0, 0, 0, 0.15) !important; } /* 音乐卡片样式 */ .music-cards-container { display: flex; gap: 1.5rem; margin-top: 1rem; flex-wrap: wrap; } .music-card { background: white; border-radius: 16px; box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1); overflow: hidden; transition: all 0.3s ease; flex: 1; min-width: 300px; animation: slideIn 0.5s ease-out; } @keyframes slideIn { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } } .music-card:hover { transform: translateY(-5px); box-shadow: 0 8px 30px rgba(0, 0, 0, 0.15); } .empty-card { border: 2px dashed #e0e0e0; background: #fafafa; min-height: 400px; display: flex; align-items: center; justify-content: center; } .empty-icon { font-size: 3rem; margin-bottom: 1rem; opacity: 0.3; } .empty-text { color: #9e9e9e; font-size: 1rem; } .card-header { position: relative; height: 200px; overflow: hidden; } .album-cover { width: 100%; height: 100%; object-fit: cover; } .track-number { position: absolute; top: 10px; right: 10px; background: rgba(0, 0, 0, 0.7); color: white; padding: 5px 12px; border-radius: 20px; font-weight: bold; font-size: 0.9rem; } .card-content { padding: 1.5rem; } .music-title { font-size: 1.4rem; font-weight: 700; color: #2c3e50; margin: 0 0 1rem 0; } .music-tags { display: flex; align-items: center; margin-bottom: 0.8rem; color: #7f8c8d; } .tag-icon, .duration-icon { margin-right: 0.5rem; font-size: 1rem; } .tag-text { font-size: 0.9rem; font-style: italic; } .music-duration { display: flex; align-items: center; margin-bottom: 1rem; color: #7f8c8d; } .duration-text { font-weight: 600; } .audio-player { width: 100%; margin: 1rem 0; border-radius: 8px; } .create-time { font-size: 0.8rem; color: #95a5a6; text-align: right; margin-top: 1rem; } /* 输入框样式 */ .gr-input, .gr-textarea { background: #ffffff !important; border: 2px solid #e1e8ed !important; border-radius: 10px !important; color: #374151 !important; font-size: 1rem !important; padding: 0.75rem 1rem !important; transition: all 0.3s ease !important; } .gr-input:focus, .gr-textarea:focus { border-color: #667eea !important; box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1) !important; } /* 下拉框样式 */ .gr-dropdown { background: #ffffff !important; border: 2px solid #e1e8ed !important; border-radius: 10px !important; } label { color: #4a5568 !important; font-weight: 600 !important; font-size: 0.9rem !important; margin-bottom: 0.5rem !important; } /* 示例样式 */ .examples-holder { margin-top: 2rem; padding: 1.5rem; background: #f8f9fa; border-radius: 12px; } /* 隐藏页脚 */ footer { display: none !important; } """ # 创建Gradio界面 with gr.Blocks(css=css, theme=gr.themes.Base()) as demo: with gr.Column(elem_classes="header-container"): gr.HTML("""

🎵 Suno AI Music API Free Online Test

Supported by the latest Suno v4.5 and Suno v4.5+ models

💡 Test Suno AI Music API for free on Hugging Face Spaces. Generate music, edit vocals, and create tracks with the latest Suno v4.5 and Suno v4.5+ models.
""") with gr.Column(elem_classes="main-content"): # 信息提示框 gr.HTML("""
Supported Features of Suno API:
Generate Music: Create original songs with or without lyrics.
Extend Music: Seamlessly extend your tracks to any length.
Generate Lyrics: Create unique lyrics from text prompts.
Add Vocals/Instrumentals: Add vocals or instrumental accompaniments to your tracks.
Separate Vocals: Isolate vocals or instruments for editing.
Convert to WAV: Export music in high-quality WAV format.
Usage Instructions:
• Get your Suno API Key 👉 here 👈
• Select your model: Choose from Suno v4.5+, Suno v4.5, Suno v4, or Suno v3.5.
• Choose Custom Mode (optional), specify if you want to generate instrumental music only, and enter your music style description or lyrics.
• Click Generate and wait ~1–2 minutes for processing.
""") with gr.Row(): # 左侧输入区域 with gr.Column(scale=1): # 第一块:API Key with gr.Group(): gr.Markdown("### 🔑 API Configuration") api_key = gr.Textbox( label="API Key", placeholder="Please enter your API Key", type="password" ) # 第二块:音乐信息 with gr.Group(): gr.Markdown("### 🎵 Music Information") title = gr.Textbox( label="Music Title", placeholder="Give your music a name", value="" ) style = gr.Textbox( label="Music Style", placeholder="Enter your desired music style", interactive=True ) prompt = gr.Textbox( label="Music Description", placeholder="Describe the music you want, e.g.: An energetic electronic dance track with futuristic synthesizer sounds...", lines=3, ) generate_btn = gr.Button( "🎼 Generate Music", variant="primary", size="lg" ) # 右侧输出区域 with gr.Column(scale=2): gr.Markdown("### 🎵 Generation Results") # 两个音乐展示区域 with gr.Row(elem_classes="music-cards-container"): music_display_1 = gr.HTML( value=create_music_card(None, 1), elem_id="music-1" ) music_display_2 = gr.HTML( value=create_music_card(None, 2), elem_id="music-2" ) # 状态显示 status_text = gr.Textbox( label="Generation Status", interactive=False, value="Ready! Please enter music description and click generate button...", lines=2 ) # 音乐创意示例 - 放在页面最下面 gr.Examples( examples=[ ["Summer Beach", "Pop", "A light and cheerful summer beach music with fresh guitar and upbeat rhythm"], ["Burning Heart", "Rock", "Passionate rock music with powerful electric guitar and drums"], ["Neon Night", "Electronic", "Futuristic electronic music with cyberpunk style"], ["Afternoon Time", "Jazz", "Gentle jazz piano piece, perfect for café ambiance"], ["Heroic Epic", "Classical", "Epic orchestral music, grand and majestic, perfect for film soundtrack"], ], inputs=[title, style, prompt], label="🎵 Music Creative Examples" ) # 绑定生成事件 generate_btn.click( fn=process_music_generation, inputs=[prompt, style, title, api_key], outputs=[music_display_1, music_display_2, status_text] ) # 启动应用 if __name__ == "__main__": demo.launch( share=True, server_name="0.0.0.0", server_port=7860 # 使用不同的端口 )