67e73b5a51
- 素材库: VoiceMaterialLibrary 支持音频/视频分类、Modal弹窗、进度弹窗 - 列表布局: 紧凑单行、灰色图标按钮、重命名功能、删除ConfirmModal - 生成配音: toast替换为ProgressModal - 私有音色显示: 描述改为createdAt日期 - 七牛上传: 修复upload_stream参数、修正put_stream参数名 - MiniMax后端: 新增Provider+Service,TTS/克隆/音色列表切到MiniMax - 前端默认音色: tianxin_xiaoling - Rust: 新增voice命令、本地音频存储、配音生成功能 - 新增shot统计组件、脚本编辑器优化
129 lines
4.0 KiB
Python
129 lines
4.0 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
上传官方预置音色试听音频到七牛云
|
|
================================================
|
|
|
|
KlingAI 已经为每个官方预置音色提供了 trial_url,我们直接下载这个音频
|
|
然后上传到七牛云存储,获取永久链接。
|
|
|
|
运行方式:
|
|
cd python-api
|
|
python scripts/upload_preset_voice_previews.py
|
|
"""
|
|
|
|
import asyncio
|
|
import tempfile
|
|
from pathlib import Path
|
|
|
|
import httpx
|
|
|
|
from app.config import get_settings
|
|
from app.services.qiniu_service import get_qiniu_service
|
|
from app.ai.providers.klingai_provider import KlingAIProvider
|
|
|
|
|
|
async def download_file(url: str, temp_path: Path) -> None:
|
|
"""下载文件到本地"""
|
|
async with httpx.AsyncClient(timeout=60.0) as client:
|
|
response = await client.get(url)
|
|
response.raise_for_status()
|
|
temp_path.write_bytes(response.content)
|
|
|
|
|
|
async def upload_all_previews():
|
|
"""下载所有官方预置音色试听并上传到七牛云"""
|
|
settings = get_settings()
|
|
qiniu = get_qiniu_service()
|
|
provider = KlingAIProvider({
|
|
"access_key": settings.KLINGAI_ACCESS_KEY or "",
|
|
"secret_key": settings.KLINGAI_SECRET_KEY or "",
|
|
})
|
|
|
|
# 获取官方预置音色列表
|
|
voices = await provider.list_preset_voices()
|
|
print(f"获取到 {len(voices)} 个官方预置音色\n")
|
|
|
|
description_map = {
|
|
"钓系女友": "甜美撒娇",
|
|
"温柔女声": "温柔细腻",
|
|
"播报男声": "沉稳播报",
|
|
"盐系少年": "清新少年",
|
|
"撒娇女友": "可爱撒娇",
|
|
}
|
|
|
|
results = []
|
|
|
|
for idx, voice in enumerate(voices, 1):
|
|
if voice.get("status") != "succeed":
|
|
print(f"[{idx}] 跳过 - 状态不为 succeed: {voice.get('status')}")
|
|
continue
|
|
|
|
voice_id = voice["voice_id"]
|
|
voice_name = voice["voice_name"]
|
|
trial_url = voice.get("trial_url")
|
|
|
|
if not trial_url:
|
|
print(f"[{idx}] {voice_name} - 没有 trial_url,跳过")
|
|
continue
|
|
|
|
print(f"[{idx}/{len(voices)}] 处理: {voice_name} ({voice_id})")
|
|
print(f" 原地址: {trial_url}")
|
|
|
|
# 下载到临时文件
|
|
ext = ".wav"
|
|
with tempfile.NamedTemporaryFile(suffix=ext, delete=False) as f:
|
|
temp_path = Path(f.name)
|
|
|
|
try:
|
|
await download_file(trial_url, temp_path)
|
|
print(f" ✓ 下载完成 ({temp_path.stat().st_size / 1024:.1f} KB)")
|
|
|
|
# 上传到七牛云
|
|
key = f"meijiaka-zj/audios/{voice_id}{ext}"
|
|
result = qiniu.upload_file(
|
|
local_path=str(temp_path),
|
|
key=key,
|
|
file_type="audio",
|
|
check_duplicate=False,
|
|
)
|
|
final_url = result["url"]
|
|
print(f" ✓ 上传成功: {final_url}")
|
|
|
|
results.append({
|
|
"voice_id": voice_id,
|
|
"name": voice_name,
|
|
"description": description_map.get(voice_name, ""),
|
|
"previewUrl": final_url,
|
|
"language": "zh",
|
|
"recommended": voice_name == "温柔女声",
|
|
})
|
|
|
|
except Exception as e:
|
|
print(f" ✗ 失败: {str(e)}")
|
|
|
|
finally:
|
|
# 清理临时文件
|
|
if temp_path.exists():
|
|
temp_path.unlink()
|
|
|
|
print()
|
|
|
|
print("\n=== 最终结果 ===")
|
|
print("复制以下内容到 TTSService.PRESET_VOICES:")
|
|
print()
|
|
for r in results:
|
|
print(f" {{")
|
|
print(f" \"voice_id\": \"{r['voice_id']}\",")
|
|
print(f" \"name\": \"{r['name']}\",")
|
|
print(f" \"language\": \"{r['language']}\",")
|
|
print(f" \"description\": \"{r['description']}\",")
|
|
print(f" \"previewUrl\": \"{r['previewUrl']}\",")
|
|
print(f" \"recommended\": {str(r['recommended']).lower()},")
|
|
print(f" }},")
|
|
|
|
return results
|
|
|
|
|
|
if __name__ == "__main__":
|
|
asyncio.run(upload_all_previews())
|