Spaces:
Running
Running
feat: 云端处理长度限制
Browse files- docs/流程文档_AI用.md +1 -0
- src/gui_cloud.py +115 -1
docs/流程文档_AI用.md
CHANGED
|
@@ -412,6 +412,7 @@ MFA 环境:
|
|
| 412 |
- **导出音源**: 上传音源包 → 选择导出插件 → 下载导出结果
|
| 413 |
- 使用临时工作空间,处理完成后自动清理
|
| 414 |
- 无需本地持久化存储
|
|
|
|
| 415 |
|
| 416 |
**用户体验优化**:
|
| 417 |
- 音频未上传完成时禁用「开始制作」按钮,防止误操作
|
|
|
|
| 412 |
- **导出音源**: 上传音源包 → 选择导出插件 → 下载导出结果
|
| 413 |
- 使用临时工作空间,处理完成后自动清理
|
| 414 |
- 无需本地持久化存储
|
| 415 |
+
- **音频时长限制**: 单个音频文件最长 10 分钟,超时文件自动过滤
|
| 416 |
|
| 417 |
**用户体验优化**:
|
| 418 |
- 音频未上传完成时禁用「开始制作」按钮,防止误操作
|
src/gui_cloud.py
CHANGED
|
@@ -194,6 +194,46 @@ def check_mfa_available() -> bool:
|
|
| 194 |
|
| 195 |
# ==================== 制作音源功能 ====================
|
| 196 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 197 |
def validate_audio_upload(files) -> Tuple[bool, str, List[str]]:
|
| 198 |
"""
|
| 199 |
验证上传的音频文件
|
|
@@ -219,6 +259,44 @@ def validate_audio_upload(files) -> Tuple[bool, str, List[str]]:
|
|
| 219 |
return True, f"找到 {len(valid_files)} 个音频文件", valid_files
|
| 220 |
|
| 221 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 222 |
@safe_gradio_handler
|
| 223 |
def process_make_voicebank(
|
| 224 |
audio_files,
|
|
@@ -264,6 +342,15 @@ def process_make_voicebank(
|
|
| 264 |
|
| 265 |
log(f"📁 {msg}")
|
| 266 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 267 |
# 创建临时工作空间
|
| 268 |
workspace = create_temp_workspace()
|
| 269 |
log(f"🔧 创建工作空间: {workspace}")
|
|
@@ -853,6 +940,17 @@ def create_cloud_ui():
|
|
| 853 |
with gr.Tabs():
|
| 854 |
# ==================== 制作音源页 ====================
|
| 855 |
with gr.Tab("🎵 制作音源"):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 856 |
gr.Markdown("### 上传音频文件")
|
| 857 |
gr.Markdown("支持格式: WAV, MP3, FLAC, OGG, M4A")
|
| 858 |
gr.Markdown("允许同时拖拽多个文件上传,也可点击上传框的右上角追加文件")
|
|
@@ -923,15 +1021,31 @@ def create_cloud_ui():
|
|
| 923 |
return "⏳ 请上传音频文件", gr.update(interactive=False)
|
| 924 |
|
| 925 |
valid_count = 0
|
|
|
|
| 926 |
for f in files:
|
| 927 |
path = f.name if hasattr(f, 'name') else str(f)
|
| 928 |
if path.lower().endswith(CloudConfig.AUDIO_EXTENSIONS):
|
| 929 |
valid_count += 1
|
|
|
|
|
|
|
|
|
|
|
|
|
| 930 |
|
| 931 |
if valid_count == 0:
|
| 932 |
return f"❌ 未找到有效音频,支持: {', '.join(CloudConfig.AUDIO_EXTENSIONS)}", gr.update(interactive=False)
|
| 933 |
|
| 934 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 935 |
|
| 936 |
audio_upload.change(
|
| 937 |
fn=check_audio_upload,
|
|
|
|
| 194 |
|
| 195 |
# ==================== 制作音源功能 ====================
|
| 196 |
|
| 197 |
+
def get_audio_duration(file_path: str) -> Optional[float]:
|
| 198 |
+
"""
|
| 199 |
+
获取音频文件时长(秒)
|
| 200 |
+
|
| 201 |
+
返回: 时长秒数,失败返回 None
|
| 202 |
+
"""
|
| 203 |
+
try:
|
| 204 |
+
import wave
|
| 205 |
+
import contextlib
|
| 206 |
+
|
| 207 |
+
# 对于 WAV 文件,使用 wave 模块快速获取时长
|
| 208 |
+
if file_path.lower().endswith('.wav'):
|
| 209 |
+
with contextlib.closing(wave.open(file_path, 'r')) as f:
|
| 210 |
+
frames = f.getnframes()
|
| 211 |
+
rate = f.getframerate()
|
| 212 |
+
return frames / float(rate)
|
| 213 |
+
|
| 214 |
+
# 对于其他格式,使用 pydub(如果可用)
|
| 215 |
+
try:
|
| 216 |
+
from pydub import AudioSegment
|
| 217 |
+
audio = AudioSegment.from_file(file_path)
|
| 218 |
+
return len(audio) / 1000.0 # 毫秒转秒
|
| 219 |
+
except ImportError:
|
| 220 |
+
# pydub 不可用,尝试使用 librosa
|
| 221 |
+
try:
|
| 222 |
+
import librosa
|
| 223 |
+
duration = librosa.get_duration(path=file_path)
|
| 224 |
+
return duration
|
| 225 |
+
except ImportError:
|
| 226 |
+
logger.warning(f"无法获取音频时长,缺少 pydub 或 librosa: {file_path}")
|
| 227 |
+
return None
|
| 228 |
+
except Exception as e:
|
| 229 |
+
logger.warning(f"获取音频时长失败 {file_path}: {e}")
|
| 230 |
+
return None
|
| 231 |
+
|
| 232 |
+
|
| 233 |
+
# 云端音频时长限制(秒)
|
| 234 |
+
MAX_AUDIO_DURATION_SECONDS = 600 # 10分钟
|
| 235 |
+
|
| 236 |
+
|
| 237 |
def validate_audio_upload(files) -> Tuple[bool, str, List[str]]:
|
| 238 |
"""
|
| 239 |
验证上传的音频文件
|
|
|
|
| 259 |
return True, f"找到 {len(valid_files)} 个音频文件", valid_files
|
| 260 |
|
| 261 |
|
| 262 |
+
def validate_audio_duration(file_paths: List[str]) -> Tuple[bool, str, List[str]]:
|
| 263 |
+
"""
|
| 264 |
+
验证音频文件时长,过滤超时文件
|
| 265 |
+
|
| 266 |
+
返回: (是否全部通过, 消息, 有效文件列表)
|
| 267 |
+
"""
|
| 268 |
+
valid_files = []
|
| 269 |
+
rejected_files = []
|
| 270 |
+
max_minutes = MAX_AUDIO_DURATION_SECONDS / 60
|
| 271 |
+
|
| 272 |
+
for path in file_paths:
|
| 273 |
+
duration = get_audio_duration(path)
|
| 274 |
+
filename = os.path.basename(path)
|
| 275 |
+
|
| 276 |
+
if duration is None:
|
| 277 |
+
# 无法获取时长,允许通过(后续处理可能会失败)
|
| 278 |
+
valid_files.append(path)
|
| 279 |
+
logger.warning(f"无法检测时长,允许通过: {filename}")
|
| 280 |
+
elif duration > MAX_AUDIO_DURATION_SECONDS:
|
| 281 |
+
duration_min = duration / 60
|
| 282 |
+
rejected_files.append(f"{filename} ({duration_min:.1f}分钟)")
|
| 283 |
+
else:
|
| 284 |
+
valid_files.append(path)
|
| 285 |
+
|
| 286 |
+
if rejected_files:
|
| 287 |
+
if not valid_files:
|
| 288 |
+
# 全部被拒绝
|
| 289 |
+
return False, f"所有音频超过{max_minutes:.0f}分钟限制: {', '.join(rejected_files)}", []
|
| 290 |
+
else:
|
| 291 |
+
# 部分被拒绝
|
| 292 |
+
msg = f"已过滤 {len(rejected_files)} 个超时音频(>{max_minutes:.0f}分钟): {', '.join(rejected_files[:3])}"
|
| 293 |
+
if len(rejected_files) > 3:
|
| 294 |
+
msg += f" 等{len(rejected_files)}个"
|
| 295 |
+
return True, msg, valid_files
|
| 296 |
+
|
| 297 |
+
return True, "", valid_files
|
| 298 |
+
|
| 299 |
+
|
| 300 |
@safe_gradio_handler
|
| 301 |
def process_make_voicebank(
|
| 302 |
audio_files,
|
|
|
|
| 342 |
|
| 343 |
log(f"📁 {msg}")
|
| 344 |
|
| 345 |
+
# 检查音频时长限制
|
| 346 |
+
valid, duration_msg, file_paths = validate_audio_duration(file_paths)
|
| 347 |
+
if not valid:
|
| 348 |
+
decrement_concurrency()
|
| 349 |
+
return f"❌ {duration_msg}", "", None, None
|
| 350 |
+
|
| 351 |
+
if duration_msg:
|
| 352 |
+
log(f"⚠️ {duration_msg}")
|
| 353 |
+
|
| 354 |
# 创建临时工作空间
|
| 355 |
workspace = create_temp_workspace()
|
| 356 |
log(f"🔧 创建工作空间: {workspace}")
|
|
|
|
| 940 |
with gr.Tabs():
|
| 941 |
# ==================== 制作音源页 ====================
|
| 942 |
with gr.Tab("🎵 制作音源"):
|
| 943 |
+
gr.Markdown("""
|
| 944 |
+
<div style="background: linear-gradient(135deg, #fff3cd 0%, #ffeeba 100%); border-left: 4px solid #ffc107; padding: 12px 16px; border-radius: 8px; margin-bottom: 16px;">
|
| 945 |
+
<p style="margin: 0 0 8px 0; font-weight: bold; color: #856404;">⚠️ 温馨提示</p>
|
| 946 |
+
<p style="margin: 0; color: #856404; line-height: 1.6;">
|
| 947 |
+
请控制上传音频的数量!经测试,<strong>8 分钟以内的高质量音频已经非常充足</strong>。<br/>
|
| 948 |
+
上传过多音频可能导致混入低质量样本,同时也会占用服务器并发资源。<br/>
|
| 949 |
+
建议大量音频先人工筛选后再上传,感谢配合!🙏
|
| 950 |
+
</p>
|
| 951 |
+
</div>
|
| 952 |
+
""")
|
| 953 |
+
|
| 954 |
gr.Markdown("### 上传音频文件")
|
| 955 |
gr.Markdown("支持格式: WAV, MP3, FLAC, OGG, M4A")
|
| 956 |
gr.Markdown("允许同时拖拽多个文件上传,也可点击上传框的右上角追加文件")
|
|
|
|
| 1021 |
return "⏳ 请上传音频文件", gr.update(interactive=False)
|
| 1022 |
|
| 1023 |
valid_count = 0
|
| 1024 |
+
total_duration = 0.0
|
| 1025 |
for f in files:
|
| 1026 |
path = f.name if hasattr(f, 'name') else str(f)
|
| 1027 |
if path.lower().endswith(CloudConfig.AUDIO_EXTENSIONS):
|
| 1028 |
valid_count += 1
|
| 1029 |
+
# 计算时长
|
| 1030 |
+
duration = get_audio_duration(path)
|
| 1031 |
+
if duration:
|
| 1032 |
+
total_duration += duration
|
| 1033 |
|
| 1034 |
if valid_count == 0:
|
| 1035 |
return f"❌ 未找到有效音频,支持: {', '.join(CloudConfig.AUDIO_EXTENSIONS)}", gr.update(interactive=False)
|
| 1036 |
|
| 1037 |
+
# 格式化总时长
|
| 1038 |
+
total_minutes = int(total_duration // 60)
|
| 1039 |
+
total_seconds = int(total_duration % 60)
|
| 1040 |
+
duration_str = f"{total_minutes}分{total_seconds}秒" if total_minutes > 0 else f"{total_seconds}秒"
|
| 1041 |
+
|
| 1042 |
+
# 根据时长给出不同提示
|
| 1043 |
+
if total_duration > MAX_AUDIO_DURATION_SECONDS:
|
| 1044 |
+
return f"⚠️ 已上传 {valid_count} 个音频,总时长 {duration_str}(超过10分钟,部分文件将被过滤)", gr.update(interactive=True)
|
| 1045 |
+
elif total_duration > 480: # 8分钟
|
| 1046 |
+
return f"⚠️ 已上传 {valid_count} 个音频,总时长 {duration_str}(建议控制在8分钟内)", gr.update(interactive=True)
|
| 1047 |
+
else:
|
| 1048 |
+
return f"✅ 已上传 {valid_count} 个音频,总时长 {duration_str}", gr.update(interactive=True)
|
| 1049 |
|
| 1050 |
audio_upload.change(
|
| 1051 |
fn=check_audio_upload,
|