B站缓存的.m4s文件怎么合并成MP4?用Qt撸了一个工具,附完整思路
手把手教你用Qt写一个B站缓存视频合并工具——BiliMerg先说点废话事情是这样的。我平时喜欢在B站缓存视频路上看但B站App那个缓存机制是真的让人头大——视频和音频是分开的两个.m4s文件想存到电脑里或者发给朋友根本没法直接看。网上倒是有现成的合并工具但要么收费要么广告满天飞要么就是那种一眼看上去就不想用的界面。于是我一拍大腿自己写一个算了。这一写吧就从一个简单的合并工具写成了一个带界面、带批量、带暂停恢复、带关机功能的完整应用。期间踩了不少坑也学到不少东西。今天就把整个过程捋一遍给想自己动手的朋友们一个参考。项目名称BiliMerger技术选型C17 Qt 6 Widgets核心依赖ffmpeg外部调用源码地址GitHub上搜BiliMerger就能找到第一步搞清楚B站的缓存结构长啥样动手之前得先搞明白我们要处理什么。B站App安卓版缓存的视频一般在手机存储的/Android/data/tv.danmaku.bili/download/目录下。每个缓存视频是一个文件夹里面通常有这几个东西一个.jpg文件——视频封面图一个video.m4s——纯视频流没有声音一个audio.m4s——纯音频流没有画面而且这两个.m4s文件开头还有9个字节的脏数据直接拿去给ffmpeg合并会报错。得先跳过这9个字节才行。所以工具的核心逻辑就很清晰了扫描目录把.jpg、video.m4s、audio.m4s三个文件配成一套读取.jpg作为封面显示调用ffmpeg把去掉头部的音视频流合并成一个MP4或MKV第二步搭架子——项目结构怎么设计既然是Qt项目自然是用Qt Creator新建一个Qt Widgets Application。但我用的CMake管理项目方便以后跨平台。项目结构长这样BiliMerger/├── CMakeLists.txt # 构建配置├── resources.qrc # 资源文件图标啥的├── icon.png / icon.ico # 程序图标├── main.cpp # 入口├── mainwindow.h/cpp/ui # 主窗口├── videocard.h/cpp # 视频卡片组件├── mergeworker.h/cpp # 单视频合并线程└── batchmergeworker.h/cpp # 批量合并线程为什么要分这么细因为界面和后台逻辑得分开啊。界面卡死是Qt新手最容易犯的毛病——你在主线程跑耗时操作界面直接冻住用户还以为是程序崩了。所以合并这种耗时活儿必须放到子线程里干。第三步最核心的——怎么用Qt调用ffmpeg合并这是整个工具的灵魂其他都是围绕这个转的。Qt里调用外部程序用QProcess。合并一条视频的ffmpeg命令大概长这样ffmpeg -y -skip_initial_bytes 9 -i video.m4s -skip_initial_bytes 9 -i audio.m4s -c:v copy -c:a copy output.mp4-c:v copy和-c:a copy表示不重新编码直接复制流速度最快而且画质音质无损。但问题来了——B站那个9字节头怎么处理我试过两种方案用QFile读取文件跳过前9个字节写到临时文件里再让ffmpeg读临时文件用ffmpeg的-skip_initial_bytes参数直接跳方案1太蠢了大文件要额外写一份数据硬盘吃不消。方案2简单高效ffmpeg -y -skip_initial_bytes 9 -i video.m4s -skip_initial_bytes 9 -i audio.m4s -c:v copy -c:a copy output.mp4就多了两个参数干净利落。这里踩了个坑-skip_initial_bytes这个参数不是所有版本的ffmpeg都支持。我一开始用的旧版ffmpeg就不认报错Option not found。后来换成release-essentials版本就好了。所以记得提醒用户ffmpeg版本别太老。第四步界面怎么做才好看又好用说实话我审美一般但我知道一个原则别让用户动脑子。所以整个界面设计思路就是选择目录 → 自动扫出视频卡片 → 点一下就能合并。具体来说卡片式展示每个视频显示封面图、标题、时长、分辨率。横向滚动排列看着直观。三种合并模式单视频点一下卡片选中点开始合并就搞这一个选择多个每个卡片带复选框勾几个合几个一键全部啥也不挑扫到的全给合了这里有个优先级逻辑如果一键全部和选择多个同时勾选以一键全部为准。用户不会想那么多我们替他们决策就行。拖拽支持用户不一定要点选择目录按钮直接把文件夹拖到窗口上也行。甚至拖单个.m4s文件进来也能自动配对找到同目录下的另一半。这个用Qt的dragEnterEvent和dropEvent实现不复杂但很贴心。深色主题纯粹是我自己喜欢而且做深色比做浅色省事——不用纠结各种颜色的搭配黑底白字就完了。第五步批量合并——暂停、恢复、取消怎么实现批量合并是需求里最麻烦的一块。用户要能随时停停完还能继续或者直接取消。我用的方案是主线程只负责调度不干活每个视频的合并单独起一个QThread通过MergeWorker批量合并由BatchMergeWorker管理一个任务队列暂停的时候当前正在跑的MergeWorker收到暂停信号ffmpeg进程被挂起QProcess::suspend但要注意——suspend在Windows上表现不太好我最后用的是另一种方式让工作线程在循环里检查一个m_paused标志如果为true就QWaitCondition阻塞住。恢复就是唤醒这个条件变量。取消的话直接QProcess::kill然后清理临时文件。这里又有个坑QProcess::kill有时候杀不干净子进程。ffmpeg自己可能还会fork子进程光杀父进程不够。解决方法是用操作系统的进程树杀法——Windows上用taskkill /T /F /PID xxx。第六步那些锦上添花的功能核心功能做完了我开始加一些边角料自动记住设置用QSettings存到注册表Windows或配置文件Linux/macOS。每次启动时读回来不用用户每次重新选输出目录、格式、模板。文件名模板用户可以用{标题} {日期} {序号}自定义输出文件名。这个解析逻辑是自己写的字符串替换不复杂但很实用。内存盘加速如果用户有内存盘比如RamDisk把临时目录设到内存盘上能提速不少。其实就是改一下QTemporaryDir的路径前缀。完成后自动关机这个需求是群友提的——说合并一堆视频可能要几十分钟不想守着合完直接关机睡觉。实现方式合并完成后弹一个窗口显示60秒倒计时用户点取消就不关机。倒计时结束执行system(“shutdown -s -t 0”)。第七步遇到的几个印象深刻的问题长视频合并卡住有个用户反馈说合并5小时以上的视频会卡在99%不动。最后发现是ffmpeg的-skip_initial_bytes在某些情况下会导致音视频不同步后面加了-fflags genpts强制生成时间戳才解决。界面拖拽时崩溃Qt的拖拽事件处理不好容易野指针。特别是拖拽过程中用户又把源文件夹删了——这种极端情况得用QPointer做防御。中文路径乱码Windows下路径含中文传给ffmpeg需要转成本地编码GBK不然ffmpeg识别不了。Qt的toLocal8Bit()就是干这个的。第八步打包发布——让不懂Qt的人也能用Qt程序不能直接扔给别人用因为缺一堆DLL。打包方法编译Release版本用Qt自带的windeployqt.exe把依赖DLL自动收集过来把ffmpeg.exe也放进去最后说几句这个项目从开始写到今天前前后后改了十几版。功能不算复杂但把每个细节做好确实花了不少心思。如果你也想写类似的工具我的建议是先把核心流程跑通扫描→配对→合并再慢慢加花活多考虑用户会怎么用而不是我怎么实现方便善用Qt的线程和进程别在主线程干重活当然如果你完全不想动手写代码直接用我打包好的版本也行。GitHub上搜BiliMergerReleases里有现成的exe。希望这篇对你有帮助。写代码这事儿吧最开始觉得难上手之后发现也就那么回事。关键是别怕踩坑踩得多了就成老手了。项目地址https://github.com/Bologna-Zaicek/Tools-BiliMerger有问题可以提Issue看到会回。