--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_media/ffdocument.py Wed Sep 02 15:31:55 2015 +0200
@@ -0,0 +1,224 @@
+#
+# 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
+
+
+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('ffprobe').info(file)
+ if info:
+ self.__metadata__ = info
+ else:
+ self.__metadata__ = []
+ else:
+ self.__metadata__ = metadata
+
+ def get_stream_info(self, codec_type=None):
+ """Get metadata info for given stream"""
+ for stream in self.__metadata__:
+ 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())
+
+ 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
+
+ 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
+
+ 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
+
+ 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
+
+ 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
+
+ def trim(self, left, right):
+ """Left and right trim (actually calls ltrim and rtrim)"""
+ return self.__clone__().ltrim(left).rtrim(right)
+
+ 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!!!