Multimedia support - Commiting from a not yet finished state - Details below
authorJoar Wandborg <git@wandborg.com>
Fri, 23 Sep 2011 00:35:57 +0000 (02:35 +0200)
committerJoar Wandborg <git@wandborg.com>
Fri, 23 Sep 2011 00:35:57 +0000 (02:35 +0200)
* DONE Initially testing with arista
** DONE Video display templates
*** TODO Multi-browser support
** TODO Video thumbnails
** TODO Link to original video
** TODO Video cropping

Also contains a lot of "debug" print's

13 files changed:
mediagoblin/db/migrations.py
mediagoblin/init/celery/__init__.py
mediagoblin/media_types/__init__.py [new file with mode: 0644]
mediagoblin/media_types/image/__init__.py [new file with mode: 0644]
mediagoblin/media_types/image/processing.py [new file with mode: 0644]
mediagoblin/media_types/video/__init__.py [new file with mode: 0644]
mediagoblin/media_types/video/processing.py [new file with mode: 0644]
mediagoblin/storage/cloudfiles.py
mediagoblin/submit/views.py
mediagoblin/templates/mediagoblin/media_displays/image.html [new file with mode: 0644]
mediagoblin/templates/mediagoblin/media_displays/video.html [new file with mode: 0644]
mediagoblin/templates/mediagoblin/user_pages/media.html
mediagoblin/user_pages/views.py

index 755f49c556e489481dbbd5f1b7697fdb14a8e209..01df72081c0d18214d8e77d8d7f78399ee0bacd1 100644 (file)
@@ -107,3 +107,11 @@ def user_add_forgot_password_token_and_expires(database):
          {'fp_token_expire': {'$exists': False}},
          {'$set': {'fp_token_expire': None}},
          multi=True)
+
+
+@RegisterMigration(7)
+def media_type_image_to_multimedia_type_image(database):
+    database['media_entries'].update(
+        {'media_type': 'image'},
+        {'$set': {'media_type': 'mediagoblin.media_types.image'}},
+        multi=True)
index c58b130520cef6944541093b8f8c8b00cbd246ce..05c54b0542b3fc79356cd86879b71cabc113743c 100644 (file)
 import os
 import sys
 
+from mediagoblin.media_types import get_media_types
+
 
 MANDATORY_CELERY_IMPORTS = ['mediagoblin.process_media']
+MANDATORY_CELERY_IMPORTS = [i for i in get_media_types()]
+
+print(MANDATORY_CELERY_IMPORTS)
 
 DEFAULT_SETTINGS_MODULE = 'mediagoblin.init.celery.dummy_settings_module'
 
diff --git a/mediagoblin/media_types/__init__.py b/mediagoblin/media_types/__init__.py
new file mode 100644 (file)
index 0000000..67dab41
--- /dev/null
@@ -0,0 +1,70 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011 MediaGoblin contributors.  See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+import os
+import sys
+
+class FileTypeNotSupported(Exception):
+    pass
+
+class InvalidFileType(Exception):
+    pass
+
+MEDIA_TYPES = [
+        'mediagoblin.media_types.image',
+        'mediagoblin.media_types.video']
+
+
+def get_media_types():
+    for media_type in MEDIA_TYPES:
+        yield media_type
+
+
+def get_media_managers():
+    for media_type in get_media_types():
+        '''
+        FIXME
+        __import__ returns the lowest-level module. If the plugin is located
+        outside the conventional plugin module tree, it will not be loaded
+        properly because of the [...]ugin.media_types.
+
+        We need this if we want to support a separate site-specific plugin
+        folder.
+        '''
+        try:
+            __import__(media_type)
+        except ImportError as e:
+            raise Exception('ERROR: Could not import {0}: {1}'.format(media_type, e))
+            
+        yield media_type, sys.modules[media_type].MEDIA_MANAGER
+
+def get_media_manager(_media_type = None):
+    for media_type, manager in get_media_managers():
+        if media_type in _media_type:
+            return manager
+
+
+def get_media_type_and_manager(filename):
+    for media_type, manager in get_media_managers():
+        if filename.find('.') > 0:
+            ext = os.path.splitext(filename)[1].lower()
+        else:
+            raise InvalidFileType(
+                'Could not find any file extension in "{0}"'.format(
+                    filename))
+
+        if ext[1:] in manager['accepted_extensions']:
+            return media_type, manager
diff --git a/mediagoblin/media_types/image/__init__.py b/mediagoblin/media_types/image/__init__.py
new file mode 100644 (file)
index 0000000..0cd0383
--- /dev/null
@@ -0,0 +1,28 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011 MediaGoblin contributors.  See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from mediagoblin.media_types.image.processing import process_media
+
+
+MEDIA_MANAGER = {
+    "human_readable": "Image",
+    "processor": process_media, # alternately a string,
+                                # 'mediagoblin.media_types.image.processing'?
+    "display_template": "mediagoblin/media_displays/image.html",
+    "default_thumb": "images/media_thumbs/image.jpg",
+    "accepted_extensions": ["jpg", "jpeg", "png", "gif", "tiff"],
+    "accepted_mimetypes": [
+        "image/jpeg", "image/png", "image/gif", "image/tiff"]}
diff --git a/mediagoblin/media_types/image/processing.py b/mediagoblin/media_types/image/processing.py
new file mode 100644 (file)
index 0000000..2c4ad2b
--- /dev/null
@@ -0,0 +1,207 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011 MediaGoblin contributors.  See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+import Image
+
+from celery.task import Task
+from celery import registry
+
+from mediagoblin.db.util import ObjectId
+from mediagoblin import mg_globals as mgg
+
+from mediagoblin.util import lazy_pass_to_ugettext as _
+
+THUMB_SIZE = 180, 180
+MEDIUM_SIZE = 640, 640
+
+
+def create_pub_filepath(entry, filename):
+    return mgg.public_store.get_unique_filepath(
+            ['media_entries',
+             unicode(entry['_id']),
+             filename])
+
+
+class BaseProcessingFail(Exception):
+    """
+    Base exception that all other processing failure messages should
+    subclass from.
+  
+    You shouldn't call this itself; instead you should subclass it
+    and provid the exception_path and general_message applicable to
+    this error.
+    """
+    general_message = u''
+  
+    @property
+    def exception_path(self):
+        return u"%s:%s" % (
+            self.__class__.__module__, self.__class__.__name__)
+
+    def __init__(self, **metadata):
+        self.metadata = metadata or {}
+  
+  
+class BadMediaFail(BaseProcessingFail):
+    """
+    Error that should be raised when an inappropriate file was given
+    for the media type specified.
+    """
+    general_message = _(u'Invalid file given for media type.')
+
+
+################################
+# Media processing initial steps
+################################
+
+class ProcessMedia(Task):
+    """
+    Pass this entry off for processing.
+    """
+    def run(self, media_id):
+        """
+        Pass the media entry off to the appropriate processing function
+        (for now just process_image...)
+        """
+        entry = mgg.database.MediaEntry.one(
+            {'_id': ObjectId(media_id)})
+
+        # Try to process, and handle expected errors.
+        try:
+            process_image(entry)
+        except BaseProcessingFail, exc:
+            mark_entry_failed(entry[u'_id'], exc)
+            return
+
+        entry['state'] = u'processed'
+        entry.save()
+
+    def on_failure(self, exc, task_id, args, kwargs, einfo):
+        """
+        If the processing failed we should mark that in the database.
+
+        Assuming that the exception raised is a subclass of BaseProcessingFail,
+        we can use that to get more information about the failure and store that
+        for conveying information to users about the failure, etc.
+        """
+        entry_id = args[0]
+        mark_entry_failed(entry_id, exc)
+
+
+process_media = registry.tasks[ProcessMedia.name]
+
+
+def mark_entry_failed(entry_id, exc):
+    """
+    Mark a media entry as having failed in its conversion.
+
+    Uses the exception that was raised to mark more information.  If the
+    exception is a derivative of BaseProcessingFail then we can store extra
+    information that can be useful for users telling them why their media failed
+    to process.
+
+    Args:
+     - entry_id: The id of the media entry
+
+    """
+    # Was this a BaseProcessingFail?  In other words, was this a
+    # type of error that we know how to handle?
+    if isinstance(exc, BaseProcessingFail):
+        # Looks like yes, so record information about that failure and any
+        # metadata the user might have supplied.
+        mgg.database['media_entries'].update(
+            {'_id': entry_id},
+            {'$set': {u'state': u'failed',
+                      u'fail_error': exc.exception_path,
+                      u'fail_metadata': exc.metadata}})
+    else:
+        # Looks like no, so just mark it as failed and don't record a
+        # failure_error (we'll assume it wasn't handled) and don't record
+        # metadata (in fact overwrite it if somehow it had previous info
+        # here)
+        mgg.database['media_entries'].update(
+            {'_id': entry_id},
+            {'$set': {u'state': u'failed',
+                      u'fail_error': None,
+                      u'fail_metadata': {}}})
+
+
+def process_image(entry):
+    """
+    Code to process an image
+    """
+    workbench = mgg.workbench_manager.create_workbench()
+
+    queued_filepath = entry['queued_media_file']
+    queued_filename = workbench.localized_file(
+        mgg.queue_store, queued_filepath,
+        'source')
+
+    try:
+        thumb = Image.open(queued_filename)
+    except IOError:
+        raise BadMediaFail()
+
+    thumb.thumbnail(THUMB_SIZE, Image.ANTIALIAS)
+    # ensure color mode is compatible with jpg
+    if thumb.mode != "RGB":
+        thumb = thumb.convert("RGB")
+
+    thumb_filepath = create_pub_filepath(entry, 'thumbnail.jpg')
+    thumb_file = mgg.public_store.get_file(thumb_filepath, 'w')
+
+    with thumb_file:
+        thumb.save(thumb_file, "JPEG", quality=90)
+
+    # If the size of the original file exceeds the specified size of a `medium`
+    # file, a `medium.jpg` files is created and later associated with the media
+    # entry.
+    medium = Image.open(queued_filename)
+    medium_processed = False
+
+    if medium.size[0] > MEDIUM_SIZE[0] or medium.size[1] > MEDIUM_SIZE[1]:
+        medium.thumbnail(MEDIUM_SIZE, Image.ANTIALIAS)
+
+        if medium.mode != "RGB":
+            medium = medium.convert("RGB")
+
+        medium_filepath = create_pub_filepath(entry, 'medium.jpg')
+        medium_file = mgg.public_store.get_file(medium_filepath, 'w')
+
+        with medium_file:
+            medium.save(medium_file, "JPEG", quality=90)
+            medium_processed = True
+
+    # we have to re-read because unlike PIL, not everything reads
+    # things in string representation :)
+    queued_file = file(queued_filename, 'rb')
+
+    with queued_file:
+        original_filepath = create_pub_filepath(entry, queued_filepath[-1])
+
+        with mgg.public_store.get_file(original_filepath, 'wb') as original_file:
+            original_file.write(queued_file.read())
+
+    mgg.queue_store.delete_file(queued_filepath)
+    entry['queued_media_file'] = []
+    media_files_dict = entry.setdefault('media_files', {})
+    media_files_dict['thumb'] = thumb_filepath
+    media_files_dict['original'] = original_filepath
+    if medium_processed:
+        media_files_dict['medium'] = medium_filepath
+
+    # clean up workbench
+    workbench.destroy_self()
diff --git a/mediagoblin/media_types/video/__init__.py b/mediagoblin/media_types/video/__init__.py
new file mode 100644 (file)
index 0000000..2a36623
--- /dev/null
@@ -0,0 +1,26 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011 MediaGoblin contributors.  See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from mediagoblin.media_types.video.processing import process_media
+
+
+MEDIA_MANAGER = {
+    "human_readable": "Video",
+    "processor": process_media, # alternately a string,
+                                # 'mediagoblin.media_types.image.processing'?
+    "display_template": "mediagoblin/media_displays/video.html",
+    "default_thumb": "images/media_thumbs/video.jpg",
+    "accepted_extensions": ["mp4", "mov", "webm", "avi", "3gp", "3gpp"]}
diff --git a/mediagoblin/media_types/video/processing.py b/mediagoblin/media_types/video/processing.py
new file mode 100644 (file)
index 0000000..9478483
--- /dev/null
@@ -0,0 +1,260 @@
+# GNU MediaGoblin -- federated, autonomous media hosting
+# Copyright (C) 2011 MediaGoblin contributors.  See AUTHORS.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+import Image
+import tempfile
+
+from celery.task import Task
+from celery import registry
+
+from mediagoblin.db.util import ObjectId
+from mediagoblin import mg_globals as mgg
+
+from mediagoblin.util import lazy_pass_to_ugettext as _
+
+import gobject
+
+import gst
+import arista
+
+from arista.transcoder import TranscoderOptions
+
+THUMB_SIZE = 180, 180
+MEDIUM_SIZE = 640, 640
+ARISTA_DEVICE_KEY = 'web'
+
+
+loop = None
+
+
+def process_video(entry):
+    """
+    Code to process a video
+    """
+    info = {}
+    workbench = mgg.workbench_manager.create_workbench()
+
+    queued_filepath = entry['queued_media_file']
+    queued_filename = workbench.localized_file(
+        mgg.queue_store, queued_filepath,
+        'source')
+
+    arista.init()
+
+    devices = arista.presets.get()
+    device = devices[ARISTA_DEVICE_KEY]
+
+    queue = arista.queue.TranscodeQueue()
+    
+    info['tmp_file'] = tmp_file = tempfile.NamedTemporaryFile()
+
+    info['medium_filepath'] = medium_filepath = create_pub_filepath(entry, 'video.webm')
+
+    output = tmp_file.name
+
+    uri = 'file://' + queued_filename
+
+    preset = device.presets[device.default]
+
+    opts = TranscoderOptions(uri, preset, output)
+
+    queue.append(opts)
+
+    info['entry'] = entry
+
+    queue.connect("entry-start", entry_start, info)
+#    queue.connect("entry-pass-setup", entry_pass_setup, options)
+    queue.connect("entry-error", entry_error, info)
+    queue.connect("entry-complete", entry_complete, info)
+
+    info['loop'] = loop = gobject.MainLoop()
+
+    loop.run()
+
+    # we have to re-read because unlike PIL, not everything reads
+    # things in string representation :)
+    queued_file = file(queued_filename, 'rb')
+
+    with queued_file:
+        original_filepath = create_pub_filepath(entry, queued_filepath[-1])
+
+        with mgg.public_store.get_file(original_filepath, 'wb') as original_file:
+            original_file.write(queued_file.read())
+
+    mgg.queue_store.delete_file(queued_filepath)
+    entry['queued_media_file'] = []
+    media_files_dict = entry.setdefault('media_files', {})
+    media_files_dict['original'] = original_filepath
+
+    # clean up workbench
+    workbench.destroy_self()
+    
+
+def create_pub_filepath(entry, filename):
+    return mgg.public_store.get_unique_filepath(
+            ['media_entries',
+             unicode(entry['_id']),
+             filename])
+
+
+class BaseProcessingFail(Exception):
+    """
+    Base exception that all other processing failure messages should
+    subclass from.
+  
+    You shouldn't call this itself; instead you should subclass it
+    and provid the exception_path and general_message applicable to
+    this error.
+    """
+    general_message = u''
+  
+    @property
+    def exception_path(self):
+        return u"%s:%s" % (
+            self.__class__.__module__, self.__class__.__name__)
+
+    def __init__(self, **metadata):
+        self.metadata = metadata or {}
+  
+  
+class BadMediaFail(BaseProcessingFail):
+    """
+    Error that should be raised when an inappropriate file was given
+    for the media type specified.
+    """
+    general_message = _(u'Invalid file given for media type.')
+
+
+################################
+# Media processing initial steps
+################################
+
+class ProcessMedia(Task):
+    """
+    Pass this entry off for processing.
+    """
+    def run(self, media_id):
+        """
+        Pass the media entry off to the appropriate processing function
+        (for now just process_image...)
+        """
+        entry = mgg.database.MediaEntry.one(
+            {'_id': ObjectId(media_id)})
+
+        # Try to process, and handle expected errors.
+        try:
+            process_video(entry)
+        except BaseProcessingFail, exc:
+            mark_entry_failed(entry[u'_id'], exc)
+            return
+
+        entry['state'] = u'processed'
+        entry.save()
+
+    def on_failure(self, exc, task_id, args, kwargs, einfo):
+        """
+        If the processing failed we should mark that in the database.
+
+        Assuming that the exception raised is a subclass of BaseProcessingFail,
+        we can use that to get more information about the failure and store that
+        for conveying information to users about the failure, etc.
+        """
+        entry_id = args[0]
+        mark_entry_failed(entry_id, exc)
+
+
+process_media = registry.tasks[ProcessMedia.name]
+
+
+def mark_entry_failed(entry_id, exc):
+    """
+    Mark a media entry as having failed in its conversion.
+
+    Uses the exception that was raised to mark more information.  If the
+    exception is a derivative of BaseProcessingFail then we can store extra
+    information that can be useful for users telling them why their media failed
+    to process.
+
+    Args:
+     - entry_id: The id of the media entry
+
+    """
+    # Was this a BaseProcessingFail?  In other words, was this a
+    # type of error that we know how to handle?
+    if isinstance(exc, BaseProcessingFail):
+        # Looks like yes, so record information about that failure and any
+        # metadata the user might have supplied.
+        mgg.database['media_entries'].update(
+            {'_id': entry_id},
+            {'$set': {u'state': u'failed',
+                      u'fail_error': exc.exception_path,
+                      u'fail_metadata': exc.metadata}})
+    else:
+        # Looks like no, so just mark it as failed and don't record a
+        # failure_error (we'll assume it wasn't handled) and don't record
+        # metadata (in fact overwrite it if somehow it had previous info
+        # here)
+        mgg.database['media_entries'].update(
+            {'_id': entry_id},
+            {'$set': {u'state': u'failed',
+                      u'fail_error': None,
+                      u'fail_metadata': {}}})
+
+
+def entry_start(queue, entry, options):
+    print(queue, entry, options)
+
+def entry_complete(queue, entry, info):
+    entry.transcoder.stop()
+    gobject.idle_add(info['loop'].quit)
+
+    with info['tmp_file'] as tmp_file:
+        mgg.public_store.get_file(info['medium_filepath'], 'wb').write(
+            tmp_file.read())
+        info['entry']['media_files']['medium'] = info['medium_filepath']
+
+    print('\n=== DONE! ===\n')
+
+    print(queue, entry, info)
+
+def entry_error(queue, entry, options):
+    print(queue, entry, options)
+
+def signal_handler(signum, frame):
+    """
+        Handle Ctr-C gracefully and shut down the transcoder.
+    """
+    global interrupted
+    print
+    print _("Interrupt caught. Cleaning up... (Ctrl-C to force exit)")
+    interrupted = True
+    signal.signal(signal.SIGINT, signal.SIG_DFL)
+
+def check_interrupted():
+    """
+        Check whether we have been interrupted by Ctrl-C and stop the
+        transcoder.
+    """
+    if interrupted:
+        try:
+            source = transcoder.pipe.get_by_name("source")
+            source.send_event(gst.event_new_eos())
+        except:
+            # Something pretty bad happened... just exit!
+            gobject.idle_add(loop.quit)
+            
+        return False
+    return True
index b1dd945010710f1ae96c393fc0d647017fc1c624..85d52242f0e845fffda3ea1d223f017933a7b044 100644 (file)
@@ -97,8 +97,14 @@ class CloudFilesStorage(StorageInterface):
     def delete_file(self, filepath):
         # TODO: Also delete unused directories if empty (safely, with
         # checks to avoid race conditions).
-        self.container.delete_object(
-            self._resolve_filepath(filepath))
+        try:
+            self.container.delete_object(
+                self._resolve_filepath(filepath))
+        except cloudfiles.container.ResponseError:
+            pass
+        finally:
+            pass
+        
 
     def file_url(self, filepath):
         return '/'.join([
index e24d78f3427a09c8484ee431673a54dbb690174d..78f52160aa02c8ff67896ac9e3128c382ea596a8 100644 (file)
@@ -28,8 +28,9 @@ from mediagoblin.util import (
 from mediagoblin.util import pass_to_ugettext as _
 from mediagoblin.decorators import require_active_login
 from mediagoblin.submit import forms as submit_forms, security
-from mediagoblin.process_media import process_media, mark_entry_failed
+from mediagoblin.process_media import mark_entry_failed
 from mediagoblin.messages import add_message, SUCCESS
+from mediagoblin.media_types import get_media_type_and_manager
 
 
 @require_active_login
@@ -45,15 +46,15 @@ def submit_start(request):
                 and request.POST['file'].file):
             submit_form.file.errors.append(
                 _(u'You must provide a file.'))
-        elif not security.check_filetype(request.POST['file']):
-            submit_form.file.errors.append(
-                _(u"The file doesn't seem to be an image!"))
         else:
             filename = request.POST['file'].filename
 
+            media_type, media_manager = get_media_type_and_manager(filename)
+
             # create entry and save in database
             entry = request.db.MediaEntry()
             entry['_id'] = ObjectId()
+            entry['media_type'] = unicode(media_type)
             entry['title'] = (
                 unicode(request.POST['title'])
                 or unicode(splitext(filename)[0]))
@@ -62,7 +63,6 @@ def submit_start(request):
             entry['description_html'] = cleaned_markdown_conversion(
                 entry['description'])
             
-            entry['media_type'] = u'image' # heh
             entry['uploader'] = request.user['_id']
 
             # Process the user's folksonomy "tags"
@@ -72,6 +72,7 @@ def submit_start(request):
             # Generate a slug from the title
             entry.generate_slug()
 
+
             # Now store generate the queueing related filename
             queue_filepath = request.app.queue_store.get_unique_filepath(
                 ['media_entries',
@@ -103,7 +104,7 @@ def submit_start(request):
             # (... don't change entry after this point to avoid race
             # conditions with changes to the document via processing code)
             try:
-                process_media.apply_async(
+                media_manager['processor'].apply_async(
                     [unicode(entry['_id'])], {},
                     task_id=task_id)
             except BaseException as exc:
diff --git a/mediagoblin/templates/mediagoblin/media_displays/image.html b/mediagoblin/templates/mediagoblin/media_displays/image.html
new file mode 100644 (file)
index 0000000..ad60fa9
--- /dev/null
@@ -0,0 +1 @@
+{% extends 'mediagoblin/user_pages/media.html' %}
diff --git a/mediagoblin/templates/mediagoblin/media_displays/video.html b/mediagoblin/templates/mediagoblin/media_displays/video.html
new file mode 100644 (file)
index 0000000..3758692
--- /dev/null
@@ -0,0 +1,8 @@
+{% extends 'mediagoblin/user_pages/media.html' %}
+{% block mediagoblin_media %}
+  <video width="640" height="" controls>
+    <source src="{{ request.app.public_store.file_url(
+                media['media_files']['medium']) }}" 
+           type='video/webm; codecs="vp8, vorbis"' />
+  </video>
+{% endblock %}
index 442bef6da27d762609ab8ebb74e158327a35a44a..82a48e7cd077ae11af8bd1acd2280f411c976e76 100644 (file)
   {% if media %}
     <div class="grid_11 alpha">
       <div class="media_image_container">
-        {% set display_media = request.app.public_store.file_url(
-                 media.get_display_media(media.media_files)) %}
-
-        {# if there's a medium file size, that means the medium size
-         #  isn't the original... so link to the original!
-         #}
-        {% if media['media_files'].has_key('medium') %}
-          <a href="{{ request.app.public_store.file_url(
-                        media['media_files']['original']) }}">
+       {% block mediagoblin_media %}
+          {% set display_media = request.app.public_store.file_url(
+                   media.get_display_media(media.media_files)) %}
+
+          {# if there's a medium file size, that means the medium size
+           #  isn't the original... so link to the original!
+           #}
+          {% if media['media_files'].has_key('medium') %}
+            <a href="{{ request.app.public_store.file_url(
+                          media['media_files']['original']) }}">
+              <img class="media_image"
+                   src="{{ display_media }}"
+                   alt="Image for {{ media.title }}" />
+            </a>
+          {% else %}
             <img class="media_image"
                  src="{{ display_media }}"
                  alt="Image for {{ media.title }}" />
-          </a>
-        {% else %}
-          <img class="media_image"
-               src="{{ display_media }}"
-               alt="Image for {{ media.title }}" />
-        {% endif %}
+          {% endif %}
+       {% endblock %}
       </div>
 
       <h2 class="media_title">
index 6a82d718b6dc25950b9b0540c895a5c6660b9458..5458c694f2a142ec82444aaaa75e10f0d52f333e 100644 (file)
@@ -29,6 +29,8 @@ from mediagoblin.decorators import (uses_pagination, get_user_media_entry,
 
 from werkzeug.contrib.atom import AtomFeed
 
+from mediagoblin.media_types import get_media_manager
+
 
 @uses_pagination
 def user_home(request, page):
@@ -113,9 +115,11 @@ def media_home(request, media, page, **kwargs):
 
     comment_form = user_forms.MediaCommentForm(request.POST)
 
+    media_template_name = get_media_manager(media['media_type'])['display_template']
+
     return render_to_response(
         request,
-        'mediagoblin/user_pages/media.html',
+        media_template_name,
         {'media': media,
          'comments': comments,
          'pagination': pagination,