Spaces:
Sleeping
Sleeping
Commit
·
be392d2
1
Parent(s):
032d5df
Upload 3 files
Browse files- app.py +51 -151
- packages.txt +1 -0
app.py
CHANGED
|
@@ -1,39 +1,16 @@
|
|
|
|
|
| 1 |
import gradio as gr
|
| 2 |
import google.generativeai as genai
|
| 3 |
import os
|
| 4 |
import time
|
| 5 |
from pydub import AudioSegment
|
| 6 |
-
import stat # 确保导入 stat 模块
|
| 7 |
-
|
| 8 |
-
# --- 启动时自动修复 FFmpeg 权限 / Auto-fix FFmpeg permissions on startup ---
|
| 9 |
-
FFMPEG_PATH = "./ffmpeg"
|
| 10 |
-
if os.path.exists(FFMPEG_PATH):
|
| 11 |
-
try:
|
| 12 |
-
# 检查当前是否已有执行权限
|
| 13 |
-
if not (os.stat(FFMPEG_PATH).st_mode & stat.S_IXUSR):
|
| 14 |
-
print(f"检测到 '{FFMPEG_PATH}' 没有执行权限,正在尝试修复...")
|
| 15 |
-
print(f"Detected '{FFMPEG_PATH}' lacks execute permission, attempting to fix...")
|
| 16 |
-
current_permissions = os.stat(FFMPEG_PATH).st_mode
|
| 17 |
-
os.chmod(FFMPEG_PATH, current_permissions | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
|
| 18 |
-
print("权限修复成功!/ Permissions fixed successfully!")
|
| 19 |
-
except Exception as e:
|
| 20 |
-
print(f"警告:自动修复 '{FFMPEG_PATH}' 权限失败: {e}")
|
| 21 |
-
print(f"Warning: Auto-fixing permissions for '{FFMPEG_PATH}' failed: {e}")
|
| 22 |
-
print("应用可能会因为无法调用ffmpeg而失败。/ The app might fail due to being unable to call ffmpeg.")
|
| 23 |
-
|
| 24 |
-
# 显式地告诉 pydub ffmpeg 的路径
|
| 25 |
-
AudioSegment.converter = FFMPEG_PATH
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
# --- -1. 网络代理配置 (如果需要) / Network Proxy (If Needed) ---
|
| 29 |
-
# os.environ['http_proxy'] = 'http://127.0.0.1:7890'
|
| 30 |
-
# os.environ['https_proxy'] = 'http://127.0.0.1:7890'
|
| 31 |
|
| 32 |
# --- 0. 全局配置 / Global Configuration ---
|
| 33 |
-
#
|
| 34 |
GOOGLE_API_KEY = os.getenv('GOOGLE_API_KEY')
|
| 35 |
if not GOOGLE_API_KEY:
|
| 36 |
-
|
|
|
|
| 37 |
|
| 38 |
genai.configure(api_key=GOOGLE_API_KEY)
|
| 39 |
gemini_model = genai.GenerativeModel('gemini-1.5-flash-latest')
|
|
@@ -45,74 +22,37 @@ EQ_RANGE_DB = 12
|
|
| 45 |
# --- 1. 双语文本库 / Bilingual Text Library ---
|
| 46 |
LANG = {
|
| 47 |
'zh': {
|
| 48 |
-
'title': "FeiMatrix 智能动态调音",
|
| 49 |
-
'
|
| 50 |
-
'
|
| 51 |
-
'
|
| 52 |
-
'
|
| 53 |
-
'
|
| 54 |
-
'
|
| 55 |
-
'
|
| 56 |
-
'
|
| 57 |
-
'
|
| 58 |
-
|
| 59 |
-
"我喜欢温暖、厚实的感觉", "给我一种现场演唱会的空间感", "保持原汁原味,但细节多一点",
|
| 60 |
-
"无 (使用上面的输入框)",
|
| 61 |
-
],
|
| 62 |
-
'default_choice': "无 (使用上面的输入框)",
|
| 63 |
-
'step3_header': "第三步:开始调音",
|
| 64 |
-
'process_button': "开始智能调音!",
|
| 65 |
-
'log_header': "AI 调音师工作日志",
|
| 66 |
-
'log_initial': "`[系统]`:我准备好啦,等你上传文件哦~",
|
| 67 |
-
'result_header': "第四步:聆听您的定制版",
|
| 68 |
-
'result_label': "这里会显示AI调音后的音频",
|
| 69 |
-
'accordion_header': "(高级)查看 AI 定制的 EQ 参数",
|
| 70 |
-
'err_no_file': "哎呀,忘了上传MP3文件啦!",
|
| 71 |
-
'info_no_pref': "您没有指定风格,将为您进行温和的细节优化。",
|
| 72 |
-
'status_analyzing': "`[AI分析师]`:收到!正在分析您的音频... ⏳",
|
| 73 |
-
'status_analysis_failed': "AI分析失败: {e}。将使用默认调音策略。",
|
| 74 |
-
'status_understanding': "`[AI分析师]`:分析完成!\n> {analysis}\n\n`[AI调音师]`:正在理解{choice}并调整EQ... 🔊",
|
| 75 |
-
'status_tuning': "`[AI调音师]`:好嘞!已经按您的要求调整好了!\n\n`[系统]`:正在生成音频... 🎶",
|
| 76 |
-
'status_done': "`[系统]`:搞定!您的AI定制版音频已生成!🎉",
|
| 77 |
},
|
| 78 |
'en': {
|
| 79 |
-
'title': "FeiMatrix AI Dynamic Equalizer",
|
| 80 |
-
'
|
| 81 |
-
'
|
| 82 |
-
'
|
| 83 |
-
'
|
| 84 |
-
'
|
| 85 |
-
'
|
| 86 |
-
'
|
| 87 |
-
'
|
| 88 |
-
'
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
"None (use the input box above)",
|
| 92 |
-
],
|
| 93 |
-
'default_choice': "None (use the input box above)",
|
| 94 |
-
'step3_header': "Step 3: Start Tuning",
|
| 95 |
-
'process_button': "Start AI Tuning!",
|
| 96 |
-
'log_header': "AI Tuning Engineer's Log",
|
| 97 |
-
'log_initial': "`[System]`: Ready when you are, just upload a file~",
|
| 98 |
-
'result_header': "Step 4: Listen to Your Custom Version",
|
| 99 |
-
'result_label': "Your AI-tuned audio will appear here",
|
| 100 |
-
'accordion_header': "(Advanced) View AI-Customized EQ Parameters",
|
| 101 |
-
'err_no_file': "Oops, you forgot to upload the MP3 file!",
|
| 102 |
-
'info_no_pref': "You didn't specify a style, so I'll perform a gentle detail enhancement.",
|
| 103 |
-
'status_analyzing': "`[AI Analyst]`: Roger that! Analyzing your audio... ⏳",
|
| 104 |
-
'status_analysis_failed': "AI analysis failed: {e}. Default tuning strategy will be used.",
|
| 105 |
-
'status_understanding': "`[AI Analyst]`: Analysis complete!\n> {analysis}\n\n`[AI Tuning Engineer]`: Understanding {choice} and adjusting EQ... 🔊",
|
| 106 |
-
'status_tuning': "`[AI Tuning Engineer]`: Alright! Tuned to your request!\n\n`[System]`: Generating audio... 🎶",
|
| 107 |
-
'status_done': "`[System]`: All set! Your AI custom audio is ready! 🎉",
|
| 108 |
}
|
| 109 |
}
|
| 110 |
LANG_MAP = {"简体中文": "zh", "English": "en"}
|
| 111 |
|
| 112 |
-
# --- 2. 核心功能函数 / Core Functions ---
|
| 113 |
def get_ai_tuned_eq_settings(audio_analysis_text, user_preference):
|
| 114 |
-
eq_values = [0.0] * len(EQ_BANDS_HZ)
|
| 115 |
-
pref_lower = user_preference.lower()
|
| 116 |
if "电子舞曲" in audio_analysis_text or "EDM" in audio_analysis_text: eq_values = [4.0, 2.5, -1.5, -0.5, 1.0, 2.0, 3.5, 4.0, 2.5, 1.0]
|
| 117 |
elif "爵士乐" in audio_analysis_text or "warm" in audio_analysis_text: eq_values = [3.0, 1.5, -0.5, -1.0, 0.5, 1.5, 2.0, 3.0, 2.0, 0.5]
|
| 118 |
elif "人声" in audio_analysis_text or "vocal" in audio_analysis_text: eq_values[4] += 0.5; eq_values[5] += 1.0
|
|
@@ -122,104 +62,64 @@ def get_ai_tuned_eq_settings(audio_analysis_text, user_preference):
|
|
| 122 |
if any(k in pref_lower for k in ["温暖", "warm", "rich", "soft"]): eq_values[1]+=1.5; eq_values[2]+=1.0; eq_values[6]-=0.5
|
| 123 |
if any(k in pref_lower for k in ["空间", "space", "live", "concert"]): eq_values[6]+=1.0; eq_values[7]+=1.0; eq_values[4]-=0.5
|
| 124 |
if any(k in pref_lower for k in ["自然", "natural", "original"]): [v*0.5 for v in eq_values]; eq_values[7]+=0.5
|
| 125 |
-
eq_values=[max(-EQ_RANGE_DB,min(EQ_RANGE_DB,v)) for v in eq_values]
|
| 126 |
-
return {f'{f} Hz':v for f,v in zip(EQ_BANDS_HZ, eq_values)}
|
| 127 |
|
| 128 |
def apply_eq_to_audio(audio_path, eq_settings):
|
| 129 |
-
if not audio_path: return None
|
| 130 |
try: audio = AudioSegment.from_file(audio_path)
|
| 131 |
except Exception as e: print(f"Audio load error: {e}"); return None
|
| 132 |
q_factor=1.414; filter_parts=[]
|
| 133 |
-
for
|
| 134 |
-
if
|
| 135 |
if not filter_parts: return audio_path
|
| 136 |
output_path = f"{os.path.splitext(audio_path)[0]}_eq.mp3"
|
| 137 |
try:
|
| 138 |
audio.export(output_path, format="mp3", parameters=["-af", ",".join(filter_parts)])
|
| 139 |
return output_path
|
| 140 |
-
except Exception as e: print(f"EQ apply error: {e}"); raise gr.Error("Failed to apply EQ!
|
| 141 |
|
| 142 |
def process_and_tune(audio_file, quick_choice, custom_input, lang_choice):
|
| 143 |
-
lang_code = LANG_MAP[lang_choice]
|
| 144 |
-
L = LANG[lang_code]
|
| 145 |
if not audio_file: raise gr.Error(L['err_no_file'])
|
| 146 |
-
|
| 147 |
if custom_input and custom_input.strip(): final_preference = custom_input
|
| 148 |
elif L['default_choice'] not in quick_choice: final_preference = quick_choice
|
| 149 |
else: final_preference = L['quick_choices'][-2]; gr.Info(L['info_no_pref'])
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
yield {status_log_md: gr.update(value=L['status_analyzing']), processed_audio_output: gr.update(value=None), eq_accordion: gr.update(visible=True, open=False), **slider_reset_updates}
|
| 153 |
-
|
| 154 |
try:
|
| 155 |
-
|
| 156 |
-
prompt = "Briefly analyze this audio's genre, mood, and key instruments."; response = gemini_model.generate_content([prompt, audio_file_for_api])
|
| 157 |
audio_analysis_text = response.text or "(AI did not provide a detailed analysis)"
|
| 158 |
except Exception as e: audio_analysis_text = L['status_analysis_failed'].format(e=e); gr.Warning(audio_analysis_text)
|
| 159 |
-
|
| 160 |
choice_desc = f"“{final_preference}”"
|
| 161 |
-
yield {status_log_md: gr.update(value=L['status_understanding'].format(analysis=audio_analysis_text, choice=choice_desc))}
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
eq_settings = get_ai_tuned_eq_settings(audio_analysis_text, final_preference)
|
| 165 |
-
slider_updates = {s: gr.update(value=v) for s, v in zip(eq_sliders, eq_settings.values())}
|
| 166 |
-
yield {status_log_md: gr.update(value=L['status_tuning']), **slider_updates}
|
| 167 |
-
time.sleep(1)
|
| 168 |
-
|
| 169 |
eq_audio_path = apply_eq_to_audio(audio_file.name, eq_settings)
|
| 170 |
if not eq_audio_path: raise gr.Error("Audio processing failed!")
|
| 171 |
-
|
| 172 |
yield {status_log_md: gr.update(value=L['status_done']), processed_audio_output: gr.update(value=eq_audio_path, label=L['result_label'], autoplay=True), eq_accordion: gr.update(open=False)}
|
| 173 |
|
| 174 |
def update_language(lang_choice):
|
| 175 |
-
|
| 176 |
-
L = LANG[lang_code]
|
| 177 |
return {
|
| 178 |
-
title_md: gr.update(value=f"# {L['title']}"),
|
| 179 |
-
|
| 180 |
-
|
| 181 |
-
|
| 182 |
-
|
| 183 |
-
custom_input: gr.update(label=L['custom_input_label'], placeholder=L['custom_input_placeholder']),
|
| 184 |
-
quick_choice: gr.update(label=L['quick_choice_label'], choices=L['quick_choices'], value=L['default_choice']),
|
| 185 |
-
step3_header_md: gr.update(value=f"### **{L['step3_header']}**"),
|
| 186 |
-
process_button: gr.update(value=L['process_button']),
|
| 187 |
-
log_header_md: gr.update(value=f"### **{L['log_header']}**"),
|
| 188 |
-
status_log_md: gr.update(value=L['log_initial']),
|
| 189 |
-
result_header_md: gr.update(value=f"### **{L['result_header']}**"),
|
| 190 |
-
processed_audio_output: gr.update(label=L['result_label']),
|
| 191 |
-
eq_accordion: gr.update(label=L['accordion_header']),
|
| 192 |
}
|
| 193 |
|
| 194 |
-
# --- 3. Gradio 界面构建 / Gradio UI Build ---
|
| 195 |
with gr.Blocks(theme=gr.themes.Soft()) as demo:
|
| 196 |
lang_switcher = gr.Radio(choices=["简体中文", "English"], value="简体中文", label="Language / 语言", info="选择界面语言 / Select UI Language")
|
| 197 |
-
|
| 198 |
title_md = gr.Markdown("# FeiMatrix 智能动态调音")
|
| 199 |
subtitle_md = gr.Markdown("上传一首歌,告诉我你想要的感觉,剩下的交给我!")
|
| 200 |
-
|
| 201 |
with gr.Column():
|
| 202 |
-
step1_header_md = gr.Markdown("### **第一步:上传音频**")
|
| 203 |
-
|
| 204 |
-
|
| 205 |
-
step2_header_md = gr.Markdown("### **第二步:告诉我你的感觉**")
|
| 206 |
-
custom_input = gr.Textbox(label="用你自己的话描述想要的感觉(推荐):", placeholder="例如:我想要吉他声更清脆,鼓声更有力...", lines=2)
|
| 207 |
quick_choice = gr.Radio(label="或者,快速选择一个预设风格:", choices=LANG['zh']['quick_choices'], value=LANG['zh']['default_choice'])
|
| 208 |
-
|
| 209 |
-
|
| 210 |
-
|
| 211 |
-
|
| 212 |
-
log_header_md = gr.Markdown("### **AI 调音师工作日志**")
|
| 213 |
-
status_log_md = gr.Markdown("`[系统]`:我准备好啦,等你上传文件哦~")
|
| 214 |
-
|
| 215 |
-
result_header_md = gr.Markdown("### **第四步:聆听您的定制版**")
|
| 216 |
-
processed_audio_output = gr.Audio(label="这里会显示AI调音后的音频", type="filepath", interactive=True)
|
| 217 |
-
|
| 218 |
with gr.Accordion("(高级)查看 AI 定制的 EQ 参数", open=False, visible=False) as eq_accordion:
|
| 219 |
-
|
| 220 |
-
eq_sliders = [gr.Slider(minimum=-EQ_RANGE_DB, maximum=EQ_RANGE_DB, value=0, step=0.5, label=f"{f} Hz", interactive=False) for f in EQ_BANDS_HZ]
|
| 221 |
-
|
| 222 |
-
# 事件绑定 / Event Bindings
|
| 223 |
all_ui_outputs = [title_md, subtitle_md, step1_header_md, audio_input, step2_header_md, custom_input, quick_choice, step3_header_md, process_button, log_header_md, status_log_md, result_header_md, processed_audio_output, eq_accordion]
|
| 224 |
lang_switcher.change(fn=update_language, inputs=lang_switcher, outputs=all_ui_outputs, queue=False)
|
| 225 |
process_button.click(fn=process_and_tune, inputs=[audio_input, quick_choice, custom_input, lang_switcher], outputs=[status_log_md, processed_audio_output, eq_accordion, *eq_sliders])
|
|
|
|
| 1 |
+
# app.py (Hugging Face Spaces Version)
|
| 2 |
import gradio as gr
|
| 3 |
import google.generativeai as genai
|
| 4 |
import os
|
| 5 |
import time
|
| 6 |
from pydub import AudioSegment
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7 |
|
| 8 |
# --- 0. 全局配置 / Global Configuration ---
|
| 9 |
+
# API Key will be loaded from Hugging Face Secrets
|
| 10 |
GOOGLE_API_KEY = os.getenv('GOOGLE_API_KEY')
|
| 11 |
if not GOOGLE_API_KEY:
|
| 12 |
+
# This error will be visible in the logs if the secret is not set
|
| 13 |
+
raise ValueError("Hugging Face Secret 'GOOGLE_API_KEY' not found!")
|
| 14 |
|
| 15 |
genai.configure(api_key=GOOGLE_API_KEY)
|
| 16 |
gemini_model = genai.GenerativeModel('gemini-1.5-flash-latest')
|
|
|
|
| 22 |
# --- 1. 双语文本库 / Bilingual Text Library ---
|
| 23 |
LANG = {
|
| 24 |
'zh': {
|
| 25 |
+
'title': "FeiMatrix 智能动态调音", 'subtitle': "上传一首歌,告诉我你想要的感觉,剩下的交给我!", 'lang_label': "语言 / Language",
|
| 26 |
+
'step1_header': "第一步:上传音频", 'upload_label': "点击或拖拽 MP3 文件到这里",
|
| 27 |
+
'step2_header': "第二步:告诉我你的感觉", 'custom_input_label': "用你自己的话描述想要的感觉(推荐):",
|
| 28 |
+
'custom_input_placeholder': "例如:我想要吉他声更清脆,鼓声更有力...", 'quick_choice_label': "或者,快速选择一个预设风格:",
|
| 29 |
+
'quick_choices': [ "我想要咚咚咚的重低音!", "让高音更亮、更清楚", "让唱歌的声音更突出", "我喜欢温暖、厚实的感觉", "给我一种现场演唱会的空间感", "保持原汁原味,但细节多一点", "无 (使用上面的输入框)", ],
|
| 30 |
+
'default_choice': "无 (使用上面的输入框)", 'step3_header': "第三步:开始调音", 'process_button': "开始智能调音!",
|
| 31 |
+
'log_header': "AI 调音师工作日志", 'log_initial': "`[系统]`:我准备好啦,等你上传文件哦~", 'result_header': "第四步:聆听您的定制版",
|
| 32 |
+
'result_label': "这里会显示AI调音后的音频", 'accordion_header': "(高级)查看 AI 定制的 EQ 参数", 'err_no_file': "哎呀,忘了上传MP3文件啦!",
|
| 33 |
+
'info_no_pref': "您没有指定风格,将为您进行温和的细节优化。", 'status_analyzing': "`[AI分析师]`:收到!正在分析您的音频... ⏳",
|
| 34 |
+
'status_analysis_failed': "AI分析失败: {e}。将使用默认调音策略。", 'status_understanding': "`[AI分析师]`:分析完成!\n> {analysis}\n\n`[AI调音师]`:正在理解{choice}并调整EQ... 🔊",
|
| 35 |
+
'status_tuning': "`[AI调音师]`:好嘞!已经按您的要求调整好了!\n\n`[系统]`:正在生成音频... 🎶", 'status_done': "`[系统]`:搞定!您的AI定制版音频已生成!🎉",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 36 |
},
|
| 37 |
'en': {
|
| 38 |
+
'title': "FeiMatrix AI Dynamic Equalizer", 'subtitle': "Upload a song, tell me the vibe you want, and I'll handle the rest!", 'lang_label': "Language / 语言",
|
| 39 |
+
'step1_header': "Step 1: Upload Audio", 'upload_label': "Click or drag your MP3 file here",
|
| 40 |
+
'step2_header': "Step 2: Tell Me Your Vibe", 'custom_input_label': "Describe the feeling you want in your own words (Recommended):",
|
| 41 |
+
'custom_input_placeholder': "e.g., I want the guitar to be crisper and the drums more powerful...", 'quick_choice_label': "Or, quickly pick a preset style:",
|
| 42 |
+
'quick_choices': [ "I want that 'thump-thump' heavy bass!", "Make the treble brighter and clearer", "Make the vocals stand out more", "I like a warm and rich sound", "Give me a live concert feeling", "Keep it natural, just add more detail", "None (use the input box above)", ],
|
| 43 |
+
'default_choice': "None (use the input box above)", 'step3_header': "Step 3: Start Tuning", 'process_button': "Start AI Tuning!",
|
| 44 |
+
'log_header': "AI Tuning Engineer's Log", 'log_initial': "`[System]`: Ready when you are, just upload a file~",
|
| 45 |
+
'result_header': "Step 4: Listen to Your Custom Version", 'result_label': "Your AI-tuned audio will appear here",
|
| 46 |
+
'accordion_header': "(Advanced) View AI-Customized EQ Parameters", 'err_no_file': "Oops, you forgot to upload the MP3 file!",
|
| 47 |
+
'info_no_pref': "You didn't specify a style, so I'll perform a gentle detail enhancement.", 'status_analyzing': "`[AI Analyst]`: Roger that! Analyzing your audio... ⏳",
|
| 48 |
+
'status_analysis_failed': "AI analysis failed: {e}. Default tuning strategy will be used.", 'status_understanding': "`[AI Analyst]`: Analysis complete!\n> {analysis}\n\n`[AI Tuning Engineer]`: Understanding {choice} and adjusting EQ... 🔊",
|
| 49 |
+
'status_tuning': "`[AI Tuning Engineer]`: Alright! Tuned to your request!\n\n`[System]`: Generating audio... 🎶", 'status_done': "`[System]`: All set! Your AI custom audio is ready! 🎉",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 50 |
}
|
| 51 |
}
|
| 52 |
LANG_MAP = {"简体中文": "zh", "English": "en"}
|
| 53 |
|
|
|
|
| 54 |
def get_ai_tuned_eq_settings(audio_analysis_text, user_preference):
|
| 55 |
+
eq_values = [0.0] * len(EQ_BANDS_HZ); pref_lower = user_preference.lower()
|
|
|
|
| 56 |
if "电子舞曲" in audio_analysis_text or "EDM" in audio_analysis_text: eq_values = [4.0, 2.5, -1.5, -0.5, 1.0, 2.0, 3.5, 4.0, 2.5, 1.0]
|
| 57 |
elif "爵士乐" in audio_analysis_text or "warm" in audio_analysis_text: eq_values = [3.0, 1.5, -0.5, -1.0, 0.5, 1.5, 2.0, 3.0, 2.0, 0.5]
|
| 58 |
elif "人声" in audio_analysis_text or "vocal" in audio_analysis_text: eq_values[4] += 0.5; eq_values[5] += 1.0
|
|
|
|
| 62 |
if any(k in pref_lower for k in ["温暖", "warm", "rich", "soft"]): eq_values[1]+=1.5; eq_values[2]+=1.0; eq_values[6]-=0.5
|
| 63 |
if any(k in pref_lower for k in ["空间", "space", "live", "concert"]): eq_values[6]+=1.0; eq_values[7]+=1.0; eq_values[4]-=0.5
|
| 64 |
if any(k in pref_lower for k in ["自然", "natural", "original"]): [v*0.5 for v in eq_values]; eq_values[7]+=0.5
|
| 65 |
+
eq_values=[max(-EQ_RANGE_DB,min(EQ_RANGE_DB,v)) for v in eq_values]; return {f'{f} Hz':v for f,v in zip(EQ_BANDS_HZ, eq_values)}
|
|
|
|
| 66 |
|
| 67 |
def apply_eq_to_audio(audio_path, eq_settings):
|
|
|
|
| 68 |
try: audio = AudioSegment.from_file(audio_path)
|
| 69 |
except Exception as e: print(f"Audio load error: {e}"); return None
|
| 70 |
q_factor=1.414; filter_parts=[]
|
| 71 |
+
for band, gain in eq_settings.items():
|
| 72 |
+
if gain != 0: filter_parts.append(f"equalizer=f={band.split(' ')[0]}:width_type=q:w={q_factor}:g={gain}")
|
| 73 |
if not filter_parts: return audio_path
|
| 74 |
output_path = f"{os.path.splitext(audio_path)[0]}_eq.mp3"
|
| 75 |
try:
|
| 76 |
audio.export(output_path, format="mp3", parameters=["-af", ",".join(filter_parts)])
|
| 77 |
return output_path
|
| 78 |
+
except Exception as e: print(f"EQ apply error: {e}"); raise gr.Error("Failed to apply EQ! This might be an issue with ffmpeg on the server.")
|
| 79 |
|
| 80 |
def process_and_tune(audio_file, quick_choice, custom_input, lang_choice):
|
| 81 |
+
lang_code = LANG_MAP[lang_choice]; L = LANG[lang_code]
|
|
|
|
| 82 |
if not audio_file: raise gr.Error(L['err_no_file'])
|
|
|
|
| 83 |
if custom_input and custom_input.strip(): final_preference = custom_input
|
| 84 |
elif L['default_choice'] not in quick_choice: final_preference = quick_choice
|
| 85 |
else: final_preference = L['quick_choices'][-2]; gr.Info(L['info_no_pref'])
|
| 86 |
+
slider_updates={s: gr.update(value=0) for s in eq_sliders}
|
| 87 |
+
yield {status_log_md: gr.update(value=L['status_analyzing']), processed_audio_output: gr.update(value=None), eq_accordion: gr.update(visible=True, open=False), **slider_updates}
|
|
|
|
|
|
|
| 88 |
try:
|
| 89 |
+
prompt = "Briefly analyze this audio's genre, mood, and key instruments."; response = gemini_model.generate_content([genai.upload_file(path=audio_file.name), prompt])
|
|
|
|
| 90 |
audio_analysis_text = response.text or "(AI did not provide a detailed analysis)"
|
| 91 |
except Exception as e: audio_analysis_text = L['status_analysis_failed'].format(e=e); gr.Warning(audio_analysis_text)
|
|
|
|
| 92 |
choice_desc = f"“{final_preference}”"
|
| 93 |
+
yield {status_log_md: gr.update(value=L['status_understanding'].format(analysis=audio_analysis_text, choice=choice_desc))}; time.sleep(1)
|
| 94 |
+
eq_settings = get_ai_tuned_eq_settings(audio_analysis_text, final_preference); slider_updates = {s: gr.update(value=v) for s, v in zip(eq_sliders, eq_settings.values())}
|
| 95 |
+
yield {status_log_md: gr.update(value=L['status_tuning']), **slider_updates}; time.sleep(1)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 96 |
eq_audio_path = apply_eq_to_audio(audio_file.name, eq_settings)
|
| 97 |
if not eq_audio_path: raise gr.Error("Audio processing failed!")
|
|
|
|
| 98 |
yield {status_log_md: gr.update(value=L['status_done']), processed_audio_output: gr.update(value=eq_audio_path, label=L['result_label'], autoplay=True), eq_accordion: gr.update(open=False)}
|
| 99 |
|
| 100 |
def update_language(lang_choice):
|
| 101 |
+
L = LANG[LANG_MAP[lang_choice]]
|
|
|
|
| 102 |
return {
|
| 103 |
+
title_md: gr.update(value=f"# {L['title']}"), subtitle_md: gr.update(value=L['subtitle']), step1_header_md: gr.update(value=f"### **{L['step1_header']}**"),
|
| 104 |
+
audio_input: gr.update(label=L['upload_label']), step2_header_md: gr.update(value=f"### **{L['step2_header']}**"), custom_input: gr.update(label=L['custom_input_label'], placeholder=L['custom_input_placeholder']),
|
| 105 |
+
quick_choice: gr.update(label=L['quick_choice_label'], choices=L['quick_choices'], value=L['default_choice']), step3_header_md: gr.update(value=f"### **{L['step3_header']}**"),
|
| 106 |
+
process_button: gr.update(value=L['process_button']), log_header_md: gr.update(value=f"### **{L['log_header']}**"), status_log_md: gr.update(value=L['log_initial']),
|
| 107 |
+
result_header_md: gr.update(value=f"### **{L['result_header']}**"), processed_audio_output: gr.update(label=L['result_label']), eq_accordion: gr.update(label=L['accordion_header']),
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 108 |
}
|
| 109 |
|
|
|
|
| 110 |
with gr.Blocks(theme=gr.themes.Soft()) as demo:
|
| 111 |
lang_switcher = gr.Radio(choices=["简体中文", "English"], value="简体中文", label="Language / 语言", info="选择界面语言 / Select UI Language")
|
|
|
|
| 112 |
title_md = gr.Markdown("# FeiMatrix 智能动态调音")
|
| 113 |
subtitle_md = gr.Markdown("上传一首歌,告诉我你想要的感觉,剩下的交给我!")
|
|
|
|
| 114 |
with gr.Column():
|
| 115 |
+
step1_header_md = gr.Markdown("### **第一步:上传音频**"); audio_input = gr.File(label="点击或拖拽 MP3 文件到这里", type="filepath", file_types=[".mp3"])
|
| 116 |
+
step2_header_md = gr.Markdown("### **第二步:告诉我你的感觉**"); custom_input = gr.Textbox(label="用你自己的话描述想要的感觉(推荐):", placeholder="例如:我想要吉他声更清脆,鼓声更有力...", lines=2)
|
|
|
|
|
|
|
|
|
|
| 117 |
quick_choice = gr.Radio(label="或者,快速选择一个预设风格:", choices=LANG['zh']['quick_choices'], value=LANG['zh']['default_choice'])
|
| 118 |
+
step3_header_md = gr.Markdown("### **第三步:开始调音**"); process_button = gr.Button("开始智能调音!", variant="primary")
|
| 119 |
+
log_header_md = gr.Markdown("### **AI 调音师工作日志**"); status_log_md = gr.Markdown("`[系统]`:我准备好啦,等你上传文件哦~")
|
| 120 |
+
result_header_md = gr.Markdown("### **第四步:聆听您的定制版**"); processed_audio_output = gr.Audio(label="这里会显示AI调音后的音频", type="filepath", interactive=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 121 |
with gr.Accordion("(高级)查看 AI 定制的 EQ 参数", open=False, visible=False) as eq_accordion:
|
| 122 |
+
eq_sliders = [gr.Slider(minimum=-EQ_RANGE_DB, maximum=EQ_RANGE_DB, value=0, step=0.5, label=f"{f} Hz", interactive=False) for f in EQ_BANDS_HZ]
|
|
|
|
|
|
|
|
|
|
| 123 |
all_ui_outputs = [title_md, subtitle_md, step1_header_md, audio_input, step2_header_md, custom_input, quick_choice, step3_header_md, process_button, log_header_md, status_log_md, result_header_md, processed_audio_output, eq_accordion]
|
| 124 |
lang_switcher.change(fn=update_language, inputs=lang_switcher, outputs=all_ui_outputs, queue=False)
|
| 125 |
process_button.click(fn=process_and_tune, inputs=[audio_input, quick_choice, custom_input, lang_switcher], outputs=[status_log_md, processed_audio_output, eq_accordion, *eq_sliders])
|
packages.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
ffmpeg
|