Files
Cyrene/scripts/voice/convert_wem.py
T
AskaEth 6ef9e082a6 feat: 语音流式输入管线 + VAD前端集成 + 插件-工具合并清理
- 前端: VAD语音检测(@ricky0123/vad-web) + useVoiceInput双模式(流式WS/REST)
- Gateway: VoiceStreamManager代理WS流式STT到voice-service
- Voice-service: DashScope REST → Realtime WS → Whisper三级引擎 + ffmpeg转码
- 共享模块: pkg/audio(音频转换) + pkg/dashscope(ASR REST客户端)
- 清理: 移除旧plugin-manager和pkg/plugins,完成插件→工具合并
- 文档: 完善gateway-api.md和voice-service.md语音API文档
- 工具: scripts/voice/ 语音转换脚本集

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-06 11:50:40 +08:00

126 lines
4.1 KiB
Python

#!/usr/bin/env python3
"""
将 .wem (Wwise Encoded Media) 文件批量转换为 .wav 格式。
使用 ffmpeg 进行转换(需预先安装 ffmpeg)。
.wem 文件本质上是 RIFF/WAVE 容器,内部编码可能是:
- PCM 16-bit (ffmpeg 直接支持)
- Wwise ADPCM (ffmpeg 需要额外解码器)
- Vorbis (部分 ffmpeg 版本支持)
用法:
python convert_wem.py <input_dir> <output_dir>
python convert_wem.py ./wem_output/ ./wav_output/
"""
import argparse
import os
import subprocess
import sys
from pathlib import Path
def convert_wem_to_wav(wem_path: str, wav_path: str) -> bool:
"""使用 ffmpeg 将单个 .wem 文件转为 .wav."""
try:
result = subprocess.run(
['ffmpeg', '-y', '-i', wem_path,
'-ar', '22050', # 22.05kHz (与 persona.yaml 设定的训练格式一致)
'-ac', '1', # mono
'-sample_fmt', 's16', # 16-bit
wav_path],
capture_output=True,
timeout=30,
)
if result.returncode == 0 and os.path.getsize(wav_path) > 100:
return True
else:
# 部分 WEM 是 Vorbis 编码,需要用不同方式
return _convert_wem_vorbis(wem_path, wav_path)
except Exception as e:
print(f" ffmpeg 错误 [{os.path.basename(wem_path)}]: {e}")
return False
def _convert_wem_vorbis(wem_path: str, wav_path: str) -> bool:
"""尝试处理 Vorbis 编码的 WEM 文件 (带 Wwise 头的 Ogg Vorbis)."""
try:
# 方式: 跳过 RIFF 头 + fmt chunk, 直接取 Vorbis 数据
# .wem 的 Vorbis 数据从 "vorb" chunk 开始
with open(wem_path, 'rb') as f:
data = f.read()
# 查找 "vorb" 标识
vorb_pos = data.find(b'vorb')
if vorb_pos == -1:
return False
# 重新封装为标准 Ogg (在 vorb 数据前加 OggS 头)
# 简化方法: 用 ffmpeg 的 libvorbis 解码
# 如果上面失败了,尝试用 -f s16le 强制读取
result = subprocess.run(
['ffmpeg', '-y', '-f', 's16le',
'-ar', '48000', '-ac', '1',
'-i', wem_path,
'-ar', '22050', '-ac', '1',
wav_path],
capture_output=True,
timeout=30,
)
return result.returncode == 0 and os.path.getsize(wav_path) > 100
except Exception:
return False
def convert_directory(input_dir: str, output_dir: str) -> tuple[int, int]:
"""批量转换目录中所有 .wem 文件."""
os.makedirs(output_dir, exist_ok=True)
wem_files = sorted(Path(input_dir).glob('*.wem'))
if not wem_files:
print(f"{input_dir} 中未找到 .wem 文件")
return 0, 0
print(f"找到 {len(wem_files)} 个 .wem 文件,开始转换...")
success = 0
failed = 0
for i, wem_path in enumerate(wem_files):
wav_name = wem_path.stem + '.wav'
wav_path = os.path.join(output_dir, wav_name)
# 跳过已存在的
if os.path.exists(wav_path) and os.path.getsize(wav_path) > 100:
success += 1
continue
if convert_wem_to_wav(str(wem_path), wav_path):
success += 1
else:
failed += 1
if (i + 1) % 50 == 0:
print(f" 进度: {i+1}/{len(wem_files)} (成功: {success}, 失败: {failed})")
print(f"\n转换完成: {success} 成功, {failed} 失败")
return success, failed
def main():
parser = argparse.ArgumentParser(description="批量转换 .wem → .wav (需要 ffmpeg)")
parser.add_argument('input_dir', help='包含 .wem 文件的输入目录')
parser.add_argument('output_dir', help='输出目录')
parser.add_argument('--single', nargs=2, metavar=('WEM', 'WAV'),
help='转换单个文件')
args = parser.parse_args()
if args.single:
ok = convert_wem_to_wav(args.single[0], args.single[1])
print(f"{'OK' if ok else 'FAILED'}: {args.single[0]} -> {args.single[1]}")
else:
convert_directory(args.input_dir, args.output_dir)
if __name__ == '__main__':
main()