addons-server/lib/video/ffmpeg.py

127 строки
4.4 KiB
Python

import logging
import re
import tempfile
from django.conf import settings
from django_statsd.clients import statsd
from tower import ugettext as _
from .utils import check_output, subprocess, VideoBase
log = logging.getLogger('z.video')
formats_re = [
re.compile('Input #0, ([\w,]+)?, from'),
re.compile('doctype\s+: (\w+)'),
]
duration_re = re.compile('Duration: (\d{2}):(\d{2}):(\d{2}.\d{2}),')
dimensions_re = re.compile('Stream #0.*?(\d+)x(\d+)')
version_re = re.compile('ffmpeg version (\d\.+)', re.I)
class Video(VideoBase):
name = settings.FFMPEG_BINARY
def _call(self, note, catch_error, *args):
with statsd.timer('video.ffmpeg.%s' % note):
args = [self.name,
'-y', # Don't prompt for overwrite of file
'-i', self.filename] + list(args)
log.info('ffmpeg called with: %s' % ' '.join(args))
try:
res = check_output(args, stderr=subprocess.STDOUT)
except subprocess.CalledProcessError, e:
# This is because to get the information about a file
# you specify the input file, but not the output file
# ffmpeg errors, but returns all the information we want.
if not catch_error:
log.error('ffmpeg failed with: %s' % e.output)
raise
else:
res = e.output
return res
def get_meta(self):
"""
Get the metadata for the file. You should call this first
so we can populate some meta data and ensure that the file is valid.
"""
result = self._call('meta', True)
data = {}
for fmt in formats_re:
formats = fmt.search(result)
if formats:
data['formats'] = formats.group(1).split(',')
duration = duration_re.search(result)
if duration:
data['duration'] = ((3600 * int(duration.group(1)))
+ (60 * int(duration.group(2)))
+ float(duration.group(3)))
dimensions = dimensions_re.search(result)
if dimensions:
data['dimensions'] = (int(dimensions.group(1)),
int(dimensions.group(2)))
self.meta = data
def get_screenshot(self, size):
"""
Gets a screenshot half way through the video. Will return the location
of the temporary file. It is up to the calling function to remove the
temporary file after its completed.
`size`: a tuple of the width and height
"""
assert self.is_valid()
assert self.meta.get('duration')
halfway = int(self.meta['duration'] / 2)
dest = tempfile.mkstemp(suffix='.png')[1]
self._call('screenshot',
False,
'-vframes', '1', # Only grab one frame.
'-ss', str(halfway), # Start half way through.
'-s', '%sx%s' % size, # Size of image.
dest)
return dest
def get_encoded(self, size):
"""
Recodes the video into a different size and sets its bit rate
to something suitable. In theory we are also doing this to ensure
that the video is cleaned. Maybe.
Will return the location of the temporary file. It is up to the
calling function to remove the temporary file after its completed.
`size`: a tuple of the width and height
"""
assert self.is_valid()
dest = tempfile.mkstemp(suffix='.webm')[1]
self._call('encode',
False,
'-s', '%sx%s' % size, # Size of video.
dest)
return dest
def is_valid(self):
assert self.meta is not None
self.errors = []
if 'webm' not in self.meta.get('formats', ''):
self.errors.append(_('Videos must be in WebM.'))
#TODO(andym): More checks on duration, file size, bit rate?
return not self.errors
@classmethod
def library_available(cls):
try:
output = check_output([cls.name, '-version'],
stderr=subprocess.STDOUT)
# If in the future we want to check for an ffmpeg version
# this is the place to do it.
return bool(version_re.match(output))
except (OSError, subprocess.CalledProcessError):
pass