代码拉取完成,页面将自动刷新
同步操作将从 全糖咖啡/bilibiliConvert 强制同步,此操作会覆盖自 Fork 仓库以来所做的任何修改,且无法恢复!!!
确定后同步将在后台操作,完成时将刷新页面,请耐心等待。
#include "convert.h"
#include "ui_convert.h"
QString sourcePath = "";
QString targetPath = "";
bool checkIsBilibiliVideo(QString path);
QStringList searchBilibiliVideo(QString path);
VideoInfo* resolveVideoInfo(QString sourcePath);
QString genrateUuid();
void decodeM4s(QString inputPath, QString outputPath);
VideoInfo* resolvePCVideo(QString sourcePath);
VideoInfo* resolveMobileVideo(QString sourcePath);
QString replaceSpeciChar(QString input);
int doubleToInt(double d);
int totalCnt = 0;
int finishCnt = 0;
qint64 currentFileSize = 0;
Convert::Convert(QWidget *parent) :
QWidget(parent),
ui(new Ui::Convert)
{
ui->setupUi(this);
this->setWindowIcon(QIcon(":/image/logo.ico"));
QDir dir(".\\.temp");
dir.removeRecursively();
dir.mkpath(".\\.temp");
auto *worker = new ExecWorker;
// 调用 moveToThread 将该任务交给 workThread
worker->moveToThread(&workerThread);
// operate 信号发射后启动线程工作
connect(this, SIGNAL(operate(QStringList, QString)), worker, SLOT(doWork(QStringList, QString)));
// 该线程结束时销毁
connect(&workerThread, &QThread::finished, worker, &QObject::deleteLater);
// 线程结束后发送信号,对结果进行处理
connect(worker, SIGNAL(onFinish(QString, QString)), this, SLOT(handleResults(QString, QString)));
// 启动线程
workerThread.start();
//处理进度条
connect(ui->ffmpegProgress, SIGNAL(inProcess(qint64)), this, SLOT(handleFFmpegProcess(qint64)));
connect(ui->ffmpegProgress, SIGNAL(onFinish()), this, SLOT(handleFFmpegFinish()));
}
Convert::~Convert()
{
delete ui;
workerThread.quit();
workerThread.wait();
}
void Convert::on_sourcePathBtn_pressed()
{
QString path = QFileDialog::getExistingDirectory(this, "缓存视频源文件路径", "./", QFileDialog::ShowDirsOnly);
sourcePath = path;
ui->sourcePathText->setText(sourcePath);
}
void Convert::on_targetPathBtn_pressed()
{
QString path = QFileDialog::getExistingDirectory(this, "MP4输出路径", "./", QFileDialog::ShowDirsOnly);
targetPath = path;
ui->targetPathText->setText(targetPath);
}
void Convert::on_convertBtn_pressed()
{
if(!checkInput())
{
return;
}
m_pTimer = new QTimer(this);
connect(m_pTimer, SIGNAL(timeout()), this, SLOT(handleTimeout()));
m_pTimer->start(TIMER_TIMEOUT);
ui->convertBtn->setDisabled(true);
totalCnt = 0;
finishCnt = 0;
ui->log->append("源路径:" + ui->sourcePathText->text());
ui->log->append("目标路径:" + ui->targetPathText->text());
QStringList videoPath = searchBilibiliVideo(ui->sourcePathText->text());
ui->log->append("找到视频数量:" + QString::number(videoPath.size()));
QStringListIterator strIterator(videoPath);
while (strIterator.hasNext()) {
convertBilibiliVideoToMp4(strIterator.next(), ui->targetPathText->text());
}
totalCnt = videoPath.size();
ui->totalCnt->setText(QString::number(totalCnt));
}
void Convert::on_clearLog_pressed()
{
ui->log->clear();
}
/**
* @brief searchBilibiliVideo 搜索文件夹下的bilibili视频并返回文件夹路径
* @param path 文件夹
* @return QStringList
*/
QStringList searchBilibiliVideo(QString path)
{
QStringList res;
QDir dir(path);
if(!dir.exists())
{
return res;
}
QStringList filters;
filters << "entry.json" << ".videoInfo";
QDirIterator dir_iterator(path, filters, QDir::Files | QDir::NoSymLinks,QDirIterator::Subdirectories);
while(dir_iterator.hasNext())
{
dir_iterator.next();
QFileInfo fileInfo = dir_iterator.fileInfo();
QString absolutePath = fileInfo.absolutePath();
if(checkIsBilibiliVideo(absolutePath))
{
res.append(absolutePath);
}
}
return res;
}
/**
* @brief checkIsBilibiliVideo 判断文件夹下[entry.json,video.m4s,audio.m4s,.videoInfo]文件是否存在
* @param path 文件夹
* @return bool
*/
bool checkIsBilibiliVideo(QString path)
{
QDir dir(path);
if(!dir.exists())
{
return false;
}
QStringList filters;
filters << "entry.json" << "*.m4s" << ".videoInfo";
QDirIterator dir_iterator(path, filters, QDir::Files | QDir::NoSymLinks,QDirIterator::Subdirectories);
QStringList string_list;
while(dir_iterator.hasNext())
{
dir_iterator.next();
QFileInfo fileInfo = dir_iterator.fileInfo();
QString absoluteFilePath = fileInfo.absoluteFilePath();
string_list.append(absoluteFilePath);
}
if(string_list.size() == 3) {
return true;
}
return false;
}
void Convert::convertBilibiliVideoToMp4(QString sourcePath, QString targetPath)
{
/** 视频信息 */
VideoInfo* info = resolveVideoInfo(sourcePath);
//计算总大小
currentFileSize = 0;
QFile s1(info->videoFilePath);
currentFileSize += s1.size();
QFile s2(info->audioFilePath);
currentFileSize += s2.size();
/** process进度文件的路径 */
QDir f1(".");
QString progressPath = f1.absoluteFilePath(".\\.temp\\" + genrateUuid() + ".progress");
QFile proFile(progressPath);
proFile.open(QIODevice::WriteOnly);
proFile.write("");
proFile.flush();
proFile.close();
ui->ffmpegProgress->setPersent(0);
ui->ffmpegProgress->setInterval(10);
ui->ffmpegProgress->updateFilePath(progressPath);
QString outputPathStr = targetPath + "\\" + info->title.trimmed() + "\\";
QDir *outputPath = new QDir;
if(!(outputPath->exists(outputPathStr)))
{
outputPath->mkpath(outputPathStr);
}
ui->log->append("添加任务【" + info->title + "-" + info->page + "-" + info->part + "......】");
/** 拼接命令 */
QStringList cmdList;
cmdList << "ffmpeg";
cmdList << "-i";
cmdList << info->videoFilePath;
cmdList << "-i";
cmdList << info->audioFilePath;
cmdList << "-progress";
cmdList << progressPath;
cmdList << "-codec";
cmdList << "copy";
cmdList << outputPathStr + QString::number(info->page) + "-" + info->part.trimmed() + ".mp4";
cmdList << "-y";
operate(cmdList, info->title.trimmed() + "\\" + QString::number(info->page) + "-" + info->part.trimmed() + ".mp4");
}
/**
* @brief resolveVideoInfo 将文件夹中的视频信息提取出来
* @param sourcePath 视频源路径
* @return 视频信息
*/
VideoInfo* resolveVideoInfo(QString sourcePath)
{
/** 判断是手机版的缓存还是PC版的缓存 */
bool isMobile = false;
QStringList filters;
filters << "entry.json" << ".videoInfo";
QDirIterator dir_iterator(sourcePath, filters, QDir::Files | QDir::NoSymLinks,QDirIterator::Subdirectories);
QStringList string_list;
while(dir_iterator.hasNext())
{
dir_iterator.next();
QFileInfo fileInfo = dir_iterator.fileInfo();
QString absoluteFilePath = fileInfo.absoluteFilePath();
string_list.append(absoluteFilePath);
}
QStringListIterator strIterator(string_list);
while (strIterator.hasNext()) {
QString temp = strIterator.next();
if(temp.contains("entry.json")){
isMobile = true;
}
}
if(isMobile){
return resolveMobileVideo(sourcePath);
}else {
return resolvePCVideo(sourcePath);
}
}
VideoInfo* resolveMobileVideo(QString sourcePath)
{
/** 获取m4s和json文件的路径 */
QDir dir(sourcePath);
if(!dir.exists())
{
throw -1;
}
QStringList filters;
filters << "entry.json" << "video.m4s" << "audio.m4s";
QDirIterator dir_iterator(sourcePath, filters, QDir::Files | QDir::NoSymLinks,QDirIterator::Subdirectories);
QStringList string_list;
while(dir_iterator.hasNext())
{
dir_iterator.next();
QFileInfo fileInfo = dir_iterator.fileInfo();
QString absoluteFilePath = fileInfo.absoluteFilePath();
string_list.append(absoluteFilePath);
}
QString jsonFilePath = "";
QString videoFilePath = "";
QString audioFilePath = "";
QStringListIterator strIterator(string_list);
while (strIterator.hasNext()) {
QString temp = strIterator.next();
if(temp.contains("entry.json")){
jsonFilePath = temp;
}else if(temp.contains("video.m4s")){
videoFilePath = temp;
}else if(temp.contains("audio.m4s")){
audioFilePath = temp;
}
}
/** 解析json */
QFile file(jsonFilePath);
file.open(QIODevice::ReadOnly | QIODevice::Text);
QString value = file.readAll();
file.close();
QJsonParseError parseJsonErr;
QJsonDocument document = QJsonDocument::fromJson(value.toUtf8(), &parseJsonErr);
if (! (parseJsonErr.error == QJsonParseError::NoError)) {
QMessageBox::about(NULL, "提示", "配置文件错误!");
throw -1;
}
QJsonObject jsonObject = document.object();
QString title = jsonObject["title"].toString();
QString quality = jsonObject["quality_pithy_description"].toString();
// 普通视频拥有该字段
QJsonValue pageData = jsonObject.value("page_data");
int page = 0;
QString part = "";
if(!pageData.isUndefined()) {
QJsonObject pageDataObj = pageData.toObject();
page = pageDataObj["page"].toInt();
part = pageDataObj["part"].toString();
}else {
// 番剧视频
pageData = jsonObject.value("ep");
if(!pageData.isUndefined()) {
QJsonObject pageDataObj = pageData.toObject();
page = pageDataObj["index"].toString().toInt();
part = pageDataObj["index_title"].toString();
}
}
if(pageData.isUndefined()) {
throw -1;
}
/** 组装信息 */
VideoInfo* temp = new VideoInfo;
temp->title = replaceSpeciChar(title);
temp->part = replaceSpeciChar(part);
temp->page = page;
temp->audioFilePath = audioFilePath;
temp->videoFilePath = videoFilePath;
return temp;
}
/**
* @brief replaceSpeciChar 替换掉window文件夹中不应出现的特殊字符
* @param input 输入字符
* @return 替换后的字符
*/
QString replaceSpeciChar(QString input)
{
input = input.replace("|","");
input = input.replace(">","");
input = input.replace("<","");
input = input.replace("\"","");
input = input.replace("?","");
input = input.replace("*","");
input = input.replace(":","");
input = input.replace("/","");
input = input.replace("\\","");
input = input.replace("&","");
input = input.replace('\a',"");
input = input.replace('\b',"");
input = input.replace('\f',"");
input = input.replace('\n',"");
input = input.replace('\r',"");
input = input.replace('\t',"");
input = input.replace('\v',"");
return input;
}
VideoInfo* resolvePCVideo(QString sourcePath)
{
/** 获取m4s和json文件的路径 */
QDir dir(sourcePath);
if(!dir.exists())
{
throw -1;
}
QStringList filters;
filters << ".videoInfo" << "*.m4s";
QDirIterator dir_iterator(sourcePath, filters, QDir::Files | QDir::NoSymLinks,QDirIterator::Subdirectories);
QStringList string_list;
while(dir_iterator.hasNext())
{
dir_iterator.next();
QFileInfo fileInfo = dir_iterator.fileInfo();
QString absoluteFilePath = fileInfo.absoluteFilePath();
string_list.append(absoluteFilePath);
}
QString jsonFilePath = "";
QStringList m4sPathList;
QStringListIterator strIterator(string_list);
while (strIterator.hasNext()) {
QString temp = strIterator.next();
if(temp.contains(".videoInfo")){
jsonFilePath = temp;
}else if(temp.contains(".m4s")){
m4sPathList << temp;
}
}
/** 解析json */
QFile file(jsonFilePath);
file.open(QIODevice::ReadOnly | QIODevice::Text);
QString value = file.readAll();
file.close();
QJsonParseError parseJsonErr;
QJsonDocument document = QJsonDocument::fromJson(value.toUtf8(), &parseJsonErr);
if (! (parseJsonErr.error == QJsonParseError::NoError)) {
QMessageBox::about(NULL, "提示", "配置文件错误!");
throw -1;
}
QJsonObject jsonObject = document.object();
QString title = jsonObject["groupTitle"].toString();
QString part = jsonObject["title"].toString();
int page = jsonObject["p"].toInt();
/** 解密m4s文件 */
QStringList decodedM4sList;
QDir f1(".");
decodedM4sList << f1.absoluteFilePath(".\\.temp\\" + genrateUuid() + ".m4s");
decodedM4sList << f1.absoluteFilePath(".\\.temp\\" + genrateUuid() + ".m4s");
decodeM4s(m4sPathList.at(0), decodedM4sList.at(0));
decodeM4s(m4sPathList.at(1), decodedM4sList.at(1));
/** 组装信息 */
VideoInfo* temp = new VideoInfo;
temp->title = replaceSpeciChar(title);
temp->part = replaceSpeciChar(part);
temp->page = page;
temp->audioFilePath = decodedM4sList.at(0);
temp->videoFilePath = decodedM4sList.at(1);
return temp;
}
/**
* @brief decodeM4s 解密b站m4s缓存视频
* @param inputPath 文件所在路径
* @param outputPath 解密文件保存路径
*/
void decodeM4s(QString inputPath, QString outputPath)
{
QFile fin(inputPath);
fin.open(QIODevice::ReadOnly);
QFile fout(outputPath);
fout.open(QIODevice::WriteOnly);
qint64 r;
bool flag = true;
do {
uchar buffer[1024];
r = fin.read((char*)buffer,sizeof(buffer));
if (r<0){
throw 1;
}
if (r<=0)
break;
QByteArray array((char*)buffer, r);
if(flag)
{
flag = false;
array.remove(0, 9);
array[3] = 0x20;
if(!(array[16] == 0x69 && array[17] == 0x73 && array[18] == 0x6f && array[19] == 0x35)) {
array.remove(16, 4);
}
}
fout.write(array);
} while (r>0);
fin.close();
fout.close();
}
/**
* @brief genrateUuid 生成不重复的文件名字
* @return
*/
QString genrateUuid()
{
QUuid id = QUuid::createUuid();
QString strId = id.toString();
QString timestamp = QString::number(QDateTime::currentMSecsSinceEpoch());
return timestamp+strId.remove("{").remove("}").remove("-");
}
/**
* @brief Convert::handleResults 处理命令行返回结果
* @param result ffmpeg返回
* @param metaStr 元数据
*/
void Convert::handleResults(QString result, QString metaStr)
{
// qDebug() << result;
ui->log->append("保存到视频【" + metaStr + "】");
finishCnt++;
ui->finishCnt->setText(QString::number(finishCnt));
}
void Convert::handleTimeout()
{
ui->convertBtn->setDisabled(false);
m_pTimer->stop();
}
/**
* @brief checkInput 检测输入是否合法
*/
bool Convert::checkInput()
{
if(ui->sourcePathText->text().isEmpty() || ui->targetPathText->text().isEmpty())
{
QMessageBox::warning(NULL, "提示", "亲,请先选择路径,不然会报错的!");
return false;
}
return true;
}
void Convert::handleMouseReleaseEvent()
{
QDesktopServices::openUrl(QUrl(QString("https://www.bilibili.com/video/BV17M411h714/?share_source=copy_web&vd_source=ecf24c957991c7b438afdd43d0d3b652")));
}
/**
* @brief Convert::handleFFmpegProcess 处理FFmpegProgress的进度
* @param p 进度值
*/
void Convert::handleFFmpegProcess(qint64 p)
{
double a = (double)p / (double)currentFileSize;
a = a * 100;
int value = doubleToInt(a);
if(value >= 100)
{
value = 99;
}
ui->ffmpegProgress->setPersent(value);
}
/**
* @brief Convert::handleFFmpegFinish 处理FFmpegProgress的完成信号
*/
void Convert::handleFFmpegFinish()
{
ui->ffmpegProgress->stopProcess();
ui->ffmpegProgress->setPersent(100);
}
/**
* @brief doubleToInt 将double转为int
* @param d
* @return
*/
int doubleToInt(double d)
{
double intPart = floor(d);
if ((d - intPart) >= (double)0.5)
{
return (intPart + 1);
}
else
{
return intPart;
}
}
void Convert::on_btnBilibili_pressed()
{
QDesktopServices::openUrl(QUrl(QString("https://www.bilibili.com/video/BV17M411h714/?share_source=copy_web&vd_source=ecf24c957991c7b438afdd43d0d3b652")));
}
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。