fixed 5068
[mediagoblin.git] / mediagoblin / media_types / __init__.py
index 91e3443b10980b9700474db53d67734e47a38a6d..97e4facd18b9e8549012062092702b2831d3843e 100644 (file)
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 import os
-import sys
 import logging
+import shutil
 import tempfile
 
-from mediagoblin import mg_globals
-from mediagoblin.tools.common import import_component
+from mediagoblin.tools.pluginapi import hook_handle
 from mediagoblin.tools.translate import lazy_pass_to_ugettext as _
 
 _log = logging.getLogger(__name__)
 
+
 class FileTypeNotSupported(Exception):
     pass
 
-class InvalidFileType(Exception):
+
+class TypeNotFound(FileTypeNotSupported):
+    '''Raised if no mediagoblin plugin supporting this file type was found'''
+    pass
+
+
+class MissingComponents(FileTypeNotSupported):
+    '''Raised if plugin found, but it can't process the file for some reason'''
     pass
 
 
@@ -38,6 +45,10 @@ class MediaManagerBase(object):
     # Please override in actual media managers
     media_fetch_order = None
 
+    @staticmethod
+    def sniff_handler(*args, **kwargs):
+        return False
+
     def __init__(self, entry):
         self.entry = entry
 
@@ -48,99 +59,104 @@ class MediaManagerBase(object):
         return hasattr(self, i)
 
 
-class CompatMediaManager(object):
-    def __init__(self, mm_dict, entry=None):
-        self.mm_dict = mm_dict
-        self.entry = entry
-       
-    def __call__(self, entry):
-        "So this object can look like a class too, somehow"
-        assert self.entry is None
-        return self.__class__(self.mm_dict, entry)
-
-    def __getitem__(self, i):
-        return self.mm_dict[i]
-
-    def __contains__(self, i):
-        return (i in self.mm_dict)
-
-    @property
-    def media_fetch_order(self):
-        return self.mm_dict.get('media_fetch_order')
-
-    def __getattr__(self, i):
-        return self.mm_dict[i]
-
-
-def sniff_media(media):
-    '''
-    Iterate through the enabled media types and find those suited
-    for a certain file.
+def sniff_media_contents(media_file, filename):
     '''
-
-    try:
-        return get_media_type_and_manager(media.filename)
-    except FileTypeNotSupported:
-        _log.info('No media handler found by file extension. Doing it the expensive way...')
-        # Create a temporary file for sniffers suchs as GStreamer-based
-        # Audio video
-        media_file = tempfile.NamedTemporaryFile()
-        media_file.write(media.stream.read())
-        media.stream.seek(0)
-
-        for media_type, manager in get_media_managers():
-            _log.info('Sniffing {0}'.format(media_type))
-            if 'sniff_handler' in manager and \
-               manager.sniff_handler(media_file, media=media):
-                _log.info('{0} accepts the file'.format(media_type))
-                return media_type, manager
-            else:
-                _log.debug('{0} did not accept the file'.format(media_type))
-
-    raise FileTypeNotSupported(
-        # TODO: Provide information on which file types are supported
-        _(u'Sorry, I don\'t support that file type :('))
-
-
-def get_media_types():
-    """
-    Generator, yields the available media types
-    """
-    for media_type in mg_globals.app_config['media_types']:
-        yield media_type
-
-
-def get_media_managers():
+    Check media contents using 'expensive' scanning. For example, for video it
+    is checking the contents using gstreamer
+    :param media_file: file-like object with 'name' attribute
+    :param filename: expected filename of the media
     '''
-    Generator, yields all enabled media managers
-    '''
-    for media_type in get_media_types():
-        mm = import_component(media_type + ":MEDIA_MANAGER")
-
-        if isinstance(mm, dict):
-            mm = CompatMediaManager(mm)
-
-        yield media_type, mm
-
+    media_type = hook_handle('sniff_handler', media_file, filename)
+    if media_type:
+        _log.info('{0} accepts the file'.format(media_type))
+        return media_type, hook_handle(('media_manager', media_type))
+    else:
+        _log.debug('{0} did not accept the file'.format(media_type))
+        raise FileTypeNotSupported(
+            # TODO: Provide information on which file types are supported
+            _(u'Sorry, I don\'t support that file type :('))
 
 def get_media_type_and_manager(filename):
     '''
     Try to find the media type based on the file name, extension
     specifically. This is used as a speedup, the sniffing functionality
     then falls back on more in-depth bitsniffing of the source file.
+
+    This hook is deprecated, 'type_match_handler' should be used instead
     '''
-    if filename.find('.') > 0:
+    if os.path.basename(filename).find('.') > 0:
         # Get the file extension
         ext = os.path.splitext(filename)[1].lower()
 
-        for media_type, manager in get_media_managers():
-            # Omit the dot from the extension and match it against
-            # the media manager
-            if ext[1:] in manager.accepted_extensions:
-                return media_type, manager
+        # Omit the dot from the extension and match it against
+        # the media manager
+        if hook_handle('get_media_type_and_manager', ext[1:]):
+            return hook_handle('get_media_type_and_manager', ext[1:])
     else:
         _log.info('File {0} has no file extension, let\'s hope the sniffers get it.'.format(
             filename))
 
-    raise FileTypeNotSupported(
+    raise TypeNotFound(
         _(u'Sorry, I don\'t support that file type :('))
+
+def type_match_handler(media_file, filename):
+    '''Check media file by name and then by content
+
+    Try to find the media type based on the file name, extension
+    specifically. After that, if media type is one of supported ones, check the
+    contents of the file
+    '''
+    if os.path.basename(filename).find('.') > 0:
+        # Get the file extension
+        ext = os.path.splitext(filename)[1].lower()
+
+        # Omit the dot from the extension and match it against
+        # the media manager
+        hook_result = hook_handle('type_match_handler', ext[1:])
+        if hook_result:
+            _log.info('Info about file found, checking further')
+            MEDIA_TYPE, Manager, sniffer = hook_result
+            if not sniffer:
+                _log.debug('sniffer is None, plugin trusts the extension')
+                return MEDIA_TYPE, Manager
+            _log.info('checking the contents with sniffer')
+            try:
+                sniffer(media_file)
+                _log.info('checked, found')
+                return MEDIA_TYPE, Manager
+            except Exception as e:
+                _log.info('sniffer says it will not accept the file')
+                _log.debug(e)
+                raise
+        else:
+            _log.info('No plugins handled extension {0}'.format(ext))
+    else:
+        _log.info('File {0} has no known file extension, let\'s hope '
+                'the sniffers get it.'.format(filename))
+    raise TypeNotFound(_(u'Sorry, I don\'t support that file type :('))
+
+
+def sniff_media(media_file, filename):
+    '''
+    Iterate through the enabled media types and find those suited
+    for a certain file.
+    '''
+    # copy the contents to a .name-enabled temporary file for further checks
+    # TODO: there are cases when copying is not required
+    tmp_media_file = tempfile.NamedTemporaryFile()
+    shutil.copyfileobj(media_file, tmp_media_file)
+    media_file.seek(0)
+    try:
+        return type_match_handler(tmp_media_file, filename)
+    except TypeNotFound as e:
+        _log.info('No plugins using two-step checking found')
+
+    # keep trying, using old `get_media_type_and_manager`
+    try:
+        return get_media_type_and_manager(filename)
+    except TypeNotFound as e:
+        # again, no luck. Do it expensive way
+        _log.info('No media handler found by file extension')
+    _log.info('Doing it the expensive way...')
+    return sniff_media_contents(tmp_media_file, filename)
+