TNOT commited on
Commit
b5c0b05
·
1 Parent(s): 75e21d7

feat: 云端处理长度限制

Browse files
Files changed (2) hide show
  1. docs/流程文档_AI用.md +1 -0
  2. 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
- return f"✅ 已上传 {valid_count} 个音频文件,可以开始制作", gr.update(interactive=True)
 
 
 
 
 
 
 
 
 
 
 
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,