文章目录

  • 前言
  • 一、源码
  • 二、使用步骤
  • 总结

前言

之前写过一个只能合成的m4s的版本,参考C++调用ffmpeg批量合并bilibili缓存视频,实际使用存在很多bug,先以及进行优化,具体如下:

1、增加blv格式的视频合成,实际缓存的视频是m4s和blv混合的,上一版的合成不了blv;
2、优化遍历算法,之前的遍历查找有问题,现在完美过滤未缓存的视频;
3、过滤部分遇到的特殊字符,部分特殊字符会导致合成失败;
4、支持官方动漫视频合成;

一、源码

示例源码如下:

#include <string>
#include <iostream>
#include <fstream>
#include <vector>
#include <thread>
#include <windows.h>
#include <string.h>
#include <io.h>
#include <direct.h>// 这里是我将jsoncpp封装成一个库了,方便使用
#include "../jsontool/jsontool.h"
#pragma comment(lib, "JsonTool.lib")using namespace std;#define AudioName     "audio.m4s"
#define VideoName       "video.m4s"
#define JsonName        "entry.json"
#define BLVVideo        ".blv"vector<string> g_vecTitleNames;
vector<string> g_vecPartNames;
vector<string> g_vecVideoPaths;
vector<string> g_vecAudioPaths;#define ERR_NO_FIND        -1int GetTitleAndPage(const char* pszJsonPath, char* pszTitle, char* pszPage);int FindJson(const char* pszVideoPath, char* pszTitle, char* pszPage)
{// 通过json文件路径获取title和partchar szJsonPath[MAX_PATH] = { 0 };int iLen = strlen(pszVideoPath);strcpy(szJsonPath, pszVideoPath);// 阶段最后面的 '\'szJsonPath[iLen - 1] = '\0';// 查找并截断路径到上一层目录char* pFind = strrchr(szJsonPath, '\\');if (pFind == NULL){return -1;}pFind[1] = '\0';strcat(szJsonPath, JsonName);int iRet = GetTitleAndPage(szJsonPath, pszTitle, pszPage);if (iRet != 0){return -1;}return 0;
}int FindFile(const char* pszPath)
{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) // 文件{// 防止video没有缓存完,json文件肯定有,video不一定,所以先找到video,再读上一层的json,vedio没缓存完,就没必要读jsonif (strFileName == VideoName){// video找到了,先返回上一层读json,再通过json文件路径获取title和partchar szTitleName[MAX_PATH] = { 0 };char szPartName[MAX_PATH] = { 0 };iRet = FindJson(pszPath, szTitleName, szPartName);if (iRet != 0){break;}// 插入title和章节名g_vecTitleNames.push_back(szTitleName);g_vecPartNames.push_back(szPartName);// 插入video路径g_vecVideoPaths.push_back(string(pszPath) + VideoName);g_vecAudioPaths.push_back(string(pszPath) + AudioName);break;}else if (strFileName.find(BLVVideo) != string::npos){// blv格式,需要特殊处理,将文件名写到txt中string strBlvTxtFile = pszPath;strBlvTxtFile += "blv.txt";// 可能存在多个*.blv文件,只要插入一次即可// 将当前所在路径和上一次插入vector的路径进行匹配,判断是否是第一次string strLastVideoPath = g_vecVideoPaths.back();if (strLastVideoPath.find(pszPath) == string::npos){// 没找到,则是第一次进入// 删除测试生成的文件DeleteFile(strBlvTxtFile.c_str());// video找到了,先返回上一层读json,再通过json文件路径获取title和partchar szTitleName[MAX_PATH] = { 0 };char szPartName[MAX_PATH] = { 0 };iRet = FindJson(pszPath, szTitleName, szPartName);if (iRet != 0){break;}// 插入title和章节名g_vecTitleNames.push_back(szTitleName);g_vecPartNames.push_back(szPartName);// 简单处理,插入相同路径作为blv格式的判断依据g_vecAudioPaths.push_back(strBlvTxtFile);g_vecVideoPaths.push_back(strBlvTxtFile);}std::ofstream fout;fout.open(strBlvTxtFile, std::ios::app);fout << "file '" << pszPath << strFileName << "'" << endl;fout.close();}}}// 查找下一个文件if (!::FindNextFile(hFindFile, &wfd)){break;}strFileName = wfd.cFileName;}::FindClose(hFindFile);return iRet;
}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;
}void removeSpecialChar(string& strValue)
{int iFindIndex = 0;// 去掉空格while ((iFindIndex = strValue.find(" ")) != string::npos){strValue.replace(iFindIndex, 1, "");}// 将 & 替换成 与,不然ffmpeg会报错while ((iFindIndex = strValue.find("&")) != string::npos){strValue.replace(iFindIndex, 1, "与");}// 去掉斜杠'/'while ((iFindIndex = strValue.find("/")) != string::npos){strValue.replace(iFindIndex, 1, "");}// 去掉反斜杠'\'while ((iFindIndex = strValue.find("\\")) != string::npos){strValue.replace(iFindIndex, 1, "");}
}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()){memset(szValue, 0, sizeof(szValue));is.getline(szValue, sizeof(szValue));strValue += szValue;}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);if (iRet != 0){// up主上传的视频,json文件存放的节点是page_data/part,缓存官方动漫节点是ep/index_titleiRet = json.GetStr("ep", "index_title", szBuff, MAX_PATH);}if (iRet != 0){is.close();return iRet;}strValue = UnicodeToANSI(UTF8ToUnicode(szBuff));// 去掉特殊字符removeSpecialChar(strValue);//cout << "GetJsonStr: " << iRet << "  " << strValue << endl;strcpy(pszPage, strValue.c_str());iRet = json.GetStr("", "title", szBuff, MAX_PATH);if (iRet != 0){is.close();return iRet;}strValue = UnicodeToANSI(UTF8ToUnicode(szBuff));// 去掉特殊字符removeSpecialChar(strValue);//cout << "GetJsonStr: " << iRet << "    " << strValue << endl;strcpy(pszTitle, strValue.c_str());is.close();return 0;
}void GenerateVideo(const char* pPath)
{if (g_vecAudioPaths.size() == 0 || g_vecAudioPaths.size() != g_vecVideoPaths.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());// 组装命令// 不相等则为m4s文件if (g_vecVideoPaths[i] != g_vecAudioPaths[i]){// 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);}else // 相等则为blv文件{// ffmpeg.exe -f concat -i blv.txt -c copy namesprintf(szCommand, "%sffmpeg.exe -f concat -safe 0 -i %s -c copy %s",pPath, g_vecVideoPaths[i].c_str(), szSvaeFileName);}cout << szCommand << endl;// 使用线程处理std::thread t(funGenerate, szCommand);t.join();cout << i << "    --- " << "输出:" << szSvaeFileName << endl;}
}
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;
}

二、使用步骤

直接将手机bilibili的缓存目录下download文件夹拷贝到当前文件夹下,双击Bilibili.exe即可自动合并生成

总结

jsontool没法提供,可直接下载jsoncpp进行替换该功能,最终生成的执行文件上传审核不过,不知道为啥?有时间上传百度云再分享吧!

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

  1. C++调用ffmpeg批量合并bilibili缓存视频

    文章目录 前言 一.先看效果 二.开始写代码 1.遍历文件 2.获取视频标题和视频名称 3.生成视频 4.主函数 总结 前言 手机bilibili缓存了很多视频,想导入电脑看,但发现缓存的视频被分割成 ...

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

最新文章

  1. 爆赞,对 volatile 关键字讲解最好的一篇文章!
  2. eclipse 关联 Maven本地仓库的配置
  3. 2020年十大机器学习框架
  4. ViewPager Indicator的使用方法
  5. 使用伪指令#pragma pack
  6. Java线上应用故障排查之一:高CPU占用
  7. 排序字段设计_「原创」第四章、模型设计
  8. ITK:具有写访问权限遍历图像的某个区域
  9. 文件包含——本地无视后缀(二)
  10. 前端学习(531):什么是等高布局
  11. 遍历集合常见的两种方式
  12. 2021年中国电子学习课程市场趋势报告、技术动态创新及2027年市场预测
  13. 断点下载 (记录上传、或下载的量
  14. BZOJ1283 序列(费用流)
  15. PDF目录的自动生成
  16. 从程序员到项目经理(29):怎样写文档
  17. c++win32项目 如何显示后再删除一个绘图_如何运用Excel,R等软件结合PPT做出你想要的矢量图...
  18. 学测绘和计算机,测绘工程就业方向与前景 女生学测绘好找工作吗
  19. 算法随笔 — 树结构基础 — 并查集
  20. finalcut剪切快捷键_Final Cut Pro 笔记(一) 常用快捷键与技巧

热门文章

  1. 学java要学哪些_想学好Java要学哪些东西
  2. Cortex-M0学习
  3. Android【语音合成TTS】
  4. 工控机上位机软件的开发历程(二)
  5. C# 淘宝商品微信返利助手开发-(一)返利助手原理
  6. 从零开始学统计 05 | 技术重复和生物学重复
  7. 计算机各个盘找不到应用程序,电脑硬盘打不开 提示找不到应用程序
  8. 【OpenGL游戏开发之一】MAC OS X And Win7 vs2010 搭建OpenGL
  9. 6个免抠素材网站,免费可商用
  10. matlab 矩阵白化,白化(预处理步骤)【转】