#
# Copyright (c) 2008-2015 Thierry Florac <tflorac AT ulthar.net>
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
# import standard library
# import interfaces
# import packages
from pyams_media.ffbase import FFVideoEffect, FFAudioEffect, FFmpeg
from pyams_media.interfaces import IMediaInfo
[docs]class FFDocument(FFVideoEffect, FFAudioEffect):
"""
audio/video document. A FFDocument describe a higer level action set
combining several FF[Audio|Video]Effect methods.
"""
def __init__(self, file, metadata=None, effects={}):
"""
x.__init__(...) initializes x; see x.__class__.__doc__ for signature
"""
FFAudioEffect.__init__(self, file)
FFVideoEffect.__init__(self, file, **effects)
if not metadata:
info = IMediaInfo(file, None)
if info is not None:
self.__metadata__ = info
else:
info = FFmpeg('avprobe').info(file)
if info:
self.__metadata__ = info
else:
self.__metadata__ = {}
else:
self.__metadata__ = metadata
[docs] def get_stream_info(self, codec_type=None):
"""Get metadata info for given stream"""
for stream in self.__metadata__.get('streams', ()):
if (not codec_type) or (stream.get('codec_type') == codec_type):
return stream
def __tlen__(self):
"""
return time length
"""
stream = self.get_stream_info()
if stream is not None:
t = self.__timeparse__(float(stream["duration"]))
if self.seek():
t = t - self.seek()
if self.duration():
t = t - (t - self.duration())
return t
def __timereference__(self, reference, time):
if isinstance(time, str):
if '%' in time:
parsed = (reference / 100.0) * int(time.split("%")[0])
else:
elts = time.split(':')
if len(elts) == 3:
hhn, mmn, ssn = [float(i) for i in elts]
parsed = hhn * 3600 + mmn * 60 + ssn
elif len(elts) == 2:
hhn, mmn = [float(i) for i in elts]
ssn = 0
parsed = hhn * 3600 + mmn * 60 + ssn
else:
parsed = 0
else:
parsed = time
return parsed
def __timeparse__(self, time):
if isinstance(time, str):
if ':' in time:
hh, mm, ss = [float(i) for i in time.split(":")]
return hh * 3600 + mm * 60 + ss
elif isinstance(time, float):
return time
def __clone__(self):
return FFDocument(self.__file__, self.__metadata__.copy(), self.__effects__.copy())
[docs] def resample(self, width=0, height=0, vstream=0):
"""Adjust video dimensions. If one dimension is specified, the re-sampling is proportional
"""
stream = self.get_stream_info('video')
if stream is not None:
w, h = stream['width'], stream['height']
if not width:
width = int(w * (float(height) / h))
elif not height:
height = int(h * (float(width) / w))
elif not width and height:
return
new = self.__clone__()
if width < w:
cropsize = (w - width) / 2
new.crop(0, 0, cropsize, cropsize)
elif width > w:
padsize = (width - w) / 2
new.pad(0, 0, padsize, padsize)
if height < h:
cropsize = (h - height) / 2
new.crop(cropsize, cropsize, 0, 0)
elif height > h:
padsize = (height - h) / 2
new.pad(padsize, padsize, 0, 0)
return new
[docs] def resize(self, width=0, height=0, vstream=0):
"""Resize video dimensions. If one dimension is specified, the re-sampling is proportional
Width and height can be pixel or % (not mixable)
"""
stream = self.get_stream_info('video')
if stream is not None:
w, h = stream['width'], stream['height']
if type(width) == str or type(height) == str:
if not width:
width = height = int(height.split("%")[0])
elif not height:
height = width = int(width.split("%")[0])
elif not width and height:
return
elif width and height:
width = int(width.split("%")[0])
height = int(height.split("%")[0])
size = "%sx%s" % (int(w / 100.0 * width), int(h / 100.0 * height))
else:
if not width:
width = int(w * (float(height) / h))
elif not height:
height = int(h * (float(width) / w))
elif not width and height:
return
size = "%sx%s" % (width, height)
new = self.__clone__()
new.size(size)
return new
[docs] def split(self, time):
"""Return a tuple of FFDocument splitted at a specified time.
Allowed formats: %, sec, hh:mm:ss.mmm
"""
stream = self.get_stream_info()
if stream is not None:
sectime = self.__timeparse__(stream["duration"])
if self.duration():
sectime = sectime - (sectime - self.duration())
if self.seek():
sectime = sectime - self.seek()
cut = self.__timereference__(sectime, time)
first = self.__clone__()
second = self.__clone__()
first.duration(cut)
second.seek(cut + 0.001)
return first, second
[docs] def ltrim(self, time):
"""Trim leftmost side (from start) of the clip"""
stream = self.get_stream_info()
if stream is not None:
sectime = self.__timeparse__(stream["duration"])
if self.duration():
sectime = sectime - (sectime - self.duration())
if self.seek():
sectime = sectime - self.seek()
trim = self.__timereference__(sectime, time)
new = self.__clone__()
if self.seek():
new.seek(self.seek() + trim)
else:
new.seek(trim)
return new
[docs] def rtrim(self, time):
"""Trim rightmost side (from end) of the clip"""
stream = self.get_stream_info()
if stream is not None:
sectime = self.__timeparse__(self.__metadata__["duration"])
if self.duration():
sectime = sectime - (sectime - self.duration())
if self.seek():
sectime = sectime - self.seek()
trim = self.__timereference__(sectime, time)
new = self.__clone__()
new.duration(trim)
return new
[docs] def trim(self, left, right):
"""Left and right trim (actually calls ltrim and rtrim)"""
return self.__clone__().ltrim(left).rtrim(right)
[docs] def chainto(self, ffdoc):
"""Prepare to append at the end of another movie clip"""
offset = 0
if ffdoc.seek():
offset = ffdoc.seek()
if ffdoc.duration():
offset = offset + ffdoc.seek()
if ffdoc.offset():
offset = offset + ffdoc.offset()
new = self.__clone__()
new.offset(offset)
return new
#TODO: more and more effects!!!