src/pyams_media/ffdocument.py
changeset 0 fd39db613f8b
child 8 e42e04460e14
equal deleted inserted replaced
-1:000000000000 0:fd39db613f8b
       
     1 #
       
     2 # Copyright (c) 2008-2015 Thierry Florac <tflorac AT ulthar.net>
       
     3 # All Rights Reserved.
       
     4 #
       
     5 # This software is subject to the provisions of the Zope Public License,
       
     6 # Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
       
     7 # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
       
     8 # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
       
     9 # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
       
    10 # FOR A PARTICULAR PURPOSE.
       
    11 #
       
    12 
       
    13 # import standard library
       
    14 
       
    15 # import interfaces
       
    16 
       
    17 # import packages
       
    18 from pyams_media.ffbase import FFVideoEffect, FFAudioEffect, FFmpeg
       
    19 from pyams_media.interfaces import IMediaInfo
       
    20 
       
    21 
       
    22 class FFDocument(FFVideoEffect, FFAudioEffect):
       
    23     """
       
    24         audio/video document. A FFDocument describe a higer level action set
       
    25         combining several FF[Audio|Video]Effect methods. 
       
    26     """
       
    27 
       
    28     def __init__(self, file, metadata=None, effects={}):
       
    29         """
       
    30             x.__init__(...) initializes x; see x.__class__.__doc__ for signature
       
    31         """
       
    32         FFAudioEffect.__init__(self, file)
       
    33         FFVideoEffect.__init__(self, file, **effects)
       
    34         if not metadata:
       
    35             info = IMediaInfo(file, None)
       
    36             if info is not None:
       
    37                 self.__metadata__ = info
       
    38             else:
       
    39                 info = FFmpeg('ffprobe').info(file)
       
    40                 if info:
       
    41                     self.__metadata__ = info
       
    42                 else:
       
    43                     self.__metadata__ = []
       
    44         else:
       
    45             self.__metadata__ = metadata
       
    46 
       
    47     def get_stream_info(self, codec_type=None):
       
    48         """Get metadata info for given stream"""
       
    49         for stream in self.__metadata__:
       
    50             if (not codec_type) or (stream.get('codec_type') == codec_type):
       
    51                 return stream
       
    52 
       
    53     def __tlen__(self):
       
    54         """
       
    55             return time length
       
    56         """
       
    57         stream = self.get_stream_info()
       
    58         if stream is not None:
       
    59             t = self.__timeparse__(float(stream["duration"]))
       
    60             if self.seek():
       
    61                 t = t - self.seek()
       
    62             if self.duration():
       
    63                 t = t - (t - self.duration())
       
    64             return t
       
    65 
       
    66     def __timereference__(self, reference, time):
       
    67         if isinstance(time, str):
       
    68             if '%' in time:
       
    69                 parsed = (reference / 100.0) * int(time.split("%")[0])
       
    70             else:
       
    71                 elts = time.split(':')
       
    72                 if len(elts) == 3:
       
    73                     hhn, mmn, ssn = [float(i) for i in elts]
       
    74                     parsed = hhn * 3600 + mmn * 60 + ssn
       
    75                 elif len(elts) == 2:
       
    76                     hhn, mmn = [float(i) for i in elts]
       
    77                     ssn = 0
       
    78                     parsed = hhn * 3600 + mmn * 60 + ssn
       
    79                 else:
       
    80                     parsed = 0
       
    81         else:
       
    82             parsed = time
       
    83         return parsed
       
    84 
       
    85     def __timeparse__(self, time):
       
    86         if isinstance(time, str):
       
    87             if ':' in time:
       
    88                 hh, mm, ss = [float(i) for i in time.split(":")]
       
    89                 return hh * 3600 + mm * 60 + ss
       
    90         elif isinstance(time, float):
       
    91             return time
       
    92 
       
    93     def __clone__(self):
       
    94         return FFDocument(self.__file__, self.__metadata__.copy(), self.__effects__.copy())
       
    95 
       
    96     def resample(self, width=0, height=0, vstream=0):
       
    97         """Adjust video dimensions. If one dimension is specified, the re-sampling is proportional
       
    98         """
       
    99         stream = self.get_stream_info('video')
       
   100         if stream is not None:
       
   101             w, h = stream['width'], stream['height']
       
   102             if not width:
       
   103                 width = int(w * (float(height) / h))
       
   104             elif not height:
       
   105                 height = int(h * (float(width) / w))
       
   106             elif not width and height:
       
   107                 return
       
   108 
       
   109         new = self.__clone__()
       
   110         if width < w:
       
   111             cropsize = (w - width) / 2
       
   112             new.crop(0, 0, cropsize, cropsize)
       
   113         elif width > w:
       
   114             padsize = (width - w) / 2
       
   115             new.pad(0, 0, padsize, padsize)
       
   116         if height < h:
       
   117             cropsize = (h - height) / 2
       
   118             new.crop(cropsize, cropsize, 0, 0)
       
   119         elif height > h:
       
   120             padsize = (height - h) / 2
       
   121             new.pad(padsize, padsize, 0, 0)
       
   122         return new
       
   123 
       
   124     def resize(self, width=0, height=0, vstream=0):
       
   125         """Resize video dimensions. If one dimension is specified, the re-sampling is proportional
       
   126             
       
   127         Width and height can be pixel or % (not mixable)
       
   128         """
       
   129         stream = self.get_stream_info('video')
       
   130         if stream is not None:
       
   131             w, h = stream['width'], stream['height']
       
   132             if type(width) == str or type(height) == str:
       
   133                 if not width:
       
   134                     width = height = int(height.split("%")[0])
       
   135                 elif not height:
       
   136                     height = width = int(width.split("%")[0])
       
   137                 elif not width and height:
       
   138                     return
       
   139                 elif width and height:
       
   140                     width = int(width.split("%")[0])
       
   141                     height = int(height.split("%")[0])
       
   142                 size = "%sx%s" % (int(w / 100.0 * width), int(h / 100.0 * height))
       
   143             else:
       
   144                 if not width:
       
   145                     width = int(w * (float(height) / h))
       
   146                 elif not height:
       
   147                     height = int(h * (float(width) / w))
       
   148                 elif not width and height:
       
   149                     return
       
   150                 size = "%sx%s" % (width, height)
       
   151             new = self.__clone__()
       
   152             new.size(size)
       
   153             return new
       
   154 
       
   155     def split(self, time):
       
   156         """Return a tuple of FFDocument splitted at a specified time.
       
   157 
       
   158         Allowed formats: %, sec, hh:mm:ss.mmm
       
   159         """
       
   160         stream = self.get_stream_info()
       
   161         if stream is not None:
       
   162             sectime = self.__timeparse__(stream["duration"])
       
   163             if self.duration():
       
   164                 sectime = sectime - (sectime - self.duration())
       
   165             if self.seek():
       
   166                 sectime = sectime - self.seek()
       
   167             cut = self.__timereference__(sectime, time)
       
   168 
       
   169             first = self.__clone__()
       
   170             second = self.__clone__()
       
   171             first.duration(cut)
       
   172             second.seek(cut + 0.001)
       
   173             return first, second
       
   174 
       
   175     def ltrim(self, time):
       
   176         """Trim leftmost side (from start) of the clip"""
       
   177         stream = self.get_stream_info()
       
   178         if stream is not None:
       
   179             sectime = self.__timeparse__(stream["duration"])
       
   180             if self.duration():
       
   181                 sectime = sectime - (sectime - self.duration())
       
   182             if self.seek():
       
   183                 sectime = sectime - self.seek()
       
   184             trim = self.__timereference__(sectime, time)
       
   185             new = self.__clone__()
       
   186             if self.seek():
       
   187                 new.seek(self.seek() + trim)
       
   188             else:
       
   189                 new.seek(trim)
       
   190             return new
       
   191 
       
   192     def rtrim(self, time):
       
   193         """Trim rightmost side (from end) of the clip"""
       
   194         stream = self.get_stream_info()
       
   195         if stream is not None:
       
   196             sectime = self.__timeparse__(self.__metadata__["duration"])
       
   197             if self.duration():
       
   198                 sectime = sectime - (sectime - self.duration())
       
   199             if self.seek():
       
   200                 sectime = sectime - self.seek()
       
   201             trim = self.__timereference__(sectime, time)
       
   202             new = self.__clone__()
       
   203             new.duration(trim)
       
   204             return new
       
   205 
       
   206     def trim(self, left, right):
       
   207         """Left and right trim (actually calls ltrim and rtrim)"""
       
   208         return self.__clone__().ltrim(left).rtrim(right)
       
   209 
       
   210     def chainto(self, ffdoc):
       
   211         """Prepare to append at the end of another movie clip"""
       
   212         offset = 0
       
   213         if ffdoc.seek():
       
   214             offset = ffdoc.seek()
       
   215         if ffdoc.duration():
       
   216             offset = offset + ffdoc.seek()
       
   217         if ffdoc.offset():
       
   218             offset = offset + ffdoc.offset()
       
   219 
       
   220         new = self.__clone__()
       
   221         new.offset(offset)
       
   222         return new
       
   223 
       
   224     #TODO: more and more effects!!!