From 5af16f1aea29f92127602000b4c64e8f10662222 Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Mon, 5 Oct 2026 12:21:53 +0800 Subject: [PATCH 1/2] =?UTF-8?q?feat(viral=5Fvideo):=20#2188=20voice=5Fsour?= =?UTF-8?q?ce=3D'my=5Fvoice'/'clone'=20=E5=85=8B=E9=9A=86=E9=9F=B3?= =?UTF-8?q?=E8=89=B2=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 _resolve_tts_voice_id(job):voice_source 为 my_voice/clone 时, 前端传的 voice_id 是 VoiceCloneProfile.id,查 DB 取 profile.voice_id(CosyVoice 真正的克隆音色ID) - profile 不存在/未就绪/voice_id为空时回退默认 longxiaochun_v3,任务不崩 - preset 或 voice_source 为空时,voice_id 直接当 CosyVoice 音色名透传(与原有行为一致) - _step_tts 改用 _resolve_tts_voice_id 解析后的 voice_id 调 synthesize - 不改 DB schema,不加字段;voice_source/voice_id schema 已在之前合入 --- apps/worker/worker_app/tasks/viral_video.py | 74 +++++++++++++++++++-- 1 file changed, 69 insertions(+), 5 deletions(-) diff --git a/apps/worker/worker_app/tasks/viral_video.py b/apps/worker/worker_app/tasks/viral_video.py index 156a0554e..ba9b533c1 100644 --- a/apps/worker/worker_app/tasks/viral_video.py +++ b/apps/worker/worker_app/tasks/viral_video.py @@ -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,12 +1478,12 @@ 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: @@ -1427,7 +1491,7 @@ def _step_tts(job: ViralVideoJob, voiceover_script: str): 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 + "[爆款视频] TTS 合成完成: voice=%s path=%s size=%d", voice_id, p, p.stat().st_size ) return p logger.warning("[爆款视频] TTS 返回路径不存在或空文件: %s", p) -- 2.54.0 From 35207ae040872381d70b7080f9ee53582212df97 Mon Sep 17 00:00:00 2001 From: CI Bot Date: Mon, 5 Oct 2026 04:26:27 +0000 Subject: [PATCH 2/2] style: auto-format with black + isort + ruff + prettier [skip ci-format-check] --- apps/worker/worker_app/tasks/viral_video.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/apps/worker/worker_app/tasks/viral_video.py b/apps/worker/worker_app/tasks/viral_video.py index ba9b533c1..5f02a5150 100644 --- a/apps/worker/worker_app/tasks/viral_video.py +++ b/apps/worker/worker_app/tasks/viral_video.py @@ -1490,9 +1490,7 @@ def _step_tts(job: ViralVideoJob, voiceover_script: str): 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, 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 -- 2.54.0