Reputation: 42143
I have a collection of video files for which I'd like to generate thumbnails. How can I extract the middle frame of the video in Python?
If I execute:
ffmpeg -i /tmp/0.mts
I get a line returned to the shell that says:
Duration: 00:00:04.49, start: 1.016689, bitrate: 25234 kb/s
Maybe I could extract the timestamp after the duration and somehow divide that by 2 to get the middle frame timestamp, and extract that?
If I'm recursing through a large collection of videos, is there a way to keep ffmpeg "open" without the overhead of opening/closing it to identify each file?
Upvotes: 3
Views: 2796
Reputation: 33202
As it turns out I had to solve more or less exactly the same thing a while back.
One additional requirement was not to use ffprobe
(as it might not be available, but ffmpeg
would). ffprobe
as @LordNeckbeard suggests would have been better for the task of getting the duration otherwise.
So here is some sparsely documented code that should be easy enough to understand nonetheless. If not, don't hesitate to ask.
#!/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)
Upvotes: 2