Add property to media_fetch_order
[mediagoblin.git] / mediagoblin / media_types / __init__.py
1 # GNU MediaGoblin -- federated, autonomous media hosting
2 # Copyright (C) 2011, 2012 MediaGoblin contributors. See AUTHORS.
3 #
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License as published by
6 # the Free Software Foundation, either version 3 of the License, or
7 # (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU Affero General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16
17 import os
18 import logging
19 import shutil
20 import tempfile
21
22 from mediagoblin.tools.pluginapi import hook_handle
23 from mediagoblin.tools.translate import lazy_pass_to_ugettext as _
24
25 _log = logging.getLogger(__name__)
26
27
28 class FileTypeNotSupported(Exception):
29 pass
30
31
32 class TypeNotFound(FileTypeNotSupported):
33 '''Raised if no mediagoblin plugin supporting this file type was found'''
34 pass
35
36
37 class MissingComponents(FileTypeNotSupported):
38 '''Raised if plugin found, but it can't process the file for some reason'''
39 pass
40
41
42 class MediaManagerBase(object):
43 "Base class for all media managers"
44
45 # Please override in actual media managers
46 media_fetch_order = None
47
48 @staticmethod
49 def sniff_handler(*args, **kwargs):
50 return False
51
52 def __init__(self, entry):
53 self.entry = entry
54
55 def __getitem__(self, i):
56 return getattr(self, i)
57
58 def __contains__(self, i):
59 return hasattr(self, i)
60
61
62 def sniff_media_contents(media_file, filename):
63 '''
64 Check media contents using 'expensive' scanning. For example, for video it
65 is checking the contents using gstreamer
66 :param media_file: file-like object with 'name' attribute
67 :param filename: expected filename of the media
68 '''
69 media_type = hook_handle('sniff_handler', media_file, filename)
70 if media_type:
71 _log.info('{0} accepts the file'.format(media_type))
72 return media_type, hook_handle(('media_manager', media_type))
73 else:
74 _log.debug('{0} did not accept the file'.format(media_type))
75 raise FileTypeNotSupported(
76 # TODO: Provide information on which file types are supported
77 _(u'Sorry, I don\'t support that file type :('))
78
79 def get_media_type_and_manager(filename):
80 '''
81 Try to find the media type based on the file name, extension
82 specifically. This is used as a speedup, the sniffing functionality
83 then falls back on more in-depth bitsniffing of the source file.
84
85 This hook is deprecated, 'type_match_handler' should be used instead
86 '''
87 if os.path.basename(filename).find('.') > 0:
88 # Get the file extension
89 ext = os.path.splitext(filename)[1].lower()
90
91 # Omit the dot from the extension and match it against
92 # the media manager
93 if hook_handle('get_media_type_and_manager', ext[1:]):
94 return hook_handle('get_media_type_and_manager', ext[1:])
95 else:
96 _log.info('File {0} has no file extension, let\'s hope the sniffers get it.'.format(
97 filename))
98
99 raise TypeNotFound(
100 _(u'Sorry, I don\'t support that file type :('))
101
102 def type_match_handler(media_file, filename):
103 '''Check media file by name and then by content
104
105 Try to find the media type based on the file name, extension
106 specifically. After that, if media type is one of supported ones, check the
107 contents of the file
108 '''
109 if os.path.basename(filename).find('.') > 0:
110 # Get the file extension
111 ext = os.path.splitext(filename)[1].lower()
112
113 # Omit the dot from the extension and match it against
114 # the media manager
115 hook_result = hook_handle('type_match_handler', ext[1:])
116 if hook_result:
117 _log.info('Info about file found, checking further')
118 MEDIA_TYPE, Manager, sniffer = hook_result
119 if not sniffer:
120 _log.debug('sniffer is None, plugin trusts the extension')
121 return MEDIA_TYPE, Manager
122 _log.info('checking the contents with sniffer')
123 try:
124 sniffer(media_file)
125 _log.info('checked, found')
126 return MEDIA_TYPE, Manager
127 except Exception as e:
128 _log.info('sniffer says it will not accept the file')
129 _log.debug(e)
130 raise
131 else:
132 _log.info('No plugins handled extension {0}'.format(ext))
133 else:
134 _log.info('File {0} has no known file extension, let\'s hope '
135 'the sniffers get it.'.format(filename))
136 raise TypeNotFound(_(u'Sorry, I don\'t support that file type :('))
137
138
139 def sniff_media(media_file, filename):
140 '''
141 Iterate through the enabled media types and find those suited
142 for a certain file.
143 '''
144 # copy the contents to a .name-enabled temporary file for further checks
145 # TODO: there are cases when copying is not required
146 tmp_media_file = tempfile.NamedTemporaryFile()
147 shutil.copyfileobj(media_file, tmp_media_file)
148 media_file.seek(0)
149 tmp_media_file.seek(0)
150 try:
151 return type_match_handler(tmp_media_file, filename)
152 except TypeNotFound as e:
153 _log.info('No plugins using two-step checking found')
154
155 # keep trying, using old `get_media_type_and_manager`
156 try:
157 return get_media_type_and_manager(filename)
158 except TypeNotFound as e:
159 # again, no luck. Do it expensive way
160 _log.info('No media handler found by file extension')
161 _log.info('Doing it the expensive way...')
162 return sniff_media_contents(tmp_media_file, filename)
163