$npx -y skills add MatrixReligio/ProductVideoCreator --skill bgm为视频添加背景音乐。支持免版权音乐来源、音量混合、淡入淡出效果。当需要为视频添加背景音乐、调整音乐与配音音量平衡时使用。
| 1 | # 背景音乐技能 |
| 2 | |
| 3 | ## 免版权音乐来源 |
| 4 | |
| 5 | | 来源 | 网址 | 特点 | |
| 6 | |------|------|------| |
| 7 | | Pixabay Music | https://pixabay.com/music/ | 完全免费,无需署名 | |
| 8 | | Free Music Archive | https://freemusicarchive.org/ | CC 授权,部分需署名 | |
| 9 | | Uppbeat | https://uppbeat.io/ | 免费版每月限量 | |
| 10 | | Mixkit | https://mixkit.co/free-stock-music/ | 完全免费 | |
| 11 | |
| 12 | **推荐**: Pixabay Music (无需署名,商用免费) |
| 13 | |
| 14 | ## 音乐风格选择 |
| 15 | |
| 16 | | 视频类型 | 推荐风格 | 关键词搜索 | |
| 17 | |----------|----------|------------| |
| 18 | | 科技历程 | Epic, Cinematic | "epic cinematic", "technology" | |
| 19 | | 产品介绍 | Corporate, Upbeat | "corporate", "business" | |
| 20 | | 教程视频 | Ambient, Soft | "ambient", "background" | |
| 21 | | 发布会 | Inspiring, Motivational | "inspiring", "motivational" | |
| 22 | | 创意内容 | Modern, Electronic | "modern", "electronic" | |
| 23 | |
| 24 | ## FFmpeg 音频混合 |
| 25 | |
| 26 | ### 基础混合(配音优先) |
| 27 | |
| 28 | ```bash |
| 29 | # 将 BGM 与配音混合,BGM 音量降低 |
| 30 | # 配音密集时推荐 volume=0.05,配音稀疏时可用 0.08-0.10 |
| 31 | ffmpeg -i voiceover.mp3 -i bgm.mp3 \ |
| 32 | -filter_complex "[1:a]volume=0.05[bgm];[0:a][bgm]amix=inputs=2:duration=first[out]" \ |
| 33 | -map "[out]" mixed_audio.mp3 |
| 34 | ``` |
| 35 | |
| 36 | ### 带淡入淡出效果 |
| 37 | |
| 38 | ```bash |
| 39 | # BGM 淡入3秒,淡出3秒 |
| 40 | ffmpeg -i voiceover.mp3 -i bgm.mp3 \ |
| 41 | -filter_complex " |
| 42 | [1:a]volume=0.05,afade=t=in:st=0:d=3,afade=t=out:st=82:d=3[bgm]; |
| 43 | [0:a][bgm]amix=inputs=2:duration=first[out] |
| 44 | " \ |
| 45 | -map "[out]" mixed_audio.mp3 |
| 46 | ``` |
| 47 | |
| 48 | ### 完整音频处理流程 |
| 49 | |
| 50 | ```bash |
| 51 | # 1. 处理 BGM(循环、淡入淡出、降低音量) |
| 52 | ffmpeg -stream_loop -1 -i bgm_original.mp3 \ |
| 53 | -t 85 \ |
| 54 | -af "volume=0.05,afade=t=in:st=0:d=3,afade=t=out:st=82:d=3" \ |
| 55 | bgm_processed.mp3 |
| 56 | |
| 57 | # 2. 混合配音和 BGM |
| 58 | ffmpeg -i voiceover.mp3 -i bgm_processed.mp3 \ |
| 59 | -filter_complex "[0:a][1:a]amix=inputs=2:duration=first[out]" \ |
| 60 | -map "[out]" final_audio.mp3 |
| 61 | |
| 62 | # 3. 音量标准化 |
| 63 | ffmpeg -i final_audio.mp3 \ |
| 64 | -af "loudnorm=I=-16:TP=-1.5:LRA=11" \ |
| 65 | final_audio_normalized.mp3 |
| 66 | ``` |
| 67 | |
| 68 | ## 音量平衡建议 |
| 69 | |
| 70 | ### 核心原则 |
| 71 | |
| 72 | **配音清晰度优先**:BGM 必须作为"背景"存在,绝不能盖过配音。人声频率(85-255Hz基频 + 泛音)与 BGM 重叠时尤其需要注意。 |
| 73 | |
| 74 | | 音频类型 | 相对音量 | 说明 | |
| 75 | |----------|----------|------| |
| 76 | | 配音 | 1.0 (100%) | 主音频,保持原音量 | |
| 77 | | BGM(有密集配音) | **0.05-0.08** | 确保配音清晰可辨 | |
| 78 | | BGM(有稀疏配音) | 0.08-0.12 | 配音间隙较多时 | |
| 79 | | BGM(无配音段落) | 0.15-0.25 | 片头/片尾/转场 | |
| 80 | | BGM(纯音乐段落) | 0.30-0.50 | 无配音时可提高 | |
| 81 | |
| 82 | ### 音量选择决策树 |
| 83 | |
| 84 | ``` |
| 85 | 视频有配音吗? |
| 86 | ├── 是 → 配音密度高吗(>70%时间有语音)? |
| 87 | │ ├── 是 → BGM volume = 0.05-0.06 (推荐 0.05) |
| 88 | │ └── 否 → BGM volume = 0.08-0.10 |
| 89 | └── 否 → BGM volume = 0.25-0.40 |
| 90 | ``` |
| 91 | |
| 92 | ### 测试方法 |
| 93 | |
| 94 | 在正式渲染前,截取30秒包含配音的片段测试: |
| 95 | ```bash |
| 96 | # 快速测试不同音量 |
| 97 | for vol in 0.04 0.05 0.06 0.08; do |
| 98 | ffmpeg -y -i bgm.mp3 -i voiceover.mp3 \ |
| 99 | -filter_complex "[0:a]volume=$vol[bgm];[1:a][bgm]amix=inputs=2[out]" \ |
| 100 | -map "[out]" -t 30 test_vol_$vol.mp3 |
| 101 | done |
| 102 | ``` |
| 103 | |
| 104 | ### 动态音量控制(高级) |
| 105 | |
| 106 | ```bash |
| 107 | # 配音出现时自动降低 BGM(侧链压缩效果) |
| 108 | ffmpeg -i voiceover.mp3 -i bgm.mp3 \ |
| 109 | -filter_complex " |
| 110 | [0:a]asplit=2[vo][vo_sidechain]; |
| 111 | [1:a]volume=0.3[bgm]; |
| 112 | [bgm][vo_sidechain]sidechaincompress=threshold=0.02:ratio=8:attack=50:release=500[bgm_ducked]; |
| 113 | [vo][bgm_ducked]amix=inputs=2:duration=first[out] |
| 114 | " \ |
| 115 | -map "[out]" ducked_audio.mp3 |
| 116 | ``` |
| 117 | |
| 118 | ## 智能 BGM 长度计算 |
| 119 | |
| 120 | ### 问题:无脑循环的弊端 |
| 121 | |
| 122 | 简单的 `-stream_loop -1` 会导致: |
| 123 | - BGM 在非自然位置截断 |
| 124 | - 音乐节奏与视频内容不匹配 |
| 125 | - 循环接缝处听感不自然 |
| 126 | |
| 127 | ### 解决方案:场景感知的 BGM 选择 |
| 128 | |
| 129 | #### 策略 1: 选择合适长度的音乐 |
| 130 | |
| 131 | **优先选择**时长接近或略长于视频的 BGM: |
| 132 | |
| 133 | | 视频时长 | 推荐 BGM 时长 | 策略 | |
| 134 | |----------|---------------|------| |
| 135 | | < 60s | 60-90s | 单曲裁剪 | |
| 136 | | 60-120s | 90-180s | 单曲裁剪 + 淡出 | |
| 137 | | 120-300s | 180-360s | 单曲或精心设计的接缝 | |
| 138 | | > 300s | 多首拼接 | 场景转换处切换 | |
| 139 | |
| 140 | #### 策略 2: 基于场景结构计算 |
| 141 | |
| 142 | ```python |
| 143 | # 场景时间配置 |
| 144 | SCENES = { |
| 145 | "opening": {"start": 0, "duration": 8}, # 片头 |
| 146 | "scene1": {"start": 8, "duration": 14}, # 场景1 |
| 147 | "scene2": {"start": 22, "duration": 16}, # 场景2 |
| 148 | "scene3": {"start": 38, "duration": 14}, # 场景3 |
| 149 | "scene4": {"start": 52, "duration": 20}, # 场景4 |
| 150 | "closing": {"start": 72, "duration": 13}, # 片尾 |
| 151 | } |
| 152 | |
| 153 | def calculate_bgm_strategy(scenes: dict, bgm_duration: float, video_duration: float): |
| 154 | """ |
| 155 | 计算 BGM 使用策略 |
| 156 | |
| 157 | Returns: |
| 158 | dict: 包含 trim_end, need_loop, loop_point 等信息 |
| 159 | """ |
| 160 | if bgm_duration >= video_duration: |
| 161 | # BGM 够长,直接裁剪 |
| 162 | return { |
| 163 | "strategy": "single_trim", |
| 164 | "trim_end": video_duration, |
| 165 | "need_loop": False |
| 166 | } |
| 167 | |
| 168 | # BGM 不够长,找最佳循环点(场景边界) |
| 169 | scene_boundaries = sorted([s["start"] for s in scenes.values()]) |
| 170 | |
| 171 | # 找到最接近 BGM 时长的场景边界 |
| 172 | best_loop_point = min( |
| 173 | scene_boundaries, |
| 174 | key=lambda x: abs(x - bgm_duration) if x <= bgm_duration else float('inf') |
| 175 | ) |
| 176 | |
| 177 | return { |
| 178 | "strategy": "scene_aware_loop", |
| 179 | "loop_point": best_loop_point, |
| 180 | "bgm_duration": bgm_duration, |
| 181 | "loops_needed": math.ceil(video_duration / best_loop_point) |
| 182 | } |
| 183 | ``` |
| 184 | |
| 185 | #### 策略 3: 动态音量适配场景 |
| 186 | |
| 187 | 不同场景使用不同 BGM 音量: |
| 188 | |
| 189 | ```python |
| 190 | # 场景音量配置 |
| 191 | SCENE_VOLUMES = { |
| 192 | "opening": 0.15, # 片头无配音,音量较高 |
| 193 | "scene1": 0.05, # 有配音场景 |
| 194 | "scene2": 0.05, |
| 195 | "scene3": 0.05, |
| 196 | "scene4": 0.05, |
| 197 | "closing": 0.12, # 片尾配音较少 |
| 198 | } |
| 199 | |
| 200 | def generate_volume_filter(scenes: dict, volumes: dict, fps: int = 30): |
| 201 | """生成 FFmpeg 音量自动化滤镜""" |
| 202 | points = [] |
| 203 | for name, scene in scenes.items(): |
| 204 | start_frame = scene["start"] * fps |
| 205 | vol = volumes.get(name, 0.05) |