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

时间:2014-06-10 13:19:47

标签: python ffmpeg

我有一组视频文件,我想为其生成缩略图。如何在Python中提取视频的中间帧?

如果我执行:

ffmpeg -i /tmp/0.mts

我得到一行返回shell:

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

也许我可以在持续时间之后提取时间戳并以某种方式将其除以2以获得中间帧时间戳,然后提取它?

如果我正在通过一大堆视频进行递归,是否有办法让ffmpeg“打开”而没有打开/关闭它来识别每个文件的开销?

1 个答案:

答案 0 :(得分:2)

事实证明,我不得不在一段时间内或多或少完全解决同样的问题。 另一个要求是不使用ffprobe(因为它可能不可用,但ffmpeg会)。 ffprobe正如@LordNeckbeard所建议的那样,对于获得持续时间的任务会更好。

所以这里有一些稀疏文档的代码,应该很容易理解。如果没有,请不要犹豫。

#!/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)