fix(worker): render_plan 保留视频原声并尊重每clip音量
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 30s
CI/CD Pipeline / Validate - Type Check (mypy) (pull_request) Successful in 1m34s
CI/CD Pipeline / Validate - Migration (alembic) (pull_request) Successful in 1m39s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 1m18s
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 39s
AI Code Review / AI Code Review (pull_request) Failing after 2m14s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 32s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 2m35s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m17s
CI/CD Pipeline / Validate - Code Quality (pull_request) Has been cancelled
CI/CD Pipeline / Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / Integration Tests (pull_request) Has been cancelled
CI/CD Pipeline / Build Production API Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Production Web Image (pull_request) Has been cancelled
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Deploy Production (pull_request) Has been cancelled
CI/CD Pipeline / Production Browser E2E (pull_request) Has been cancelled
CI/CD Pipeline / Canary Release to Production (pull_request) Has been cancelled
CI/CD Pipeline / CI Gate (pull_request) Has been cancelled
CI/CD Pipeline / ACR Image Cleanup (pull_request) Failing after 567h28m26s
CI/CD Pipeline / PR Build Web Image (pull_request) Failing after 567h28m32s
CI/CD Pipeline / Frontend Unit Tests (pull_request) Failing after 567h29m13s
CI/CD Pipeline / Build Staging Worker Image (pull_request) Failing after 567h30m47s
CI/CD Pipeline / Build Staging Web Image (pull_request) Failing after 567h30m49s
CI/CD Pipeline / Staging API Integration Tests (pull_request) Failing after 567h28m28s
CI/CD Pipeline / Frontend Lint (pull_request) Failing after 567h29m15s
CI/CD Pipeline / Build Staging API Image (pull_request) Failing after 567h30m51s
CI/CD Pipeline / Staging E2E Tests (pull_request) Failing after 568h2m20s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Failing after 568h3m22s

根因:render_audio.mix_audio 中 main_clips = [] 无条件丢弃主图层源视频
原声(PR #1249 "避免录入杂音"),导致走新路径 render_plan 生成的成片
没有原声,与预览不一致(用户反馈"音频没有")。

修复:
- mix_audio 保留 main/broll 原声,按 clip.config.volume 控制音量,
  无音频流/静音(volume=0)的 clip 安全过滤
- concat_main_audio 单clip与多clip路径均应用 per-clip volume 滤镜
- 直通渲染(_render_pass_through)改为先 probe_has_audio 再决定是否编码
  音频,并应用 per-clip volume;不再无条件假设 main 带音频
- stream copy 在音量非默认时回退重编码
- 新增 _clip_volume 辅助函数(render_audio + UnifiedRenderService)
- 更新 test_unified_render_service 旧"丢弃原声"断言为新行为
- 新增 test_original_audio_retained.py 9 个回归测试

验证:161 个相关单测全部通过
This commit is contained in:
saas-backend-agent
2026-08-23 23:54:44 +08:00
parent bbf27ec3f6
commit c2a510331e
4 changed files with 357 additions and 94 deletions
@@ -36,6 +36,7 @@ from video_processing.ffmpeg_utils import (
DEFAULT_TRANSITION_DURATION,
FFMPEG_BIN,
probe_duration,
probe_has_audio,
probe_video_info,
run_ffmpeg,
)
@@ -1000,6 +1001,11 @@ class UnifiedRenderService:
if abs(speed - 1.0) >= 1e-6:
return False, f"有调速: speed={speed:.2f}x"
# 音量非默认(静音/放大)→ 需要音频滤镜重编码 → 不能 copy
vol = UnifiedRenderService._clip_volume(clip)
if abs(vol - 1.0) >= 1e-6:
return False, f"音量非默认: volume={vol:.2f}"
# 有倒放 → 需要重编码 → 不能 copy
reverse_config = ReverseConfig.from_dict(clip.config.get("reverse"))
if reverse_config.enabled and (reverse_config.reverse_video or reverse_config.reverse_audio):
@@ -1270,12 +1276,23 @@ class UnifiedRenderService:
"+faststart",
]
# 音频处理:background 通常是图片无音频,跳过;其他编码为 aac
# background 以外的视频素材,默认带音频
has_audio = role != "background"
# 音频处理:background 通常是图片无音频,跳过;其他角色先探测是否真有音频流。
# volume=0 表示该片段静音,直接丢弃音频(与多片段 mix_audio 行为一致)。
clip_volume = UnifiedRenderService._clip_volume(clip)
if role != "background" and clip_volume > 0:
try:
has_audio = probe_has_audio(clip.local_path)
except Exception:
has_audio = True
else:
has_audio = False
if has_audio:
af_parts: list[str] = []
# 片段音量(原声=1.0,静音已在上层排除)
if abs(clip_volume - 1.0) >= 1e-6:
af_parts.append(f"volume={clip_volume:.4f}")
# 音频降噪
try:
from video_processing.noise_reduction_engine import NoiseReductionConfig, NoiseReductionEngine
@@ -1976,6 +1993,16 @@ class UnifiedRenderService:
"""
return _clip_playback_speed_pure(getattr(clip, "playback_speed", 1.0))
@staticmethod
def _clip_volume(clip: ResolvedClip) -> float:
"""获取 clip 的音量(config.volume)。缺省 1.0 原声,0.0 静音。"""
cfg = getattr(clip, "config", None) or {}
try:
vol = float(cfg.get("volume", 1.0))
except (TypeError, ValueError):
return 1.0
return max(0.0, vol)
@staticmethod
def _clip_adjusted_duration(clip: ResolvedClip) -> float:
"""计算调速后的 clip 实际时长(用于拼接计算)。