feat: 视频生成页面改造、字幕冻结修复及多项前端优化

- 修复字幕切换模板后冻结的 bug:ASS.js 新实例在视频播放中创建时收不到
  play/playing 事件,RAF 循环不会启动。创建实例后手动触发 play 事件。
- VideoGeneration 页面 overhaul:卡片点击预览、左右箭头导航、换一个素材、
  动态按钮文案和占位提示。
- 修复私有音色素材预览播放 trialUrl 的问题,改为播放 sourceUrl。
- 放宽空镜素材匹配逻辑:优先满足时长,fallback 到最近时长并随机选择。
- 隐藏脚本生成页面的时长滑块。
- 修复登录页和侧边栏标题渐变 WebKit 兼容问题。
- 清理旧计划文档、测试文件和临时脚本。
- 更新 Makefile、prompts、materials.json 等配置。
This commit is contained in:
小鱼开发
2026-04-23 23:17:10 +08:00
parent 26db375a84
commit 285257905e
56 changed files with 1554 additions and 6316 deletions
@@ -1,83 +0,0 @@
#!/usr/bin/env python3
"""
生成预设音色试听音频并上传到七牛云
================================================
这个脚本会为所有预设音色生成试听音频(文案:"您好,我是您的家装顾问。需要我为您生成几版效果图看看吗?"),
然后自动上传到七牛云存储。
运行方式:
cd python-api
python scripts/generate_preset_voice_previews.py
"""
import asyncio
import tempfile
from pathlib import Path
from app.config import get_settings
from app.services.tts_service import TTSService
from app.services.qiniu_service import get_qiniu_service
# 试听文案
PREVIEW_TEXT = "您好,我是您的家装顾问。需要我为您生成几版效果图看看吗?"
async def generate_all_previews():
"""为所有预设音色生成试听音频并上传"""
settings = get_settings()
tts_service = TTSService()
qiniu = get_qiniu_service()
# 获取所有预设音色
preset_voices = TTSService.PRESET_VOICES
print(f"开始为 {len(preset_voices)} 个预设音色生成试听音频...\n")
for idx, voice in enumerate(preset_voices, 1):
voice_id = voice["voice_id"]
name = voice["name"]
preview_key = voice["previewKey"]
print(f"[{idx}/{len(preset_voices)}] 正在生成: {name} ({voice_id})")
# 创建临时文件
with tempfile.NamedTemporaryFile(suffix='.mp3', delete=False) as f:
temp_path = Path(f.name)
try:
# 生成音频
output_path = await tts_service.synthesize_to_file(
text=PREVIEW_TEXT,
output_path=temp_path,
voice_id=voice_id,
speed=1.0,
voice_language=voice.get("language", "zh"),
)
print(f" ✓ 生成完成,正在上传到七牛云...")
# 上传到七牛云
result = qiniu.upload_file(
local_path=str(output_path),
key=preview_key,
file_type="audio",
check_duplicate=False, # 强制覆盖
)
url = result["url"]
print(f" ✓ 上传成功: {url}")
except Exception as e:
print(f" ✗ 失败: {str(e)}")
finally:
# 清理临时文件
if temp_path.exists():
temp_path.unlink()
print()
print("\n全部完成!")
if __name__ == "__main__":
asyncio.run(generate_all_previews())
@@ -1,128 +0,0 @@
#!/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())