aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--docs/source/devel/codebase.rst140
-rw-r--r--mediagoblin/media_types/video/transcoders.py348
-rw-r--r--mediagoblin/user_pages/views.py8
-rwxr-xr-xruntests.sh14
-rw-r--r--setup.py2
5 files changed, 126 insertions, 386 deletions
diff --git a/docs/source/devel/codebase.rst b/docs/source/devel/codebase.rst
index 98e0c26e..cd46242c 100644
--- a/docs/source/devel/codebase.rst
+++ b/docs/source/devel/codebase.rst
@@ -41,6 +41,76 @@ which explains generally how to get going with running an instance for
development.
+What's where
+============
+
+After you've run checked out mediagoblin and followed the virtualenv
+instantiation instructions, you're faced with the following directory
+tree::
+
+ mediagoblin/
+ |- mediagoblin/ # source code
+ | |- db/ # database setup
+ | |- tools/ # various utilities
+ | |- init/ # "initialization" tools (arguably should be in tools/)
+ | |- tests/ # unit tests
+ | |- templates/ # templates for this application
+ | |- media_types/ # code for processing, displaying different media
+ | |- storage/ # different storage backends
+ | |- gmg_commands/ # command line tools (./bin/gmg)
+ | |- themes/ # pre-bundled themes
+ | |
+ | | # ... some submodules here as well for different sections
+ | | # of the application... here's just a few
+ | |- auth/ # authentication (login/registration) code
+ | |- user_dev/ # user pages (under /u/), including media pages
+ | \- submit/ # submitting media for processing
+ |
+ |- docs/ # documentation
+ |- devtools/ # some scripts for developer convenience
+ |
+ |- user_dev/ # local instance sessions, media, etc
+ |
+ | # the below directories are installed into your virtualenv checkout
+ |
+ |- bin/ # scripts
+ |- develop-eggs/
+ |- lib/ # python libraries installed into your virtualenv
+ |- include/
+ |- mediagoblin.egg-info/
+ \- parts/
+
+
+As you can see, all the code for GNU MediaGoblin is in the
+``mediagoblin`` directory.
+
+Here are some interesting files and what they do:
+
+:routing.py: maps url paths to views
+:views.py: views handle http requests
+:forms.py: wtforms stuff for this submodule
+
+You'll notice that there are several sub-directories: tests,
+templates, auth, submit, ...
+
+``tests`` holds the unit test code.
+
+``templates`` holds all the templates for the output.
+
+``auth`` and ``submit`` are modules that enacpsulate authentication
+and media item submission. If you look in these directories, you'll
+see they have their own ``routing.py``, ``view.py``, and forms.py in
+addition to some other code.
+
+You'll also notice that mediagoblin/db/ contains quite a few things,
+including the following:
+
+:models.py: This is where the database is set up
+:mixin.py: Certain functions appended to models from here
+:migrations.py: When creating a new migration (a change to the
+ database structure), we put it here
+
+
Software Stack
==============
@@ -111,73 +181,3 @@ Software Stack
* `JQuery <http://jquery.com/>`_: for groovy JavaScript things
-
-What's where
-============
-
-After you've run checked out mediagoblin and followed the virtualenv
-instantiation instructions, you're faced with the following directory
-tree::
-
- mediagoblin/
- |- mediagoblin/ # source code
- | |- db/ # database setup
- | |- tools/ # various utilities
- | |- init/ # "initialization" tools (arguably should be in tools/)
- | |- tests/ # unit tests
- | |- templates/ # templates for this application
- | |- media_types/ # code for processing, displaying different media
- | |- storage/ # different storage backends
- | |- gmg_commands/ # command line tools (./bin/gmg)
- | |- themes/ # pre-bundled themes
- | |
- | | # ... some submodules here as well for different sections
- | | # of the application... here's just a few
- | |- auth/ # authentication (login/registration) code
- | |- user_dev/ # user pages (under /u/), including media pages
- | \- submit/ # submitting media for processing
- |
- |- docs/ # documentation
- |- devtools/ # some scripts for developer convenience
- |
- |- user_dev/ # local instance sessions, media, etc
- |
- | # the below directories are installed into your virtualenv checkout
- |
- |- bin/ # scripts
- |- develop-eggs/
- |- lib/ # python libraries installed into your virtualenv
- |- include/
- |- mediagoblin.egg-info/
- \- parts/
-
-
-As you can see, all the code for GNU MediaGoblin is in the
-``mediagoblin`` directory.
-
-Here are some interesting files and what they do:
-
-:routing.py: maps url paths to views
-:views.py: views handle http requests
-:forms.py: wtforms stuff for this submodule
-
-You'll notice that there are several sub-directories: tests,
-templates, auth, submit, ...
-
-``tests`` holds the unit test code.
-
-``templates`` holds all the templates for the output.
-
-``auth`` and ``submit`` are modules that enacpsulate authentication
-and media item submission. If you look in these directories, you'll
-see they have their own ``routing.py``, ``view.py``, and forms.py in
-addition to some other code.
-
-You'll also notice that mediagoblin/db/ contains quite a few things,
-including the following:
-
-:models.py: This is where the database is set up
-:mixin.py: Certain functions appended to models from here
-:migrations.py: When creating a new migration (a change to the
- database structure), we put it here
-
diff --git a/mediagoblin/media_types/video/transcoders.py b/mediagoblin/media_types/video/transcoders.py
index d8290d41..58b2c0d4 100644
--- a/mediagoblin/media_types/video/transcoders.py
+++ b/mediagoblin/media_types/video/transcoders.py
@@ -53,283 +53,6 @@ def pixbuf_to_pilbuf(buf):
return data
-class VideoThumbnailer:
- # Declaration of thumbnailer states
- STATE_NULL = 0
- STATE_HALTING = 1
- STATE_PROCESSING = 2
-
- # The current thumbnailer state
-
- def __init__(self, source_path, dest_path):
- '''
- Set up playbin pipeline in order to get video properties.
-
- Initializes and runs the gobject.MainLoop()
-
- Abstract
- - Set up a playbin with a fake audio sink and video sink. Load the video
- into the playbin
- - Initialize
- '''
- # This will contain the thumbnailing pipeline
- self.state = self.STATE_NULL
- self.thumbnail_pipeline = None
- self.buffer_probes = {}
- self.errors = []
-
- self.source_path = source_path
- self.dest_path = dest_path
-
- self.loop = gobject.MainLoop()
-
- # Set up the playbin. It will be used to discover certain
- # properties of the input file
- self.playbin = gst.element_factory_make('playbin')
-
- self.videosink = gst.element_factory_make('fakesink', 'videosink')
- self.playbin.set_property('video-sink', self.videosink)
-
- self.audiosink = gst.element_factory_make('fakesink', 'audiosink')
- self.playbin.set_property('audio-sink', self.audiosink)
-
- self.bus = self.playbin.get_bus()
- self.bus.add_signal_watch()
- self.watch_id = self.bus.connect('message', self._on_bus_message)
-
- self.playbin.set_property('uri', 'file:{0}'.format(
- urllib.pathname2url(self.source_path)))
-
- self.playbin.set_state(gst.STATE_PAUSED)
-
- self.run()
-
- def run(self):
- self.loop.run()
-
- def _on_bus_message(self, bus, message):
- _log.debug(' thumbnail playbin: {0}'.format(message))
-
- if message.type == gst.MESSAGE_ERROR:
- _log.error('thumbnail playbin: {0}'.format(message))
- gobject.idle_add(self._on_bus_error)
-
- elif message.type == gst.MESSAGE_STATE_CHANGED:
- # The pipeline state has changed
- # Parse state changing data
- _prev, state, _pending = message.parse_state_changed()
-
- _log.debug('State changed: {0}'.format(state))
-
- if state == gst.STATE_PAUSED:
- if message.src == self.playbin:
- gobject.idle_add(self._on_bus_paused)
-
- def _on_bus_paused(self):
- '''
- Set up thumbnailing pipeline
- '''
- current_video = self.playbin.get_property('current-video')
-
- if current_video == 0:
- _log.debug('Found current video from playbin')
- else:
- _log.error('Could not get any current video from playbin!')
-
- self.duration = self._get_duration(self.playbin)
- _log.info('Video length: {0}'.format(self.duration / gst.SECOND))
-
- _log.info('Setting up thumbnailing pipeline')
- self.thumbnail_pipeline = gst.parse_launch(
- 'filesrc location="{0}" ! decodebin ! '
- 'ffmpegcolorspace ! videoscale ! '
- 'video/x-raw-rgb,depth=24,bpp=24,pixel-aspect-ratio=1/1,width=180 ! '
- 'fakesink signal-handoffs=True'.format(self.source_path))
-
- self.thumbnail_bus = self.thumbnail_pipeline.get_bus()
- self.thumbnail_bus.add_signal_watch()
- self.thumbnail_watch_id = self.thumbnail_bus.connect(
- 'message', self._on_thumbnail_bus_message)
-
- self.thumbnail_pipeline.set_state(gst.STATE_PAUSED)
-
- #gobject.timeout_add(3000, self._on_timeout)
-
- return False
-
- def _on_thumbnail_bus_message(self, bus, message):
- _log.debug('thumbnail: {0}'.format(message))
-
- if message.type == gst.MESSAGE_ERROR:
- _log.error(message)
- gobject.idle_add(self._on_bus_error)
-
- if message.type == gst.MESSAGE_STATE_CHANGED:
- _log.debug('State changed')
- _prev, state, _pending = message.parse_state_changed()
-
- if (state == gst.STATE_PAUSED and
- not self.state == self.STATE_PROCESSING and
- message.src == self.thumbnail_pipeline):
- _log.info('Pipeline paused, processing')
- self.state = self.STATE_PROCESSING
-
- for sink in self.thumbnail_pipeline.sinks():
- name = sink.get_name()
- factoryname = sink.get_factory().get_name()
-
- if factoryname == 'fakesink':
- sinkpad = sink.get_pad('sink')
-
- self.buffer_probes[name] = sinkpad.add_buffer_probe(
- self.buffer_probe_handler, name)
-
- _log.info('Added buffer probe')
-
- break
-
- # Apply the wadsworth constant, fallback to 1 second
- # TODO: Will break if video is shorter than 1 sec
- seek_amount = max(self.duration / 100 * 30, 1 * gst.SECOND)
-
- _log.debug('seek amount: {0}'.format(seek_amount))
-
- seek_result = self.thumbnail_pipeline.seek(
- 1.0,
- gst.FORMAT_TIME,
- gst.SEEK_FLAG_FLUSH | gst.SEEK_FLAG_ACCURATE,
- gst.SEEK_TYPE_SET,
- seek_amount,
- gst.SEEK_TYPE_NONE,
- 0)
-
- if not seek_result:
- self.errors.append('COULD_NOT_SEEK')
- _log.error('Couldn\'t seek! result: {0}'.format(
- seek_result))
- _log.info(message)
- self.shutdown()
- else:
- _log.debug('Seek successful')
- self.thumbnail_pipeline.set_state(gst.STATE_PAUSED)
- else:
- _log.debug('Won\'t seek: \t{0}\n\t{1}'.format(
- self.state,
- message.src))
-
- def buffer_probe_handler_real(self, pad, buff, name):
- '''
- Capture buffers as gdk_pixbufs when told to.
- '''
- _log.info('Capturing frame')
- try:
- caps = buff.caps
- if caps is None:
- _log.error('No caps passed to buffer probe handler!')
- self.shutdown()
- return False
-
- _log.debug('caps: {0}'.format(caps))
-
- filters = caps[0]
- width = filters["width"]
- height = filters["height"]
-
- im = Image.new('RGB', (width, height))
-
- data = pixbuf_to_pilbuf(buff.data)
-
- im.putdata(data)
-
- im.save(self.dest_path)
-
- _log.info('Saved thumbnail')
-
- self.shutdown()
-
- except gst.QueryError as e:
- _log.error('QueryError: {0}'.format(e))
-
- return False
-
- def buffer_probe_handler(self, pad, buff, name):
- '''
- Proxy function for buffer_probe_handler_real
- '''
- _log.debug('Attaching real buffer handler to gobject idle event')
- gobject.idle_add(
- lambda: self.buffer_probe_handler_real(pad, buff, name))
-
- return True
-
- def _get_duration(self, pipeline, retries=0):
- '''
- Get the duration of a pipeline.
-
- Retries 5 times.
- '''
- if retries == 5:
- return 0
-
- try:
- return pipeline.query_duration(gst.FORMAT_TIME)[0]
- except gst.QueryError:
- return self._get_duration(pipeline, retries + 1)
-
- def _on_timeout(self):
- _log.error('Timeout in thumbnailer!')
- self.shutdown()
-
- def _on_bus_error(self, *args):
- _log.error('AHAHAHA! Error! args: {0}'.format(args))
-
- def shutdown(self):
- '''
- Tell gobject to call __halt when the mainloop is idle.
- '''
- _log.info('Shutting down')
- self.__halt()
-
- def __halt(self):
- '''
- Halt all pipelines and shut down the main loop
- '''
- _log.info('Halting...')
- self.state = self.STATE_HALTING
-
- self.__disconnect()
-
- gobject.idle_add(self.__halt_final)
-
- def __disconnect(self):
- _log.debug('Disconnecting...')
- if not self.playbin is None:
- self.playbin.set_state(gst.STATE_NULL)
- for sink in self.playbin.sinks():
- name = sink.get_name()
- factoryname = sink.get_factory().get_name()
-
- _log.debug('Disconnecting {0}'.format(name))
-
- if factoryname == "fakesink":
- pad = sink.get_pad("sink")
- pad.remove_buffer_probe(self.buffer_probes[name])
- del self.buffer_probes[name]
-
- self.playbin = None
-
- if self.bus is not None:
- self.bus.disconnect(self.watch_id)
- self.bus = None
-
- def __halt_final(self):
- _log.info('Done')
- if self.errors:
- _log.error(','.join(self.errors))
-
- self.loop.quit()
-
-
class VideoThumbnailerMarkII(object):
'''
Creates a thumbnail from a video file. Rewrite of VideoThumbnailer.
@@ -398,8 +121,8 @@ class VideoThumbnailerMarkII(object):
self.run()
except Exception as exc:
_log.critical(
- 'Exception "{0}" caught, disconnecting and re-raising'\
- .format(exc))
+ 'Exception "{0}" caught, shutting down mainloop and re-raising'\
+ .format(exc))
self.disconnect()
raise
@@ -410,7 +133,8 @@ class VideoThumbnailerMarkII(object):
self.mainloop.run()
def on_playbin_message(self, message_bus, message):
- _log.debug('playbin message: {0}'.format(message))
+ # Silenced to prevent clobbering of output
+ #_log.debug('playbin message: {0}'.format(message))
if message.type == gst.MESSAGE_ERROR:
_log.error('playbin error: {0}'.format(message))
@@ -433,17 +157,20 @@ pending: {2}'.format(
def on_playbin_paused(self):
if self.has_reached_playbin_pause:
- _log.warn('Has already reached logic for playbin pause. Aborting \
+ _log.warn('Has already reached on_playbin_paused. Aborting \
without doing anything this time.')
return False
self.has_reached_playbin_pause = True
+ # XXX: Why is this even needed at this point?
current_video = self.playbin.get_property('current-video')
if not current_video:
- _log.critical('thumbnail could not get any video data \
+ _log.critical('Could not get any video data \
from playbin')
+ else:
+ _log.info('Got video data from playbin')
self.duration = self.get_duration(self.playbin)
self.permission_to_take_picture = True
@@ -474,7 +201,8 @@ from playbin')
return False
def on_thumbnail_message(self, message_bus, message):
- _log.debug('thumbnail message: {0}'.format(message))
+ # This is silenced to prevent clobbering of the terminal window
+ #_log.debug('thumbnail message: {0}'.format(message))
if message.type == gst.MESSAGE_ERROR:
_log.error('thumbnail error: {0}'.format(message.parse_error()))
@@ -490,29 +218,10 @@ pending: {2}'.format(
cur_state,
pending_state))
- if cur_state == gst.STATE_PAUSED and\
- not self.state == self.STATE_PROCESSING_THUMBNAIL:
- self.state = self.STATE_PROCESSING_THUMBNAIL
-
+ if cur_state == gst.STATE_PAUSED and \
+ not self.state == self.STATE_PROCESSING_THUMBNAIL:
# Find the fakesink sink pad and attach the on_buffer_probe
# handler to it.
- for sink in self.thumbnail_pipeline.sinks():
- sink_name = sink.get_name()
- sink_factory_name = sink.get_factory().get_name()
-
- if sink_factory_name == 'fakesink':
- sink_pad = sink.get_pad('sink')
-
- self.buffer_probes[sink_name] = sink_pad\
- .add_buffer_probe(
- self.on_pad_buffer_probe,
- sink_name)
-
- _log.info('Attached buffer probes: {0}'.format(
- self.buffer_probes))
-
- break
-
seek_amount = self.position_callback(self.duration, gst)
seek_result = self.thumbnail_pipeline.seek(
@@ -525,10 +234,30 @@ pending: {2}'.format(
0)
if not seek_result:
- _log.critical('Could not seek.')
+ _log.info('Could not seek.')
+ else:
+ _log.info('Seek successful, attaching buffer probe')
+ self.state = self.STATE_PROCESSING_THUMBNAIL
+ for sink in self.thumbnail_pipeline.sinks():
+ sink_name = sink.get_name()
+ sink_factory_name = sink.get_factory().get_name()
+
+ if sink_factory_name == 'fakesink':
+ sink_pad = sink.get_pad('sink')
+
+ self.buffer_probes[sink_name] = sink_pad\
+ .add_buffer_probe(
+ self.on_pad_buffer_probe,
+ sink_name)
+
+ _log.info('Attached buffer probes: {0}'.format(
+ self.buffer_probes))
+
+ break
+
elif self.state == self.STATE_PROCESSING_THUMBNAIL:
- _log.debug('Already processing thumbnail')
+ _log.info('Already processing thumbnail')
def on_pad_buffer_probe(self, *args):
_log.debug('buffer probe handler: {0}'.format(args))
@@ -649,7 +378,7 @@ pending: {2}'.format(
return self.get_duration(pipeline, attempt + 1)
-class VideoTranscoder:
+class VideoTranscoder(object):
'''
Video transcoder
@@ -1011,6 +740,10 @@ if __name__ == '__main__':
action='store_true',
help='Dear program, please be quiet unless *error*')
+ parser.add_option('-w', '--width',
+ type=int,
+ default=180)
+
(options, args) = parser.parse_args()
if options.verbose:
@@ -1030,6 +763,7 @@ if __name__ == '__main__':
transcoder = VideoTranscoder()
if options.action == 'thumbnail':
+ args.append(options.width)
VideoThumbnailerMarkII(*args)
elif options.action == 'video':
def cb(data):
diff --git a/mediagoblin/user_pages/views.py b/mediagoblin/user_pages/views.py
index c611daa1..09ec7df9 100644
--- a/mediagoblin/user_pages/views.py
+++ b/mediagoblin/user_pages/views.py
@@ -208,7 +208,7 @@ def media_collect(request, media):
# Make sure this user isn't duplicating an existing collection
existing_collection = Collection.query.filter_by(
creator=request.user.id,
- title=request.form['collection_title']).first()
+ title=form.collection_title.data).first()
if existing_collection:
messages.add_message(request, messages.ERROR,
_('You already have a collection called "%s"!')
@@ -218,8 +218,8 @@ def media_collect(request, media):
media=media.slug_or_id)
collection = Collection()
- collection.title = request.form['collection_title']
- collection.description = request.form.get('collection_description')
+ collection.title = form.collection_title.data
+ collection.description = form.collection_description.data
collection.creator = request.user.id
collection.generate_slug()
collection.save()
@@ -251,7 +251,7 @@ def media_collect(request, media):
collection_item = request.db.CollectionItem()
collection_item.collection = collection.id
collection_item.media_entry = media.id
- collection_item.note = request.form['note']
+ collection_item.note = form.note.data
collection_item.save()
collection.items = collection.items + 1
diff --git a/runtests.sh b/runtests.sh
index a4ceec2e..fd19caf8 100755
--- a/runtests.sh
+++ b/runtests.sh
@@ -38,6 +38,16 @@ else
exit 1
fi
+
+CELERY_CONFIG_MODULE=mediagoblin.init.celery.from_tests
+export CELERY_CONFIG_MODULE
+echo "+ CELERY_CONFIG_MODULE=$CELERY_CONFIG_MODULE"
+
+# Look to see if the user has specified a specific directory/file to
+# run tests out of. If not we'll need to pass along
+# mediagoblin/tests/ later very specifically. Otherwise nosetests
+# will try to read all directories, and this turns into a mess!
+
need_arg=1
for i in "$@"
do
@@ -47,10 +57,6 @@ do
esac
done
-CELERY_CONFIG_MODULE=mediagoblin.init.celery.from_tests
-export CELERY_CONFIG_MODULE
-echo "+ CELERY_CONFIG_MODULE=$CELERY_CONFIG_MODULE"
-
if [ "$need_arg" = 1 ]
then
testdir="$basedir/mediagoblin/tests"
diff --git a/setup.py b/setup.py
index 9c295dc4..7264f249 100644
--- a/setup.py
+++ b/setup.py
@@ -54,7 +54,7 @@ setup(
'sphinx',
'Babel',
'argparse',
- 'webtest',
+ 'webtest<2',
'ConfigObj',
'Markdown',
'sqlalchemy>=0.7.0',