feat(audio): 添加全局音频淡入淡出功能

- 在 Task 类中新增 get_global_audio_fade_in_ms 和 get_global_audio_fade_out_ms 方法
- 修改 prepare_audio.py 中的音频混音逻辑,支持全局淡入淡出参数
- 新增 _build_global_fade_filters 静态方法构建全局淡入淡出滤镜
- 更新音频混音命令构建逻辑,支持在混音后应用全局淡入淡出效果
- 为无BGM和仅BGM的情况添加全局淡入淡出滤镜支持
- 在amix混音后追加全局淡入淡出滤镜,与片段级音频效果独立处理
This commit is contained in:
2026-02-11 11:53:34 +08:00
parent 952b8f5c01
commit c2ece02ecf
2 changed files with 82 additions and 6 deletions

View File

@@ -541,6 +541,14 @@ class Task:
"""获取片段列表"""
return self.payload.get('segments', [])
def get_global_audio_fade_in_ms(self) -> int:
"""获取全局音频淡入时长(毫秒),0 表示不淡入"""
return int(self.payload.get('globalAudioFadeInMs', 0))
def get_global_audio_fade_out_ms(self) -> int:
"""获取全局音频淡出时长(毫秒),0 表示不淡出"""
return int(self.payload.get('globalAudioFadeOutMs', 0))
def get_audio_profile(self) -> AudioProfile:
"""获取音频配置"""
return AudioProfile.from_dict(self.payload.get('audioProfile'))

View File

@@ -110,12 +110,16 @@ class PrepareJobAudioHandler(BaseHandler):
# 2. 构建音频混音命令
output_file = os.path.join(work_dir, 'audio_full.aac')
global_fade_in_ms = task.get_global_audio_fade_in_ms()
global_fade_out_ms = task.get_global_audio_fade_out_ms()
cmd = self._build_audio_command(
bgm_file=bgm_file,
sfx_files=sfx_files,
output_file=output_file,
total_duration_sec=total_duration_sec,
audio_profile=audio_profile
audio_profile=audio_profile,
global_fade_in_ms=global_fade_in_ms,
global_fade_out_ms=global_fade_out_ms
)
# 3. 执行 FFmpeg
@@ -157,7 +161,9 @@ class PrepareJobAudioHandler(BaseHandler):
sfx_files: List[Dict],
output_file: str,
total_duration_sec: float,
audio_profile: AudioProfile
audio_profile: AudioProfile,
global_fade_in_ms: int = 0,
global_fade_out_ms: int = 0
) -> List[str]:
"""
构建音频混音命令
@@ -168,6 +174,8 @@ class PrepareJobAudioHandler(BaseHandler):
output_file: 输出文件路径
total_duration_sec: 总时长(秒)
audio_profile: 音频配置
global_fade_in_ms: 全局音频淡入时长(毫秒),0 不应用
global_fade_out_ms: 全局音频淡出时长(毫秒),0 不应用
Returns:
FFmpeg 命令参数列表
@@ -175,8 +183,23 @@ class PrepareJobAudioHandler(BaseHandler):
sample_rate = audio_profile.sample_rate
channels = audio_profile.channels
# 构建全局 afade 滤镜(作用于最终混合音频,在 amix 之后)
global_fade_filters = self._build_global_fade_filters(
total_duration_sec, global_fade_in_ms, global_fade_out_ms
)
# 情况1:无 BGM 也无叠加音效 -> 生成静音
if not bgm_file and not sfx_files:
if global_fade_filters:
return [
'ffmpeg', '-y', '-hide_banner',
'-f', 'lavfi',
'-i', f'anullsrc=r={sample_rate}:cl=stereo',
'-t', str(total_duration_sec),
'-af', ','.join(global_fade_filters),
'-c:a', 'aac', '-b:a', '128k',
output_file
]
return [
'ffmpeg', '-y', '-hide_banner',
'-f', 'lavfi',
@@ -188,10 +211,14 @@ class PrepareJobAudioHandler(BaseHandler):
# 情况2:仅 BGM,无叠加音效
if not sfx_files:
af_arg = []
if global_fade_filters:
af_arg = ['-af', ','.join(global_fade_filters)]
return [
'ffmpeg', '-y', '-hide_banner',
'-i', bgm_file,
'-t', str(total_duration_sec),
*af_arg,
'-c:a', 'aac', '-b:a', '128k',
'-ar', str(sample_rate), '-ac', str(channels),
output_file
@@ -261,6 +288,17 @@ class PrepareJobAudioHandler(BaseHandler):
# dropout_transition=0 表示输入结束时不做渐变
mix_inputs = "[bgm]" + "".join(sfx_labels)
num_inputs = 1 + len(sfx_files)
# 如果有全局 fade,amix 输出到中间标签后再追加 afade;否则直接输出到 [out]
if global_fade_filters:
filter_parts.append(
f"{mix_inputs}amix=inputs={num_inputs}:duration=first:"
f"dropout_transition=0:normalize=0[mixed]"
)
filter_parts.append(
f"[mixed]{','.join(global_fade_filters)}[out]"
)
else:
filter_parts.append(
f"{mix_inputs}amix=inputs={num_inputs}:duration=first:"
f"dropout_transition=0:normalize=0[out]"
@@ -277,3 +315,33 @@ class PrepareJobAudioHandler(BaseHandler):
]
return cmd
@staticmethod
def _build_global_fade_filters(
total_duration_sec: float,
global_fade_in_ms: int,
global_fade_out_ms: int
) -> List[str]:
"""
构建全局音频淡入/淡出滤镜列表
在 amix 混音输出之后追加,作用于最终混合音频。
与片段级 audioSpecJson 中的 fadeInMs/fadeOutMs(仅作用于单个叠加音效)独立。
Args:
total_duration_sec: 总时长(秒)
global_fade_in_ms: 全局淡入时长(毫秒),0 不应用
global_fade_out_ms: 全局淡出时长(毫秒),0 不应用
Returns:
afade 滤镜字符串列表,可能为空
"""
filters = []
if global_fade_in_ms > 0:
fade_in_sec = global_fade_in_ms / 1000.0
filters.append(f"afade=t=in:st=0:d={fade_in_sec}")
if global_fade_out_ms > 0:
fade_out_sec = global_fade_out_ms / 1000.0
fade_out_start = total_duration_sec - fade_out_sec
filters.append(f"afade=t=out:st={fade_out_start}:d={fade_out_sec}")
return filters