Install
openclaw skills install mp3-list-to-video将指定文件夹下的所有 MP3 文件拼接成一个 MP4 视频文件,可生成黑色背景视频,也可基于已合并 MP4 生成带歌曲清单、当前歌曲高亮和右侧旋转黑胶唱片动效的菜单背景视频。菜单高亮默认使用智能分段渲染:并行生成关键帧、每首歌开头高帧密度、其余静态区间单帧拉长,避免整段 30fps 慢速重渲染;黑胶唱片作为独立动态层持续旋转,让整体画面不再完全静态。适用于:播放列表合并、批量歌曲拼接、MP3转视频、播放列表转视频、歌曲菜单高亮、黑胶唱片动效、音乐合集视频、分段视频渲染、并行生成菜单视频。当用户提到"拼接歌曲"、"合并MP3"、"播放列表转视频"、"歌曲列表背景"、"当前歌曲高亮"、"黑胶唱片"、"唱片转圈"、"画面太静态"、"生成太慢"、"多线程生成视频"、"batch mp3 to video"、"merge audio files into video"时使用此 skill。
openclaw skills install mp3-list-to-video将文件夹下的所有 MP3 文件按自然顺序拼接成一个 MP4 视频文件。支持两种输出:
运行时会自动做依赖检查、输入顺序确认和输出媒体流校验。
ass、rotate、overlay 滤镜检查依赖:
which ffmpeg
which ffprobe
python3 --version
ffmpeg -hide_banner -filters | rg ' ass '
ffmpeg -hide_banner -filters | rg ' rotate | overlay '
默认从工作区根目录的 playlist/ 目录读取所有 .mp3 文件,按自然文件名排序后拼接。自然排序会让 10.xxx.mp3 排在 9.xxx.mp3 后面,而不是排在 2.xxx.mp3 前面。
playlist/
├── 1.朵.mp3
├── 2.感官先生.mp3
├── 3.I Want It That Way.mp3
└── ...
默认输出到工作区根目录:playlist_output.mp4
生成菜单高亮视频时,默认输出:
playlist_menu_output.mp4output/playlist_menu_background.mp4output/playlist_menu.assoutput/playlist_menu_timeline.jsonoutput/playlist_menu_preview_*.pngoutput/playlist_menu_frames/frame_*.pngoutput/playlist_menu_frame_plan.jsonoutput/playlist_vinyl.pngoutput/playlist_menu_visual.mp4生成黑色背景的合并视频:
脚本已包含在 skill 的 scripts/ 目录下,直接运行:
python3 scripts/merge_playlist.py
在当前工作区直接调用本 skill 中的脚本时:
python3 .opencode/skill/mp3-list-to-video/scripts/merge_playlist.py
基于已生成的 playlist_output.mp4 生成带歌曲清单和当前歌曲高亮的版本:
python3 .opencode/skill/mp3-list-to-video/scripts/add_playlist_menu.py
上面的命令默认使用优化后的 smart 模式。除非用户明确要求逐帧完整渲染,否则不要使用 --render-mode full。
脚本支持命令行参数,优先使用参数而不是手改脚本:
python3 scripts/merge_playlist.py \
--playlist-dir playlist \
--output playlist_output.mp4 \
--temp-dir output
可选参数:
--playlist-dir: MP3 输入目录--output: MP4 输出文件--temp-dir: 临时文件目录,会写入 concat_list.txt--skip-verify: 跳过合成后的 ffprobe 校验,仅在排查问题时使用菜单高亮脚本支持:
python3 scripts/add_playlist_menu.py \
--playlist-dir playlist \
--source-video playlist_output.mp4 \
--output playlist_menu_output.mp4 \
--temp-dir output \
--render-mode smart \
--jobs 4 \
--active-seconds 3 \
--active-frames 60
可选参数:
--playlist-dir: MP3 输入目录,用来计算每首歌时长和歌曲顺序--source-video: 已合并 MP4,用来复用最终音频--output: 带菜单高亮的最终 MP4--temp-dir: 中间文件目录,会写入 ASS 菜单层、背景视频和时间轴 JSON--menu-video: 单独指定菜单背景视频输出路径--render-mode: 菜单背景渲染模式,默认 smart;可选 full--jobs: smart 模式下并行生成菜单帧的任务数--active-seconds: 每首歌开头按高帧密度生成的秒数,默认 3--active-frames: 每首歌开头高帧密度段生成的帧数,默认 60--disable-vinyl: 禁用右侧旋转黑胶唱片动效--vinyl-size: 黑胶唱片尺寸,默认 360--vinyl-x: 黑胶唱片左上角 X 坐标,默认 1480--vinyl-y: 黑胶唱片左上角 Y 坐标,默认 370--vinyl-rotation-seconds: 黑胶唱片转一圈所需秒数,默认 4--preview-times: 逗号分隔的抽帧检查时间,例如 00:00:10,00:05:00,00:34:10--skip-previews: 跳过合成后的预览帧抽取--preview-only: 不重新生成视频,只对已有输出抽取预览帧只复查已有菜单视频的画面时:
python3 scripts/add_playlist_menu.py \
--output playlist_menu_output.mp4 \
--preview-only \
--preview-times 00:00:10,00:05:00,00:34:10
运行脚本时会自动执行以下检查:
ffmpeg、ffprobe 和 Python 可用,并打印实际路径/版本.mp3 文件1, 2, ..., 10 是否正确ffprobe 确认输出包含 H.264 视频流和 AAC 音频流1920x1080,音频为 44100Hz 双声道菜单高亮脚本还会额外检查:
source-video 是否存在ass、rotate、overlay 滤镜source-video 的总时长,避免封装误差导致结尾空白smart 模式会写出帧计划 JSON,记录 active/static 帧数量和每帧持续时间playlist_vinyl.png 并叠加旋转动效;最终视频即使当前歌曲不切换,右侧黑胶也会持续运动合成完成后应看到类似输出:
输出校验通过:
- 文件大小: 39.2MB
- 时长: 37:35 (2254.633s)
- 视频流: h264 1920x1080
- 音频流: aac 44100Hz 2ch
pathlib 扫描 playlist/ 目录,按自然文件名排序ffprobe 校验文件大小、时长、视频流和音频流核心 FFmpeg 命令:
ffmpeg -y \
-f concat -safe 0 -i concat_list.txt \
-f lavfi -i color=black:size=1920x1080:rate=30 \
-c:v libx264 -preset ultrafast -pix_fmt yuv420p \
-c:a aac -shortest \
output.mp4
ffprobe 获取每个 MP3 的真实时长00:00 - 04:45smart 模式:只渲染必要 PNG 帧。每首歌开头 active-seconds 内生成 active-frames 帧,其余静态段只生成 1 帧并用 concat duration 拉长;帧生成会按 --jobs 并行执行full 模式:回退到整段 30fps 渲染,速度较慢但实现直接rotate + overlay 将黑胶放到菜单右侧,并按 --vinyl-rotation-seconds 持续转动playlist_output.mp4 的音频流合并full 模式核心 FFmpeg 命令:
ffmpeg -y \
-f lavfi -i color=c=0x111214:size=1920x1080:rate=30:duration=2255.2 \
-vf "ass=filename='output/playlist_menu.ass'" \
-an -c:v libx264 -preset ultrafast -pix_fmt yuv420p \
output/playlist_menu_background.mp4
ffmpeg -y \
-i output/playlist_menu_background.mp4 \
-i playlist_output.mp4 \
-map 0:v:0 -map 1:a:0 \
-c:v copy -c:a copy -shortest \
playlist_menu_output.mp4
smart 模式的核心思路:
# 对每个需要的时间点生成一张菜单帧;通过 setpts 让 ASS 按全局时间渲染对应高亮
ffmpeg -y -loglevel error \
-f lavfi -i color=c=0x111214:size=1920x1080:rate=1:duration=1 \
-vf "setpts=PTS+300/TB,ass=filename='output/playlist_menu.ass',setpts=PTS-STARTPTS" \
-frames:v 1 -update 1 output/playlist_menu_frames/frame_00100.png
# 使用 concat duration 拉长静态帧,再统一输出 30fps 标准 MP4
ffmpeg -y \
-f concat -safe 0 -i output/playlist_menu_frames.txt \
-vf "fps=30,format=yuv420p" \
-an -c:v libx264 -preset ultrafast -pix_fmt yuv420p \
output/playlist_menu_background.mp4
# 黑胶动效层叠加
ffmpeg -y \
-i output/playlist_menu_background.mp4 \
-loop 1 -framerate 30 -i output/playlist_vinyl.png \
-filter_complex "[1:v]format=rgba,rotate='2*PI*t/4':ow=iw:oh=ih:c=none[vinyl];[0:v][vinyl]overlay=1480:370,fps=30,format=yuv420p[v]" \
-map "[v]" -an -c:v libx264 -preset ultrafast \
output/playlist_menu_visual.mp4
抽帧复查命令:
ffmpeg -y -loglevel error \
-ss 00:05:00 \
-i playlist_menu_output.mp4 \
-frames:v 1 -update 1 \
output/playlist_menu_preview_0500.png
默认按自然排序(如 1.mp3, 2.mp3, 10.mp3),适合带数字前缀的播放列表。脚本中的排序逻辑:
import re
files.sort(key=natural_key)
使用 -f concat -safe 0 格式拼接音频文件,要求:
-preset ultrafast 加速视频编码(牺牲少量压缩率)smart 模式,避免整段 30fps 重渲染smart 模式下,长静态区间只生成 1 帧,通过 concat duration 保持时长--jobs 控制并行生成帧的 ffmpeg 进程数;机器负载过高时调低,CPU 空闲时可调高--render-mode full推荐默认策略:
--active-seconds 3 --active-frames 60--active-seconds 0 --active-frames 1--active-frames--jobs如果需要在脚本外手动复查黑色背景合并输出,使用:
ls -lh playlist_output.mp4 output/concat_list.txt
ffprobe -v error \
-show_entries format=duration,size \
-show_entries stream=index,codec_type,codec_name,width,height,sample_rate,channels \
-of default=noprint_wrappers=1 \
playlist_output.mp4
sed -n '1,20p' output/concat_list.txt
菜单高亮视频复查重点:
ls -lh playlist_menu_output.mp4 output/playlist_menu_background.mp4 output/playlist_menu.ass output/playlist_menu_timeline.json
ls -lh output/playlist_menu_frame_plan.json output/playlist_menu_frames.txt output/playlist_vinyl.png output/playlist_menu_visual.mp4
ffprobe -v error \
-show_entries format=duration,size \
-show_entries stream=index,codec_type,codec_name,width,height,sample_rate,channels \
-of default=noprint_wrappers=1 \
playlist_menu_output.mp4
python3 scripts/add_playlist_menu.py --preview-only --preview-times 00:00:10,00:05:00,00:34:10
验收标准:
playlist_menu_output.mp4 同时包含视频流和音频流1920x1080start_text 和 end_textsmart 模式下,帧计划 JSON 中应包含 active/static 帧数量duration_sum 应接近 total_duration此 skill 为第一阶段实现(仅拼接音频)。后续可扩展:
菜单高亮视频当前使用静态列表和当前歌曲高亮。可继续扩展:
安装 ffmpeg:
# macOS
brew install ffmpeg
# Ubuntu/Debian
sudo apt install ffmpeg
脚本会自动用 ffprobe 检查时长。如果失败:
output/concat_list.txt 是否包含所有 MP3-shortest 参数ffprobe playlist_output.mp4 查看输出媒体信息检查脚本打印的“合并顺序”。脚本使用自然排序,推荐输入文件使用数字前缀,例如 1.xxx.mp3、2.xxx.mp3、10.xxx.mp3。
脚本会直接列出失败项,例如缺少视频流、缺少音频流、时长为 0、分辨率不符或采样率不符。先查看脚本打印的错误,再用“手动复查命令”确认具体媒体流信息。
菜单脚本默认使用 macOS 上常见的 Hiragino Sans GB。如果目标机器没有该字体,修改 scripts/add_playlist_menu.py 中的 FONT_NAME,或安装支持中日文的字体。修改字体后运行 --preview-only 抽帧确认中文、日文和英文都正常显示。
菜单脚本中的布局常量控制文字位置:
NUMBER_X: 序号列TIME_X: 时间列TITLE_X: 歌名列ROW_START_Y: 第一行纵向位置ROW_HEIGHT: 行高如果时间和歌名贴得太近,优先调大 TITLE_X。调整后先运行:
python3 scripts/add_playlist_menu.py --preview-only --preview-times 00:05:00
如果已改动 ASS 生成逻辑且需要完整重渲染,再运行完整菜单生成命令。
默认参数针对“每首歌开头 3 秒生成 60 帧,其余静态段 1 帧”的场景:
python3 scripts/add_playlist_menu.py \
--render-mode smart \
--jobs 4 \
--active-seconds 3 \
--active-frames 60
调整建议:
--active-frames 或 --jobs--active-frames--jobs--render-mode full 回退整段渲染默认黑胶参数:
python3 scripts/add_playlist_menu.py \
--vinyl-size 360 \
--vinyl-x 1480 \
--vinyl-y 370 \
--vinyl-rotation-seconds 4
调整建议:
--vinyl-x 或调小 --vinyl-size--vinyl-x--vinyl-rotation-seconds--vinyl-rotation-seconds--disable-vinyl菜单高亮视频依赖 ass 滤镜。检查:
ffmpeg -hide_banner -filters | rg ' ass '
如果没有输出,需要安装带 libass 的 FFmpeg。