使用ffmpeg在Python中获取视频时长

12

我已经在我的电脑上使用pip ffprobe命令安装了ffprobe,并从这里安装了ffmpeg。

然而,我仍然无法成功运行此处列出的代码。

我试图不成功地使用以下代码。

SyntaxError: Non-ASCII character '\xe2' in file GetVideoDurations.py
on line 12, but no encoding declared; see
http://python.org/dev/peps/pep-0263/ for details

有人知道是什么问题吗?我的目录引用有问题吗?我需要确保.py 和视频文件在特定的位置吗?

import subprocess

def getLength(filename):
    result = subprocess.Popen(["ffprobe", "filename"],
    stdout = subprocess.PIPE, stderr = subprocess.STDOUT)
    return [x for x in result.stdout.readlines() if "Duration" in x]

fileToWorkWith = ‪'C:\Users\PC\Desktop\Video.mkv'

getLength(fileToWorkWith)

如果问题有些基础,我表示抱歉。我只需要能够迭代一组视频文件并获取它们的开始时间和结束时间。

谢谢!

9个回答

20

不需要循环遍历FFprobe的输出。有一个简单的命令返回输入文件的长度

ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 <input_video>

您可以使用以下方法来获取持续时间:

def get_length(input_video):
    result = subprocess.run(['ffprobe', '-v', 'error', '-show_entries', 'format=duration', '-of', 'default=noprint_wrappers=1:nokey=1', input_video], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
    return float(result.stdout)

@JavierC.H. 不要使用字符串格式化来创建Bash命令。如果用户提供了“input_video”,那么您会让他们轻松地从Python代码中运行任意的Bash命令。 - user3064538
ffmpeg -i file.mkv 2>&1 | grep -o -P "(?<=Duration: ).*?(?=,)" 的输出格式能否转换成 HH:MM:SS 格式? - alper
对于任何获得“N/A”持续时间的人,也许您需要重新打包文件以添加标题。请参见https://dev59.com/AVsX5IYBdhLWcg3wNdHj#40117749 - Fabien Snauwaert

12

我建议使用FFmpeg自带的FFprobe。

Chamath给出的答案非常接近,但最终对我来说失败了。

仅作为一条附注,我正在使用Python 3.5和3.6,以下是对我有效的内容。

import subprocess 

def get_duration(file):
    """Get the duration of a video using ffprobe."""
    cmd = 'ffprobe -i {} -show_entries format=duration -v quiet -of csv="p=0"'.format(file)
    output = subprocess.check_output(
        cmd,
        shell=True, # Let this run in the shell
        stderr=subprocess.STDOUT
    )
    # return round(float(output))  # ugly, but rounds your seconds up or down
    return float(output)
如果您想将此函数放入类中并在Django(1.8-1.11)中使用它,只需更改一行代码并将此函数放入类中,如下所示:
def get_duration(file):
def get_duration(self, file):

注意: 在本地使用相对路径可以正常工作,但生产服务器需要绝对路径。您可以使用os.path.abspath(os.path.dirname(file))来获取视频或音频文件的路径。


3
shell=True 不推荐使用。 - alper

10
使用ffmpeg-python包(https://pypi.org/project/ffmpeg-python/
import ffmpeg
duration = ffmpeg.probe(local_file_path)["format"]["duration"]

其中local_file_path是您文件的相对或绝对路径。


1
应该是被接受的答案。适用于Python 3.9和3.11,不需要子进程输出检查。 - Fipsi
这对我来说不起作用: AttributeError: 模块 'ffmpeg' 没有 'probe' 属性 - undefined
@SaeedEsmaili,我的错:应该是ffmpeg-python(而不是python-ffmpeg) - undefined

2
我认为Chamath的第二条评论回答了这个问题:你的脚本中有一个奇怪的字符,可能是因为你使用了`而不是',或者你有一个带有非英语重音的单词,类似于这样。
另外,对于你正在做的事情,你也可以尝试使用MoviePy,它像你一样解析ffmpeg输出(但也许将来我会使用Chamath的ffprobe方法,它看起来更干净):
import moviepy.editor as mp
duration =  mp.VideoFileClip("my_video.mp4").duration

2

基于 @llogan 给出的指导和指向 链接,使用 ffprobe 更新解决方案:

import subprocess

def get_duration(input_video):
    cmd = ["ffprobe", "-i", input_video, "-show_entries", "format=duration",
           "-v", "quiet", "-sexagesimal", "-of", "csv=p=0"]
    return subprocess.check_output(cmd).decode("utf-8").strip()

由于stderr输出,解决方案不稳定:

ffmpegstderr输出不适用于机器解析,因此被认为是不稳定的。

我从以下文档 (https://codingwithcody.com/2014/05/14/get-video-duration-with-ffmpeg-and-python/) 和 https://dev59.com/5m025IYBdhLWcg3wEhdb#6239379 获得帮助。

实际上,sed是不必要的:ffmpeg -i file.mp4 2>&1 | grep -o -P "(?<=Duration: ).*?(?=,)"


您可以使用以下方法以HH:MM:SS格式获取持续时间:

import subprocess

def get_duration(input_video):
    # cmd: ffmpeg -i file.mkv 2>&1 | grep -o -P "(?<=Duration: ).*?(?=,)"
    p1 = subprocess.Popen(['ffmpeg',  '-i', input_video], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
    p2 = subprocess.Popen(["grep",  "-o", "-P", "(?<=Duration: ).*?(?=,)"], stdin=p1.stdout, stdout=subprocess.PIPE)
    p1.stdout.close()
    return p2.communicate()[0].decode("utf-8").strip()

两者的示例输出均为:01:37:11.83

如果您仅使用ffprobe,则无需使用grep。例如,请参阅如何从ffmpeg输出中提取持续时间?如何获取视频持续时间(以秒为单位)?。此外,来自ffmpeg的stderr输出不适用于机器解析,并且被认为是脆弱的。这就是ffprobe的作用。 - llogan
@llogan:感谢您的回复。根据您的评论,我已经更新了我的答案。为了更好的理解,我保留了旧答案。 - alper

0
你尝试过添加编码吗?正如Chamath所说,那个错误是典型的编码问题。 在你的脚本头部添加utf-8编码:
#!/usr/bin/env python
# -*- coding: utf-8 -*- 

0

我喜欢使用ffmpeg构建一个共享库,并在Python中加载它。
C++代码:

#ifdef __WIN32__
#define LIB_CLASS __declspec(dllexport)
#else
#define LIB_CLASS
#endif
extern "C" {
#define __STDC_CONSTANT_MACROS
#include "libavformat/avformat.h"
}
extern "C" LIB_CLASS int64_t getDur(const char* url) {
    AVFormatContext* pFormatContext = avformat_alloc_context();
    if (avformat_open_input(&pFormatContext, url, NULL, NULL)) {
        avformat_free_context(pFormatContext);
        return -1;
    }
    int64_t t = pFormatContext->duration;
    avformat_close_input(&pFormatContext);
    avformat_free_context(pFormatContext);
    return t;
}

然后使用gcc编译它并获得一个共享库。
Python代码:

from ctypes import *
lib = CDLL('/the/path/to/your/library')
getDur = lib.getDur
getDur.restype = c_longlong
duration = getDur('the path/URL to your file')

在我的Python程序中它运行良好。


-1

Python 代码

<code>
cmnd = ['/root/bin/ffmpeg',  '-i', videopath]
process = subprocess.Popen(cmnd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
stdout, stderr = process.communicate()

#This matches regex to get the time in H:M:S format
matches = re.search(r"Duration:\s{1}(?P<hours>\d+?):(?P<minutes>\d+?):(?P<seconds>\d+\.\d+?),", stdout, re.DOTALL).groupdict()
t_hour = matches['hours']
t_min  = matches['minutes']
t_sec  = matches['seconds']

t_hour_sec = int(t_hour) * 3600
t_min_sec = int(t_min) * 60
t_s_sec   = int(round(float(t_sec)))

total_sec = t_hour_sec + t_min_sec + t_s_sec

#This matches1 is to get the frame rate of a video
matches1 = re.search(r'(\d+) fps', stdout)
frame_rate = matches1.group(0) // This will give 20fps
frame_rate = matches1.group(1) //It will give 20

</code>

ffmpeg解析输出是脆弱的,不适合用于脚本。请使用ffprobe,如Chamath的答案所示。 - llogan
对于那些刚接触ffmpeg/ffprobe和Python的人,你可能应该添加一些解释来说明这个程序在做什么。 - Kalob Taulien

-2
我们还可以使用ffmpeg获取任何视频或音频文件的时长。 要安装ffmpeg,请按照此link 连接。
import subprocess
import re

process = subprocess.Popen(['ffmpeg',  '-i', path_of_video_file], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
stdout, stderr = process.communicate()
matches = re.search(r"Duration:\s{1}(?P<hours>\d+?):(?P<minutes>\d+?):(?P<seconds>\d+\.\d+?),", stdout, re.DOTALL).groupdict()

print (matches['hours'])
print (matches['minutes'])
print (matches['seconds'])

ffmpeg解析输出是脆弱的,不适合用于脚本。请使用ffprobe,如Chamath的答案所示。 - llogan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接