C#:将blibli缓存文件批量转换为MP4文件
一、概述:我们主要处理哔哩哔哩缓存文件夹每一集中的audio.m4s和video.m4s以及entry.json文件,audio.m4s内容是每一集的音频文件,而vedio.m4s内容是视频的画面,也就是说哔哩哔哩将一个视频分解为音频部分和视频画面两部分,而我们要做的将这两部分合成并转换为MP4格式的文件,那么这个MP4文件到底取什么名字呢?这是就需要entry.json文件了,这里面存放的是视频每一集的基本信息,通过这个文件里面的信息来给出输出文件命名。
二、实现步骤:
1.递归访问指定文件目录找出每一集的audio.4ms文件、video.4ms文件以及entry.json文件
2.获取entry.json中的视频信息(视频集数名字)
3.调用进程ffmpeg(程序)将audio.4ms文件、video.4ms文件合成MP4文件
具体步骤:
1.递归找出所有需要文件的路径:
var cap=new List<string>(){};//定义的保存所有文件路径的容器
var cap1=new List<string>(){};
cap=GetAllFilesFromPath(inpath,true);//保存所有的audio.4ms文件和video.4ms文件路径
cap1=GetAllFilesFromPath(inpath,"*entry.json",true);//保存所有entry.json文件路径
具体代码如下:
public static bool ExistsDirectory(string direName)
{
return System.IO.Directory.Exists(direName);
}
// 递归获取某一目录下的符合条件的所有文件
public static List<string> _GetAllFilesFromPath(string path, string filter, bool DiGui = true)//这里DiGui为true代表使用递归调用
{
List<string> files = new List<string>();
if (!ExistsDirectory(path))//如果该文件目录存在
return files;
if (DiGui)
{
string[] dirs = Directory.GetDirectories(path);//得到子目录
foreach (var str in dirs)
files.AddRange(GetAllFilesFromPath(str, filter, DiGui));
}
string[] tempfiles = Directory.GetFiles(path, filter);
files.AddRange(tempfiles);
return files;
}
public static List<string> GetAllFilesFromPath(string path, string filters, bool DiGui = true)
{
List<string> files = new List<string>();
if (!string.IsNullOrWhiteSpace(filters))
{
foreach (var item in filters.Split(new Char[] { ',', '|' }, StringSplitOptions.RemoveEmptyEntries))
{
var _files = _GetAllFilesFromPath(path, item, DiGui);
foreach (var _file in _files)
{
files.Add(_file);
}
}
return files;
}
else
return GetAllFilesFromPath(path, DiGui);
}
// 递归获取某一目录下的mp4所有文件
public static List<string> GetAllFilesFromPath(string path, bool DiGui = true)
{
return GetAllFilesFromPath(path, "*.m4s", DiGui);
}
2.通过访问每一集的entry.json文件,获得输出文件名
a.OutFile=outpath+@"\"+GetFileJson(cap1[i]);//这里的a.outFile就是输出路径和文件名, GetFileJson(cap1[i])就是获取每个entry文件中的part部分后面的字符串名,至于我为啥要写字符串处理函数GetFileJson(cap1[i])呢?那是因为我的SharpDevelop 5.1软件无法引入处理.json的文件,所以就自己写了一个。
至于.json文件里面到底有些啥呢?我们打开一个来看看
{"media_type":2,"has_dash_audio":true,"is_completed":true,"total_bytes":6436658,"downloaded_bytes":6436658,"title":"ITZY新曲Mafia In the morning舞蹈版MV公开","type_tag":"16","cover":"http:\/\/i0.hdslb.com\/bfs\/archive\/8a5f741fd079af38481b177877ec4f0350d80651.jpg","video_quality":16,"prefered_video_quality":16,"guessed_total_bytes":0,"total_time_milli":182101,"danmaku_count":85,"time_update_stamp":1620308481186,"time_create_stamp":1620308477092,"can_play_in_advance":true,"interrupt_transform_temp_file":false,"quality_pithy_description":"360P","quality_superscript":"","cache_version_code":6235200,"preferred_audio_quality":0,"audio_quality":0,"avid":417942654,"spid":0,"seasion_id":0,"bvid":"BV1SV41177mc","owner_id":16720403,"page_data":{"cid":334369010,"page":1,"from":"vupload","part":"MV","link":"","vid":"","has_alias":false,"tid":193,"width":3840,"height":2160,"rotate":0,"download_title":"视频已缓存完成","download_subtitle":"ITZY新曲Mafia In the morning舞蹈版MV公开 MV"}}
而我主要将part后面部分截取了下来也就是MV,即将MV作为文件的一个输出名。
代码如下:
//获取文件名字
public static string GetFileJson(string filepath)
{
string json = string.Empty;
string c="";
using (FileStream fs = new FileStream(filepath, FileMode.Open, System.IO.FileAccess.Read, FileShare.ReadWrite))
{
using (StreamReader sr = new StreamReader(fs, Encoding.GetEncoding("UTF-8")))
{
json = sr.ReadLine().ToString();
int j=0;
for(int i=0;i<json.Length;i++)
{
if(json[i]=='"')
if(json[i+1]=='p')
if(json[i+2]=='a')
if(json[i+3]=='r')
if(json[i+4]=='t')
{j=i;
j=j+8;
break;
}
}
while(json[j]!='"')
{ c=c+json[j];
j++;
}
c=c+".mp4";
}
}
return c;
}
3.调用ffmpeg文件合成.mp4文件
a.ConvertVideo();//调用ffmpeg文件
代码如下:
public class CFfmpeg
{
public static string ffmpegtool = @"D:\ffmpeg\bin\ffmpeg.exe";//ffmpeg.exe文件下载路径
public static string outFile = @"D:\ffmpeg\bin\972637678\c_328764377\16\output2.mp4";//默认输出路径
public static string sourceFile = @"D:\ffmpeg\input\417942654\c_334370165\16\audio.m4s";//默认音频路径
public static string sourceFile1 = @"D:\ffmpeg\input\417942654\c_334370165\16\video.m4s";//默认视频画面路径
public string SourceFile{
set{
sourceFile=value;
}
get{
return sourceFile;
}
}
public string SourceFile1{
set{
sourceFile1=value;
}
get{
return sourceFile1;
}
}
public string OutFile{
set{
outFile=value;
}
get{
return outFile;
}
}
public void ConvertVideo()
{
Process p = new Process();//建立外部调用线程
p.StartInfo.FileName = ffmpegtool;//要调用外部程序的绝对路径
//传入FFMPEG的参数
string strArg = " -i " + sourceFile +" -i "+sourceFile1 + " -codec copy "+outFile ;
p.StartInfo.Arguments = strArg;
p.StartInfo.UseShellExecute = false;//不使用操作系统外壳程序启动线程
p.StartInfo.RedirectStandardError = true;//把外部程序错误输出写到StandardError流中
p.StartInfo.CreateNoWindow = false;//不创建进程窗口
p.ErrorDataReceived += new DataReceivedEventHandler(Output);//这里是FFMPEG输出流的时候产生的事件,这里是把流的处理过程转移到下面的方法中
p.Start();//启动线程
p.BeginErrorReadLine();//开始异步读取
p.WaitForExit();//阻塞等待进程结束
p.Close();//关闭进程
p.Dispose();//释放资源
}
private void Output(object sendProcess, DataReceivedEventArgs output)
{
if (!String.IsNullOrEmpty(output.Data))
{
Console.WriteLine(output.Data);
}
}
}
三、完整代码
/*
* Created by SharpDevelop.
* User: 12875
* Date: 2021/4/23
* Time: 14:24
*
* To change this template use Tools | Options | Coding | Edit Standard Headers.
*/
/*1.如何调用外部命令。(解决)
* 2.如何解析集数(entry.json)文件中。
* 3.连续的递归查找要转换的每一集。
*
*
*
*/
using System;
using System.Linq;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Collections.Generic;
//\ffmpeg.exe -i video.m4s -i audio.m4s -codec copy Output.mp4
namespace c20210423
{
class Program
{
//获取文件名字
public static string GetFileJson(string filepath)
{
string json = string.Empty;
string c="";
using (FileStream fs = new FileStream(filepath, FileMode.Open, System.IO.FileAccess.Read, FileShare.ReadWrite))
{
using (StreamReader sr = new StreamReader(fs, Encoding.GetEncoding("UTF-8")))
{
json = sr.ReadLine().ToString();
int j=0;
for(int i=0;i<json.Length;i++)
{
if(json[i]=='"')
if(json[i+1]=='p')
if(json[i+2]=='a')
if(json[i+3]=='r')
if(json[i+4]=='t')
{j=i;
j=j+8;
break;
}
}
while(json[j]!='"')
{ c=c+json[j];
j++;
}
c=c+".mp4";
}
}
return c;
}
public static bool ExistsDirectory(string direName)
{
return System.IO.Directory.Exists(direName);
}
// 递归获取某一目录下的符合条件的所有文件
public static List<string> _GetAllFilesFromPath(string path, string filter, bool DiGui = true)//这里DiGui为true代表使用递归调用
{
List<string> files = new List<string>();
if (!ExistsDirectory(path))//如果该文件目录存在
return files;
if (DiGui)
{
string[] dirs = Directory.GetDirectories(path);//得到子目录
foreach (var str in dirs)
files.AddRange(GetAllFilesFromPath(str, filter, DiGui));
}
string[] tempfiles = Directory.GetFiles(path, filter);
files.AddRange(tempfiles);
return files;
}
public static List<string> GetAllFilesFromPath(string path, string filters, bool DiGui = true)
{
List<string> files = new List<string>();
if (!string.IsNullOrWhiteSpace(filters))
{
foreach (var item in filters.Split(new Char[] { ',', '|' }, StringSplitOptions.RemoveEmptyEntries))
{
var _files = _GetAllFilesFromPath(path, item, DiGui);
foreach (var _file in _files)
{
files.Add(_file);
}
}
return files;
}
else
return GetAllFilesFromPath(path, DiGui);
}
// 递归获取某一目录下的mp4所有文件
public static List<string> GetAllFilesFromPath(string path, bool DiGui = true)
{
return GetAllFilesFromPath(path, "*.m4s", DiGui);
}
//合成音频文件
public class CFfmpeg
{
public static string ffmpegtool = @"D:\ffmpeg\bin\ffmpeg.exe";//ffmpeg.exe文件下载路径
public static string outFile = @"D:\ffmpeg\bin\972637678\c_328764377\16\output2.mp4";//默认输出路径
public static string sourceFile = @"D:\ffmpeg\input\417942654\c_334370165\16\audio.m4s";//默认音频路径
public static string sourceFile1 = @"D:\ffmpeg\input\417942654\c_334370165\16\video.m4s";//默认视频画面路径
public string SourceFile{
set{
sourceFile=value;
}
get{
return sourceFile;
}
}
public string SourceFile1{
set{
sourceFile1=value;
}
get{
return sourceFile1;
}
}
public string OutFile{
set{
outFile=value;
}
get{
return outFile;
}
}
public void ConvertVideo()
{
Process p = new Process();//建立外部调用线程
p.StartInfo.FileName = ffmpegtool;//要调用外部程序的绝对路径
//传入FFMPEG的参数
string strArg = " -i " + sourceFile +" -i "+sourceFile1 + " -codec copy "+outFile ;
p.StartInfo.Arguments = strArg;
p.StartInfo.UseShellExecute = false;//不使用操作系统外壳程序启动线程
p.StartInfo.RedirectStandardError = true;//把外部程序错误输出写到StandardError流中
p.StartInfo.CreateNoWindow = false;//不创建进程窗口
p.ErrorDataReceived += new DataReceivedEventHandler(Output);//这里是FFMPEG输出流的时候产生的事件,这里是把流的处理过程转移到下面的方法中
p.Start();//启动线程
p.BeginErrorReadLine();//开始异步读取
p.WaitForExit();//阻塞等待进程结束
p.Close();//关闭进程
p.Dispose();//释放资源
}
private void Output(object sendProcess, DataReceivedEventArgs output)
{
if (!String.IsNullOrEmpty(output.Data))
{
Console.WriteLine(output.Data);
}
}
}
public static void Main(string[] args)
{
Console.WriteLine("Hello World!");
string inpath=@"D:\ffmpeg\input";//要递归访问的文件目录默认路径
string outpath=@"D:\ffmpeg\output";//生成文件输出目录默认路径
var cap=new List<string>(){};
var cap1=new List<string>(){};
if(args.Length==0)
{
inpath=@"D:\ffmpeg\input";
outpath=@"D:\ffmpeg\output";}
else if(args.Length==2)
{
inpath=args[0];
outpath=args[1];
}
cap=GetAllFilesFromPath(inpath,true);//保存路径
cap1=GetAllFilesFromPath(inpath,"*entry.json",true);//保存的是文件名
CFfmpeg a=new CFfmpeg();
for(int j=0;j<cap1.Count;j++)
{
Console.WriteLine(GetFileJson(cap1[j]));
}
for(int i=0;i<cap1.Count;i++)
{
a.SourceFile1=cap[2*i];
a.SourceFile=cap[2*i+1];
Console.WriteLine(cap[2*i]);
Console.WriteLine(cap[2*i+1]);
a.OutFile=outpath+@"\"+GetFileJson(cap1[i]);
a.ConvertVideo();
}
Console.Write("Press any key to continue . . . ");
Console.ReadKey(true);
}
}
}
四、效果显示