Merge remote-tracking branch 'refs/remotes/breton/new_gst10'
[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 tempfile
20
21 from mediagoblin.tools.pluginapi import hook_handle
22 from mediagoblin.tools.translate import lazy_pass_to_ugettext as _
23
24 _log = logging.getLogger(__name__)
25
26
27 class FileTypeNotSupported(Exception):
28 pass
29
30
31 class TypeNotFound(FileTypeNotSupported):
32 '''Raised if no mediagoblin plugin supporting this file type was found'''
33 pass
34
35
36 class MissingComponents(FileTypeNotSupported):
37 '''Raised if plugin found, but it can't process the file for some reason'''
38 pass
39
40
41 class MediaManagerBase(object):
42 "Base class for all media managers"
43
44 # Please override in actual media managers
45 media_fetch_order = None
46
47 @staticmethod
48 def sniff_handler(*args, **kwargs):
49 return False
50
51 def __init__(self, entry):
52 self.entry = entry
53
54 def __getitem__(self, i):
55 return getattr(self, i)
56
57 def __contains__(self, i):
58 return hasattr(self, i)
59
60
61 def sniff_media_contents(media_file, filename):
62 '''
63 Check media contents using 'expensive' scanning. For example, for video it
64 is checking the contents using gstreamer
65 :param media_file: file-like object with 'name' attribute
66 :param filename: expected filename of the media
67 '''
68 media_type = hook_handle('sniff_handler', media_file, filename)
69 if media_type:
70 _log.info('{0} accepts the file'.format(media_type))
71 return media_type, hook_handle(('media_manager', media_type))
72 else:
73 _log.debug('{0} did not accept the file'.format(media_type))
74 raise FileTypeNotSupported(
75 # TODO: Provide information on which file types are supported
76 _(u'Sorry, I don\'t support that file type :('))
77
78 def get_media_type_and_manager(filename):
79 '''
80 Try to find the media type based on the file name, extension
81 specifically. This is used as a speedup, the sniffing functionality
82 then falls back on more in-depth bitsniffing of the source file.
83
84 This hook is deprecated, 'type_match_handler' should be used instead
85 '''
86 if filename.find('.') > 0:
87 # Get the file extension
88 ext = os.path.splitext(filename)[1].lower()
89
90 # Omit the dot from the extension and match it against
91 # the media manager
92 if hook_handle('get_media_type_and_manager', ext[1:]):
93 return hook_handle('get_media_type_and_manager', ext[1:])
94 else:
95 _log.info('File {0} has no file extension, let\'s hope the sniffers get it.'.format(
96 filename))
97
98 raise TypeNotFound(
99 _(u'Sorry, I don\'t support that file type :('))
100
101 def type_match_handler(media_file, filename):
102 '''Check media file by name and then by content
103
104 Try to find the media type based on the file name, extension
105 specifically. After that, if media type is one of supported ones, check the
106 contents of the file
107 '''
108 if filename.find('.') > 0:
109 # Get the file extension
110 ext = os.path.splitext(filename)[1].lower()
111
112 # Omit the dot from the extension and match it against
113 # the media manager
114 hook_result = hook_handle('type_match_handler', ext[1:])
115 if hook_result:
116 _log.info('Info about file found, checking further')
117 MEDIA_TYPE, Manager, sniffer = hook_result
118 if not sniffer:
119 _log.debug('sniffer is None, plugin trusts the extension')
120 return MEDIA_TYPE, Manager
121 _log.info('checking the contents with sniffer')
122 try:
123 sniffer(media_file)
124 _log.info('checked, found')
125 return MEDIA_TYPE, Manager
126 except Exception as e:
127 _log.info('sniffer says it will not accept the file')
128 _log.debug(e)
129 raise
130 else:
131 _log.info('No plugins handled extension {0}'.format(ext))
132 else:
133 _log.info('File {0} has no known file extension, let\'s hope '
134 'the sniffers get it.'.format(filename))
135 raise TypeNotFound(_(u'Sorry, I don\'t support that file type :('))
136
137
138 def sniff_media(media_file, filename):
139 '''
140 Iterate through the enabled media types and find those suited
141 for a certain file.
142 '''
143 # copy the contents to a .name-enabled temporary file for further checks
144 # TODO: there are cases when copying is not required
145 tmp_media_file = tempfile.NamedTemporaryFile()
146 media_file.save(tmp_media_file.name)
147 media_file.seek(0)
148 try:
149 return type_match_handler(tmp_media_file, filename)
150 except TypeNotFound as e:
151 _log.info('No plugins using two-step checking found')
152
153 # keep trying, using old `get_media_type_and_manager`
154 try:
155 return get_media_type_and_manager(filename)
156 except TypeNotFound as e:
157 # again, no luck. Do it expensive way
158 _log.info('No media handler found by file extension')
159 _log.info('Doing it the expensive way...')
160 return sniff_media_contents(tmp_media_file, filename)
161