src/pyams_media/video.py
changeset 0 fd39db613f8b
child 10 9296741c1470
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 __docformat__ = 'restructuredtext'
       
    14 
       
    15 
       
    16 # import standard library
       
    17 import os.path
       
    18 import subprocess
       
    19 
       
    20 from tempfile import NamedTemporaryFile
       
    21 
       
    22 # import interfaces
       
    23 from pyams_file.interfaces import IVideo, IThumbnail
       
    24 from zope.annotation.interfaces import IAnnotations
       
    25 from zope.traversing.interfaces import ITraversable
       
    26 
       
    27 # import packages
       
    28 import transaction
       
    29 
       
    30 from pyams_file.file import ImageFile, get_magic_content_type
       
    31 from pyams_file.image import ThumbnailGeometrry
       
    32 from pyams_media.ffbase import FFmpeg
       
    33 from pyams_utils.adapter import adapter_config, ContextAdapter
       
    34 from pyramid.threadlocal import get_current_registry
       
    35 from zope.lifecycleevent import ObjectCreatedEvent, ObjectAddedEvent
       
    36 from zope.location import locate
       
    37 
       
    38 
       
    39 THUMBNAIL_ANNOTATION_KEY = 'pyams_media.video.thumbnail'
       
    40 
       
    41 
       
    42 @adapter_config(context=IVideo, provides=IThumbnail)
       
    43 class VideoThumbnailAdapter(object):
       
    44     """Video thumbnail adapter"""
       
    45 
       
    46     def __init__(self, video):
       
    47         self.video = video
       
    48         annotations = IAnnotations(video)
       
    49         self.thumbnail = annotations.get(THUMBNAIL_ANNOTATION_KEY)
       
    50 
       
    51     def get_image_size(self):
       
    52         if self.thumbnail is not None:
       
    53             return self.thumbnail.get_image_size()
       
    54         else:
       
    55             mpeg = FFmpeg('ffprobe')
       
    56             streams = mpeg.info(self.video)
       
    57             if streams:
       
    58                 for stream in streams:
       
    59                     if stream.get('codec_type') != 'video':
       
    60                         continue
       
    61                     return stream.get('width'), stream.get('height')
       
    62 
       
    63     def get_thumbnail_size(self, thumbnail_name, forced=False):
       
    64         if self.thumbnail is not None:
       
    65             return IThumbnail(self.thumbnail).get_thumbnail_size(thumbnail_name, forced)
       
    66         else:
       
    67             return self.get_image_size()
       
    68 
       
    69     def get_thumbnail_geometry(self, thumbnail_name):
       
    70         if self.thumbnail is not None:
       
    71             return IThumbnail(self.thumbnail).get_thumbnail_geometry(thumbnail_name)
       
    72         else:
       
    73             size = self.get_image_size()
       
    74             if size:
       
    75                 geometry = ThumbnailGeometrry()
       
    76                 geometry.x1 = 0
       
    77                 geometry.y1 = 0
       
    78                 geometry.x2 = size[0]
       
    79                 geometry.y2 = size[1]
       
    80                 return geometry
       
    81 
       
    82     def set_thumbnail_geometry(self, thumbnail_name, geometry):
       
    83         if self.thumbnail is not None:
       
    84             IThumbnail(self.thumbnail).set_thumbnail_geometry(thumbnail_name, geometry)
       
    85 
       
    86     def clear_geometries(self):
       
    87         if self.thumbnail is not None:
       
    88             IThumbnail(self.thumbnail).clear_geometries()
       
    89 
       
    90     def get_thumbnail_name(self, thumbnail_name, with_size=False):
       
    91         if self.thumbnail is not None:
       
    92             return IThumbnail(self.thumbnail).get_thumbnail_name(thumbnail_name, with_size)
       
    93         else:
       
    94             size = self.get_image_size()
       
    95             if size is not None:
       
    96                 if with_size:
       
    97                     return '{0}x{1}'.format(*size), size
       
    98                 else:
       
    99                     return '{0}x{1}'.format(*size)
       
   100             else:
       
   101                 return None, None
       
   102 
       
   103     def get_thumbnail(self, thumbnail_name, format=None, time=5):
       
   104         if self.thumbnail is None:
       
   105             pipe = subprocess.Popen(('ffmpeg', '-i', '-', '-ss', str(time), '-f', 'image2', '-vframes', '1', '-'),
       
   106                                     stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
       
   107             if pipe:
       
   108                 stdout, stderr = pipe.communicate(self.video.data)
       
   109                 # Some videos formats can't be converted via pipes
       
   110                 # If so, we must provide a temporay file...
       
   111                 if not stdout:
       
   112                     output = NamedTemporaryFile(prefix='video_', suffix='.thumb')
       
   113                     output.write(self.video.data)
       
   114                     output.file.flush()
       
   115                     pipe = subprocess.Popen(('ffmpeg', '-i', output.name, '-ss', str(time), '-f', 'image2',
       
   116                                              '-vframes', '1', '-'),
       
   117                                             stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
       
   118                     if pipe:
       
   119                         stdout, stderr = pipe.communicate()
       
   120                 # Create final image
       
   121                 registry = get_current_registry()
       
   122                 annotations = IAnnotations(self.video)
       
   123                 image = ImageFile(stdout)
       
   124                 image.content_type = get_magic_content_type(image.data)
       
   125                 registry.notify(ObjectCreatedEvent(image))
       
   126                 self.thumbnail = annotations[THUMBNAIL_ANNOTATION_KEY] = image
       
   127                 locate(self.thumbnail, self.video)
       
   128                 registry.notify(ObjectAddedEvent(image, self.video))
       
   129         if self.thumbnail is not None:
       
   130             size_name = '{0[0]}x{0[1]}'.format(self.get_image_size())
       
   131             if thumbnail_name != size_name:
       
   132                 watermark = os.path.abspath(os.path.join(__file__, '..',
       
   133                                                          'skin', 'resources', 'img', 'video-play-mask.png'))
       
   134                 return IThumbnail(self.thumbnail).get_thumbnail(thumbnail_name, format, watermark)
       
   135             else:
       
   136                 return IThumbnail(self.thumbnail).get_thumbnail(thumbnail_name, format)
       
   137 
       
   138     def delete_thumbnail(self, thumbnail_name):
       
   139         annotations = IAnnotations(self.video)
       
   140         if THUMBNAIL_ANNOTATION_KEY in annotations:
       
   141             del annotations[THUMBNAIL_ANNOTATION_KEY]
       
   142 
       
   143     def clear_thumbnails(self):
       
   144         annotations = IAnnotations(self.video)
       
   145         if THUMBNAIL_ANNOTATION_KEY in annotations:
       
   146             del annotations[THUMBNAIL_ANNOTATION_KEY]
       
   147         self.thumbnail = None
       
   148 
       
   149 
       
   150 @adapter_config(name='thumb', context=IVideo, provides=ITraversable)
       
   151 class ThumbnailTraverser(ContextAdapter):
       
   152     """++thumb++ video namespace traverser"""
       
   153 
       
   154     def traverse(self, name, furtherpath=None):
       
   155         if '.' in name:
       
   156             thumbnail_name, format = name.rsplit('.', 1)
       
   157         else:
       
   158             thumbnail_name = name
       
   159             format = None
       
   160         thumbnails = IThumbnail(self.context)
       
   161         result = thumbnails.get_thumbnail(thumbnail_name, format)
       
   162         transaction.commit()
       
   163         return result