这个功能也是我个人需求。
需求:我在dy下载了同一个产品介绍的几个短视频,每个视频又是由多个场景组成,这个时候我需要每个视频里抽取一个场景,来组合成一个新视频。
这个就需要用到【根据场景拆分视频】。
大家先看看如下动图,感知场景:
场景即:连续连贯的一段影像为一个场景,上面动图有6个场景。
解决方案:ffmpeg命令计算出新场景起点时候戳,导出到log.txt中,c#解析log.txt,提取到各个场景的pts_time和总长,然后循环截取各个场景视频。
开整。。。。。
一、提取各个场景pts_time和总长度
命令:ffmpeg -i aa.mp4 -filter:v "select='gt(scene,0.15)',showinfo" -f null - 1> log.txt 2>&1
log.txt文件中的场景起始点关键数据:
gt(scene,0.15),这里这个0.15,我的理解是两帧之面差异值0-1,1为完全不同,我测试下来0.15就可以检测出各个场景了,你自己可以根据自己的需要来微调,上代码
public static List GetVideoSenueTime(string filename,float flag,out double duration)
{
string logfile = "log.txt";
string cmd = string.Format(" -i {0} "
+ "-filter:v \"select='gt(scene,{1})',showinfo\" -f null - 1>{2} 2>&1 -y", filename, flag, logfile);
//执行ffmpeg命令
RunCmd(@cmd);
duration = 0;
if (File.Exists(logfile))
{
List lst = new List();
string tmpstr;
int n;
foreach (string line in System.IO.File.ReadLines(logfile))
{
//先找总时长
if (duration == 0)
{
n = line.IndexOf("Duration: ");
if (n < 0) continue;
tmpstr = GetTimeStr(line, n + 10,",");
if (tmpstr.Length > 0)
{
string[] ary = tmpstr.Split('.');
if (ary.Length == 2)
{
TimeSpan t = TimeSpan.Parse(ary[0]);
duration = t.Seconds + double.Parse("0." + ary[1]);
}
else duration = -1;
}
continue;
}
n=line.IndexOf("pts_time:");
if (n < 0) continue;
tmpstr = GetTimeStr(line,n+9," ");
if(tmpstr.Length>0)lst.Add(tmpstr);
}
return lst;
}
return null;
}
2,计算每个场景时长,循环截取各个场景的视频
private void button2_Click(object sender, EventArgs e)
{
if (textbox.Text.Length < 1)
{
MessageBox.Show("请选择文件!!!");
return;
}
count=0;
logno("查询视频各个场景时间起点");
int n = (int)numericUpDown1.Value;
float f = n*1.0f / 100;
double duration;
List list = FFMEPG.GetVideoSenueTime(textbox.Text, f,out duration);
if (list == null || list.Count < 1)
{
log("出错,没有找点场景起点");
return;
}
log("找到"+list.Count+"段场景...");
logno("开始导出各个场景视频");
string file;
double begin = 0;
double end;
double d;
int i = 0;
for (; i < list.Count; i++)
{
end = double.Parse(list[i]);
d = end - begin;
file = FFMEPG.CutFromTime(textbox.Text, begin.ToString(), d.ToString(), i + ".mp4");
if (file.Length > 0)
log("导出文件" + i + ".mp4,From:" + begin + ",To:" + end);
else
log("导出文件" + i + ".mp4 失败");
begin = end;
}
if (duration > 0)
{
file = FFMEPG.CutFromTime(textbox.Text, begin.ToString(), (duration-begin).ToString(), i + ".mp4");
if (file.Length > 0)
log("导出文件" + i + ".mp4,From:" + begin + ",To:" + duration);
else
log("导出文件" + i + ".mp4 失败");
}
}
这是操作按钮事件,在获取场景pts_time的时候,返回了视频总时长,这个是因为pts_time只是表示上一个场景终点,本场景起点,当不是最后一个场景时,两个pts_time相减就得出了场景长度,如果是最后一个pts_time,就无法用这种方法,所以返回的总时长减去最后的pts_time,就得到了最后一个场景长度。
3、截取视频场景函数FFMPEG.CutFromTime
获取到场景起止点长度都已经搞点,我先照常的截取视频来操作,如下命令
ffmpeg -i aaa.mp4 -ss 0 -t 1.23333 -c:v copy output.mp4
剪出的来视频,是找到了交接点,但是多截取了一帧,有些多截取了两帧,查询百度时,得到了很多解释,最后,找到终极答案,修改命令如下:
ffmpeg -i aaa.mp4 -max_muxing_queue_size 1024 -ss 0 -t 1.069 -strict -2 -keyint_min 8 -g 8 -sc_threshold 0 output.mp4
测试也确实正常,完美分割。
代码:
public static string CutFromTime(string OriginFile/*视频源文件*/, string startTime/*开始时间*/, string durationTime/*结束时间*/,string DstFile)
{
//精确剪辑命令
//ffmpeg -i aaa.mp4 -max_muxing_queue_size 1024 -ss 0 -t 1.069 -strict -2 -keyint_min 8 -g 8 -sc_threshold 0 output.mp4
string strCmd = " -ss " + startTime
+ " -i " + OriginFile
+ " -t " + durationTime
+ " -max_muxing_queue_size 1024"
+ " -strict -2 -keyint_min 8 -g 8 -sc_threshold 0"
+ " " + DstFile + " -y ";
RunCmd(strCmd);
if (System.IO.File.Exists(DstFile))
{
return DstFile;
}
return "";
}
4、最后一键完美将视频按场景分解成8个片断
在剪映里原视频与场景视频拼合对比,场景视频播放基本是连续的,丢帧那是肯定的。。。
源码下载:https://download.csdn.net/download/xchenbb/87610784