feat(#632): 配音素材库音频架构统一,迁移到audio图层混音 #750

Merged
auto-approve-bot merged 1 commits from feat/voice-library-audio-unified-arch into develop 2026-07-23 10:43:48 +08:00
3 changed files with 77 additions and 11 deletions
@@ -483,6 +483,7 @@ class RenderAdapter:
progress_cb: ProgressCallback | None = None,
rendered_clip_ids: list[str] | None = None,
failed_clip_ids: list[str] | None = None,
voiceover_audio_path: str | None = None,
) -> RenderAdapterResult:
"""执行统一渲染核心流程(BGM + ASR + 渲染 + 缩略图 + 上传)。
@@ -491,6 +492,7 @@ class RenderAdapter:
Args:
rendered_clip_ids: 成功下载/准备的 clip id 列表(render_plan 从下载阶段传入)
failed_clip_ids: 失败的 clip id 列表
voiceover_audio_path: 配音素材库音频本地路径(一键生成场景使用)
Returns:
RenderAdapterResult
@@ -525,6 +527,7 @@ class RenderAdapter:
output_height=output_height,
bgm_path=bgm_path,
asr_service=asr_service,
voiceover_audio_path=voiceover_audio_path,
)
result = render_svc.render()
@@ -593,6 +596,7 @@ class RenderAdapter:
job_id: str = "",
work_dir: Path | None = None,
progress_cb: ProgressCallback | None = None,
voiceover_audio_path: str | None = None,
) -> RenderAdapterResult:
"""使用内存中的 plan/clips/asset_path_map 直接渲染。
@@ -606,6 +610,7 @@ class RenderAdapter:
job_id: 关联的 Job ID
work_dir: 工作目录,不传则用临时目录
progress_cb: 进度回调
voiceover_audio_path: 配音素材库音频本地路径
Returns:
RenderAdapterResult
@@ -649,6 +654,7 @@ class RenderAdapter:
plan_id=actual_plan_id,
job_id=job_id,
progress_cb=progress_cb,
voiceover_audio_path=voiceover_audio_path,
)
except subprocess.CalledProcessError as exc:
@@ -175,6 +175,7 @@ class UnifiedRenderService:
transition_duration: float = DEFAULT_TRANSITION_DURATION,
asr_service: Any = None, # ASRService 实例,用于自动生成字幕
bgm_path: str | None = None, # BGM 本地文件路径
voiceover_audio_path: str | None = None, # 配音素材库音频本地路径
):
self.plan = plan
self.clips = clips
@@ -186,6 +187,7 @@ class UnifiedRenderService:
self.transition_duration = transition_duration
self.asr_service = asr_service
self.bgm_path = bgm_path
self.voiceover_audio_path = voiceover_audio_path
self._transition_engine = TransitionEngine(default_duration=transition_duration)
self._speed_engine = SpeedEngine()
self._asr_timeline_cache: Any = None # ASR 字幕结果缓存,避免重复调用
@@ -226,6 +228,9 @@ class UnifiedRenderService:
# 3.5 TTS 配音生成(如果配置了)
self._maybe_add_voiceover_layer(layers, video_duration=video_duration)
# 3.6 配音素材库音频(如果传入了本地路径)
self._maybe_add_voice_library_layer(layers, video_duration=video_duration)
# 4. 生成 ASS 字幕文件(如果有 title/subtitle 配置)
ass_path = self._maybe_generate_ass(video_duration)
@@ -843,6 +848,69 @@ class UnifiedRenderService:
logger.warning("TTS 配音异常,跳过: %s", e)
return False
def _maybe_add_voice_library_layer(
self,
layers: list[RenderLayer],
*,
video_duration: float,
) -> bool:
"""将配音素材库音频作为整段配音加到 audio 图层.
与 TTS 配音共享同一套 audio 图层混音架构,
支持与 BGM、TTS 的音量平衡,不再走独立的后处理 mux 链路。
Returns:
是否成功添加了配音音轨
"""
if not self.voiceover_audio_path:
return False
audio_path = Path(self.voiceover_audio_path)
if not audio_path.exists() or audio_path.stat().st_size == 0:
logger.warning("配音素材库音频文件不存在或为空,跳过: %s", self.voiceover_audio_path)
return False
try:
# 找到或创建 audio 图层
audio_layer = None
for layer in layers:
if layer.role == "audio":
audio_layer = layer
break
if audio_layer is None:
from video_processing.unified_render_service import _LAYER_Z_INDEX # type: ignore
z_index = _LAYER_Z_INDEX.get("audio", 2)
audio_layer = RenderLayer(role="audio", z_index=z_index)
layers.append(audio_layer)
# 配音素材作为整段配音:从 0 开始,覆盖整个视频时长
# 音频不足视频时长时,混音层会按实际长度处理(amix 不自动循环)
vo_clip = ResolvedClip(
clip_id="voice_library_main",
asset_id="voice_library",
local_path=audio_path,
clip_type="audio",
order=len(audio_layer.clips),
start_time=0.0,
duration=video_duration,
config={"volume": 1.0, "voice_library": True},
actual_duration=video_duration,
)
audio_layer.clips.append(vo_clip)
logger.info(
"配音素材库音频已添加到 audio 图层: plan_id=%s duration=%.2fs",
self.plan.id,
video_duration,
)
return True
except Exception as e:
logger.warning("配音素材库音频添加失败,跳过: %s", e)
return False
@staticmethod
def _resolve_watermark_config(plan_config: dict[str, Any] | None) -> WatermarkConfig | None:
"""从 plan config 中解析水印配置,兼容两种存储格式.
+3 -11
View File
@@ -1238,6 +1238,7 @@ def _render_video(
plan_id=f"gen_{task_id}",
job_id=task_id,
work_dir=temp_path,
voiceover_audio_path=voice_path,
)
finally:
db.close()
@@ -1256,17 +1257,8 @@ def _render_video(
render_duration,
)
# 配音混音(素材库音频,后处理混音)
if voice_path:
final_path = temp_path / f"final-{task_id}.mp4"
try:
_mux_audio_track(render_output_path, voice_path, final_path)
output_path = final_path
except Exception as mux_err:
logger.warning("[task_id=%s] [混音] 音频混合失败,使用无音频版本: %s", task_id, mux_err)
output_path = render_output_path
else:
output_path = render_output_path
# 配音素材库音频已在统一渲染引擎内部通过 audio 图层混音处理
output_path = render_output_path
return output_path, render_duration