feat(viral_video): voice_source=my_voice/clone 克隆音色TTS支持 (#2188) #2191
@@ -1398,15 +1398,79 @@ def _step_review(job: ViralVideoJob, copy_result: dict) -> dict:
|
||||
return {"passed": True, "score": 75, "details": {}, "issues": []}
|
||||
|
||||
|
||||
def _resolve_tts_voice_id(job: ViralVideoJob) -> str:
|
||||
"""#2188: 根据 voice_source + voice_id 解析真正传给 CosyVoice 的 voice_id。
|
||||
|
||||
- voice_source 在 ("my_voice", "clone"):voice_id 是 VoiceCloneProfile.id,
|
||||
需要从 DB 查 profile.voice_id(CosyVoice 返回的音色 ID)。
|
||||
- 其他/空:voice_id 直接视为 CosyVoice preset 音色名(longxiaochun_v3 等)。
|
||||
- 任何解析失败都回退默认 longxiaochun_v3,保证任务不崩。
|
||||
"""
|
||||
default_voice = "longxiaochun_v3"
|
||||
raw_voice_id = (getattr(job, "voice_id", "") or "").strip()
|
||||
voice_source = (getattr(job, "voice_source", "") or "").strip().lower()
|
||||
|
||||
if not raw_voice_id:
|
||||
return default_voice
|
||||
|
||||
# 克隆音色:前端传 profile.id,需查 DB 取 cosyvoice_voice_id
|
||||
if voice_source in ("my_voice", "clone"):
|
||||
session = None
|
||||
try:
|
||||
from packages.adapters.sqlalchemy_impl.voice_clone_profile_repository import (
|
||||
SQLAlchemyVoiceCloneProfileRepository,
|
||||
)
|
||||
|
||||
session = SessionLocal()
|
||||
_repo = SQLAlchemyVoiceCloneProfileRepository(session)
|
||||
_profile = _repo.get(raw_voice_id)
|
||||
if _profile and _profile.status.value == "ready" and (_profile.voice_id or "").strip():
|
||||
logger.info(
|
||||
"[爆款视频] 克隆音色解析: profile_id=%s → cosyvoice_voice_id=%s",
|
||||
raw_voice_id,
|
||||
_profile.voice_id,
|
||||
)
|
||||
return _profile.voice_id.strip()
|
||||
# 找不到/未就绪/无voice_id
|
||||
if _profile is None:
|
||||
logger.warning("[爆款视频] 克隆音色 profile_id=%s 不存在,回退默认音色", raw_voice_id)
|
||||
elif _profile.status.value != "ready":
|
||||
logger.warning(
|
||||
"[爆款视频] 克隆音色 profile_id=%s 状态=%s 未就绪,回退默认音色",
|
||||
raw_voice_id,
|
||||
_profile.status.value,
|
||||
)
|
||||
else:
|
||||
logger.warning("[爆款视频] 克隆音色 profile_id=%s 就绪但 voice_id 为空,回退默认音色", raw_voice_id)
|
||||
return default_voice
|
||||
except Exception as _e:
|
||||
logger.warning("[爆款视频] 克隆音色解析异常 profile_id=%s err=%s,回退默认音色", raw_voice_id, _e)
|
||||
return default_voice
|
||||
finally:
|
||||
if session is not None:
|
||||
try:
|
||||
session.close()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# preset/my-voice 直传/空 source:voice_id 就是 CosyVoice 音色名
|
||||
return raw_voice_id or default_voice
|
||||
|
||||
|
||||
def _step_tts(job: ViralVideoJob, voiceover_script: str):
|
||||
"""步骤 5: CosyVoice 整段配音 → 返回本地 MP3 Path;失败返回 None。"""
|
||||
"""步骤 5: CosyVoice 整段配音 → 返回本地 MP3 Path;失败返回 None。
|
||||
|
||||
#2188: 支持 voice_source='my_voice'/'clone',先把前端传的 profile.id
|
||||
解析成 CosyVoice 真正的克隆音色 voice_id,再走同一条 synthesize 链路。
|
||||
"""
|
||||
try:
|
||||
from pathlib import Path as _Path
|
||||
|
||||
from apps.worker.services.tts_service_factory import get_tts_service
|
||||
|
||||
tts_service = get_tts_service()
|
||||
voice_id = (getattr(job, "voice_id", "") or "").strip()
|
||||
# #2188: 根据 voice_source 解析实际传给 CosyVoice 的 voice_id
|
||||
voice_id = _resolve_tts_voice_id(job)
|
||||
text = (voiceover_script or "").strip()
|
||||
if not text:
|
||||
logger.warning("[爆款视频] voiceover_script 为空,跳过 TTS")
|
||||
@@ -1414,21 +1478,19 @@ def _step_tts(job: ViralVideoJob, voiceover_script: str):
|
||||
try:
|
||||
result = tts_service.synthesize(
|
||||
text=text,
|
||||
voice_id=voice_id or "longxiaochun_v3",
|
||||
voice_id=voice_id,
|
||||
format="mp3",
|
||||
)
|
||||
except TypeError:
|
||||
try:
|
||||
result = tts_service.synthesize(text=text, voice_id=voice_id or "longxiaochun_v3")
|
||||
result = tts_service.synthesize(text=text, voice_id=voice_id)
|
||||
except TypeError:
|
||||
result = tts_service.synthesize(text=text)
|
||||
if result is None:
|
||||
return None
|
||||
p = _Path(result) if not isinstance(result, _Path) else result
|
||||
if p.exists() and p.stat().st_size > 0:
|
||||
logger.info(
|
||||
"[爆款视频] TTS 合成完成: voice=%s path=%s size=%d", voice_id or "longxiaochun_v3", p, p.stat().st_size
|
||||
)
|
||||
logger.info("[爆款视频] TTS 合成完成: voice=%s path=%s size=%d", voice_id, p, p.stat().st_size)
|
||||
return p
|
||||
logger.warning("[爆款视频] TTS 返回路径不存在或空文件: %s", p)
|
||||
return None
|
||||
|
||||
Reference in New Issue
Block a user