引子
我需要实现一个功能
就是把一些段的视频片段批量打乱并且剪在一起
并不要求逻辑性以及完整性
找了很久都没有相关的轮子
我想也是
这个需求确实比较古怪
所以只好自己重新造轮子
大致思路
怎样才能把视频片段打散呢?
- 我想先把视频合并成一个完整的整体,然后导出一个长视频。这样的话,至少不会存在格式之间的冲突。
- 从中截取一些固定时长的小段落,比如10秒一个。
- 随机选择片段并且进行合并。
实现方法
安装ffmpeg
视频处理这方面还是绕不过ffmpeg
mac的话使用brew可以直接安装:
brew install ffmpeg
安装过程不是那么顺利,网络需要比较稳定。还有叫做gcc的依赖不能安装,根据提示下载了开发者工具才装上。
如果是win的话,安装过程相对来说是要简单一些。可以直接去ffmpeg的官网下载,然后在环境变量添加变量就行。
安装ffmpy
这是pyhton的一个库,可以使用python对ffmeg进行调用,使用pip直接安装即可:
pip install ffmpy
分割视频
这里参照了知乎上的一篇文章,讲的非常清楚。
我按照操作写了代码:
split_video=ffmpy.FFmpeg(
inputs={'test.mov': None},
outputs={'test.mov': [
'-ss', '00:00:00',
'-t', '120',
'-vcodec', 'copy',
'-acodec', 'copy'
]}
split_video.run()
以上代码可以成功的运行,并且路径可以采用绝对路径。
上述代码运行之后,则是把视频从最开始裁截一段长为120秒的视频。如果我需要批量裁剪,那么就需要加上一个循环,每次都更新裁剪的起点-ss
参数的数值。而裁剪的长度-t
数值则可以固定不变为需要的长度。
所以我需要一个可以方便计算时间的函数。
计算时间
我参照了这篇博客
这里面使用了一个datetime
的库
里面有几点比较重要
strptime
可以输入字符串转换成一个日期实例,起到自定义时间。不需要输入所有的数值,如果只输入时、分,那么年、月、日就是默认的数值。timedelta
可以对时间进行加减。strftime
可以格式化输出字符串,例如只输出年月日,或者调整输出顺序等。
于是我写了如下代码:
def time_adjust(complete_time:str,split_time:int):
split_point_l=['00:00:00']
# 总时间的实例
complete_time_t=datetime.datetime.strptime(complete_time,'%H:%M:%S')
# 初始时间的实例
start_time_t=datetime.datetime.strptime('00:00:00','%H:%M:%S')
# 开始循环
while start_time_t<complete_time_t:
# 更新后的时间
new_time_t=(start_time_t+datetime.timedelta(seconds=split_time))
# 赋予新的开始时间
start_time_t=new_time_t
split_point_l.append(new_time_t.strftime('%H:%M:%S'))
# 舍弃列表最后一个元素,并计算出最后还剩多少
split_point_l=split_point_l[:-1]
# 计算最后跟总时间差多少秒
gap_time_t=complete_time_t-datetime.datetime.strptime(split_point_l[-1],'%H:%M:%S')
final_time=(datetime.datetime.strptime('00:00:00','%H:%M:%S')+gap_time_t).strftime('%H:%M:%S')
return split_point_l,final_time
这两天有点头晕,写的有点乱。
不过实现的功能就是,输入视频的总时间,和预期每个视频的长度,最后输出一个列表和一个字符串。
这个列表包括了所有即将被分割的小视频的时间点,而字符串则是代表最后一个视频的长度(因为基本上不可能完美的分割每一个视频,最后一个视频肯定会不够设定的时长。)
输出视频时间
以上的函数可以对输入的文件时长计算时间点,那么能不能自动获取视频文件的时长呢?
我看网上很多多说只需要
ffmpeg -i test.mov
在命令行下确实会输出相关的信息,以及一个报错提示(大概是没有指定输出还是什么意思)
但是在ffmpy的调用下就会报错,而且不能传出任何信息
所以我参考了知乎上一位给的代码。
def get_video_duration(filename):
cap = cv2.VideoCapture(filename)
if cap.isOpened():
rate = cap.get(5)
frame_num =cap.get(7)
duration = frame_num/rate
return duration
return -1
虽然看不明白,但确实可以正确的输出。输出的是视频的秒数,再调用datetime转成正确的格式即可。
循环分割视频
因为已经有了时间点的列表,所以继续写了循环分割视频的代码:
def split(complete_time:str,split_time:int=10):
split_point_l,final_time=time_adjust(complete_time,split_time)
# 制作一个每次切割时间的列表
split_time_l=[split_time]*(len(split_point_l)-1)
split_time_l.append(final_time)
print(len(split_point_l)) # 输出分割视频的数量
# 开始循环
i=0
for v1,v2 in zip(split_point_l,split_time_l):
i+=1
split_video= ffmpy.FFmpeg(
inputs={'test.mov': None},
outputs={'%d.mov'%i: [
'-ss', '%s'%v1,
'-t', '%s'%v2,
'-vcodec', 'copy',
'-acodec', 'copy'
]}
)
split_video.run()
return
以上代码实现的效果是,输入完整的时长和分割的长度,就会自动循环分割视频。
合并视频
上文中知乎作者的一篇文章中,已经提到了如何合并视频。
不过是采用创建临时文件的方式。
def concat():
concat = ffmpy.FFmpeg(
global_options=['-f', 'concat','-safe 0'],
inputs={'file_l.txt': None},
outputs={'output.mov': ['-c', 'copy']}
)
concat.run()
return
以上的代码我稍微做了一定的调整,在合并之前先随机生成一个txt文件即可。
这里有一个非常重要的点,让我卡了很久。
txt文件里面如果放着是文件的相对路径,是可以顺利读取并合并的。
但如果是绝对路径则不行。
我一开始以为是斜杠转义之类的问题,研究了很久。
后来在b站的一篇文章上找到了答案。
需要在参数中添加'-safe 0'
,同时txt中的文件路径需要用''
进行包围。
我在windows的系统下做测试,所以路径都是反斜杠,不知道别的平台需不需要。
批量调整音量
视频之间会存在一个音量不统一的情况,观看的效果是非常差的。
我参照了下面up主宰fcpx的设置。
(简单粗暴)fcpx批量统一音量,学会这个,一万条音频也可以搞定,_哔哩哔哩_bilibili
流程总结
所有的操作已经理顺了,最后把流程理一遍,方便自己记住。
- 首先把视频全部导入fcpx,然后批量调整音量。
- 筛选、剪辑最后导出视频。
- 在handbrake中进行压缩,并转换成mp4格式的文件。
- 启动程序进行分割。
- 移动到NAS中的固定文件夹,以方便合并程序调用。
- 根据需要随机合并视频。
最后
ffmpeg实在是太难懂了
我以为看看官方的文档就可以掌握
事实上都是借助网上的中文资料才学会一些操作