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
|