python调用ffmpeg对截取视频片段,可批量处理
本文完全
免费
,非VIP文章,如果您发现需VIP可看全文,请邮箱
联系我:openwebsite@foxmail.com
文章目录
- python调用ffmpeg对截取视频片段,可批量处理
- 用到的ffmpeg命令
- python调用bash指令的方法
- python处理代码
- 准备函数
- python批量处理
- 特殊情况处理
- 可选操作:填补csv数据
- python根据csv数据裁剪视频
- 视频具有多个片段的处理 [TODO]
python调用ffmpeg对截取视频片段,可批量处理
背景:我本地下载了一些番剧,但是片头片尾无用还占空间,因此决定使用ffmpeg对视频切割,只保留中间的正片内容。
用到的ffmpeg命令
ffmpeg
中文文档:https://ffmpeg.github.net.cn/ffmpeg.html
由于csdn点击链接会跳转到安全确认页面,因此选择直接给出链接文本,在edge浏览器中,可以选中链接文本并拖动到标签页区域,直接打开文档,更方便。
先说用到的ffmpeg
命令,你可以自行在cmd窗口中执行以处理单个视频。
- 获取视频时长:
ffprobe -show_entries format=duration -v error -select_streams v:0 <视频路径>
示例输出:
[FORMAT]
duration=1200.981333
[/FORMAT]
ffmpeg -threads 4 -i <视频路径> -ss <开始时间点> -t <持续时间(s)> -c copy -y <输出路径>
-threads 4
:多线程,感觉作用不是很大<开始时间点>
:格式可以为hh:mm:ss
,也可以为具体的视频第几秒<持续时间(s)>
:从指定的开始时间点往后截取多少秒,不是结束时间点-c copy
:音视频都直接复制,不重新编码,速度快-y
:如果指定的输出文件已经存在,则覆盖- 注意:如果输出路径在其他文件夹内,则必须提前创建好文件夹,比如输出文件为
./trimed_video/video.mp4
,则需要提前创建好trimed_video
文件夹,否则报错
python调用bash指令的方法
使用subprocess
包,详细信息可以自己查,简单用法如下:
import subprocesscommand = "ffprobe -show_entries format=duration -v error -select_streams v:0 video.mp4"
result = subprocess.run(command, check=True,capture_output=True,text=True)
print(result.stdout) # 获取命令执行后的输出数据
参数说明:
command
:要执行的命令字符串,可以通过字符串操作来拼接需要的命令check=True
:如果进程退出码不为0,则抛出异常subprocess.CalledProcessError
,可以通过try-catch进行错误处理capture_output=True
:捕获标准输出或标准错误,获取命令执行的输出信息text=True
:默认标准输出为字节类型,这个可以改为字符串类型,方便字符串解析
python处理代码
注意,我所有代码都没考虑时长超过1小时的视频,如果需要操作1小时以上的视频,请自行修改相关代码
准备函数
由于这两个函数可能在多个文件中使用,因此单独创建了一个文件,其他文件需要调用时通过import myfunc
即可
"""myfunc.py"""
import os,re
from pathlib import Path
def match_files(dir,extension,content_range=[], not_content_range=[]):"""在指定目录下找符合扩展名的文件,不递归用法示例:match_files("./",[".mp4", ".mkv"],range(74,80+1))extension:需要的扩展名,字符串列表content_range:包含的范围,为空则不限制,整数列表not_content_range:不包含的范围,整数列表"""matchs = []files = os.listdir(dir)for f in files:if Path(f).suffix in extension: # 检查文件扩展名是否在指定的扩展名列表中# 提取文件名中的第一个数字序列作为编号number = int(re.findall(r'\d+',f)[0])if content_range:# 判断是否指定了包含范围,如果指定则判断是否在范围内if number in content_range and number not in not_content_range :matchs.append(f)else: # 如果不指定范围,则匹配所有if number not in not_content_range :matchs.append(f)return matchsdef time_to_seconds(time_str):"""将时间字符串转换为秒,格式mm:ss"""minutes, seconds = map(int, time_str.split(':'))return minutes * 60 + seconds
python批量处理
import myfunc
import subprocess
import re
"""
注意写好路径,扩展名,以及需要处理的序号范围,排除的序号范围
"""
videos = myfunc.match_files("./",[".mp4", ".mkv"],[140])
start_time_point = "02:35"
end_time_point = "17:42"
for video in videos:# 如果文件名有空格,需要加引号command1 = "ffprobe -show_entries format=duration -v error -select_streams v:0 \""+video+"\""try:# 先获取视频时长result = subprocess.run(command1, check=True,capture_output=True,text=True)duration = round(float(re.search(r"duration=([\d.]+)",result.stdout).group(1)))"""注意修改command2的参数,00默认小时为00,即不考虑时长超过1小时的情况,按需修改"\"./trimed_video/"+video+"\""是输出视频路径需要根据自己的视频情况修改"""command2 = "ffmpeg -threads 4 -i "+"\""+ video +"\""+ " -ss 00:" + start_time_point + " -t "+str(myfunc.time_to_seconds(end_time_point)-myfunc.time_to_seconds(start_time_point)) +" -c copy -y "+"\"./trimed_video/"+video+"\""try:# 运行FFmpeg命令subprocess.run(command2, check=True,capture_output=True)print(f"视频已成功裁剪到 {video}")except subprocess.CalledProcessError as e:print(f"FFmpeg命令执行失败: {e}", video)except subprocess.CalledProcessError as e:print(f"FFmpeg命令执行失败: {e}", video)
特殊情况处理
可能视频的片头和片尾时长并不总是固定的,导致不能方便地按照 python批量处理的代码,直接按相同的片头长度和片尾长度操作,因此我使用了csv表格来记录视频的片头片尾长度,并使用python按照csv表格内的数据裁剪视频。
csv表格示例内容如下片头片尾信息.csv
序号,片头时间点,片尾时间点,总时长,片尾长度,有用视频长度
120,2:16,18:28,20:44,,
122,2:16,17:25,19:41,,
123,2:16,19:11,21:27,,
127,2:16,19:13,20:49,,
序号
,片头时间点
,片尾时间点
,总时长
是必填项,剩余两项可以空着,但是必须填写英文分号。
其实总时长
可以通过ffmpeg命令获取,但是既然是特殊情况,手动打开视频了,填一下总时长也不麻烦
可选操作:填补csv数据
有时候需要填写几个视频信息,来判断这两个序号之间的视频是不是片头片尾时长一样,如果一样就可以通过python批量处理的代码来操作,因此写了下面的代码,可以自动计算csv表格中的最后两列数据,观察片头时间点
和片尾长度
是否一直可以粗略判断
import csv# 文件路径
file_path = "./片头片尾信息.csv"
# 将时间字符串转换为秒
def time_to_seconds(time_str):minutes, seconds = map(int, time_str.split(':'))return minutes * 60 + seconds# 读取CSV文件
with open(file_path, mode='r', encoding='utf-8') as file:reader = csv.reader(file)rows = list(reader)rows = [row for row in rows if row]
for i in range(1, len(rows)):end_time_str = rows[i][2]if rows[i][4] != '':continue # 如果已经有值了,则不再计算total_duration_str = rows[i][3]end_time_seconds = time_to_seconds(end_time_str)total_duration_seconds = time_to_seconds(total_duration_str)tail_length_seconds = total_duration_seconds - end_time_secondsrows[i][4] = str(tail_length_seconds)start_time_seconds = time_to_seconds(rows[i][1])rows[i][5] = str(end_time_seconds - start_time_seconds)# 将更新后的内容写回CSV文件
with open(file_path, mode='w', newline='', encoding='utf-8') as file:writer = csv.writer(file)writer.writerows(rows)
python根据csv数据裁剪视频
import myfunc
import csv, re,subprocess
"""注意修改你想要匹配的文件扩展名"""
videos = myfunc.match_files("./",[".mp4", ".mkv"])
"""注意改成你的csv文件路径"""
with open("./片头片尾信息.csv", mode='r', encoding='utf-8') as file:reader = csv.reader(file)rows = list(reader)# 提取第一列数据
del rows[0]# 删除表头
first_column = [int(row[0]) for row in rows if row] # 使用列表推导式,跳过空行
videos = [video for video in videos if int(re.findall(r'\d+',video)[0]) in first_column]count = 0
for video in videos:command1 = "ffprobe -show_entries format=duration -v error -select_streams v:0 \""+video+"\""try:# 先获取视频时长result = subprocess.run(command1, check=True,capture_output=True,text=True)duration = round(float(re.search(r"duration=([\d.]+)",result.stdout).group(1)))start_time_pint = myfunc.time_to_seconds(rows[count][1])end_time_pount = myfunc.time_to_seconds(rows[count][2])"""注意替换你想要的输出路径"""command2 = "ffmpeg -threads 4 -i "+video + " -ss " + str(start_time_pint) + " -t "+str(end_time_pount-start_time_pint) +" -c copy -y "+"\"./trimed_video/"+video+"\""# print(command2)try:# 运行FFmpeg命令subprocess.run(command2, check=True,capture_output=True)print(f"视频已成功裁剪到 {video}")except subprocess.CalledProcessError as e:print(f"FFmpeg命令执行失败: {e}", video)except subprocess.CalledProcessError as e:print(f"FFmpeg命令执行失败: {e}", video)count += 1
视频具有多个片段的处理 [TODO]
TODO有大佬知道的话欢迎讨论,我觉得先切片再合并太麻烦。
这种特殊情况一般出现在,视频有彩蛋之类的,在片头之前或片尾之后仍有正片内容。
网上搜了但没找到特别好的,找到一个文章但测试后不好用,所以选择了手动切片再合并,
多个视频合并的ffmpeg命令:
- 创建文本文件
filelist.txt
,并写入要合并的多个视频片段
file 'input1.mp4'
file 'input2.mp4'
- 执行合并命令:
ffmpeg -f concat -safe 0 -i filelist.txt -c copy output.mp4
由于这种情况比较少,懒得写python代码,自己手动在cmd执行吧