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窗口中执行以处理单个视频。

    1. 获取视频时长:ffprobe -show_entries format=duration -v error -select_streams v:0 <视频路径>
      示例输出:
    [FORMAT]
    duration=1200.981333
    [/FORMAT]
    
    1. ffmpeg -threads 4 -i <视频路径> -ss <开始时间点> -t <持续时间(s)> -c copy -y <输出路径>
    2. -threads 4:多线程,感觉作用不是很大
    3. <开始时间点>:格式可以为hh:mm:ss,也可以为具体的视频第几秒
    4. <持续时间(s)>:从指定的开始时间点往后截取多少秒,不是结束时间点
    5. -c copy:音视频都直接复制,不重新编码,速度快
    6. -y:如果指定的输出文件已经存在,则覆盖
    7. 注意:如果输出路径在其他文件夹内,则必须提前创建好文件夹,比如输出文件为./trimed_video/video.mp4,则需要提前创建好trimed_video文件夹,否则报错

    python调用bash指令的方法

    使用subprocess包,详细信息可以自己查,简单用法如下:

    import subprocess
    
    command = "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 = [f for f in os.listdir(dir) if os.path.isfile(f)]
        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 matchs
    
    def 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_seconds
        rows[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 = ) in first_column]
    # 使videos按照first_column顺序排列,避免顺序不匹配导致裁剪了错误的文件
    temp = copy.copy(videos) # 临时变量,备份一下
    for video in videos:
        index = int(re.findall(r'\d+',video)[0]) # 视频序号
        row_index = first_column.index(index)
        temp[row_index] = video
    videos = temp
    
    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命令:

    1. 创建文本文件filelist.txt,并写入要合并的多个视频片段
    file 'input1.mp4' 
    file 'input2.mp4'
    
    1. 执行合并命令:ffmpeg -f concat -safe 0 -i filelist.txt -c copy output.mp4
      由于这种情况比较少,懒得写python代码,自己手动在cmd执行吧

    作者:BO_S__

    物联沃分享整理
    物联沃-IOTWORD物联网 » Python使用FFmpeg实现视频片段批量截取指南

    发表回复