Use when you have a written implementation plan to execute in a separate session with review checkpoints
npx skills add MatrixReligio/ProductVideoCreator --skill "bgm"
Install specific skill from multi-skill repository
# Description
为视频添加背景音乐。支持免版权音乐来源、音量混合、淡入淡出效果。当需要为视频添加背景音乐、调整音乐与配音音量平衡时使用。
# SKILL.md
name: bgm
description: 为视频添加背景音乐。支持免版权音乐来源、音量混合、淡入淡出效果。当需要为视频添加背景音乐、调整音乐与配音音量平衡时使用。
argument-hint: [视频类型: epic/corporate/upbeat/ambient]
背景音乐技能
免版权音乐来源
| 来源 | 网址 | 特点 |
|---|---|---|
| Pixabay Music | https://pixabay.com/music/ | 完全免费,无需署名 |
| Free Music Archive | https://freemusicarchive.org/ | CC 授权,部分需署名 |
| Uppbeat | https://uppbeat.io/ | 免费版每月限量 |
| Mixkit | https://mixkit.co/free-stock-music/ | 完全免费 |
推荐: Pixabay Music (无需署名,商用免费)
音乐风格选择
| 视频类型 | 推荐风格 | 关键词搜索 |
|---|---|---|
| 科技历程 | Epic, Cinematic | "epic cinematic", "technology" |
| 产品介绍 | Corporate, Upbeat | "corporate", "business" |
| 教程视频 | Ambient, Soft | "ambient", "background" |
| 发布会 | Inspiring, Motivational | "inspiring", "motivational" |
| 创意内容 | Modern, Electronic | "modern", "electronic" |
FFmpeg 音频混合
基础混合(配音优先)
# 将 BGM 与配音混合,BGM 音量降低
# 配音密集时推荐 volume=0.05,配音稀疏时可用 0.08-0.10
ffmpeg -i voiceover.mp3 -i bgm.mp3 \
-filter_complex "[1:a]volume=0.05[bgm];[0:a][bgm]amix=inputs=2:duration=first[out]" \
-map "[out]" mixed_audio.mp3
带淡入淡出效果
# BGM 淡入3秒,淡出3秒
ffmpeg -i voiceover.mp3 -i bgm.mp3 \
-filter_complex "
[1:a]volume=0.05,afade=t=in:st=0:d=3,afade=t=out:st=82:d=3[bgm];
[0:a][bgm]amix=inputs=2:duration=first[out]
" \
-map "[out]" mixed_audio.mp3
完整音频处理流程
# 1. 处理 BGM(循环、淡入淡出、降低音量)
ffmpeg -stream_loop -1 -i bgm_original.mp3 \
-t 85 \
-af "volume=0.05,afade=t=in:st=0:d=3,afade=t=out:st=82:d=3" \
bgm_processed.mp3
# 2. 混合配音和 BGM
ffmpeg -i voiceover.mp3 -i bgm_processed.mp3 \
-filter_complex "[0:a][1:a]amix=inputs=2:duration=first[out]" \
-map "[out]" final_audio.mp3
# 3. 音量标准化
ffmpeg -i final_audio.mp3 \
-af "loudnorm=I=-16:TP=-1.5:LRA=11" \
final_audio_normalized.mp3
音量平衡建议
核心原则
配音清晰度优先:BGM 必须作为"背景"存在,绝不能盖过配音。人声频率(85-255Hz基频 + 泛音)与 BGM 重叠时尤其需要注意。
| 音频类型 | 相对音量 | 说明 |
|---|---|---|
| 配音 | 1.0 (100%) | 主音频,保持原音量 |
| BGM(有密集配音) | 0.05-0.08 | 确保配音清晰可辨 |
| BGM(有稀疏配音) | 0.08-0.12 | 配音间隙较多时 |
| BGM(无配音段落) | 0.15-0.25 | 片头/片尾/转场 |
| BGM(纯音乐段落) | 0.30-0.50 | 无配音时可提高 |
音量选择决策树
视频有配音吗?
├── 是 → 配音密度高吗(>70%时间有语音)?
│ ├── 是 → BGM volume = 0.05-0.06 (推荐 0.05)
│ └── 否 → BGM volume = 0.08-0.10
└── 否 → BGM volume = 0.25-0.40
测试方法
在正式渲染前,截取30秒包含配音的片段测试:
# 快速测试不同音量
for vol in 0.04 0.05 0.06 0.08; do
ffmpeg -y -i bgm.mp3 -i voiceover.mp3 \
-filter_complex "[0:a]volume=$vol[bgm];[1:a][bgm]amix=inputs=2[out]" \
-map "[out]" -t 30 test_vol_$vol.mp3
done
动态音量控制(高级)
# 配音出现时自动降低 BGM(侧链压缩效果)
ffmpeg -i voiceover.mp3 -i bgm.mp3 \
-filter_complex "
[0:a]asplit=2[vo][vo_sidechain];
[1:a]volume=0.3[bgm];
[bgm][vo_sidechain]sidechaincompress=threshold=0.02:ratio=8:attack=50:release=500[bgm_ducked];
[vo][bgm_ducked]amix=inputs=2:duration=first[out]
" \
-map "[out]" ducked_audio.mp3
智能 BGM 长度计算
问题:无脑循环的弊端
简单的 -stream_loop -1 会导致:
- BGM 在非自然位置截断
- 音乐节奏与视频内容不匹配
- 循环接缝处听感不自然
解决方案:场景感知的 BGM 选择
策略 1: 选择合适长度的音乐
优先选择时长接近或略长于视频的 BGM:
| 视频时长 | 推荐 BGM 时长 | 策略 |
|---|---|---|
| < 60s | 60-90s | 单曲裁剪 |
| 60-120s | 90-180s | 单曲裁剪 + 淡出 |
| 120-300s | 180-360s | 单曲或精心设计的接缝 |
| > 300s | 多首拼接 | 场景转换处切换 |
策略 2: 基于场景结构计算
# 场景时间配置
SCENES = {
"opening": {"start": 0, "duration": 8}, # 片头
"scene1": {"start": 8, "duration": 14}, # 场景1
"scene2": {"start": 22, "duration": 16}, # 场景2
"scene3": {"start": 38, "duration": 14}, # 场景3
"scene4": {"start": 52, "duration": 20}, # 场景4
"closing": {"start": 72, "duration": 13}, # 片尾
}
def calculate_bgm_strategy(scenes: dict, bgm_duration: float, video_duration: float):
"""
计算 BGM 使用策略
Returns:
dict: 包含 trim_end, need_loop, loop_point 等信息
"""
if bgm_duration >= video_duration:
# BGM 够长,直接裁剪
return {
"strategy": "single_trim",
"trim_end": video_duration,
"need_loop": False
}
# BGM 不够长,找最佳循环点(场景边界)
scene_boundaries = sorted([s["start"] for s in scenes.values()])
# 找到最接近 BGM 时长的场景边界
best_loop_point = min(
scene_boundaries,
key=lambda x: abs(x - bgm_duration) if x <= bgm_duration else float('inf')
)
return {
"strategy": "scene_aware_loop",
"loop_point": best_loop_point,
"bgm_duration": bgm_duration,
"loops_needed": math.ceil(video_duration / best_loop_point)
}
策略 3: 动态音量适配场景
不同场景使用不同 BGM 音量:
# 场景音量配置
SCENE_VOLUMES = {
"opening": 0.15, # 片头无配音,音量较高
"scene1": 0.05, # 有配音场景
"scene2": 0.05,
"scene3": 0.05,
"scene4": 0.05,
"closing": 0.12, # 片尾配音较少
}
def generate_volume_filter(scenes: dict, volumes: dict, fps: int = 30):
"""生成 FFmpeg 音量自动化滤镜"""
points = []
for name, scene in scenes.items():
start_frame = scene["start"] * fps
vol = volumes.get(name, 0.05)
points.append(f"volume=enable='between(t,{scene['start']},{scene['start']+scene['duration']})':volume={vol}")
return ",".join(points)
FFmpeg 场景感知混合
# 使用 aeval 实现场景动态音量
# 片头(0-8s): 0.15, 正片(8-72s): 0.05, 片尾(72-85s): 0.12
ffmpeg -i voiceover.mp3 -i bgm.mp3 \
-filter_complex "
[1:a]volume='if(lt(t,8),0.15,if(lt(t,72),0.05,0.12))':eval=frame,
afade=t=in:st=0:d=3,
afade=t=out:st=82:d=3[bgm];
[0:a][bgm]amix=inputs=2:duration=first[out]
" \
-map "[out]" mixed_audio.mp3
最佳实践
- 优先选择时长合适的 BGM:避免循环,选择 >= 视频时长的音乐
- 如果必须循环:在场景转换处设置循环点,而非音乐结尾
- 场景边界淡入淡出:循环接缝处使用交叉淡化
- 动态音量:片头片尾可略高,配音密集段落降低
Remotion 动态音量控制 (推荐)
除了使用 FFmpeg 预处理,还可以在 Remotion 中直接实现动态音量控制。这种方式更灵活,便于调试。
基础用法:场景感知音量
import { Audio, staticFile, useCurrentFrame, useVideoConfig } from "remotion";
import { SCENES } from "./config/scenes";
// 场景音量配置
const SCENE_VOLUMES = {
opening: 0.15, // 片头无配音,音量较高
scene1: 0.05, // 有配音场景
scene2: 0.05,
scene3: 0.05,
scene4: 0.05,
closing: 0.12, // 片尾配音较少
};
// 动态音量函数
const getDynamicVolume = (frame: number, fps: number): number => {
const currentTime = frame / fps;
// 遍历场景找到当前所在场景
for (const [sceneName, scene] of Object.entries(SCENES)) {
const sceneEnd = scene.start + scene.duration;
if (currentTime >= scene.start && currentTime < sceneEnd) {
return SCENE_VOLUMES[sceneName as keyof typeof SCENE_VOLUMES] ?? 0.05;
}
}
return 0.05; // 默认音量
};
// 在组件中使用
export const VideoWithDynamicBGM: React.FC = () => {
const { fps } = useVideoConfig();
return (
<>
{/* 配音 - 固定音量 */}
<Audio src={staticFile("audio/voiceover.mp3")} volume={1} />
{/* BGM - 动态音量 */}
<Audio
src={staticFile("audio/bgm.mp3")}
volume={(frame) => getDynamicVolume(frame, fps)}
/>
</>
);
};
进阶用法:平滑过渡
import { interpolate } from "remotion";
const getSmoothVolume = (frame: number, fps: number): number => {
const currentTime = frame / fps;
// 片头 (0-8s): 0.15 -> 片头结束前0.5s开始降低
if (currentTime < 7.5) return 0.15;
if (currentTime < 8) {
return interpolate(currentTime, [7.5, 8], [0.15, 0.05]);
}
// 正片 (8-71.5s): 0.05
if (currentTime < 71.5) return 0.05;
// 正片结束 -> 片尾 (71.5-72s): 平滑过渡到 0.12
if (currentTime < 72) {
return interpolate(currentTime, [71.5, 72], [0.05, 0.12]);
}
// 片尾 (72-85s): 0.12
return 0.12;
};
组件模板
// components/DynamicBGM.tsx
import React from "react";
import { Audio, staticFile, useVideoConfig } from "remotion";
interface DynamicBGMProps {
/** BGM 文件路径 (相对于 public/) */
src: string;
/** 场景音量配置 */
sceneVolumes: Record<string, number>;
/** 场景时间配置 */
scenes: Record<string, { start: number; duration: number }>;
/** 是否启用平滑过渡 (默认 true) */
smoothTransition?: boolean;
/** 过渡时长 (秒, 默认 0.5) */
transitionDuration?: number;
}
export const DynamicBGM: React.FC<DynamicBGMProps> = ({
src,
sceneVolumes,
scenes,
smoothTransition = true,
transitionDuration = 0.5,
}) => {
const { fps } = useVideoConfig();
const getVolume = (frame: number): number => {
const currentTime = frame / fps;
for (const [sceneName, scene] of Object.entries(scenes)) {
const sceneEnd = scene.start + scene.duration;
if (currentTime >= scene.start && currentTime < sceneEnd) {
return sceneVolumes[sceneName] ?? 0.05;
}
}
return 0.05;
};
return (
<Audio
src={staticFile(src)}
volume={(frame) => getVolume(frame)}
/>
);
};
FFmpeg vs Remotion 对比
| 方面 | FFmpeg 预处理 | Remotion 动态控制 |
|---|---|---|
| 调试便捷性 | ⚠️ 需重新处理音频 | ✅ 实时预览 |
| 灵活性 | ⚠️ 固定一次 | ✅ 随时调整 |
| 渲染性能 | ✅ 无额外开销 | ⚠️ 微小开销 |
| 文件大小 | ⚠️ 预混合体积大 | ✅ 分离存储 |
| 推荐场景 | 最终交付 | 开发调试 |
建议工作流:
1. 开发阶段使用 Remotion 动态控制快速调试
2. 确定最终参数后,使用 FFmpeg 预处理生成交付版本
Python 脚本模板
#!/usr/bin/env python3
"""
背景音乐处理脚本
"""
import subprocess
from pathlib import Path
def process_bgm(
bgm_path: str,
voiceover_path: str,
output_path: str,
video_duration: float,
bgm_volume: float = 0.05, # 默认低音量,确保配音清晰
fade_duration: float = 3.0
):
"""
处理背景音乐并与配音混合
Args:
bgm_path: BGM 文件路径
voiceover_path: 配音文件路径
output_path: 输出文件路径
video_duration: 视频总时长(秒)
bgm_volume: BGM 相对音量 (0.0-1.0)
fade_duration: 淡入淡出时长(秒)
"""
fade_out_start = video_duration - fade_duration
# 构建 filter_complex
filter_complex = f"""
[1:a]aloop=loop=-1:size=2e+09,atrim=0:{video_duration},
volume={bgm_volume},
afade=t=in:st=0:d={fade_duration},
afade=t=out:st={fade_out_start}:d={fade_duration}[bgm];
[0:a][bgm]amix=inputs=2:duration=first[out]
""".replace("\n", "").replace(" ", "")
cmd = [
"ffmpeg", "-y",
"-i", voiceover_path,
"-i", bgm_path,
"-filter_complex", filter_complex,
"-map", "[out]",
output_path
]
subprocess.run(cmd, check=True)
print(f"✓ 混合音频输出: {output_path}")
def normalize_audio(input_path: str, output_path: str):
"""音量标准化到 -16 LUFS"""
cmd = [
"ffmpeg", "-y",
"-i", input_path,
"-af", "loudnorm=I=-16:TP=-1.5:LRA=11",
output_path
]
subprocess.run(cmd, check=True)
print(f"✓ 标准化输出: {output_path}")
# 使用示例
if __name__ == "__main__":
process_bgm(
bgm_path="public/audio/bgm_epic.mp3",
voiceover_path="public/audio/synced_voiceover.mp3",
output_path="public/audio/mixed_audio.mp3",
video_duration=85,
bgm_volume=0.05, # 配音密集时推荐 0.05
fade_duration=3.0
)
normalize_audio(
input_path="public/audio/mixed_audio.mp3",
output_path="public/audio/final_audio.mp3"
)
Remotion 集成
在 Remotion 组件中使用混合后的音频:
import { Audio, staticFile } from "remotion";
export const FinalVideo: React.FC = () => {
return (
<>
{/* 使用混合后的音频(配音 + BGM) */}
<Audio src={staticFile("audio/final_audio.mp3")} volume={1} />
{/* 或者分开控制 */}
<Audio src={staticFile("audio/synced_voiceover.mp3")} volume={1} />
<Audio
src={staticFile("audio/bgm.mp3")}
volume={(f) => {
// 淡入淡出控制
const fps = 30;
if (f < 3 * fps) return (f / (3 * fps)) * 0.12;
if (f > 82 * fps) return ((85 * fps - f) / (3 * fps)) * 0.12;
return 0.12;
}}
/>
</>
);
};
工作流程
1. 确定视频风格 → 选择音乐关键词
2. 从免版权网站下载 BGM
3. 处理 BGM(循环/裁剪到视频时长)
4. 添加淡入淡出效果
5. 与配音混合(配音优先)
6. 音量标准化
7. 集成到视频
常见问题
| 问题 | 解决方案 |
|---|---|
| BGM 太响干扰配音 | 降低 volume 到 0.04-0.06(配音密集时推荐 0.05) |
| BGM 不够长 | 优先选择更长的音乐;必须循环时在场景边界处接缝 |
| 淡入淡出太突然 | 增加 fade_duration 到 4-5 秒 |
| 混合后音量不一致 | 使用 loudnorm 滤镜标准化 |
| 风格不匹配 | 重新选择更合适的 BGM |
| 循环接缝明显 | 使用交叉淡化或在场景转换处设置循环点 |
# Supported AI Coding Agents
This skill is compatible with the SKILL.md standard and works with all major AI coding agents:
Learn more about the SKILL.md standard and how to use these skills with your preferred AI coding agent.