文章目录

  • 前言
  • 一、先看效果
  • 二、开始写代码
    • 1.遍历文件
    • 2.获取视频标题和视频名称
    • 3.生成视频
    • 4.主函数
  • 总结

前言

手机bilibili缓存了很多视频,想导入电脑看,但发现缓存的视频被分割成两个文件:音频(audio.m4s)和视频(video.m4s),上网查了下教程,说下载安装ffmpeg,在音视频文件夹下,打开dos窗口,输入命令:

ffmpeg -i video.m4s -i audio.m4s -codec copy output.mp4

可行是可行,但是我视频比较多,而且没有文件名,一个个这样来一遍也太麻烦了,自己动手丰衣足食,写一个自动生成的;
备注:手机bilibili的缓存目录位于:/Android/data/tv.danmaku.bili/download


新版本已经上线,修复了部分bug,详情请查靠:C++调用ffmpeg批量合并bilibili缓存视频 2.0


一、先看效果

将bilili缓存视频的文件夹拷贝到程序生成目录下,如下图:
然后直接运行程序,会创建文件夹,并将视频生成到指定文件夹下面,效果如下:

二、开始写代码

1.遍历文件

因为每一个缓存视频的信息都存放在一个文件夹中,故针对每一个视频文件,遍历主要获取信息有:
    1)存放视频标题和视频名称的json文件
    2)文件audio.m4s路径
    3)文件video.m4s文件路径
示例代码如下:

// 使用全局变量了,保存每条视频信息
#include <string>
#include <iostream>
#include <fstream>
#include <vector>
#include <thread>
#include <windows.h>
#include <string.h>
#include <io.h>
#include <direct.h>using namespace std;// 这里是我将jsoncpp封装成一个库了,方便使用
#include "../jsontool/jsontool.h"
#pragma comment(lib, "JsonTool.lib")#define AudioName     "audio.m4s"
#define VideoName       "video.m4s"
#define JsonName        "entry.json"vector<string> g_vecTitleNames;
vector<string> g_vecPartNames;
vector<string> g_vecVideoPaths;
vector<string> g_vecAudioPaths;int FindFile(const char* pszPath)
{if(pszPath == NULL){return -1;}int iRet = 0;char szFindPath[MAX_PATH] = { 0 };DWORD dwFileAttributes;string strFileName;WIN32_FIND_DATA wfd;sprintf(szFindPath, "%s*.*", pszPath);HANDLE hFindFile = ::FindFirstFile(szFindPath, &wfd);if (hFindFile == INVALID_HANDLE_VALUE){// 没有找到任何文件return ERR_NO_FIND;}// 找到文件,开始遍历strFileName = wfd.cFileName;while (strFileName.size() > 0){// 过滤 . 和 ..if (strFileName != "." && strFileName != ".."){dwFileAttributes = wfd.dwFileAttributes;if (dwFileAttributes == FILE_ATTRIBUTE_DIRECTORY)  // 目录{// 如果是目录,则继续递归查找// 查找json所在路径char szSubFindPath[MAX_PATH] = { 0 };sprintf(szSubFindPath, "%s%s\\", pszPath, strFileName.c_str());iRet = FindFile(szSubFindPath);if (iRet != 0){break;}}else if (dwFileAttributes == FILE_ATTRIBUTE_ARCHIVE) // 文件{if (strFileName == JsonName){// 通过json文件路径获取title和partchar szJsonPath[MAX_PATH] = { 0 };char szTitleName[MAX_PATH] = { 0 };char szPartName[MAX_PATH] = { 0 };sprintf(szJsonPath, "%s%s", pszPath, JsonName);GetTitleAndPage(szJsonPath, szTitleName, szPartName);g_vecTitleNames.push_back(szTitleName);g_vecPartNames.push_back(szPartName);// 做个取巧处理,按bilibili的文件存储方式,读vidio会在json之前,防止vedio未下载完成的情况int iTotal = g_vecTitleNames.size();// 补齐vectorif (iTotal > g_vecVideoPaths.size()){g_vecVideoPaths.push_back("");}// 补齐vectorif (iTotal > g_vecAudioPaths.size()){g_vecAudioPaths.push_back("");}//cout << szTitleName << "    " << szPartName << endl;}else if (strFileName == VideoName){// video找到了,可以直接返回了,不用再往下找g_vecVideoPaths.push_back(string(pszPath) + VideoName);g_vecAudioPaths.push_back(string(pszPath) + AudioName);//cout << pszPath << "  " << VideoName << endl;//cout << pszPath << " " << AudioName << endl;break;}}}// 查找下一个文件if (!::FindNextFile(hFindFile, &wfd)){break;}strFileName = wfd.cFileName;}::FindClose(hFindFile);return iRet;
}

2.获取视频标题和视频名称

获取到json文件后,需要获取其中的视频标题和视频名称;但是json文件编码是utf-8,故需要转码;
代码如下(示例):

string UnicodeToANSI(const wstring& str)
{char*     pElementText;int    iTextLen;// wide char to multi chariTextLen = WideCharToMultiByte(CP_ACP,0,str.c_str(),-1,NULL,0,NULL,NULL);pElementText = new char[iTextLen + 1];memset((void*)pElementText, 0, sizeof(char) * (iTextLen + 1));::WideCharToMultiByte(CP_ACP,0,str.c_str(),-1,pElementText,iTextLen,NULL,NULL);string strText;strText = pElementText;delete[] pElementText;return strText;
}wstring UTF8ToUnicode(const string& str)
{int  len = 0;len = str.length();int  unicodeLen = ::MultiByteToWideChar(CP_UTF8,0,str.c_str(),-1,NULL,0);wchar_t *  pUnicode;pUnicode = new  wchar_t[unicodeLen + 1];memset(pUnicode, 0, (unicodeLen + 1)*sizeof(wchar_t));::MultiByteToWideChar(CP_UTF8,0,str.c_str(),-1,(LPWSTR)pUnicode,unicodeLen);wstring  rt;rt = (wchar_t*)pUnicode;delete  pUnicode;return  rt;
}int GetTitleAndPage(const char* pszJsonPath, char* pszTitle, char* pszPage)
{string strValue;char szValue[4096] = { 0 };std::ifstream is;is.open(pszJsonPath, std::ios::binary);while (!is.eof()){// json数据有空格,只能一行一行读memset(szValue, 0, sizeof(szValue));is.getline(szValue, sizeof(szValue));strValue += szValue;}// 将jsoncpp封装成一个类,方便调用,给公司项目用了,不方便提供CJsonTool json;int iRet = json.InitJson(strValue.c_str());//cout << "InitJsonStr: " << iRet << endl;char szBuff[MAX_PATH] = { 0 };iRet = json.GetStr("page_data", "part", szBuff, MAX_PATH);strValue = UnicodeToANSI(UTF8ToUnicode(szBuff));int iFindIndex = 0;// 去掉空格while ((iFindIndex = strValue.find(" ")) != string::npos){strValue.replace(iFindIndex, 1, "");}//cout << "GetJsonStr: " << iRet << "   " << strValue << endl;if (iRet == 0){strcpy(pszPage, strValue.c_str());}iRet = json.GetStr("", "title", szBuff, MAX_PATH);strValue = UnicodeToANSI(UTF8ToUnicode(szBuff));// 去掉空格while ((iFindIndex = strValue.find(" ")) != string::npos){strValue.replace(iFindIndex, 1, "");}//cout << "GetJsonStr: " << iRet << "   " << strValue << endl;if (iRet == 0){strcpy(pszTitle, strValue.c_str());}is.close();return 0;
}

3.生成视频

遍历完所有文件夹后,所有信息都应该保存到全局的4个vector中,直接遍历容器生成文件;
代码如下(示例):

void GenerateVideo(const char* pPath)
{if (g_vecAudioPaths.size() == 0 || g_vecAudioPaths.size() != g_vecAudioPaths.size() ||g_vecAudioPaths.size() != g_vecTitleNames.size() ||g_vecAudioPaths.size() != g_vecPartNames.size()){cout << "查找视频资源失败" << endl;return;}cout << "--------------------开始合成-------------------------" << endl;char szSavePath[MAX_PATH] = { 0 };char szSvaeFileName[MAX_PATH] = { 0 };char szCommand[1024] = { 0 };auto funGenerate = [](string strCommand) {system(strCommand.c_str());};for (int i = 0; i < g_vecTitleNames.size(); i++){// 跳过空if (g_vecAudioPaths[i].size() == 0 ||g_vecVideoPaths[i].size() == 0 ||g_vecTitleNames[i].size() == 0 ||g_vecPartNames[i].size() == 0){continue;}// 创建保存文件夹sprintf(szSavePath, "%s%s\\", pPath, g_vecTitleNames[i].c_str());if (_access(szSavePath, 0) == -1)        // 如果文件夹不存在{_mkdir(szSavePath);}// 保存文件名sprintf(szSvaeFileName, "%s%s.mp4", szSavePath, g_vecPartNames[i].c_str());// 组装命令// ffmpeg.exe -i video.m4s -i audio.m4s -codec copy namesprintf(szCommand, "%sffmpeg.exe -i %s -i %s -codec copy %s",pPath, g_vecVideoPaths[i].c_str(), g_vecAudioPaths[i].c_str(), szSvaeFileName);// 使用线程处理,没什么用,直接调用system(strCommand.c_str())更省事std::thread t(funGenerate, szCommand);t.join();cout << i << "  --- " << "输出:" << szSvaeFileName << endl;}
}

4.主函数

获取当前执行文件所在路径,直接调用查找和生成函数;
代码如下(示例)

int main()
{// 获取当前模块(exe)所在路径char szModuleFileName[MAX_PATH] = { 0 };::GetModuleFileName(NULL, szModuleFileName, MAX_PATH);// 查找目录char szFindPath[MAX_PATH] = { 0 };strcpy(szFindPath, szModuleFileName);char *pPos = strrchr(szFindPath, '\\');if (pPos == NULL){return -1;}// 截断文件名pPos[1] = '\0';FindFile(szFindPath);GenerateVideo(szFindPath);system("pause");return 0;
}

总结

写的有点乱,主要怎么方便怎么来,能基本应付我现在的需求,执行文件下载链接请访问:B站缓存视频m4s文件批量合成mp4,免积分下载

C++调用ffmpeg批量合并bilibili缓存视频相关推荐

  1. 用python和ffmpeg批量合成bilibili缓存的m4s成mp4

    我的目录格式是这样的 所以直接脚本处理一下就好了 ffmpeg安装:https://ffmpeg.zeranoe.com/builds/win64/static/ 直接去上面链接里找一个下载解压 哪个 ...

  2. bilibili缓存文件在哪里_Android——bilibili缓存视频合并教程[2.1]

    这可能是最后一次更新了,由2.0版脚本新添加自动命名功能,也出现了一些小Bug,当遇到空格时会报错,并且该视频不会合成输出 这个Bug在创建的目录上,也有一些问题,当遇到空格是就会停止,所以目录命名只 ...

  3. .net 2.0安装包打不开_Android——bilibili缓存视频合并教程[2.0]

    尽管发布了1.0的合并脚本,但还是不完美.输出的视频文件命名和哔哩哔哩视频文件名不一致,只是采用数字命名 本着对技术的负责,对广大b站的人民负责,决心让脚本达到预期的效果,就像这样,有目录,视频有命名 ...

  4. 用MATLAB将bilibili缓存视频批量转换成MP4的方法

    1.下载和安装ffmpeg详情参考博客 https://blog.csdn.net/weixin_41690708/article/details/90237568 下载地址:http://ffmpe ...

  5. 使用FFMpeg合并bilibili缓存的视频文件

    首先下载FFMpeg并配置环境变量 下载链接: https://www.gyan.dev/ffmpeg/builds/ffmpeg-git-full.7z 下载后解压文件到指定目录下,并配置环境变量& ...

  6. 软件分享——Bilibili缓存视频合并软件,m4s音视频合并工具

    bilibili缓存下来的视频,路径android/data/tv.danmaku.bili/download 此目录是bilibili缓存的视频存放目录,是m4s格式的音频和视频文件,不能正常播放. ...

  7. 编写Bash脚本实现使用FFmpeg批量合并视频

    前言 使用FFmpeg能够很方便的合并同分辨率的视频.很多时候我们可能需要批量化合并视频,此时可以通过编写Bash脚本来实现. 实现 批量合并视频 合并三个文件夹中的视频结果: result_path ...

  8. Python使用you-get批量下载bilibili网站视频

    需要安装python,然后pip install you-get. 安装步骤在官方说明上很清楚,就不写了. 使用 you-get --playlist  -o F:\bilibili https:// ...

  9. ffmpeg mac 批量脚本_使用批处理脚本(BAT)调用FFMPEG批量编码视频

    使用批处理脚本(BAT)编码视频非常方便,尤其当视频序列非常多的时候,更是省了不少简单重复性劳动. 只要学会批处理里面几个基本的命令就行了,感觉和c/c++差不多. set:设置变量(注意:变量一般情 ...

最新文章

  1. SPFA-DFS P3385 模板 判断负环===vector为啥过不了?
  2. 关于ffmpeg中rtmp无法设置超时时间的bug修复
  3. dojo中的dojo/dom-construct
  4. Linux 命令之 mv -- 移动文件/重命名文件
  5. android拍照功能无预览,Android 无预览拍照
  6. NET 下数据库图片的存入与读取
  7. 想转行,是要入坑Python还是Java?这问题还用问?
  8. jiacu的css,css 加粗(css font
  9. [转]c# 语音卡控制--语音卡电话呼叫系统
  10. 【NLP】常见衡量指标(metrics)
  11. css滤镜、混合模式、hls颜色值
  12. rs485全双工中只接一组_终于有人把RS485通讯的正确接线方式讲明白了,网友:这下好办了...
  13. Honey Badger BFT(异步共识算法)笔记
  14. tableau货架图制作_Tableau代写制作地图可视化和树形图、条形图
  15. Asp.Net CRM客户关系管理系统
  16. 高版本Android,鸿蒙2.0,debug安装解析失败
  17. 东方财富股票交易接口数据怎么执行出来?
  18. 移动应用Kony Solutions并购Sky Technologies
  19. 山外KL26的J-link连接与程序下载
  20. r语言java环境安装_【R语言入门】R语言环境搭建

热门文章

  1. 客户机是计算机网络硬件吗,计算机网络中硬件连接设备有哪些?
  2. 简述机器指令与微指令之间的关系_机器指令与微指令有什么联系和区别?
  3. (2)Hadoop核心 -- java代码对MapReduce的例子1
  4. win10打开计算机管理闪退,win10系统下任务管理器闪退的解决方法
  5. ubuntu14.04 有道辞典 安装成功后 打不开 的 解决办法
  6. 【补充习题七】积分不等式及定积分性质
  7. 独家秘技||如何快速入门一个陌生知识领域?
  8. qimgv(图片视频浏览器)-小工具
  9. # kubeadm ——k8S新节点加入集群-notready 问题解决
  10. 图像篡改检测C语言,图像篡改检测和定位(二)