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:
parent
3662a1f4b4
commit
fc850dc449
8 changed files with 560 additions and 11 deletions
|
@ -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
|
|
||||||
|
|
|
@ -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)
|
#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()
|
||||||
|
|
||||||
myfile.file.close()
|
myfile.file.close()
|
||||||
permanent_file.close()
|
permanent_file.close()
|
||||||
|
|
||||||
return 'Successfully uploaded: %s'%myfile.filename
|
return 'Successfully uploaded: %s'%""
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
0
trunk/yolanda/lib/gstreamer/__init__.py
Normal file
0
trunk/yolanda/lib/gstreamer/__init__.py
Normal file
139
trunk/yolanda/lib/gstreamer/encode.py
Normal file
139
trunk/yolanda/lib/gstreamer/encode.py
Normal 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))
|
185
trunk/yolanda/lib/gstreamer/info.py
Normal file
185
trunk/yolanda/lib/gstreamer/info.py
Normal 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))
|
220
trunk/yolanda/lib/gstreamer/snapshot.py
Normal file
220
trunk/yolanda/lib/gstreamer/snapshot.py
Normal 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))
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue