如何在Python中使用ffmpeg提取视频的中间帧?

3 投票
1 回答
2708 浏览
提问于 2025-04-18 09:17

我有一堆视频文件,想要为它们生成缩略图。请问我该如何在Python中提取视频的中间帧呢?

如果我执行:

ffmpeg -i /tmp/0.mts

我在命令行上看到一行返回的信息:

Duration: 00:00:04.49, start: 1.016689, bitrate: 25234 kb/s

也许我可以提取视频时长后的时间戳,然后把它除以2,这样就能得到中间帧的时间戳,再提取出来?

如果我在处理大量视频文件,有没有办法让ffmpeg保持“打开”状态,这样就不用每次都打开和关闭它来识别每个文件呢?

1 个回答

2

其实我之前也遇到过差不多一样的问题。还有一个额外的要求就是不能使用 ffprobe(因为它可能不太好找,而 ffmpeg 是可以用的)。正如 @LordNeckbeard 所说,使用 ffprobe 会更适合用来获取时长。

所以这里有一些文档不太多的代码,应该还是比较容易理解的。如果不明白,随时可以问我。

#!/usr/bin/env python

# Any copyright is dedicated to the Public Domain.
# http://creativecommons.org/publicdomain/zero/1.0/
# Written in 2013 - Nils Maier

import datetime
import os
import re
import subprocess
import sys


def which(program):
    """ Somewhat equivalent to which(1) """

    def is_executable(fpath):
        return os.path.isfile(fpath) and os.access(fpath, os.X_OK)

    if is_executable(program):
        return program
    path, program = os.path.split(program)
    if path:
        return None
    for path in os.environ["PATH"].split(os.pathsep):
        path = path.strip('"')
        exe = os.path.join(path, program)
        if is_executable(exe):
            return exe
        # Windows-style
        exe = os.path.join(path, "{}.exe".format(program))
        if is_executable(exe):
            return exe
    return None


def thumb_with_ffmpeg(infile, position=0.5, executable=None):
    """
    Extract a thumbnail using ffmpeg

    :param infile: File to thumbnail.
    :param position: Position at which to take the thumbnail. Default: 0.5
    :param executable: Executable to use. Default: first "ffmpeg" in $PATH
    :returns: The thumbnail data (binary string)
    """

    ffmpeg = which(executable or "ffmpeg")
    if not ffmpeg:
        raise RuntimeError(
            "Failed to find ffmpeg executable: {}".format(executable))
    if position < 0 or position >= 1.0:
        raise ValueError(
            "Position {} is not between 0.0 and 1.0".format(position))

    proc = subprocess.Popen([ffmpeg, "-i", infile], stderr=subprocess.PIPE)
    _, result = proc.communicate()
    m = re.search(r"Duration:\s*(\d+):(\d+):(\d+)\.(\d+)", result)
    if not m:
        raise KeyError("Cannot determine duration")
    # Avoiding strptime here because it has some issues handling milliseconds.
    m = [int(m.group(i)) for i in range(1, 5)]
    duration = datetime.timedelta(hours=m[0],
                                  minutes=m[1],
                                  seconds=m[2],
                                  # * 10 because truncated to 2 decimal places
                                  milliseconds=m[3] * 10
                                  ).total_seconds()
    target = max(0, min(duration * position, duration - 0.1))
    target = "{:.3f}".format(target)
    args = [ffmpeg,
            "-ss", target,
            "-i", infile,
            "-map", "v:0",     # first video stream
            "-frames:v", "1",  # 1 frame
            "-f", "mjpeg",     # motion jpeg (aka. jpeg since 1 frame) output
            "pipe:"            # pipe output to stdout
            ]
    proc = subprocess.Popen(args, stdout=subprocess.PIPE,
                            stderr=subprocess.PIPE)
    output, _ = proc.communicate()
    if proc.returncode:
        raise subprocess.CalledProcessError(proc.returncode, args)
    if not output:
        raise subprocess.CalledProcessError(-2, args)
    return output


if __name__ == "__main__":
    from argparse import ArgumentParser, ArgumentTypeError

    def percentage(x):
        x = float(x)
        if x < 0.0 or x >= 1.0:
            raise ArgumentTypeError(
                "{} not in percentage range [0.0, 1.0)".format(x))
        return x

    parser = ArgumentParser(
        description="Extract a thumbnail from a media file using ffmpeg")
    parser.add_argument("infile", type=str, help="Input file")
    parser.add_argument("outfile", type=str, help="Output file")
    parser.add_argument("-f", "--ffmpeg", type=str, default=None,
                        help="use this ffmpeg binary, "
                             "default: check $PATH for ffmpeg")
    parser.add_argument("-p", "--position", type=percentage, default=0.5,
                        help="thumbnail at this position (percentage), "
                             "default: 0.5")
    args = parser.parse_args()

    try:
        output = thumb_with_ffmpeg(args.infile, args.position, args.ffmpeg)
        with open(args.outfile, "wb") as op:
            op.write(output)
    except Exception as ex:
        print >>sys.stderr, "Error:", ex
        sys.exit(ex.returncode or 1)

撰写回答