sanitized elixir usage and added huge gstreamer libs

git-svn-id: http://yolanda.mister-muffin.de/svn@375 7eef14d0-6ed0-489d-bf55-20463b2d70db
This commit is contained in:
josch 2008-08-26 12:49:17 +00:00
parent 3662a1f4b4
commit fc850dc449
8 changed files with 560 additions and 11 deletions

View file

@ -35,7 +35,5 @@ def load_environment(global_conf, app_conf):
# CONFIGURATION OPTIONS HERE (note: all config options will override # CONFIGURATION OPTIONS HERE (note: all config options will override
# any Pylons config options) # any Pylons config options)
engine = engine_from_config(config, 'sqlalchemy.') model.metadata.bind = engine_from_config(config, 'sqlalchemy.')
model.metadata.bind = engine
model.metadata.bind.echo = True model.metadata.bind.echo = True
model.Session.bind = engine

View file

@ -1,6 +1,8 @@
import logging import logging
from yolanda.lib.base import * from yolanda.lib.base import *
from yolanda.lib.gstreamer import info, snapshot
import os
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@ -11,13 +13,19 @@ class UploadController(BaseController):
def upload(self): def upload(self):
myfile = request.params['file'] myfile = request.params['file']
permanent_file = open(os.path.join( permanent_file = open(os.path.join(myfile.filename.lstrip(os.sep)),'w')
myfile.filename.lstrip(os.sep)),
'w') #u.copyfileobj(myfile.file, permanent_file)
foo=model.Video(title=u"foooooo")
model.session.commit()
videoinfo = info.Info(myfile.file)
videoinfo.get_info()
print videoinfo.print_info()
u.copyfileobj(myfile.file, permanent_file)
myfile.file.close() myfile.file.close()
permanent_file.close() permanent_file.close()
return 'Successfully uploaded: %s'%myfile.filename return 'Successfully uploaded: %s'%""

View file

@ -13,7 +13,6 @@ from pylons.templating import render
import yolanda.lib.helpers as h import yolanda.lib.helpers as h
import yolanda.lib.utils as u import yolanda.lib.utils as u
import yolanda.model as model import yolanda.model as model
import os
class BaseController(WSGIController): class BaseController(WSGIController):
@ -22,7 +21,7 @@ class BaseController(WSGIController):
# WSGIController.__call__ dispatches to the Controller method # WSGIController.__call__ dispatches to the Controller method
# the request is routed to. This routing information is # the request is routed to. This routing information is
# available in environ['pylons.routes_dict'] # available in environ['pylons.routes_dict']
response.headers['Content-type'] = "application/xml" response.headers['Content-type'] = "application/xhtml+xml"
return WSGIController.__call__(self, environ, start_response) return WSGIController.__call__(self, environ, start_response)
# Include the '_' function in the public names # Include the '_' function in the public names

View file

View file

@ -0,0 +1,139 @@
#!/usr/bin/env python
"""
Encode - convert video to theora through gstreamer
copyright 2008 - Johannes 'josch' Schauer <j.schauer@email.de>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
import sys, os
import gobject
#DO NOT FORGET THIS OR A RAPTOR WILL COME AND GET YOU!
gobject.threads_init()
import pygst
pygst.require("0.10")
import gst
class Encode:
"""
Encode will allow you to convert all your media that gstreamer is
capable of playing into an ogg/vorbis/theora file
"""
def _build_pipeline(self):
self.player = gst.Pipeline("player")
source = gst.element_factory_make("filesrc", "file-source")
source.set_property("location", self.filename)
decodebin = gst.element_factory_make("decodebin", "decodebin")
decodebin.connect("pad-added", self.decodebin_callback)
audioconvert = gst.element_factory_make("audioconvert", "audioconvert")
rsmpl = gst.element_factory_make('audioresample')
vorbisenc = gst.element_factory_make("vorbisenc", "vorbisenc")
#set audio quality (max: 1.0, default: TODO)
vorbisenc.set_property("quality", self.audioquality)
ffmpeg = gst.element_factory_make("ffmpegcolorspace", "ffmpeg")
filter = gst.element_factory_make("capsfilter", "filter")
#filter.set_property("caps", gst.caps_from_string("width=64,height=64"))
videoscale = gst.element_factory_make("videoscale", "videoscale")
videoscale.set_property("method", 1)
videorate = gst.element_factory_make("videorate", "videorate")
theoraenc = gst.element_factory_make("theoraenc", "theoraenc")
#set video quality (max: 63, default: 16)
theoraenc.set_property("quality", self.videoquality)
#set quick property (default: True)
theoraenc.set_property("quick", self.quick)
#set sharpness property (default: TODO)
theoraenc.set_property("sharpness", self.sharpness)
queuea = gst.element_factory_make("queue", "queuea")
queuev = gst.element_factory_make("queue", "queuev")
muxer = gst.element_factory_make("oggmux", "muxer")
filesink = gst.element_factory_make("filesink", "filesink")
filesink.set_property("location", self.filename+".ogg")
self.player.add(source, decodebin, ffmpeg, muxer, filesink, videorate,
videoscale, audioconvert, vorbisenc, theoraenc, queuea, queuev,
rsmpl, filter)
gst.element_link_many(source, decodebin)
if self.video:
gst.element_link_many(queuev, ffmpeg, filter, videoscale,
videorate, theoraenc, muxer)
if self.audio:
gst.element_link_many(queuea, audioconvert, rsmpl, vorbisenc,
muxer)
gst.element_link_many(muxer, filesink)
bus = self.player.get_bus()
bus.add_signal_watch()
bus.connect("message", self.on_message)
self.player.set_state(gst.STATE_PLAYING)
def __init__(self, filename, videoquality=16, quick=True, sharpness=2,
video=True, audio=True, audioquality=0.1):
if not os.path.isfile(filename):
raise IOError, "cannot read file"
self.filename = filename
if not video and not audio:
raise AttributeError, "must contain video or audio"
self.video = bool(video)
self.audio = bool(audio)
if not 1 <= int(videoquality) <= 63:
raise ValueError, "videoquality ranges from 1-63"
self.videoquality=int(videoquality)
self.quick = bool(quick)
if not 0 <= int(sharpness) <= 2:
raise ValueError, "sharpness ranges from 0-2"
self.sharpness = int(sharpness)
if not 0 <= float(audioquality) <= 1.0:
raise ValueError, "audioquality ranges from 0.0-1.0"
self.audioquality = float(audioquality)
self.failed = False
gobject.idle_add(self._build_pipeline)
self.mainloop = gobject.MainLoop()
def run(self):
self.mainloop.run()
return not self.failed
def on_message(self, bus, message):
t = message.type
if t == gst.MESSAGE_EOS:
self.player.set_state(gst.STATE_NULL)
gobject.idle_add(self.mainloop.quit)
elif t == gst.MESSAGE_ERROR:
err, debug = message.parse_error()
print "Error: %s" % err, debug
self.failed = True
self.player.set_state(gst.STATE_NULL)
gobject.idle_add(self.mainloop.quit)
def decodebin_callback(self, decodebin, pad):
if not pad.is_linked():
if "video" in pad.get_caps()[0].get_name():
pad.link(self.player.get_by_name("queuev").get_pad("sink"))
elif "audio" in pad.get_caps()[0].get_name():
pad.link(self.player.get_by_name("queuea").get_pad("sink"))
def main(args):
if len(args) != 2:
print 'usage: %s file' % args[0]
return 2
encode = Encode(args[1])
encode.run()
if __name__ == '__main__':
sys.exit(main(sys.argv))

View file

@ -0,0 +1,185 @@
#!/usr/bin/env python
"""
Info - get video information through gstreamer
copyright 2008 - Johannes 'josch' Schauer <j.schauer@email.de>
derived from discoverer.py from the python gstreamer examples.
kudos to Edward Hervey
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
import sys, os
import gobject
#DO NOT FORGET THIS OR A RAPTOR WILL COME AND GET YOU!
gobject.threads_init()
import pygst
pygst.require("0.10")
import gst
from gst.extend.discoverer import Discoverer
class FDSource(gst.BaseSrc):
"""
borrowed from filesrc.py in the gstreamer examples
kudos to David I. Lehn and Johan Dahlin
only changed input from file location to file descriptor
"""
__gsttemplates__ = (
gst.PadTemplate("src",
gst.PAD_SRC,
gst.PAD_ALWAYS,
gst.caps_new_any()),
)
blocksize = 4096
fd = None
def __init__(self, name):
self.__gobject_init__()
self.curoffset = 0
self.set_name(name)
def set_property(self, name, value):
if name == 'fd':
self.fd = value
def do_create(self, offset, size):
if offset != self.curoffset:
self.fd.seek(offset, 0)
data = self.fd.read(self.blocksize)
if data:
self.curoffset += len(data)
return gst.FLOW_OK, gst.Buffer(data)
else:
return gst.FLOW_UNEXPECTED, None
gobject.type_register(FDSource)
class Info(Discoverer):
"""
Because we derive from the Discoverer class itself here, all attributes
are directly accessible from outside as usual.
Added a synchronous interface with get_info() and filedescriptor input.
By implementing the MainLoop through subclassing we get maximum
flexibility and clean code.
"""
def __init__(self, fd, max_interleave=1.0):
"""
fd: filedescriptor of the file to be discovered.
max_interleave: int or float; the maximum frame interleave in seconds.
The value must be greater than the input file frame interleave
or the discoverer may not find out all input file's streams.
The default value is 1 second and you shouldn't have to change it,
changing it mean larger discovering time and bigger memory usage.
this init is 90% copy of the original but with a filedescriptor instead
a filename and three lines to init the main event loop
"""
gobject.GObject.__init__(self)
self.mimetype = None
self.audiocaps = {}
self.videocaps = {}
self.videowidth = 0
self.videoheight = 0
self.videorate = gst.Fraction(0,1)
self.audiofloat = False
self.audiorate = 0
self.audiodepth = 0
self.audiowidth = 0
self.audiochannels = 0
self.audiolength = 0L
self.videolength = 0L
self.is_video = False
self.is_audio = False
self.otherstreams = []
self.finished = False
self.tags = {}
self._success = False
self._nomorepads = False
self._timeoutid = 0
self._max_interleave = max_interleave
# first mod of original __init__ to use a filedescriptor source
if type(fd) is not file:
raise TypeError, "expected file like input, got %s"%type(fd)
# the initial elements of the pipeline
self.src = FDSource('filesrc')
self.src.set_property("fd", fd)
self.dbin = gst.element_factory_make("decodebin")
self.add(self.src, self.dbin)
self.src.link(self.dbin)
self.typefind = self.dbin.get_by_name("typefind")
# callbacks
self.typefind.connect("have-type", self._have_type_cb)
self.dbin.connect("new-decoded-pad", self._new_decoded_pad_cb)
self.dbin.connect("no-more-pads", self._no_more_pads_cb)
self.dbin.connect("unknown-type", self._unknown_type_cb)
# second mod to the discoverer __init__ to implement a main loop
self.connect('discovered', self._discovered)
gobject.idle_add(self._discover)
self.mainloop = gobject.MainLoop()
def get_info(self):
"""
By running the main loop this will fire off the discover function.
The main loop will return when something was discovered.
The function returns wether or not the discovering was successful.
"""
self.mainloop.run()
return self.finished and self.mimetype and \
(self.is_video or self.is_audio)
def _discovered(self, discoverer, ismedia):
"""When we discover something - quit main loop"""
gobject.idle_add(self.mainloop.quit)
def _discover(self):
"""
when we are not finished (eg. because the file is invalid) then try
to discover video information (that will call the discovered function)
otherwise stop the main loop
"""
if self.finished:
gobject.idle_add(self.mainloop.quit)
else:
self.discover()
return False
def main(args):
"""here we add a nice cli interface and some example how to use the lib"""
if len(args) != 2:
print 'usage: %s file' % args[0]
return 2
input = open(args[1], "rb")
info = Info(input)
if info.get_info():
info.print_info()
if __name__ == '__main__':
sys.exit(main(sys.argv))

View file

@ -0,0 +1,220 @@
#!/usr/bin/env python
"""
Snapshot - get video thumbnail through gstreamer
copyright 2008 - Johannes 'josch' Schauer <j.schauer@email.de>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
import sys, os
import gobject
#DO NOT FORGET THIS OR A RAPTOR WILL COME AND GET YOU!
gobject.threads_init()
import pygst
pygst.require("0.10")
import gst
import Image
import ImageStat
import ImageOps
import random
#adjust this if necessary
BORING_IMAGE_VARIANCE = 500
NUMBER_OF_TRIES = 10
class Snapshot:
"""
take interesting video snapshots.
"""
def _capture_interesting_frame(self, pad, buffer):
"""
this is the buffer probe which processes every frame that is being
played by the capture pipeline. since the first frame is not random, we
skip this frame by setting the self.first_analyze flag.
if the current frame is found intersting we save it. if not we decrease
self.tries to limit the number of tries
"""
if not self.first_analyze:
#get current buffer's capabilities
caps = buffer.get_caps ()
#we are interested in it's dimension
h, w = caps[0]['height'], caps[0]['width']
#using PIL we grab the image in raw RGB mode from the buffer data
im = Image.frombuffer('RGB', (w, h), buffer.data,
'raw', 'RGB', 0, 1)
#here we check the standard variance of a grayscale version of the
#current frame against the BORING_IMAGE_VARIANCE
if ImageStat.Stat(ImageOps.grayscale(im)).var[0] > \
BORING_IMAGE_VARIANCE:
#success! save our interesting image
self.image = im
else:
#the image is just useless... retry...
self.tries -= 1
else:
self.first_analyze = False
return True
def _build_pipeline(self):
"""
here we init our capturing pipeline.
"""
self.player = gst.Pipeline("player")
source = gst.element_factory_make("filesrc", "file-source")
source.set_property("location", self.filename)
decodebin = gst.element_factory_make("decodebin", "decodebin")
#connect the pad-added signal to the apropriate callback
decodebin.connect("pad-added", self._decodebin_cb)
queuev = gst.element_factory_make("queue", "queuev")
ffmpeg = gst.element_factory_make("ffmpegcolorspace", "ffmpeg")
#we use the capsfilter to convert our videostream from YUV to RGB
filter = gst.element_factory_make("capsfilter", "filter")
filter.set_property("caps", gst.caps_from_string("video/x-raw-rgb"))
fakesink = gst.element_factory_make("fakesink", "fakesink")
#add all elements to the capture pipeline
self.player.add(source, decodebin, ffmpeg, filter, fakesink, queuev)
#do some linking - only the link between decodebin and queuev is done
#later by the _decodebin_cb callback function
gst.element_link_many(source, decodebin)
gst.element_link_many(queuev, ffmpeg, filter, fakesink)
#we add a buffer probe to the capsfilter output pad. every frame will
#get passed to the _capture_interesting_frame function
self.player.get_by_name('filter').get_pad('src').\
add_buffer_probe(self._capture_interesting_frame)
#watch for messages
bus = self.player.get_bus()
bus.add_signal_watch()
bus.connect("message", self._on_message)
#start pipeline
self.player.set_state(gst.STATE_PLAYING)
def __init__(self, filename):
"""
lets init some variables, check if the video file exists and add a
signal to the mainloop that executes _build_pipeline the instant the
mainloop is run
"""
if not os.path.isfile(filename):
raise IOError, "cannot read file"
self.filename = filename
self.time_format = gst.Format(gst.FORMAT_TIME)
#we add a signal to immediately execute the pipeline builder after
#the mainloop is actually run
gobject.idle_add(self._build_pipeline)
self.mainloop = gobject.MainLoop()
#this is set to False after the first run. this prevents the first
#frame processed by the buffer probe from not being random
self.first_analyze = True
#set the number of retries
self.tries = NUMBER_OF_TRIES
#if this is set the main loop is quit and the image returned
self.image = None
#we find this out before seeking
self.duration = None
def get_snapshot(self):
"""
by starting the mainloop the capture pipeline will be build. this will
at some point issue an ASYNC_DONE message. this again will seek the
playing capture pipeline to a random position. a buffer probe will grab
the first frame played and test wether it is an intersting fellow.
if so, the random seeking will stop and we will quit the mainloop. this
will result in this function returning the captured image
"""
self.mainloop.run()
return self.image
def _seek_and_play_random(self):
"""
this function gets repeatedly called because it is linked to the
ASYNC_DONE event which it itself also emits when seeking is finished.
here we only seek to a random position in the video stream
"""
#there are some cases in which this tries to seek after the maximum
#number of tries is reached and the player is already nullyfied
if self.tries > 0:
#if we didn't already set the duration - do it now
#meaybe this can be done earlier but i didnt figure out when
if self.duration is None:
self.duration = self.player.query_duration(self.time_format,
None)[0]
#seek to random position. this will again fire an ASYNC_DONE and
#let the frame we seeked to be rendered by the buffer probe
self.player.seek_simple(self.time_format, gst.SEEK_FLAG_FLUSH,
random.randint(1, self.duration))
def _on_message(self, bus, message):
"""
since seeking issues the ASYNC signal we create an infinite loop here.
on each frame being seeked to the buffer probe function
_capture_interesting_frame gets called. this will fill self.im with a
non-boring screenshot which will then cause this _seek_and_play_random
loop to be finished
"""
t = message.type
if t == gst.MESSAGE_ASYNC_DONE:
#only loop again if there is still no image and tries are left
if self.image is not None or self.tries < 1:
#success! clean everything up
self.player.set_state(gst.STATE_NULL)
gobject.idle_add(self.mainloop.quit)
else:
#issue new seeking that probably will bring us here again
gobject.idle_add(self._seek_and_play_random)
elif t == gst.MESSAGE_EOS:
#somehow we seeded into the end of file - lets seek to a new pos
gobject.idle_add(self._seek_and_play_random)
elif t == gst.MESSAGE_ERROR:
#oups error - this shouldn't happen'
err, debug = message.parse_error()
print "Error: %s" % err, debug
self.player.set_state(gst.STATE_NULL)
gobject.idle_add(self.mainloop.quit)
def _decodebin_cb(self, decodebin, pad):
"""
when the decode bin is ready, a new pad is added for the video stream
we connect this pad to our video queue sink here
"""
#only do so if pad is not already linked to something
if not pad.is_linked():
#only do this for the video stream - we are not interested in audio
if "video" in pad.get_caps()[0].get_name():
#do the linking from the source pad to the queue's sink
pad.link(self.player.get_by_name("queuev").get_pad("sink"))
def main(args):
"""here we add a nice cli interface and some example how to use the lib"""
if len(args) != 2:
print 'usage: %s file' % args[0]
return 2
snapshot = Snapshot(args[1])
im = snapshot.get_snapshot()
if im:
print "SUCCESS!"
im.save("%s.jpg" %args[1], "JPEG")
if __name__ == '__main__':
sys.exit(main(sys.argv))

View file

@ -1,7 +1,7 @@
import elixir import elixir
# replace the elixir session with our own # replace the elixir session with our own
Session = elixir.session(autoflush=True, transactional=True) session = elixir.session
# use the elixir metadata # use the elixir metadata
metadata = elixir.metadata metadata = elixir.metadata