简介面向需要将B站缓存视频中的audio.m4s与video.m4s合并为mp4的用户此压缩包提供了一套完整的小型自动化方案。资源仅5个文件含3个bat批处理脚本和2个Python脚本压缩包体积仅2KB非常轻量。Python脚本承担核心逻辑调用ffmpeg将音视频流重新封装为mp4批处理脚本则用于批量调用、文件移动和命令行交互让使用者无需手动输入复杂命令。这套工具直接解决了B站离线缓存分段文件无法直接播放的问题操作简单运行即可完成合并目前已有8324人学习下载适合需要整理本地B站视频的普通用户也是学习Python自动化处理文件的入门范例。通过阅读源码可以掌握subprocess调用外部程序、路径拼接、批量循环和异常处理等编程技巧还能学会将ffmpeg命令行封装成可用工具的思路对于希望提升脚本能力的开发者具有参考价值。1. 从两个.m4s到一个 MP4哔哩哔哩视频合并到底在解决什么问题说到哔哩哔哩视频合并很多人都是从手机缓存目录里翻出两个不认识的文件开始的。一个叫video.m4s一个叫audio.m4s语音里搜不到播放器打开直接报错。其实这两个文件不是损坏而是视频网站把画面和声音拆成了两条独立的媒体流用 DASH 协议分开发给你方便切换码率和节省流量。想把这些缓存变成能在电脑或电视上正常离线播放的单个 MP4不能直接改后缀也不能把两个文件简单拼一起必须让工具按时间戳把两条流重新封装进同一个 MP4 容器。这套资源解决的就是这个完整过程从定位缓存目录、识别音视频轨到输出一个音画同步、可拖动进度条的画面文件。适合经常用 App 缓存视频的人、需要离线剪辑素材的人以及想把收藏整理成本地资料库的下载党。2. 先搞懂.m4s是什么合并是「重新封装」而不是「格式转换」2.1 为什么 B 站要把一条视频拆成两个文件DASH 流的取舍很多用户第一次在两个并列的文件夹里看到video.m4s和audio.m4s时第一反应是缓存出错了。实际上这是现代流媒体普遍采用的 DASHDynamic Adaptive Streaming over HTTP方案的典型产物。与过去直接给你一个完整 MP4 文件不同DASH 会把整个视频切片成若干以秒为单位的片段每个片段独立编码、独立请求。这样做的好处是播放器可以根据当前的网络带宽动态请求不同码率的片段实现无缝切换清晰度。而更重要的是视频和音频被拆成两个独立的轨道流互不干扰因为网络条件突变时解码器可以只丢弃视频片段而保留音频卡顿感更小。回到 B 站 App 的缓存行为上。用户在客户端点「离线缓存」实际下载到本地的是一个目录结构里面通常有entry.json、danmaku.xml弹幕、video.m4s和audio.m4s。这个video.m4s存着一整段的视频画面流audio.m4s存着对应的声音流。它们之所以被拆开是播放器在线播放时本来就要分别请求两路流。缓存本地后为了让离线播放也能保持同样的架构客户端没有把它们重新拼成一个文件而是原样保存为两个.m4s。.m4s并不是一种独立的视频编码格式而是一种媒体分段封装。它内部通常是 fragmented MP4简称 fMP4结构。fMP4 与普通 MP4 最大的不同在于普通 MP4 的索引信息moov box要么在文件开头要么在文件末尾而 fMP4 会把一些索引信息打散成多个moofmdat小段便于流式读取。视频编码一般是 H.264部分 1080P 高帧率内容是 H.265/HEVC音频编码常见的是 AAC偶尔是 Opus。搞清楚这一点就明白为什么「改名成 .mp4 不能播」播放器需要从文件头寻找标准的 moov 元数据fMP4 的元数据位置不符合普通播放器的预期。这也解释了为什么很多缓存视频在播放器图库里显示为已下载但导出到电脑后就成了两个「神秘文件」。本质上你手里的两个.m4s是两个可以独立解码的流只是缺一个混流步骤。理解了这一点你再看所有合并工具都会直接戳穿它们的本质它们不是在修文件而是在做 mux混流。2.2 合并的本质把两路流按时间线写进同一个 MP4 容器合并不是一个简单的二进制拼接过程也不是把两个文件交替写入。要生成一个正常的 MP4你需要让视频流和音频流中的样本按时间戳交错排列并且把编码信息如分辨率、编码器、码率写进新容器的 moov box。这一步在音视频开发中叫 remux重新封装与转码有本质区别。转码是重新编码会有质量损失和较高的 CPU 耗时remux 只是把编码数据从旧容器搬到新容器几乎不损失画质和音质速度极快。所有稍微正规一点的合并工具底层都会调用 ffmpeg 这类开源工具来实现 remux。ffmpeg 处理这种情况的核心参数是-c copy意思是「直接复制编码数据不重新编码」。它会把video.m4s中每一个 sample也就是一帧的时间戳解析出来再把audio.m4s中每个音频包的时间戳解析出来两者按 PTSPresentation Time Stamp顺序写进一个全新的 MP4 容器。这个操作对 CPU 的消耗非常低一个 40 分钟的高清内容通常只需要几秒到十几秒速度取决于磁盘 IO。这里有一个常见的困惑为什么合并后的文件大小与两个源文件之和不一样很多人以为是「被压缩了」。其实 MP4 容器的封装本身有开销码流在源文件的 fMP4 分段中会有一些重复的 meta 信息合并成标准 MP4 时这些重复信息会合并因此最终体积可能比两个.m4s加起来小一点也可能差不多但视频和音频本身的编码数据没有变码率没有变不能视为有损。判断方式很简单看输出文件的分辨率、帧率和音频声道数是否和缓存内容一致。另外很多 GUI 工具默认会在合并时加上-movflags faststart这个参数的作用是把 moov索引信息移动到文件开头。这样播放器在刚打开文件时就能拿到完整的索引进度条拖动会流畅很多。如果你只用命令行手工合并而没加这个参数输出的 MP4 可能会出现「本地播放正常但上传到网盘或放到电视上后拖动卡顿」的问题。这不是文件坏了只是索引位置对某些设备不友好。2.3 这套资源里可能装了什么GUI 程序、批处理脚本还是命令行工具拿到BiliBili视频合并软件-audio.m4s-video.m4s合并成mp4.rar这样的压缩包解压后你大概率会看到三种形态之一第一种是图形界面程序用 C#、PyQt 或 Electron 编写把 ffmpeg 打包进资源目录你只需要选择视频文件、选择音频文件、点「开始合并」第二种是批处理脚本例如合并.bat或merge.sh配合一个ffmpeg.exe使用通过拖拽文件到图标上运行第三种是 Python 脚本需要你本机有 Python 环境以及安装 imageio-ffmpeg 或直接调用系统 ffmpeg。资源包一般会附带一个 readme 或说明文档写清楚依赖要求和操作步骤。我的建议是拿到资源后先不要急着双击运行先看一眼目录结构和说明。如果目录下没有ffmpeg.exe而是只有脚本说明它依赖系统 PATH 中的 ffmpeg。此时你在命令行输入ffmpeg -version能正常输出脚本才能工作如果提示找不到命令你需要自行下载 ffmpeg 放到同目录或者在脚本中写死 ffmpeg 的路径。很多翻车现场就是因为「脚本找到了但 ffmpeg 没找到」弹出一堆乱码和 error看起来像软件坏了其实是环境问题。至于选图形界面还是命令行我的看法是单集合并、偶尔用一次GUI 足够但如果一次要合并整个季度几十集GUI 会让你点鼠标点到手酸脚本循环一次搞定。后面章节里的命令就是把 GUI 底下那层黑匣子打开给你看。你理解之后再遇到 GUI 里「音画不同步」「输出文件打不开」这类问题就知道是哪个环节出了岔子而不是只能干瞪眼。3. 把缓存变成 MP4手把手操作步骤与关键参数3.1 准备工作找到缓存目录并确认哪个是视频、哪个是音频在用工具合并之前必须先找到两个.m4s文件。Android 手机上的 B 站 App缓存路径一般是一串包名目录下的download文件夹每集视频单独一个子目录子目录里才是video.m4s、audio.m4s、danmaku.xml这些文件。如果你用的是 Android 11 及以上系统Android/data目录被限制访问文件管理器直接进不去需要先把整个缓存目录导出到电脑上再操作。iOS 由于沙盒机制缓存文件只能通过 App 内分享或 iTunes 文件共享导出无法直接在文件系统层面找到。把缓存拷贝出来后正常情况下一个文件夹里正好有一个大的video.m4s和一个相对小的audio.m4s。视频文件比音频文件大得多因为画面码率通常远高于音频码率。但也有例外某些清晰度下音频可能比视频大吗基本不会所以用文件大小判断最直观。如果缓存目录里有两个.m4s但名字不是video和audio而是类似2068、6201之类的数字编号就需要用 ffprobe 来判断哪条是视频流。命令如下ffprobe -v error -show_entries streamcodec_type,codec_name,width,height -of defaultnoprint_wrappers1 2068.m4s代码说明-v error只输出错误级别以上的日志避免刷屏-show_entries stream...指定要显示流类型、编码名称、宽高-of defaultnoprint_wrappers1让输出变成一行字段方便识别。执行后如果看到codec_typevideo和width、height有值那这个文件就是视频流如果只有codec_typeaudio则是音频流。这一步虽然简单但在处理批量文件时能帮你避免把两个文件写反。很多 GUI 工具会自动识别但如果你是用批处理脚本合并顺序写反会直接导致输出文件倒置后面会专门讲。3.2 用 ffmpeg 一条命令完成合并每个参数都对应一个动作当确认了两个文件后最核心的合并命令如下。这里以video.m4s和audio.m4s位于当前目录为例ffmpeg -i video.m4s -i audio.m4s -map 0:v:0 -map 1:a:0 -c copy -movflags faststart -y output.mp4这条命令是合并操作的基础模板。-i video.m4s声明第一个输入文件是画面轨-i audio.m4s声明第二个输入文件是声音轨。-map 0:v:0表示从第一个输入文件中选取视频轨-map 1:a:0表示从第二个输入文件中选取音频轨。这里的三个数字分别是输入文件序号、流类型、流序号。加上显式 map 可以避免 ffmpeg 自动选择流时的误判尤其当某个.m4s里同时存在多条音轨时自动选择可能挑错。-c copy是这条命令的灵魂它告诉 ffmpeg 对选中的流不重新编码直接拷贝编码数据。对于 flv、mkv、mp4 等容器这种操作速度非常快。如果去掉-c copyffmpeg 会按默认编码器重新压缩视频耗时巨大且画质下降。-movflags faststart是一个封装优化项它将 MP4 的索引 moov 移动到文件头部让输出文件更适合网络播放和拖动进度条。-y则是在输出文件已存在时自动覆盖避免中断确认。如果执行成功终端会显示进度条和速度最终生成output.mp4。如果合并过程中提示类似Invalid data found when processing input或者moov atom not found说明输入流不是标准状态。这时可以在每条-i之前加-fflags genpts让 ffmpeg 忽略输入流里有问题的 PTS重新生成时间戳ffmpeg -fflags genpts -i video.m4s -fflags genpts -i audio.m4s -map 0:v:0 -map 1:a:0 -c copy -movflags faststart -y output.mp4-fflags genpts会对输入流重新生成 PTS 时间戳解决大量 DASH 缓存文件中时间戳不连续的问题。如果加上这个参数后仍然报错可以尝试把-c copy改成-c:v libx264 -c:a aac强制转码但那是最后手段因为会花很长时间且画质有损。常规操作都应该从无损 remux 开始。3.3 用图形界面工具操作选项和底层命令的对应关系如果你不想记命令直接用资源包里的 GUI 软件操作逻辑和上面的参数是逐项对应的。常规流程是双击打开主程序 → 通过「打开文件」或拖拽方式把video.m4s和audio.m4s分别填入视频框和音频框 → 设置输出目录和文件名 → 点击「开始合并」。有些工具做得更智能会自动搜索目标目录里的两个.m4s并自动配对你只需要点一下「合并」即可。GUI 工具还有几个常用开关一个是「快速合并不重新编码」对应-c copy默认开启速度很快另一个是「输出为 MP4适合网络播放」对应-movflags faststart建议保持勾选。有些工具提供「修复音画同步」选项对应-fflags genpts或-itsoffset。当你遇到音画不同步问题时打开这个选项往往比手动处理更省事但代价是合并时间略长一点。使用 GUI 时有一个容易忽略的点输出文件的路径不要和输入文件在同一目录且同名。如果输出文件叫video.mp4而输入叫video.m4s一起放在同一个文件夹里很多工具会误把新生成的video.mp4当成输入导致二次处理或覆盖出错。更稳妥的做法是单独建一个output目录把所有合并结果集中放进去既不污染原始缓存也方便后续批量管理。此外GUI 工具通常会在界面底部显示底层命令日志如果你熟悉命令行可以直接复制这段日志去排查。如果日志中出现红色error别急着忽略绝大多数翻车都能从最后一行找到线索。3.4 一次合并一整个季度用目录遍历脚本解锁批量操作当你有几十集缓存要合并再一个个打开 GUI 点按钮就不现实了。此时写一个简单的循环脚本配合 ffmpeg可以一次性处理整个目录。以下是一个在 Linux / macOS 终端中可用的 Bash 示例它会遍历当前目录的所有子目录找到名为video.m4s的文件并在同一目录下寻找audio.m4s然后输出为以目录名命名的 MP4#!/bin/bash for video in */video.m4s; do dir$(dirname $video) audio$dir/audio.m4s out${dir}/${dir%/}.mp4 if [ ! -f $audio ]; then echo [跳过] $dir 中缺少 audio.m4s continue fi echo [正在合并] $dir ffmpeg -y -i $video -i $audio -map 0:v:0 -map 1:a:0 \ -c copy -movflags faststart $out /dev/null 2merge.log if [ $? -eq 0 ]; then echo [完成] $out else echo [失败] $dir 合并出错查看 merge.log fi done脚本的逻辑其实很直接for video in */video.m4s负责把当前目录下每个子文件夹里的video.m4s路径取出来。dirname去掉文件名部分得到目录路径。然后拼出audio.m4s路径和输出文件名。/dev/null 2merge.log把正常的进度信息丢弃把错误信息追加到日志文件避免终端刷屏。脚本运行结束后如果某些集失败merge.log里会记录具体的 ffmpeg 输入输出路径和报错信息。在运行这个脚本前最好先确认所有子目录里都是同一部作品的多个分集因为脚本会把当前目录下所有子目录都处理一遍。如果混入了其他视频目录输出文件名也会跟着变乱。Windows 用户可以用for /d %%i in (*) do (...)的批处理版本但涉及路径含空格时需要额外加引号。我一般会优先用 Bash因为路径处理比 CMD 干净很多也不容易踩到编码问题。4. 避坑指南合并失败的四类症状与排查方法4.1 音画不同步先看起始时间戳再决定是否偏移现象合并后的 MP4 播放时嘴巴对不上声音画面比声音快了半秒而且越到后面偏差越明显变成「伪双轨」。原因DASH 缓存文件里的视频流和音频流起始时间戳不是对齐的。例如视频流的 PTS 从0.017秒开始音频流的 PTS 从0.000秒开始两者相差 17 毫秒在 30 分钟的视频里累积起来就是几十毫秒级别的偏移加上播放器的缓冲策略不同最终体现为持续的错位。另一个常见原因是 fMP4 分段文件里存在丢帧或重复帧导致时间戳序列不连续而 ffmpeg 在-c copy模式下会原样保留这些时间戳。解决合并前先用 ffprobe 查看两条流的 start_time 是否一致ffprobe -v error -show_entries streamcodec_type,start_time -of defaultnoprint_wrappers1 video.m4s ffprobe -v error -show_entries streamcodec_type,start_time -of defaultnoprint_wrappers1 audio.m4s如果start_time不一样可以用-itsoffset对其中一条流做固定时间偏移。比如视频流 start_time 是0.017音频流是0.000可以这样合并ffmpeg -itsoffset 0.017 -i video.m4s -i audio.m4s -map 0:v:0 -map 1:a:0 -c copy -movflags faststart -y output.mp4-itsoffset 0.017会把第一个输入流的所有时间戳整体延迟 17 毫秒。注意这个偏移量有正负之分如果视频比音频晚偏移量用负值。如果不知道具体差多少更稳妥的做法是加-fflags genpts让 ffmpeg 重新生成时间戳然后再播放检查。实战中八成以上的音画不同步都可以靠genpts解决因为它会强制让 ffmpeg 为新容器内容打上连续的时序。4.2 合并后没有声音或声音是杂音问题多半在音频编码现象画面正常但播放器显示没有音频轨道或者有声音但像是失真、沙沙声、音量爆炸。原因B 站部分缓存音频不是标准的 AAC-LC而是 Opus 或 HE-AAC。虽然 MP4 容器支持 Opus但很多播放器尤其是电视、车载系统的 MP4 解码器只对 AAC-LC 做了支持。另有一种情况是audio.m4s文件本身不完整比如缓存中途被清理导致音频流缺少结束标记。解决先用 ffprobe 看音频编码是什么ffprobe -v error -show_entries streamcodec_name,sample_rate,channels -of defaultnoprint_wrappers1 audio.m4s如果codec_name是opus那就别用-c copy了把音频转成 AAC 再封装ffmpeg -i video.m4s -i audio.m4s -map 0:v:0 -map 1:a:0 -c:v copy -c:a aac -b:a 192k -movflags faststart -y output.mp4这条命令中-c:v copy仍然保持视频不重编码只把音频重新编码成 192kbps 的 AAC。192k 是 44.1kHz 立体声音频在 128k 和 256k 之间的平衡点人耳听起来足够通透文件也不会膨胀太多。如果你的缓存音量本来是正常的转码后不应该变差。如果转码后依然没有声音那就把audio.m4s单独丢进 ffmpeg 转成 WAV 试听一下确定是源文件损坏而不是封装问题。源损坏的情况下任何合并工具都救不回来只能重新缓存。4.3 报错moov atom not found或Invalid data:先尝试重封装备用方案现象ffmpeg 在执行合并时直接退出终端提示moov atom not found或Invalid data found when processing inputGUI 工具也弹窗报错合并进度条一直停在 0%。原因.m4s是分段式 fMP4它的索引信息散布在多个moof里而不是像普通 MP4 那样集中在moov。如果工具读取时没有正确识别分段结构就会认为文件头缺少必要的 moov或者缓存过程中文件没有完整写盘尾部缺失了mfra分片索引信息。这也解释了为什么同一个文件用某些播放器能播放到 ffmpeg 里却报错——不同工具对 fMP4 的容错程度不一样。解决第一步先尝试加-fflags genpts重新生成时间戳有时只是 PTS 问题导致解析中断。第二步如果第一步不行用-c copy附加-movflags skip_trailer输出试一下ffmpeg -fflags genpts -i video.m4s -fflags genpts -i audio.m4s \ -map 0:v:0 -map 1:a:0 -c copy -movflags faststartskip_trailer -y output.mp4skip_trailer会忽略文件尾部的某些损坏块对 M4S 这种分段流比较实用。但如果源文件本身不完整输出视频很可能在最后几秒出现声音消失或画面凝固这时只能回去重新检查缓存。第三步如果以上都不行就放弃零损耗改用转码方案兜底ffmpeg -i video.m4s -i audio.m4s -map 0:v:0 -map 1:a:0 -c:v libx264 -preset veryfast -crf 20 -c:a aac -b:a 192k -y output.mp4-preset veryfast让 x264 在编码速度和压缩率之间偏向速度-crf 20是接近视觉无损的质量档位。这个命令会重新编码视频虽然耗时增加但能绕过几乎所有源文件解析问题。作为一个经验原则先试 remux再试genpts最后才用重编码。不要一开始就转码那是性能杀手段。4.4 合并后无法拖动或手机不播放封装兼容性参数没调对现象在电脑上用 PotPlayer 能正常播放到 iPhone 相册或某些电视播放器里却打不开或者打开进度条能拖动但一拖就卡死然后软件崩掉。原因最常见的两个坑一是输出 MP4 没有加faststartmoov 在文件尾部某些播放器需要先下载整个文件才能读到索引所以无法快进二是视频编码是 H.265/HEVC而旧手机或电视盒子不支持硬解 HEVC一旦拖动到关键帧解码器跟不上就表现成卡死。还有一种情况是输出文件虽然扩展名是.mp4但内部 brand 不是标准 MP4某些播放器会识别为未知格式。解决如果你用命令行合并建议把输出品牌强制为 MP4 标准并加上 faststartffmpeg -i video.m4s -i audio.m4s -map 0:v:0 -map 1:a:0 -c copy -brand mp42 -movflags faststart -y output.mp4-brand mp42把容器品牌标记为 MP4 v2这是兼容性最好的 MP4 标志之一。如果视频编码是 HEVC无法换硬件解码那就需要转成 H.264ffmpeg -i video.m4s -i audio.m4s -map 0:v:0 -map 1:a:0 -c:v libx264 -preset fast -crf 21 -c:a copy -movflags faststart -y output.mp4这里-c:a copy表示音频流不重编码因为 AAC 本来就是 MP4 标准音频没必要二次处理。如果你只打算在自己的电脑或现代电视上播放HEVC 通常没问题但如果要给朋友传文件或者放到老设备上H.264 依然是保险的选择。判断是否 HEVC 的方法用 ffprobe 看codec_name如果是hevc就说明命中了这个坑。5. 进阶玩法批量合并、字幕封装与输出验收5.1 批量合并的完整形态支持异常目录和日志记录的脚本第 3 章的循环脚本可以处理「每个子目录里恰好有标准命名的两个 m4s」的理想情况但实际缓存目录里经常混有不完整的文件、多音轨目录、甚至是已经合并过的 MP4。为了避免处理到旧输出我通常会在脚本里加上类型判断和输出日志形成一套可反复使用的流程。#!/bin/bash mkdir -p merged for dir in */; do dir${dir%/} video$dir/video.m4s audio$dir/audio.m4s if [ ! -f $video ]; then continue fi if [ ! -f $audio ]; then echo [警告] $dir 缺少 audio.m4s跳过 merge_report.txt continue fi outmerged/${dir}.mp4 if [ -f $out ]; then echo [跳过] $out 已存在 | tee -a merge_report.txt continue fi ffmpeg -y -fflags genpts -i $video -fflags genpts -i $audio \ -map 0:v:0 -map 1:a:0 -c copy -movflags faststart -brand mp42 $out \ 2merge_report.txt if [ $? -eq 0 ]; then echo [OK] $out | tee -a merge_report.txt else echo [FAIL] $dir | tee -a merge_report.txt fi done这个脚本最大的改进是merged输出目录和对已有文件的检查。-brand mp42避免兼容性问题genpts预处理时间戳tee -a让终端显示和日志文件双轨记录方便事后查看哪些集失败了。还有一个细节用dir${dir%/}去掉目录末尾的斜杠否则拼接路径时会出现双斜杠虽然 Linux 不介意但 Windows 环境就容易出问题。对于大量文件的场景我不建议用find -exec ... 并行合并。ffmpeg 本身对磁盘 IO 和 CPU 的压力都不小并行 4 个任务往往会把硬盘怼到 100%反而拖慢总时间。串行循环虽然慢但是稳定、能排查、不会出现几十个进程同时写日志导致乱序的问题。如果你想要并行可以限定最多 2 个后台任务并等它们结束再继续下一批这个复杂度对于家庭用户来说不值得。5.2 弹幕字幕和封面一起封装进 MP4三个命令把收藏做完整B 站缓存的danmaku.xml是弹幕它不是标准字幕文件。想把它放到 MP4 里得先转成 ass 格式。很多资源包没有集成这一步所以需要借助额外工具我这里只说封装命令。假设你已经把danmaku.xml转成了subtitle.ass现在把字幕轨道加进之前合并好的output.mp4ffmpeg -i output.mp4 -i subtitle.ass -map 0 -map 1:0 -c copy -c:s mov_text -metadata:s:s:0 languagechi output_with_sub.mp4这个命令把第一个输入已合并的视频的所有流和第二个输入的字幕流同时映射到输出。-c copy对视频和音频生效-c:s mov_text把 ass 字幕转成 MP4 内置字幕常用的 mov_text 编码。mov_text的优点是兼容性好缺点是只支持基本的样式弹幕中的彩色字体、滚动效果会丢失。如果你的播放器支持外部字幕保留独立的.ass文件反而更清晰。这一步只是「锦上添花」不是合并缓存视频的必要步骤。封面封装稍微冷门一点。如果你下载了视频封面cover.jpg想把它设成 MP4 的封面在某些播放器列表里显示缩略图ffmpeg -i output.mp4 -i cover.jpg -map 0 -map 1 -c copy -disposition:v:1 attached_pic -metadata:s:v:1 mimetypeimage/jpeg output_cover.mp4-map 1把封面图片作为第二条视频流输入-disposition:v:1 attached_pic将这条图片流标记为「附件图」主流的 MP4 播放器会把它当成封面而不会当作额外视频轨道显示出来。如果你不需要这两个功能完全可以忽略。核心思路是合并工具只解决音视频混流而完整的本地视频库管理还需要字幕封面这些元数据你用命令操作时等于是把工具的功能向前延伸了一点点。5.3 用 ffprobe 验收一份输出文件到底能不能用合并完成不是终点我建议养成「合并后必查流信息」的习惯否则万一输出文件有问题等到看完整部剧才发现音轨延迟那才叫血亏。验收命令只有一个ffprobe -v error \ -show_entries streamindex,codec_name,codec_type,width,height,channels,duration \ -of compact output.mp4这段命令会把每个流的序号、编码、类型、宽高、声道数和时长逐条列出来。你需要检查三处视频流宽高是否和缓存清晰度一致音频流 codec_name 是否为 aac以及视频时长与音频时长差是否在 0.2 秒以内。如果视频流显示 hevc 且你目标设备是旧手机就要考虑转码如果音频流显示 opus说明前面的-c copy命令把非标准编码也带出来了需要重封装成 AAC如果时长差超过 0.5 秒基本可以判定音画不同步已经存在。一个更严苛的验收方式是检查视频流的 start_timeffprobe -v error -show_entries streamcodec_type,start_time -of csv output.mp4输出会显示视频流和音频流的 start_time。它们相差越小越好一般小于0.050秒都可以接受。如果视频流是0.000音频流是0.100那就有 100 毫秒的延迟虽然肉眼不一定看得出来但如果要做剪辑素材这 100 毫秒会在剪辑软件里变成明显的错位。遇到这种情况要么重新用-itsoffset修复要么就用 GUI 工具的「修复音画同步」重来一次。验收这一步看起来多花 30 秒但它能避免你日后在播放器里反复定位问题。6. 一个让合并提速的小技巧把工具变成「拖拽即用」6.1 在 Windows 上做一个小脚本把两个文件拖进图标直接合并如果你已经熟悉了命令行仍然会觉得每次输入ffmpeg -i ...很啰嗦。一个实用的小技巧是在资源包所在目录放一个批处理脚本平时不需要打开终端直接把video.m4s和audio.m4s按顺序拖到这个脚本图标上就能自动调用 ffmpeg 输出结果。以下是一个适合 Windows 的merge.bat脚本echo off chcp 65001 nul if %~1 goto usage if %~2 goto usage set VIDEO%~1 set AUDIO%~2 set OUT%~dp1_merged.mp4 ffmpeg -y -fflags genpts -i %VIDEO% -fflags genpts -i %AUDIO% ^ -map 0:v:0 -map 1:a:0 -c copy -movflags faststart -brand mp42 %OUT% if %errorlevel%0 ( echo. echo 合并完成: %OUT% ) else ( echo. echo 合并失败请检查文件是否完整 ) pause exit /b :usage echo 请把 video.m4s 和 audio.m4s 两个文件依次拖到本脚本图标上 pause exit /b脚本里的%1是第一个拖入文件的完整路径%2是第二个。%~dp1是第一个文件所在目录%~dp1_merged.mp4会把合并结果放在原始视频目录下并加上_merged后缀避免覆盖输入文件。chcp 65001将命令行编码切换为 UTF-8否则包含中文的路径会显示乱码或传参错误。if %errorlevel%0判断 ffmpeg 的退出码为 0 说明合并成功否则就提示失败。这个脚本有一个前提你拖入的顺序必须是视频在前、音频在后。如果顺序反了-map 0:v:0会取到音频文件里的视频流通常不存在合并就会失败。为了更稳妥你可以在脚本里加一个按文件大小交换顺序的步骤。视频流文件通常比音频大比较一下两个文件大小自动把大的放在视频位置。做法是在set VIDEO%~1后加一段 if 比较%~z1和%~z2但文件大小在批处理里获取比较麻烦用for %%F in (%1) do set size1%%~zF也能拿到。我个人习惯是在拖入前先看一下文件名毕竟video.m4s比audio.m4s多几个字母肉眼就能分出来。6.2 把它扩展到更多场景拖一部剧的文件夹进去自动找文件如果你觉得拖两个文件还不够省事可以再写一个merge_folder.bat把整个缓存目录拖进去脚本自动遍历该目录下的所有子文件夹找到每组video.m4s和audio.m4s并合并。这样你就不需要在乎文件名和顺序了只要缓存目录结构是 B 站默认的形态脚本就能处理。这个进阶脚本的批处理版本会比较长因为 CMD 的for /r递归遍历和中文字符编码处理都不够优雅。我一般会建议这类需求直接用 PowerShell 或 Bash 写如果非要 Bat也可以借助for /d %%i in (%~1\*) do遍历子目录然后对每个子目录检查文件是否存在调用同一个 ffmpeg 命令。核心思想和前面第 5 章的 Bash 脚本一致只是语法换了个牌子。从那以后我每次合并 B 站缓存视频都会先做两件事用 ffprobe 看一眼时间戳和编码然后把文件拖进自制的批处理脚本里。顺序错了不用担心输出名自动带_merged后缀再补一条 ffprobe 验收整个过程不超过一分钟。这种「命令 小脚本」的组合比单双击图形按钮更可复用出了问题时也能定位到是 ffmpeg 哪一步在报错而不是对着软件弹窗干着急。如果你也经常整理缓存视频希望这个习惯能帮你少走一些弯路。本文还有配套的精品资源点击获取