Update
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- Mode: Python -*-
|
||||
# vi:si:et:sw=4:sts=4:ts=4
|
||||
|
||||
# audio-controller.py
|
||||
# (c) 2005 Edward Hervey <edward at fluendo dot com>
|
||||
# Test case for the GstController on sinesrc -> alsasink
|
||||
# Inspired from ensonic's examples/controller/audio-controller.c
|
||||
|
||||
import pygst
|
||||
pygst.require('0.10')
|
||||
import gst
|
||||
import time
|
||||
|
||||
def main():
|
||||
pipeline = gst.Pipeline("audiocontroller")
|
||||
src = gst.element_factory_make("audiotestsrc", "src")
|
||||
sink = gst.element_factory_make("alsasink", "sink")
|
||||
pipeline.add(src, sink)
|
||||
src.link(sink)
|
||||
|
||||
control = gst.Controller(src, "freq", "volume")
|
||||
control.set_interpolation_mode("volume", gst.INTERPOLATE_LINEAR)
|
||||
control.set_interpolation_mode("freq", gst.INTERPOLATE_LINEAR)
|
||||
|
||||
control.set("volume", 0, 0.0)
|
||||
control.set("volume", 2 * gst.SECOND, 1.0)
|
||||
control.set("volume", 4 * gst.SECOND, 0.0)
|
||||
control.set("volume", 6 * gst.SECOND, 1.0)
|
||||
|
||||
control.set("freq", 0, 440.0)
|
||||
control.set("freq", 3 * gst.SECOND, 3000.0)
|
||||
control.set("freq", 6 * gst.SECOND, 880.0)
|
||||
|
||||
pipeline.set_state(gst.STATE_PLAYING)
|
||||
|
||||
time.sleep(7)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,192 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- Mode: Python -*-
|
||||
# vi:si:et:sw=4:sts=4:ts=4
|
||||
|
||||
# audioconcat.py - Concatenates multiple audio files to single ogg/vorbis file
|
||||
# Uses the gnonlin elements (http://gnonlin.sf.net/)
|
||||
# Copyright (C) 2005 Edward Hervey <edward@fluendo.com>
|
||||
# 2006 Jason Gerard DeRose <jderose@jasonderose.org>
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU Library General Public
|
||||
# License as published by the Free Software Foundation; either
|
||||
# version 2 of the License, or (at your option) any later version.
|
||||
#
|
||||
# This library 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
|
||||
# Library General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Library General Public
|
||||
# License along with this library; if not, write to the
|
||||
# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
||||
# Boston, MA 02110-1301, USA.
|
||||
#
|
||||
|
||||
import sys
|
||||
|
||||
import gobject
|
||||
gobject.threads_init()
|
||||
|
||||
import pygst
|
||||
pygst.require('0.10')
|
||||
import gst
|
||||
from gst.extend.discoverer import Discoverer
|
||||
|
||||
|
||||
|
||||
class AudioDec(gst.Bin):
|
||||
'''Decodes audio file, outputs at specified caps'''
|
||||
|
||||
def __init__(self, location, caps):
|
||||
gst.Bin.__init__(self)
|
||||
|
||||
# Create elements
|
||||
src = gst.element_factory_make('filesrc')
|
||||
dec = gst.element_factory_make('decodebin')
|
||||
conv = gst.element_factory_make('audioconvert')
|
||||
rsmpl = gst.element_factory_make('audioresample')
|
||||
ident = gst.element_factory_make('identity')
|
||||
|
||||
# Set 'location' property on filesrc
|
||||
src.set_property('location', location)
|
||||
|
||||
# Connect handler for 'new-decoded-pad' signal
|
||||
dec.connect('new-decoded-pad', self.__on_new_decoded_pad)
|
||||
|
||||
# Add elements to bin
|
||||
self.add(src, dec, conv, rsmpl, ident)
|
||||
|
||||
# Link *some* elements
|
||||
# This is completed in self.__on_new_decoded_pad()
|
||||
src.link(dec)
|
||||
conv.link(rsmpl)
|
||||
rsmpl.link(ident, caps)
|
||||
|
||||
# Reference used in self.__on_new_decoded_pad()
|
||||
self.__apad = conv.get_pad('sink')
|
||||
|
||||
# Add ghost pad
|
||||
self.add_pad(gst.GhostPad('src', ident.get_pad('src')))
|
||||
|
||||
|
||||
def __on_new_decoded_pad(self, element, pad, last):
|
||||
caps = pad.get_caps()
|
||||
name = caps[0].get_name()
|
||||
print '\n__on_new_decoded_pad:', name
|
||||
if 'audio' in name:
|
||||
if not self.__apad.is_linked(): # Only link once
|
||||
pad.link(self.__apad)
|
||||
|
||||
|
||||
|
||||
|
||||
class AudioConcat:
|
||||
'''Concatenates multiple audio files to single ogg/vorbis file'''
|
||||
|
||||
caps = gst.caps_from_string('audio/x-raw-float, rate=44100, channels=2, endianness=1234, width=32')
|
||||
|
||||
def __init__(self, infiles, outfile):
|
||||
# These are used in iteration through infiles
|
||||
self.infiles = infiles
|
||||
self.i = 0
|
||||
self.start = 0L
|
||||
|
||||
# The pipeline
|
||||
self.pipeline = gst.Pipeline()
|
||||
|
||||
# Create bus and connect 'eos' and 'error' handlers
|
||||
self.bus = self.pipeline.get_bus()
|
||||
self.bus.add_signal_watch()
|
||||
self.bus.connect('message::eos', self.on_eos)
|
||||
self.bus.connect('message::error', self.on_error)
|
||||
|
||||
# Create elements
|
||||
self.comp = gst.element_factory_make('gnlcomposition')
|
||||
self.enc = gst.element_factory_make('vorbisenc')
|
||||
self.mux = gst.element_factory_make('oggmux')
|
||||
self.sink = gst.element_factory_make('filesink')
|
||||
|
||||
# Connect handler for 'pad-added' signal
|
||||
self.comp.connect('pad-added', self.on_pad_added)
|
||||
|
||||
# Set 'location' property on filesink
|
||||
self.sink.set_property('location', outfile)
|
||||
|
||||
# Add elements to pipeline
|
||||
self.pipeline.add(self.comp, self.enc, self.mux, self.sink)
|
||||
|
||||
# Link *some* elements
|
||||
# This in completed in self.on_pad_added()
|
||||
gst.element_link_many(self.enc, self.mux, self.sink)
|
||||
|
||||
# Reference used in self.on_pad_added()
|
||||
self.apad = self.enc.get_pad('sink')
|
||||
|
||||
# The MainLoop
|
||||
self.mainloop = gobject.MainLoop()
|
||||
|
||||
# Iterate through infiles
|
||||
gobject.idle_add(self.discover)
|
||||
self.mainloop.run()
|
||||
|
||||
|
||||
def discover(self):
|
||||
infile = self.infiles[self.i]
|
||||
discoverer = Discoverer(infile)
|
||||
discoverer.connect('discovered', self.on_discovered, infile)
|
||||
discoverer.discover()
|
||||
return False # Don't repeat idle call
|
||||
|
||||
|
||||
def on_discovered(self, discoverer, ismedia, infile):
|
||||
print '\non_discovered:', infile
|
||||
discoverer.print_info()
|
||||
if discoverer.is_audio:
|
||||
dec = AudioDec(infile, self.caps)
|
||||
src = gst.element_factory_make('gnlsource')
|
||||
src.add(dec)
|
||||
src.set_property('media-start', 0L)
|
||||
src.set_property('media-duration', discoverer.audiolength)
|
||||
src.set_property('start', self.start)
|
||||
src.set_property('duration', discoverer.audiolength)
|
||||
self.comp.add(src)
|
||||
self.start += discoverer.audiolength
|
||||
self.i += 1
|
||||
if self.i < len(self.infiles):
|
||||
gobject.idle_add(self.discover)
|
||||
else:
|
||||
if self.start > 0: # At least 1 infile is_audio and audiolength > 0
|
||||
self.pipeline.set_state(gst.STATE_PLAYING)
|
||||
else:
|
||||
self.mainloop.quit()
|
||||
|
||||
|
||||
def on_pad_added(self, element, pad):
|
||||
caps = pad.get_caps()
|
||||
name = caps[0].get_name()
|
||||
print '\non_pad_added:', name
|
||||
if name == 'audio/x-raw-float':
|
||||
if not self.apad.is_linked(): # Only link once
|
||||
pad.link(self.apad)
|
||||
|
||||
|
||||
def on_eos(self, bus, msg):
|
||||
print '\non_eos'
|
||||
self.mainloop.quit()
|
||||
|
||||
|
||||
def on_error(self, bus, msg):
|
||||
error = msg.parse_error()
|
||||
print '\non_error:', error[1]
|
||||
self.mainloop.quit()
|
||||
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if len(sys.argv) >= 3:
|
||||
AudioConcat(sys.argv[1:-1], sys.argv[-1])
|
||||
else:
|
||||
print 'Usage: %s <input_file(s)> <output_file>' % sys.argv[0]
|
||||
print 'Example: %s song1.mp3 song2.ogg output.ogg' % sys.argv[0]
|
||||
Executable
+120
@@ -0,0 +1,120 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- Mode: Python -*-
|
||||
# vi:si:et:sw=4:sts=4:ts=4
|
||||
|
||||
# gst-python
|
||||
# Copyright (C) 2003 David I. Lehn
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU Library General Public
|
||||
# License as published by the Free Software Foundation; either
|
||||
# version 2 of the License, or (at your option) any later version.
|
||||
#
|
||||
# This library 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
|
||||
# Library General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Library General Public
|
||||
# License along with this library; if not, write to the
|
||||
# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
||||
# Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# Author: David I. Lehn <dlehn@users.sourceforge.net>
|
||||
#
|
||||
|
||||
import pygtk
|
||||
pygtk.require('2.0')
|
||||
|
||||
import sys
|
||||
import time
|
||||
import gobject
|
||||
import gtk
|
||||
|
||||
import pygst
|
||||
pygst.require('0.10')
|
||||
|
||||
import gst
|
||||
|
||||
class BPS(object):
|
||||
def __init__(self):
|
||||
self.buffers = 0
|
||||
self.start = 0
|
||||
|
||||
def done(self):
|
||||
end = time.time()
|
||||
dt = end - self.start
|
||||
bps = self.buffers/dt
|
||||
spb = dt/self.buffers
|
||||
print '\t%d buffers / %fs\t= %f bps\t= %f spb' % (self.buffers, dt, bps, spb)
|
||||
|
||||
def fakesrc(self, buffers):
|
||||
src = gst.element_factory_make('fakesrc','src')
|
||||
src.set_property('silent', 1)
|
||||
src.set_property('num_buffers', buffers)
|
||||
return src
|
||||
|
||||
def fakesink(self):
|
||||
sink = gst.element_factory_make('fakesink','sink')
|
||||
sink.set_property('silent', 1)
|
||||
return sink
|
||||
|
||||
def build_pipeline(self, buffers):
|
||||
pipeline = gst.Pipeline('pipeline')
|
||||
|
||||
src = self.fakesrc(buffers)
|
||||
pipeline.add(src)
|
||||
sink = self.fakesink()
|
||||
pipeline.add(sink)
|
||||
src.link(sink)
|
||||
|
||||
return pipeline
|
||||
|
||||
def idle(self, pipeline):
|
||||
return pipeline.iterate()
|
||||
|
||||
def test(self):
|
||||
self.bus = self.pipeline.get_bus()
|
||||
|
||||
self.start = time.time()
|
||||
|
||||
self.pipeline.set_state(gst.STATE_PLAYING)
|
||||
|
||||
while 1:
|
||||
msg = self.bus.poll(gst.MESSAGE_EOS | gst.MESSAGE_ERROR, gst.SECOND)
|
||||
if msg:
|
||||
break
|
||||
|
||||
self.pipeline.set_state(gst.STATE_NULL)
|
||||
self.done()
|
||||
|
||||
def run(self, buffers):
|
||||
self.buffers = buffers
|
||||
|
||||
print '# Testing buffer processing rate for "fakesrc ! fakesink"'
|
||||
print '# bps = buffers per second'
|
||||
print '# spb = seconds per buffer'
|
||||
|
||||
self.pipeline = self.build_pipeline(buffers)
|
||||
assert self.pipeline
|
||||
|
||||
self.test()
|
||||
|
||||
def main(args):
|
||||
"GStreamer Buffers-Per-Second tester"
|
||||
|
||||
if len(args) < 2:
|
||||
print 'usage: %s buffers' % args[0]
|
||||
return 1
|
||||
|
||||
bps = BPS()
|
||||
|
||||
buffers = int(args[1])
|
||||
if buffers < 1:
|
||||
print 'buffers must be higher than 0'
|
||||
return
|
||||
|
||||
bps.run(buffers)
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main(sys.argv))
|
||||
@@ -0,0 +1,138 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
import sys
|
||||
import traceback
|
||||
from math import pi
|
||||
|
||||
import pygtk
|
||||
pygtk.require ("2.0")
|
||||
import gobject
|
||||
gobject.threads_init()
|
||||
|
||||
import pygst
|
||||
pygst.require('0.10')
|
||||
import gst
|
||||
|
||||
import cairo
|
||||
|
||||
WIDTH, HEIGHT = 640, 480
|
||||
FRAMES = 300
|
||||
FRAMERATE = 15
|
||||
|
||||
class PyGstBufferDraw(gst.Element):
|
||||
_sinkpadtemplate = gst.PadTemplate ("sink",
|
||||
gst.PAD_SINK,
|
||||
gst.PAD_ALWAYS,
|
||||
gst.caps_from_string ("video/x-raw-rgb,bpp=32,depth=32,blue_mask=-16777216,green_mask=16711680, red_mask=65280, alpha_mask=255,width=[ 1, 2147483647 ],height=[ 1, 2147483647 ],framerate=[ 0/1, 2147483647/1 ]"))
|
||||
_srcpadtemplate = gst.PadTemplate ("src",
|
||||
gst.PAD_SRC,
|
||||
gst.PAD_ALWAYS,
|
||||
gst.caps_from_string ("video/x-raw-rgb,bpp=32,depth=32,blue_mask=-16777216,green_mask=16711680, red_mask=65280, alpha_mask=255,width=[ 1, 2147483647 ],height=[ 1, 2147483647 ],framerate=[ 0/1, 2147483647/1 ]"))
|
||||
|
||||
def __init__(self):
|
||||
gst.Element.__init__(self)
|
||||
|
||||
self.sinkpad = gst.Pad(self._sinkpadtemplate, "sink")
|
||||
self.sinkpad.set_chain_function(self.chainfunc)
|
||||
self.sinkpad.set_event_function(self.eventfunc)
|
||||
self.sinkpad.set_getcaps_function(gst.Pad.proxy_getcaps)
|
||||
self.sinkpad.set_setcaps_function(gst.Pad.proxy_setcaps)
|
||||
self.add_pad (self.sinkpad)
|
||||
|
||||
self.srcpad = gst.Pad(self._srcpadtemplate, "src")
|
||||
|
||||
self.srcpad.set_event_function(self.srceventfunc)
|
||||
self.srcpad.set_query_function(self.srcqueryfunc)
|
||||
self.srcpad.set_getcaps_function(gst.Pad.proxy_getcaps)
|
||||
self.srcpad.set_setcaps_function(gst.Pad.proxy_setcaps)
|
||||
self.add_pad (self.srcpad)
|
||||
|
||||
def chainfunc(self, pad, buffer):
|
||||
try:
|
||||
outbuf = buffer.copy_on_write ()
|
||||
self.draw_on (outbuf)
|
||||
return self.srcpad.push (outbuf)
|
||||
except:
|
||||
return GST_FLOW_ERROR
|
||||
|
||||
def eventfunc(self, pad, event):
|
||||
return self.srcpad.push_event (event)
|
||||
|
||||
def srcqueryfunc (self, pad, query):
|
||||
return self.sinkpad.query (query)
|
||||
def srceventfunc (self, pad, event):
|
||||
return self.sinkpad.push_event (event)
|
||||
|
||||
def draw_on (self, buf):
|
||||
try:
|
||||
caps = buf.get_caps()
|
||||
width = caps[0]['width']
|
||||
height = caps[0]['height']
|
||||
framerate = caps[0]['framerate']
|
||||
surface = cairo.ImageSurface.create_for_data (buf, cairo.FORMAT_ARGB32, width, height, 4 * width)
|
||||
ctx = cairo.Context(surface)
|
||||
except:
|
||||
print "Failed to create cairo surface for buffer"
|
||||
traceback.print_exc()
|
||||
return
|
||||
|
||||
try:
|
||||
center_x = width/4
|
||||
center_y = 3*height/4
|
||||
|
||||
# draw a circle
|
||||
radius = float (min (width, height)) * 0.25
|
||||
ctx.set_source_rgba (0.0, 0.0, 0.0, 0.9)
|
||||
ctx.move_to (center_x, center_y)
|
||||
ctx.arc (center_x, center_y, radius, 0, 2.0*pi)
|
||||
ctx.close_path()
|
||||
ctx.fill()
|
||||
ctx.set_source_rgba (1.0, 1.0, 1.0, 1.0)
|
||||
ctx.set_font_size(0.3 * radius)
|
||||
txt = "Hello World"
|
||||
extents = ctx.text_extents (txt)
|
||||
ctx.move_to(center_x - extents[2]/2, center_y + extents[3]/2)
|
||||
ctx.text_path(txt)
|
||||
ctx.fill()
|
||||
|
||||
except:
|
||||
print "Failed cairo render"
|
||||
traceback.print_exc()
|
||||
|
||||
gobject.type_register(PyGstBufferDraw)
|
||||
|
||||
pipe = gst.Pipeline()
|
||||
vt = gst.element_factory_make ("videotestsrc")
|
||||
cf = gst.element_factory_make ("capsfilter")
|
||||
c1 = PyGstBufferDraw()
|
||||
color = gst.element_factory_make ("ffmpegcolorspace")
|
||||
scale = gst.element_factory_make ("videoscale")
|
||||
q1 = gst.element_factory_make ("queue")
|
||||
sink = gst.element_factory_make ("autovideosink")
|
||||
|
||||
caps = gst.caps_from_string ("video/x-raw-rgb,width=%d,height=%d,framerate=%d/1" % (WIDTH, HEIGHT, FRAMERATE))
|
||||
cf.set_property ("caps", caps)
|
||||
|
||||
vt.set_property ("num-buffers", FRAMES)
|
||||
|
||||
pipe.add (vt, cf, c1, q1, color, scale, sink)
|
||||
gst.element_link_many (vt, cf, c1, q1, color, scale, sink)
|
||||
|
||||
def on_eos (bus, msg):
|
||||
mainloop.quit()
|
||||
|
||||
bus = pipe.get_bus()
|
||||
bus.add_signal_watch()
|
||||
bus.connect('message::eos', on_eos)
|
||||
|
||||
pipe.set_state (gst.STATE_PLAYING)
|
||||
|
||||
mainloop = gobject.MainLoop()
|
||||
try:
|
||||
mainloop.run()
|
||||
except:
|
||||
pass
|
||||
|
||||
pipe.set_state (gst.STATE_NULL)
|
||||
pipe.get_state (gst.CLOCK_TIME_NONE)
|
||||
|
||||
Executable
+83
@@ -0,0 +1,83 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- Mode: Python -*-
|
||||
# vi:si:et:sw=4:sts=4:ts=4
|
||||
|
||||
# gst-python
|
||||
# Copyright (C) 2002 David I. Lehn <dlehn@users.sourceforge.net>
|
||||
# 2004 Johan Dahlin <johan@gnome.org>
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU Library General Public
|
||||
# License as published by the Free Software Foundation; either
|
||||
# version 2 of the License, or (at your option) any later version.
|
||||
#
|
||||
# This library 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
|
||||
# Library General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Library General Public
|
||||
# License along with this library; if not, write to the
|
||||
# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
||||
# Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# Author: David I. Lehn <dlehn@users.sourceforge.net>
|
||||
#
|
||||
|
||||
import sys
|
||||
|
||||
import gobject
|
||||
gobject.threads_init()
|
||||
|
||||
import pygst
|
||||
pygst.require('0.10')
|
||||
import gst
|
||||
|
||||
|
||||
mainloop = gobject.MainLoop()
|
||||
|
||||
def on_eos(bus, msg):
|
||||
mainloop.quit()
|
||||
|
||||
def filter(input, output):
|
||||
"A GStreamer copy pipeline which can add arbitrary filters"
|
||||
|
||||
# create a new bin to hold the elements
|
||||
bin = gst.parse_launch('filesrc name=source ! ' +
|
||||
'progressreport ! ' +
|
||||
# This 'statistics' element is depreciated in 0.10
|
||||
#'statistics silent=false buffer-update-freq=1 ' +
|
||||
#'update_on_eos=true ! ' +
|
||||
'filesink name=sink')
|
||||
filesrc = bin.get_by_name('source')
|
||||
filesrc.set_property('location', input)
|
||||
|
||||
filesink = bin.get_by_name('sink')
|
||||
filesink.set_property('location', output)
|
||||
|
||||
bus = bin.get_bus()
|
||||
bus.add_signal_watch()
|
||||
bus.connect('message::eos', on_eos)
|
||||
|
||||
# start playing
|
||||
bin.set_state(gst.STATE_PLAYING)
|
||||
|
||||
try:
|
||||
mainloop.run()
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
|
||||
# stop the bin
|
||||
bin.set_state(gst.STATE_NULL)
|
||||
|
||||
def main(args):
|
||||
"A GStreamer based cp(1) with stats"
|
||||
|
||||
if len(args) != 3:
|
||||
print 'usage: %s source dest' % (sys.argv[0])
|
||||
return -1
|
||||
|
||||
return filter(args[1], args[2])
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main(sys.argv))
|
||||
@@ -0,0 +1,80 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- Mode: Python -*-
|
||||
# vi:si:et:sw=4:sts=4:ts=4
|
||||
|
||||
# gst-python
|
||||
# Copyright (C) 2005 Thomas Vander Stichele
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU Library General Public
|
||||
# License as published by the Free Software Foundation; either
|
||||
# version 2 of the License, or (at your option) any later version.
|
||||
#
|
||||
# This library 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
|
||||
# Library General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Library General Public
|
||||
# License along with this library; if not, write to the
|
||||
# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
||||
# Boston, MA 02110-1301, USA.
|
||||
|
||||
import gst
|
||||
import time
|
||||
|
||||
import gobject
|
||||
#gobject.threads_init() # so we can safely receive signals from threads
|
||||
|
||||
count = 0
|
||||
|
||||
def on_message_application(cutter, message, loop):
|
||||
global count
|
||||
s = message.structure
|
||||
which = 'below'
|
||||
if s['above']: which = 'above'
|
||||
print "%s: %s threshold" % (gst.TIME_ARGS(s['timestamp']), which)
|
||||
if s['above']: count += 1
|
||||
if count > 2: loop.quit()
|
||||
|
||||
def main():
|
||||
type = 'async'
|
||||
loop = gobject.MainLoop()
|
||||
|
||||
pipeline = gst.Pipeline("cutter")
|
||||
src = gst.element_factory_make("sinesrc", "src")
|
||||
cutter = gst.element_factory_make("cutter")
|
||||
cutter.set_property('threshold', 0.5)
|
||||
sink = gst.element_factory_make("fakesink", "sink")
|
||||
pipeline.add(src, cutter, sink)
|
||||
src.link(cutter)
|
||||
cutter.link(sink)
|
||||
|
||||
control = gst.Controller(src, "volume")
|
||||
control.set_interpolation_mode("volume", gst.INTERPOLATE_LINEAR)
|
||||
|
||||
control.set("volume", 0, 0.0)
|
||||
control.set("volume", 2 * gst.SECOND, 1.0)
|
||||
control.set("volume", 4 * gst.SECOND, 0.0)
|
||||
control.set("volume", 6 * gst.SECOND, 1.0)
|
||||
control.set("volume", 8 * gst.SECOND, 0.0)
|
||||
control.set("volume", 10 * gst.SECOND, 1.0)
|
||||
|
||||
bus = pipeline.get_bus()
|
||||
|
||||
if type == 'async':
|
||||
bus.add_signal_watch()
|
||||
bus.connect('message::element', on_message_application, loop)
|
||||
else:
|
||||
# FIXME: needs wrapping in gst-python
|
||||
bus.set_sync_handler(bus.sync_signal_handler)
|
||||
bus.connect('sync-message::element', on_message_application, loop)
|
||||
|
||||
pipeline.set_state(gst.STATE_PLAYING)
|
||||
|
||||
loop.run()
|
||||
|
||||
pipeline.set_state(gst.STATE_NULL)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,59 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- Mode: Python -*-
|
||||
# vi:si:et:sw=4:sts=4:ts=4
|
||||
|
||||
# gst-python
|
||||
# Copyright (C) 2005 Fluendo S.L.
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU Library General Public
|
||||
# License as published by the Free Software Foundation; either
|
||||
# version 2 of the License, or (at your option) any later version.
|
||||
#
|
||||
# This library 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
|
||||
# Library General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Library General Public
|
||||
# License along with this library; if not, write to the
|
||||
# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
||||
# Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# Author: Andy Wingo <wingo@pobox.com>
|
||||
|
||||
import gtk
|
||||
from gtk import gdk
|
||||
import gobject
|
||||
|
||||
import pygst
|
||||
pygst.require('0.10')
|
||||
import gst
|
||||
|
||||
class DebugSlider(gtk.HScale):
|
||||
def __init__(self):
|
||||
adj = gtk.Adjustment(int(gst.debug_get_default_threshold()),
|
||||
0, 5, 1, 0, 0)
|
||||
gtk.HScale.__init__(self, adj)
|
||||
self.set_digits(0)
|
||||
self.set_draw_value(True)
|
||||
self.set_value_pos(gtk.POS_TOP)
|
||||
|
||||
def value_changed(self):
|
||||
newlevel = int(self.get_adjustment().get_value())
|
||||
gst.debug_set_default_threshold(newlevel)
|
||||
|
||||
self.connect('value-changed', value_changed)
|
||||
|
||||
if __name__ == '__main__':
|
||||
p = gst.parse_launch('fakesrc ! fakesink')
|
||||
p.set_state(gst.STATE_PLAYING)
|
||||
|
||||
w = gtk.Window()
|
||||
s = DebugSlider()
|
||||
w.add(s)
|
||||
s.show()
|
||||
w.set_default_size(200, 40)
|
||||
w.show()
|
||||
w.connect('delete-event', lambda *args: gtk.main_quit())
|
||||
gtk.main()
|
||||
@@ -0,0 +1,109 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# decodebin.py - Audio autopluging example using 'decodebin' element
|
||||
# Copyright (C) 2006 Jason Gerard DeRose <jderose@jasonderose.org>
|
||||
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU Library General Public
|
||||
# License as published by the Free Software Foundation; either
|
||||
# version 2 of the License, or (at your option) any later version.
|
||||
#
|
||||
# This library 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
|
||||
# Library General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Library General Public
|
||||
# License along with this library; if not, write to the
|
||||
# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
||||
# Boston, MA 02110-1301, USA.
|
||||
|
||||
import sys
|
||||
|
||||
import gobject
|
||||
gobject.threads_init()
|
||||
|
||||
import pygst
|
||||
pygst.require('0.10')
|
||||
import gst
|
||||
|
||||
|
||||
class Decodebin:
|
||||
def __init__(self, location):
|
||||
# The pipeline
|
||||
self.pipeline = gst.Pipeline()
|
||||
|
||||
# Create bus and connect several handlers
|
||||
self.bus = self.pipeline.get_bus()
|
||||
self.bus.add_signal_watch()
|
||||
self.bus.connect('message::eos', self.on_eos)
|
||||
self.bus.connect('message::tag', self.on_tag)
|
||||
self.bus.connect('message::error', self.on_error)
|
||||
|
||||
# Create elements
|
||||
self.src = gst.element_factory_make('filesrc')
|
||||
self.dec = gst.element_factory_make('decodebin')
|
||||
self.conv = gst.element_factory_make('audioconvert')
|
||||
self.rsmpl = gst.element_factory_make('audioresample')
|
||||
self.sink = gst.element_factory_make('alsasink')
|
||||
|
||||
# Set 'location' property on filesrc
|
||||
self.src.set_property('location', location)
|
||||
|
||||
# Connect handler for 'new-decoded-pad' signal
|
||||
self.dec.connect('new-decoded-pad', self.on_new_decoded_pad)
|
||||
|
||||
# Add elements to pipeline
|
||||
self.pipeline.add(self.src, self.dec, self.conv, self.rsmpl, self.sink)
|
||||
|
||||
# Link *some* elements
|
||||
# This is completed in self.on_new_decoded_pad()
|
||||
self.src.link(self.dec)
|
||||
gst.element_link_many(self.conv, self.rsmpl, self.sink)
|
||||
|
||||
# Reference used in self.on_new_decoded_pad()
|
||||
self.apad = self.conv.get_pad('sink')
|
||||
|
||||
# The MainLoop
|
||||
self.mainloop = gobject.MainLoop()
|
||||
|
||||
# And off we go!
|
||||
self.pipeline.set_state(gst.STATE_PLAYING)
|
||||
self.mainloop.run()
|
||||
|
||||
|
||||
def on_new_decoded_pad(self, element, pad, last):
|
||||
caps = pad.get_caps()
|
||||
name = caps[0].get_name()
|
||||
print 'on_new_decoded_pad:', name
|
||||
if name == 'audio/x-raw-float' or name == 'audio/x-raw-int':
|
||||
if not self.apad.is_linked(): # Only link once
|
||||
pad.link(self.apad)
|
||||
|
||||
|
||||
def on_eos(self, bus, msg):
|
||||
print 'on_eos'
|
||||
self.mainloop.quit()
|
||||
|
||||
|
||||
def on_tag(self, bus, msg):
|
||||
taglist = msg.parse_tag()
|
||||
print 'on_tag:'
|
||||
for key in taglist.keys():
|
||||
print '\t%s = %s' % (key, taglist[key])
|
||||
|
||||
|
||||
def on_error(self, bus, msg):
|
||||
error = msg.parse_error()
|
||||
print 'on_error:', error[1]
|
||||
self.mainloop.quit()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if len(sys.argv) == 2:
|
||||
Decodebin(sys.argv[1])
|
||||
else:
|
||||
print 'Usage: %s /path/to/media/file' % sys.argv[0]
|
||||
Executable
+64
@@ -0,0 +1,64 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- Mode: Python -*-
|
||||
# vi:si:et:sw=4:sts=4:ts=4
|
||||
|
||||
# gst-python
|
||||
# Copyright (C) 2002 David I. Lehn
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU Library General Public
|
||||
# License as published by the Free Software Foundation; either
|
||||
# version 2 of the License, or (at your option) any later version.
|
||||
#
|
||||
# This library 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
|
||||
# Library General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Library General Public
|
||||
# License along with this library; if not, write to the
|
||||
# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
||||
# Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# Author: David I. Lehn <dlehn@users.sourceforge.net>
|
||||
#
|
||||
|
||||
import sys
|
||||
|
||||
import pygst
|
||||
pygst.require('0.10')
|
||||
|
||||
import gst
|
||||
|
||||
def handoff_cb(sender, *args):
|
||||
print sender.get_name(), args
|
||||
|
||||
def main(args):
|
||||
# create a new bin to hold the elements
|
||||
#gst_debug_set_categories(-1)
|
||||
bin = gst.parse_launch('fakesrc name=source silent=1 num-buffers=10 signal-handoffs=true ! ' +
|
||||
'fakesink name=sink silent=1 signal-handoffs=true')
|
||||
source = bin.get_by_name('source')
|
||||
source.connect('handoff', handoff_cb)
|
||||
source.get_pad("src").connect("have-data", handoff_cb)
|
||||
sink = bin.get_by_name('sink')
|
||||
sink.connect('handoff', handoff_cb)
|
||||
sink.get_pad("sink").connect('have-data', handoff_cb)
|
||||
|
||||
print source, sink
|
||||
|
||||
bus = bin.get_bus()
|
||||
|
||||
res = bin.set_state(gst.STATE_PLAYING);
|
||||
assert res
|
||||
|
||||
while 1:
|
||||
msg = bus.poll(gst.MESSAGE_EOS | gst.MESSAGE_ERROR, gst.SECOND)
|
||||
if msg:
|
||||
break
|
||||
|
||||
res = bin.set_state(gst.STATE_NULL)
|
||||
assert res
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main(sys.argv))
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- Mode: Python -*-
|
||||
# vi:si:et:sw=4:sts=4:ts=4
|
||||
|
||||
# GStreamer python bindings
|
||||
# Copyright (C) 2002 David I. Lehn <dlehn@users.sourceforge.net>
|
||||
# 2004 Johan Dahlin <johan@gnome.org>
|
||||
#
|
||||
# filesrc.py: implements a file source element completely in python
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU Library General Public
|
||||
# License as published by the Free Software Foundation; either
|
||||
# version 2 of the License, or (at your option) any later version.
|
||||
#
|
||||
# This library 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
|
||||
# Library General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Library General Public
|
||||
# License along with this library; if not, write to the
|
||||
# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
||||
# Boston, MA 02110-1301, USA.
|
||||
|
||||
import sys
|
||||
import gobject; gobject.threads_init()
|
||||
import pygst
|
||||
pygst.require('0.10')
|
||||
import gst
|
||||
|
||||
class FileSource(gst.BaseSrc):
|
||||
__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 == 'location':
|
||||
self.fd = open(value, 'r')
|
||||
|
||||
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(FileSource)
|
||||
|
||||
def main(args):
|
||||
if len(args) != 3:
|
||||
print 'Usage: %s input output' % (args[0])
|
||||
return -1
|
||||
|
||||
bin = gst.Pipeline('pipeline')
|
||||
|
||||
filesrc = FileSource('filesource')
|
||||
assert filesrc
|
||||
filesrc.set_property('location', args[1])
|
||||
|
||||
filesink = gst.element_factory_make('filesink', 'sink')
|
||||
filesink.set_property('location', args[2])
|
||||
|
||||
bin.add(filesrc, filesink)
|
||||
gst.element_link_many(filesrc, filesink)
|
||||
|
||||
bin.set_state(gst.STATE_PLAYING);
|
||||
mainloop = gobject.MainLoop()
|
||||
|
||||
def bus_event(bus, message):
|
||||
t = message.type
|
||||
if t == gst.MESSAGE_EOS:
|
||||
mainloop.quit()
|
||||
elif t == gst.MESSAGE_ERROR:
|
||||
err, debug = message.parse_error()
|
||||
print "Error: %s" % err, debug
|
||||
mainloop.quit()
|
||||
return True
|
||||
bin.get_bus().add_watch(bus_event)
|
||||
|
||||
mainloop.run()
|
||||
bin.set_state(gst.STATE_NULL)
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main(sys.argv))
|
||||
|
||||
@@ -0,0 +1,239 @@
|
||||
# -*- Mode: Python -*-
|
||||
# vi:si:et:sw=4:sts=4:ts=4
|
||||
|
||||
# gst-python
|
||||
# Copyright (C) 2005 Fluendo S.L.
|
||||
# Originally from the Flumotion streaming server.
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU Library General Public
|
||||
# License as published by the Free Software Foundation; either
|
||||
# version 2 of the License, or (at your option) any later version.
|
||||
#
|
||||
# This library 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
|
||||
# Library General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Library General Public
|
||||
# License along with this library; if not, write to the
|
||||
# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
||||
# Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# Author: Zaheer Merali <zaheermerali at gmail dot com>
|
||||
|
||||
import gtk
|
||||
from gtk import gdk
|
||||
import gobject
|
||||
|
||||
|
||||
# this VUMeter respects IEC standard
|
||||
# BS 6840-18:1996/IEC-268-18
|
||||
# and is inspired by JACK's meterbridge dpm_meters.c
|
||||
|
||||
class FVUMeter(gtk.DrawingArea):
|
||||
__gsignals__ = { 'expose-event' : 'override',
|
||||
'size-allocate': 'override',
|
||||
'size-request': 'override',
|
||||
'realize' : 'override'
|
||||
}
|
||||
__gproperties__ = {
|
||||
'peak' : (gobject.TYPE_FLOAT,
|
||||
'peak volume level',
|
||||
'peak volume level in dB',
|
||||
-90.0,
|
||||
0,
|
||||
-90.0,
|
||||
gobject.PARAM_READWRITE),
|
||||
'decay' : (gobject.TYPE_FLOAT,
|
||||
'decay volume level',
|
||||
'decay volume level in dB',
|
||||
-90.0,
|
||||
0,
|
||||
-90.0,
|
||||
gobject.PARAM_READWRITE),
|
||||
'orange-threshold': (gobject.TYPE_FLOAT,
|
||||
'threshold for orange',
|
||||
'threshold for orange use in dB',
|
||||
-90.0,
|
||||
0,
|
||||
-10.0,
|
||||
gobject.PARAM_READWRITE),
|
||||
'red-threshold': (gobject.TYPE_FLOAT,
|
||||
'threshold for red',
|
||||
'threshold for red use in dB',
|
||||
-90.0,
|
||||
0,
|
||||
-1.0,
|
||||
gobject.PARAM_READWRITE)
|
||||
|
||||
}
|
||||
green_gc = None
|
||||
orange_gc = None
|
||||
red_gc = None
|
||||
yellow_gc = None
|
||||
|
||||
topborder = 7
|
||||
peaklevel = -90.0
|
||||
decaylevel = -90.0
|
||||
orange_threshold = -10.0
|
||||
red_threshold = -1.0
|
||||
bottomborder = 25
|
||||
leftborder = 15
|
||||
rightborder = 65
|
||||
|
||||
# Returns the meter deflection percentage given a db value
|
||||
def iec_scale(self, db):
|
||||
pct = 0.0
|
||||
|
||||
if db < -70.0:
|
||||
pct = 0.0
|
||||
elif db < -60.0:
|
||||
pct = (db + 70.0) * 0.25
|
||||
elif db < -50.0:
|
||||
pct = (db + 60.0) * 0.5 + 2.5
|
||||
elif db < -40.0:
|
||||
pct = (db + 50.0) * 0.75 + 7.5
|
||||
elif db < -30.0:
|
||||
pct = (db + 40.0) * 1.5 + 15.0
|
||||
elif db < -20.0:
|
||||
pct = (db + 30.0) * 2.0 + 30.0
|
||||
elif db < 0.0:
|
||||
pct = (db + 20.0) * 2.5 + 50.0
|
||||
else:
|
||||
pct = 100.0
|
||||
|
||||
return pct
|
||||
|
||||
def do_get_property(self, property):
|
||||
if property.name == 'peak':
|
||||
return self.peaklevel
|
||||
elif property.name == 'decay':
|
||||
return self.decaylevel
|
||||
elif property.name == 'orange-threshold':
|
||||
return self.orange_threshold
|
||||
elif property.name == 'red-threshold':
|
||||
return self.red_threshold
|
||||
else:
|
||||
raise AttributeError, 'unknown property %s' % property.name
|
||||
|
||||
def do_set_property(self, property, value):
|
||||
if property.name == 'peak':
|
||||
self.peaklevel = value
|
||||
elif property.name == 'decay':
|
||||
self.decaylevel = value
|
||||
elif property.name == 'orange-threshold':
|
||||
self.orange_threshold = value
|
||||
elif property.name == 'red-threshold':
|
||||
self.red_threshold = value
|
||||
else:
|
||||
raise AttributeError, 'unknown property %s' % property.name
|
||||
|
||||
self.queue_draw()
|
||||
|
||||
def do_size_request(self, requisition):
|
||||
requisition.width = 250
|
||||
requisition.height = 50
|
||||
|
||||
def do_size_allocate(self, allocation):
|
||||
self.allocation = allocation
|
||||
if self.flags() & gtk.REALIZED:
|
||||
self.window.move_resize(*allocation)
|
||||
|
||||
def do_realize(self):
|
||||
self.set_flags(self.flags() | gtk.REALIZED)
|
||||
|
||||
self.window = gdk.Window(self.get_parent_window(),
|
||||
width=self.allocation.width,
|
||||
height=self.allocation.height,
|
||||
window_type=gdk.WINDOW_CHILD,
|
||||
wclass=gdk.INPUT_OUTPUT,
|
||||
event_mask=self.get_events() | gdk.EXPOSURE_MASK)
|
||||
|
||||
colormap = gtk.gdk.colormap_get_system()
|
||||
green = colormap.alloc_color(0, 65535, 0)
|
||||
orange = colormap.alloc_color(65535, 32768, 0)
|
||||
red = colormap.alloc_color(65535, 0, 0)
|
||||
yellow = colormap.alloc_color(65535, 65535, 0)
|
||||
self.green_gc = gdk.GC(self.window, foreground=green)
|
||||
self.orange_gc = gdk.GC(self.window, foreground=orange)
|
||||
self.red_gc = gdk.GC(self.window, foreground=red)
|
||||
self.yellow_gc = gdk.GC(self.window, foreground=yellow)
|
||||
|
||||
self.window.set_user_data(self)
|
||||
self.style.attach(self.window)
|
||||
self.style.set_background(self.window, gtk.STATE_NORMAL)
|
||||
|
||||
def do_expose_event(self, event):
|
||||
self.chain(event)
|
||||
|
||||
x, y, w, h = self.allocation
|
||||
vumeter_width = w - (self.leftborder + self.rightborder)
|
||||
vumeter_height = h - (self.topborder + self.bottomborder)
|
||||
self.window.draw_rectangle(self.style.black_gc, True,
|
||||
self.leftborder, self.topborder,
|
||||
vumeter_width,
|
||||
vumeter_height)
|
||||
# draw peak level
|
||||
# 0 maps to width of 0, full scale maps to total width
|
||||
peaklevelpct = self.iec_scale(self.peaklevel)
|
||||
peakwidth = int(vumeter_width * (peaklevelpct / 100))
|
||||
draw_gc = self.green_gc
|
||||
if self.peaklevel >= self.orange_threshold:
|
||||
draw_gc = self.orange_gc
|
||||
if self.peaklevel >= self.red_threshold:
|
||||
draw_gc = self.red_gc
|
||||
if peakwidth > 0:
|
||||
self.window.draw_rectangle(draw_gc, True,
|
||||
self.leftborder, self.topborder,
|
||||
peakwidth, vumeter_height)
|
||||
|
||||
# draw yellow decay level
|
||||
if self.decaylevel > -90.0:
|
||||
decaylevelpct = self.iec_scale(self.decaylevel)
|
||||
decaywidth = int(vumeter_width * (decaylevelpct / 100))
|
||||
# cheat the geometry by drawing 0% level at pixel 0,
|
||||
# which is same position as just above 0%
|
||||
if decaywidth == 0:
|
||||
decaywidth = 1
|
||||
self.window.draw_line(self.yellow_gc,
|
||||
self.leftborder + decaywidth - 1,
|
||||
self.topborder,
|
||||
self.leftborder + decaywidth - 1,
|
||||
self.topborder + vumeter_height - 1)
|
||||
|
||||
# draw tick marks
|
||||
scalers = [
|
||||
('-90', 0.0),
|
||||
('-40', 0.15),
|
||||
('-30', 0.30),
|
||||
('-20', 0.50),
|
||||
('-10', 0.75),
|
||||
( '-5', 0.875),
|
||||
( '0', 1.0),
|
||||
]
|
||||
for level, scale in scalers:
|
||||
# tick mark, 6 pixels high
|
||||
# we cheat again here by putting the 0 at the first pixel
|
||||
self.window.draw_line(self.style.black_gc,
|
||||
self.leftborder + int(scale * (vumeter_width - 1)),
|
||||
h - self.bottomborder,
|
||||
self.leftborder + int(scale * (vumeter_width - 1)),
|
||||
h - self.bottomborder + 5)
|
||||
# tick label
|
||||
layout = self.create_pango_layout(level)
|
||||
layout_width, layout_height = layout.get_pixel_size()
|
||||
self.window.draw_layout(self.style.black_gc,
|
||||
self.leftborder + int(scale * vumeter_width)
|
||||
- int(layout_width / 2),
|
||||
h - self.bottomborder + 7, layout)
|
||||
|
||||
# draw the peak level to the right
|
||||
layout = self.create_pango_layout("%.2fdB" % self.peaklevel)
|
||||
layout_width, layout_height = layout.get_pixel_size()
|
||||
self.window.draw_layout(self.style.black_gc,
|
||||
self.leftborder + vumeter_width + 5,
|
||||
self.topborder + int(vumeter_height / 2 - layout_height / 2),
|
||||
layout)
|
||||
|
||||
gobject.type_register(FVUMeter)
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
#!/usr/bin/env python
|
||||
# gst-python
|
||||
# Copyright (C) 2006 Andy Wingo <wingo at pobox.com>
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU Library General Public
|
||||
# License as published by the Free Software Foundation; either
|
||||
# version 2 of the License, or (at your option) any later version.
|
||||
#
|
||||
# This library 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
|
||||
# Library General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Library General Public
|
||||
# License along with this library; if not, write to the
|
||||
# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
||||
# Boston, MA 02110-1301, USA.
|
||||
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
import pygtk
|
||||
pygtk.require('2.0')
|
||||
import gobject
|
||||
gobject.threads_init()
|
||||
import pygst
|
||||
pygst.require('0.10')
|
||||
import gst
|
||||
from gst.extend import discoverer
|
||||
|
||||
def fail(path):
|
||||
print "error: %r does not appear to be a media file" % path
|
||||
sys.exit(1)
|
||||
|
||||
def succeed(d):
|
||||
def pp(prop, val):
|
||||
print '%s: %s' % (prop, val)
|
||||
pp('media type', d.mimetype)
|
||||
|
||||
pp('has video', d.is_video)
|
||||
if d.is_video:
|
||||
pp('video caps', d.videocaps)
|
||||
pp('video width (pixels)', d.videowidth)
|
||||
pp('video height (pixels)', d.videoheight)
|
||||
pp('video length (hh:mm:ss)', gst.TIME_ARGS(d.videolength))
|
||||
pp('framerate (fps)', '%s/%s' % (d.videorate.num, d.videorate.denom))
|
||||
|
||||
pp('has audio', d.is_audio)
|
||||
if d.is_audio:
|
||||
pp('audio caps', d.audiocaps)
|
||||
pp('audio format', d.audiofloat and 'floating-point' or 'integer')
|
||||
pp('sample rate (Hz)', d.audiorate)
|
||||
pp('sample width (bits)', d.audiowidth)
|
||||
pp('sample depth (bits)', d.audiodepth)
|
||||
pp('audio length (hh:mm:ss)', gst.TIME_ARGS(d.audiolength))
|
||||
pp('audio channels', d.audiochannels)
|
||||
|
||||
sys.exit(0)
|
||||
|
||||
def discover(path):
|
||||
def discovered(d, is_media):
|
||||
if is_media:
|
||||
succeed(d)
|
||||
else:
|
||||
fail(path)
|
||||
|
||||
d = discoverer.Discoverer(path)
|
||||
d.connect('discovered', discovered)
|
||||
d.discover()
|
||||
gobject.MainLoop().run()
|
||||
|
||||
def usage():
|
||||
print >>sys.stderr, "usage: gst-discover PATH-TO-MEDIA-FILE"
|
||||
sys.exit(1)
|
||||
|
||||
def main(argv):
|
||||
if len(argv) != 2:
|
||||
usage()
|
||||
path = argv.pop()
|
||||
if not os.path.isfile(path):
|
||||
print >>sys.stderr, "error: file %r does not exist" % path
|
||||
usage()
|
||||
|
||||
return discover(path)
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main(sys.argv))
|
||||
@@ -0,0 +1,73 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- Mode: Python -*-
|
||||
# vi:si:et:sw=4:sts=4:ts=4
|
||||
|
||||
# gstfile.py
|
||||
# (c) 2005 Edward Hervey <edward at fluendo dot com>
|
||||
# Discovers and prints out multimedia information of files
|
||||
|
||||
# This example shows how to use gst-python:
|
||||
# _ in an object-oriented way (Discoverer class)
|
||||
# _ subclassing a gst.Pipeline
|
||||
# _ and overidding existing methods (do_iterate())
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
import gobject
|
||||
gobject.threads_init()
|
||||
|
||||
import pygst
|
||||
pygst.require('0.10')
|
||||
|
||||
from gst.extend.discoverer import Discoverer
|
||||
|
||||
class GstFile:
|
||||
"""
|
||||
Analyses one or more files and prints out the multimedia information of
|
||||
each file.
|
||||
"""
|
||||
|
||||
def __init__(self, files):
|
||||
self.files = files
|
||||
self.mainloop = gobject.MainLoop()
|
||||
self.current = None
|
||||
|
||||
def run(self):
|
||||
gobject.idle_add(self._discover_one)
|
||||
self.mainloop.run()
|
||||
|
||||
def _discovered(self, discoverer, ismedia):
|
||||
discoverer.print_info()
|
||||
self.current = None
|
||||
if len(self.files):
|
||||
print "\n"
|
||||
gobject.idle_add(self._discover_one)
|
||||
|
||||
def _discover_one(self):
|
||||
if not len(self.files):
|
||||
gobject.idle_add(self.mainloop.quit)
|
||||
return False
|
||||
filename = self.files.pop(0)
|
||||
if not os.path.isfile(filename):
|
||||
gobject.idle_add(self._discover_one)
|
||||
return False
|
||||
print "Running on", filename
|
||||
# create a discoverer for that file
|
||||
self.current = Discoverer(filename)
|
||||
# connect a callback on the 'discovered' signal
|
||||
self.current.connect('discovered', self._discovered)
|
||||
# start the discovery
|
||||
self.current.discover()
|
||||
return False
|
||||
|
||||
def main(args):
|
||||
if len(args) < 2:
|
||||
print 'usage: %s files...' % args[0]
|
||||
return 2
|
||||
|
||||
gstfile = GstFile(args[1:])
|
||||
gstfile.run()
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main(sys.argv))
|
||||
@@ -0,0 +1,54 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
import sys
|
||||
|
||||
import gobject
|
||||
gobject.threads_init()
|
||||
|
||||
import pygst
|
||||
pygst.require('0.10')
|
||||
import gst
|
||||
|
||||
def bus_call(bus, message, loop):
|
||||
t = message.type
|
||||
if t == gst.MESSAGE_EOS:
|
||||
sys.stout.write("End-of-stream\n")
|
||||
loop.quit()
|
||||
elif t == gst.MESSAGE_ERROR:
|
||||
err, debug = message.parse_error()
|
||||
sys.stderr.write("Error: %s: %s\n" % err, debug)
|
||||
loop.quit()
|
||||
return True
|
||||
|
||||
def main(args):
|
||||
if len(args) != 2:
|
||||
sys.stderr.write("usage: %s <media file or uri>\n" % args[0])
|
||||
sys.exit(1)
|
||||
|
||||
playbin = gst.element_factory_make("playbin2", None)
|
||||
if not playbin:
|
||||
sys.stderr.write("'playbin2' gstreamer plugin missing\n")
|
||||
sys.exit(1)
|
||||
|
||||
# take the commandline argument and ensure that it is a uri
|
||||
if gst.uri_is_valid(args[1]):
|
||||
uri = args[1]
|
||||
else:
|
||||
uri = gst.filename_to_uri(args[1])
|
||||
playbin.set_property('uri', uri)
|
||||
|
||||
# create and event loop and feed gstreamer bus mesages to it
|
||||
loop = gobject.MainLoop()
|
||||
|
||||
bus = playbin.get_bus()
|
||||
bus.add_watch(bus_call, loop)
|
||||
|
||||
# start play back and listed to events
|
||||
playbin.set_state(gst.STATE_PLAYING)
|
||||
loop.run()
|
||||
|
||||
# cleanup
|
||||
playbin.set_state(gst.STATE_NULL)
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main(sys.argv))
|
||||
@@ -0,0 +1,101 @@
|
||||
import gobject
|
||||
gobject.threads_init()
|
||||
import gtk
|
||||
gtk.gdk.threads_init()
|
||||
import hildon
|
||||
import gst
|
||||
import sys
|
||||
|
||||
# VideoWidget taken from play.py in gst-python examples
|
||||
class VideoWidget(gtk.DrawingArea):
|
||||
def __init__(self):
|
||||
gtk.DrawingArea.__init__(self)
|
||||
self.imagesink = None
|
||||
self.unset_flags(gtk.DOUBLE_BUFFERED)
|
||||
|
||||
def do_expose_event(self, event):
|
||||
if self.imagesink:
|
||||
self.imagesink.expose()
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
def set_sink(self, sink):
|
||||
assert self.window.xid
|
||||
self.imagesink = sink
|
||||
self.imagesink.set_xwindow_id(self.window.xid)
|
||||
|
||||
class MaemoGstView:
|
||||
|
||||
def __init__(self):
|
||||
# hildon has one program instance per app, so get instance
|
||||
self.p = hildon.Program.get_instance()
|
||||
# set name of application: this shows in titlebar
|
||||
gtk.set_application_name("Maemo GStreamer VideoTest")
|
||||
# stackable window in case we want more windows in future in app
|
||||
self.w = hildon.StackableWindow()
|
||||
box = gtk.VBox()
|
||||
self.video_widget = VideoWidget()
|
||||
# video widget we want to expand to size
|
||||
box.pack_start(self.video_widget, True, True, 0)
|
||||
# a button finger height to play/pause
|
||||
self.button = hildon.Button(gtk.HILDON_SIZE_FINGER_HEIGHT,
|
||||
hildon.BUTTON_ARRANGEMENT_VERTICAL, title="Pause")
|
||||
self.button.connect_after("clicked", self.on_button_clicked)
|
||||
# don't want button to expand or fill, just stay finger height
|
||||
box.pack_start(self.button, False, False, 0)
|
||||
self.w.add(box)
|
||||
self.w.connect("delete-event", gtk.main_quit)
|
||||
self.p.add_window(self.w)
|
||||
self.w.show_all()
|
||||
self.start_streaming()
|
||||
|
||||
def start_streaming(self):
|
||||
# we use ximagesink solely for screenshotting ability
|
||||
# less cpu usage would happen with videotestsrc ! xvimagesink
|
||||
self.pipeline = \
|
||||
gst.parse_launch("videotestsrc ! videoscale ! ximagesink")
|
||||
bus = self.pipeline.get_bus()
|
||||
# need to connect to sync message handler so we get the sink to be
|
||||
# embedded at the right time and not have a temporary new window
|
||||
bus.enable_sync_message_emission()
|
||||
bus.add_signal_watch()
|
||||
bus.connect("sync-message::element", self.on_sync_message)
|
||||
bus.connect("message", self.on_message)
|
||||
self.pipeline.set_state(gst.STATE_PLAYING)
|
||||
|
||||
def on_sync_message(self, bus, message):
|
||||
if message.structure is None:
|
||||
return
|
||||
if message.structure.get_name() == 'prepare-xwindow-id':
|
||||
# all this is needed to sync with the X server before giving the
|
||||
# x id to the sink
|
||||
gtk.gdk.threads_enter()
|
||||
gtk.gdk.display_get_default().sync()
|
||||
self.video_widget.set_sink(message.src)
|
||||
message.src.set_property("force-aspect-ratio", True)
|
||||
gtk.gdk.threads_leave()
|
||||
|
||||
def on_message(self, bus, message):
|
||||
if message.type == gst.MESSAGE_ERROR:
|
||||
err, debug = message.parse_error()
|
||||
hildon.hildon_banner_show_information(self.w, '',
|
||||
"Error: %s" % err)
|
||||
|
||||
def on_button_clicked(self, widget):
|
||||
success, state, pending = self.pipeline.get_state(1)
|
||||
# do not listen if in middle of state change
|
||||
if not pending:
|
||||
if state == gst.STATE_PLAYING:
|
||||
self.pipeline.set_state(gst.STATE_PAUSED)
|
||||
self.button.set_label("Play")
|
||||
else:
|
||||
self.pipeline.set_state(gst.STATE_PLAYING)
|
||||
self.button.set_label("Pause")
|
||||
|
||||
def main():
|
||||
view = MaemoGstView()
|
||||
gtk.main()
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
@@ -0,0 +1,31 @@
|
||||
# -*- Mode: Python -*-
|
||||
# vi:si:et:sw=4:sts=4:ts=4
|
||||
|
||||
import sys
|
||||
|
||||
import gst
|
||||
import gst.interfaces
|
||||
|
||||
pipeline = "alsasrc"
|
||||
if sys.argv[1:]:
|
||||
pipeline = " ".join(sys.argv[1:])
|
||||
a = gst.element_factory_make(pipeline)
|
||||
print dir(a)
|
||||
|
||||
res = a.set_state(gst.STATE_PAUSED)
|
||||
if res != gst.STATE_CHANGE_SUCCESS:
|
||||
print "Could not set pipeline %s to PAUSED" % pipeline
|
||||
|
||||
print "Inputs:"
|
||||
for t in a.list_tracks():
|
||||
if t.flags & gst.interfaces.MIXER_TRACK_INPUT:
|
||||
sys.stdout.write(t.label)
|
||||
sys.stdout.write(': %d - %d' % (t.min_volume, t.max_volume))
|
||||
volumes = a.get_volume(t)
|
||||
sys.stdout.write(': %r' % (volumes, ))
|
||||
if t.props.num_channels > 0:
|
||||
a.set_volume(t, volumes=volumes)
|
||||
if t.flags & gst.interfaces.MIXER_TRACK_RECORD:
|
||||
sys.stdout.write(' (selected)')
|
||||
sys.stdout.write('\n')
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- Mode: Python -*-
|
||||
# vi:si:et:sw=4:sts=4:ts=4
|
||||
|
||||
import sys
|
||||
|
||||
import pygtk
|
||||
pygtk.require('2.0')
|
||||
|
||||
from gobject.option import OptionParser, OptionGroup
|
||||
import pygst
|
||||
pygst.require('0.10')
|
||||
|
||||
import gstoption
|
||||
|
||||
def main(args):
|
||||
parser = OptionParser()
|
||||
|
||||
group = OptionGroup('flumotion', 'Flumotion options',
|
||||
option_list=[])
|
||||
group.add_option('-v', '--verbose',
|
||||
action="store_true", dest="verbose",
|
||||
help="be verbose")
|
||||
group.add_option('', '--version',
|
||||
action="store_true", dest="version",
|
||||
default=False,
|
||||
help="show version information")
|
||||
parser.add_option_group(group)
|
||||
|
||||
parser.add_option_group(gstoption.get_group())
|
||||
|
||||
options, args = parser.parse_args(args)
|
||||
|
||||
if options.verbose:
|
||||
print 'Verbose mode'
|
||||
|
||||
import gst
|
||||
|
||||
if options.version:
|
||||
print sys.version, gst.version
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main(sys.argv))
|
||||
+263
@@ -0,0 +1,263 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# gst-python
|
||||
# Copyright (C) 2005 Andy Wingo <wingo@pobox.com>
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU Library General Public
|
||||
# License as published by the Free Software Foundation; either
|
||||
# version 2 of the License, or (at your option) any later version.
|
||||
#
|
||||
# This library 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
|
||||
# Library General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Library General Public
|
||||
# License along with this library; if not, write to the
|
||||
# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
||||
# Boston, MA 02110-1301, USA.
|
||||
|
||||
|
||||
# A test more of gst-plugins than of gst-python.
|
||||
|
||||
|
||||
import sys
|
||||
|
||||
import pygtk
|
||||
pygtk.require('2.0')
|
||||
import gtk
|
||||
import gtk.gdk
|
||||
import pango
|
||||
import gobject
|
||||
|
||||
import pygst
|
||||
pygst.require('0.10')
|
||||
import gst
|
||||
|
||||
import debugslider
|
||||
|
||||
|
||||
data = (('Video capture via V4L',
|
||||
'v4lsrc name=source \n'
|
||||
' ! videorate \n'
|
||||
' ! ffmpegcolorspace ! autovideosink'),
|
||||
('Video capture via V4L, fixed frame rate',
|
||||
'v4lsrc name=source autoprobe=false autoprobe-fps=false \n'
|
||||
' ! video/x-raw-yuv,format=(fourcc)I420,framerate=(double)7.5 \n'
|
||||
' ! videorate \n'
|
||||
' ! ffmpegcolorspace \n'
|
||||
' ! autovideosink'),
|
||||
('Sound capture',
|
||||
'gconfaudiosrc\n'
|
||||
' ! audio/x-raw-int,rate=22050,depth=16,channels=1,width=16,signed=(boolean)TRUE,endianness=(int)BYTE_ORDER\n'
|
||||
' ! level message=true\n'
|
||||
' ! fakesink'),
|
||||
('Streaming Ogg/Theora+Vorbis playback, tee to disk',
|
||||
'gnomevfssrc location=http://gstreamer.freedesktop.org/media/small/cooldance.ogg \n'
|
||||
' ! tee name=tee \n'
|
||||
' tee. ! oggdemux name=demux \n'
|
||||
' demux. ! queue ! theoradec ! ffmpegcolorspace ! autovideosink \n'
|
||||
' demux. ! queue ! vorbisdec ! audioconvert ! autoaudiosink \n'
|
||||
' tee. ! queue ! filesink location=/tmp/cooldance.ogg'),
|
||||
('Video test, YUV format',
|
||||
'videotestsrc \n'
|
||||
' ! video/x-raw-yuv,format=(fourcc)I420 \n'
|
||||
' ! ffmpegcolorspace ! autovideosink'),
|
||||
('Video test, RGB format',
|
||||
'videotestsrc \n'
|
||||
' ! video/x-raw-rgb,red_mask=0xff00 \n'
|
||||
' ! ffmpegcolorspace \n'
|
||||
' ! autovideosink'),
|
||||
('Software scaling',
|
||||
'videotestsrc \n'
|
||||
' ! video/x-raw-rgb,height=200,width=320 \n'
|
||||
' ! videoscale method=2 \n'
|
||||
' ! ffmpegcolorspace ! autovideosink'),
|
||||
('Reencode Vorbis to mulaw, play',
|
||||
'filesrc location=/tmp/cooldance.ogg \n'
|
||||
' ! oggdemux \n'
|
||||
' ! vorbisdec ! audioconvert \n'
|
||||
' ! mulawenc ! mulawdec ! autoaudiosink'),
|
||||
('Capture DV via firewire, transcode into Ogg',
|
||||
'dv1394src \n'
|
||||
' ! dvdemux name=demux \n'
|
||||
' ! queue \n'
|
||||
' ! video/x-dv,systemstream=(boolean)false \n'
|
||||
' ! dvdec drop-factor=2 \n'
|
||||
' ! videorate \n'
|
||||
' ! videoscale \n'
|
||||
' ! video/x-raw-yuv,width=360,height=288 \n'
|
||||
' ! videoscale \n'
|
||||
' ! video/x-raw-yuv,width=240,height=192,framerate=10.0,format=(fourcc)YUY2 \n'
|
||||
' ! ffmpegcolorspace \n'
|
||||
' ! theoraenc \n'
|
||||
' ! oggmux name=mux \n'
|
||||
' ! filesink location=/tmp/dv.ogg \n'
|
||||
' \n'
|
||||
' demux. \n'
|
||||
' ! audio/x-raw-int \n'
|
||||
' ! queue \n'
|
||||
' ! audioconvert \n'
|
||||
' ! vorbisenc \n'
|
||||
' ! mux.'))
|
||||
|
||||
|
||||
def escape(s, chars, escaper='\\'):
|
||||
for c in chars:
|
||||
s = s.replace(c, '%s%s' % (escaper, c))
|
||||
return s
|
||||
|
||||
|
||||
def make_model():
|
||||
m = gtk.ListStore(str, str)
|
||||
for pair in data:
|
||||
i = m.append()
|
||||
m.set_value(i, 0, pair[0])
|
||||
m.set_value(i, 1, pair[1])
|
||||
return m
|
||||
|
||||
|
||||
class Window(gtk.Window):
|
||||
def __init__(self):
|
||||
gtk.Window.__init__(self, gtk.WINDOW_TOPLEVEL)
|
||||
self.playing = False
|
||||
self.selected_pipe = None
|
||||
self.pipeline = None
|
||||
self.prepare_ui()
|
||||
|
||||
def prepare_ui(self):
|
||||
self.set_default_size(300,400)
|
||||
self.set_title('GStreamer Pipeline Tester')
|
||||
self.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_DIALOG)
|
||||
self.connect('delete-event', lambda *x: gtk.main_quit())
|
||||
self.set_border_width(18)
|
||||
b = gtk.VBox(False, 12)
|
||||
b.show()
|
||||
self.add(b)
|
||||
l = gtk.Label()
|
||||
l.set_markup('<big><b>GStreamer Pipeline Tester</b></big>')
|
||||
l.show()
|
||||
b.pack_start(l, False, False, 6)
|
||||
l = gtk.Label('Choose a pipeline below to run.')
|
||||
l.show()
|
||||
b.pack_start(l, False, False, 0)
|
||||
sw = gtk.ScrolledWindow()
|
||||
sw.set_policy(gtk.POLICY_NEVER, gtk.POLICY_NEVER)
|
||||
sw.set_shadow_type(gtk.SHADOW_IN)
|
||||
sw.show()
|
||||
b.pack_start(sw, True, True, 6)
|
||||
tv = gtk.TreeView(make_model())
|
||||
tv.set_property('can-default', False)
|
||||
r = gtk.CellRendererText()
|
||||
r.set_property('xalign', 0.5)
|
||||
c = gtk.TreeViewColumn('System', r, text=0)
|
||||
tv.append_column(c)
|
||||
tv.set_headers_visible(False)
|
||||
tv.show()
|
||||
sw.add(tv)
|
||||
ds = debugslider.DebugSlider()
|
||||
ds.show()
|
||||
b.pack_start(ds, False, False, 0)
|
||||
l = gtk.Label()
|
||||
l.set_selectable(True)
|
||||
l.show()
|
||||
b.pack_start(l, False, False, 0)
|
||||
bb = gtk.HButtonBox()
|
||||
bb.set_layout(gtk.BUTTONBOX_SPREAD)
|
||||
bb.show()
|
||||
b.pack_start(bb, False, False, 0)
|
||||
bu = gtk.Button(stock=gtk.STOCK_MEDIA_PLAY)
|
||||
bu.set_property('can-default', True)
|
||||
bu.set_focus_on_click(False)
|
||||
bu.show()
|
||||
bb.pack_start(bu, True, False, 0)
|
||||
bu.set_property('has-default', True)
|
||||
self.button = bu
|
||||
|
||||
def on_changed(s):
|
||||
m, i = s.get_selected()
|
||||
if m:
|
||||
self.selected_pipe = m.get_value(i, 1)
|
||||
pasteable = escape(self.selected_pipe, '\n)(')
|
||||
l.set_markup('<small><tt>%s</tt></small>' % pasteable)
|
||||
else:
|
||||
self.selected_pipe = None
|
||||
l.set_markup('')
|
||||
tv.get_selection().connect('changed', on_changed)
|
||||
|
||||
tv.connect('row-activated', lambda *x: self.play_toggled())
|
||||
|
||||
bu.connect('clicked', lambda *x: self.play_toggled())
|
||||
|
||||
def error(self, message, secondary=None):
|
||||
m = gtk.MessageDialog(self,
|
||||
gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
|
||||
gtk.MESSAGE_ERROR,
|
||||
gtk.BUTTONS_OK,
|
||||
message)
|
||||
if secondary:
|
||||
m.format_secondary_text(secondary)
|
||||
m.run()
|
||||
m.destroy()
|
||||
self.stop()
|
||||
|
||||
def on_message(self, bus, message):
|
||||
t = message.type
|
||||
print message
|
||||
if t == gst.MESSAGE_STATE_CHANGED:
|
||||
pass
|
||||
elif t == gst.MESSAGE_ERROR:
|
||||
err, debug = message.parse_error()
|
||||
self.error("%s" % err, debug)
|
||||
elif t == gst.MESSAGE_EOS:
|
||||
self.play_toggled()
|
||||
else:
|
||||
print '%s: %s:' % (message.src.get_path_string(),
|
||||
message.type.value_nicks[1])
|
||||
if message.structure:
|
||||
print ' %s' % message.structure.to_string()
|
||||
else:
|
||||
print ' (no structure)'
|
||||
return True
|
||||
|
||||
def play(self):
|
||||
pipestr = self.selected_pipe
|
||||
try:
|
||||
self.set_sensitive(False)
|
||||
pipeline = gst.parse_launch(pipestr)
|
||||
self.set_sensitive(True)
|
||||
except gobject.GError, e:
|
||||
self.set_sensitive(True)
|
||||
self.error('Could not create pipeline', str(e))
|
||||
return False
|
||||
|
||||
bus = pipeline.get_bus()
|
||||
bus.add_signal_watch()
|
||||
watch_id = bus.connect('message', self.on_message)
|
||||
self.pipeline = pipeline
|
||||
self.watch_id = watch_id
|
||||
pipeline.set_state(gst.STATE_PLAYING)
|
||||
|
||||
def stop(self):
|
||||
bus = self.pipeline.get_bus()
|
||||
bus.disconnect(self.watch_id)
|
||||
bus.remove_signal_watch()
|
||||
self.pipeline.set_state(gst.STATE_NULL)
|
||||
self.pipeline = None
|
||||
del self.watch_id
|
||||
|
||||
def play_toggled(self):
|
||||
if self.playing:
|
||||
self.stop()
|
||||
self.button.set_label(gtk.STOCK_MEDIA_PLAY)
|
||||
self.playing = False
|
||||
else:
|
||||
self.play()
|
||||
self.playing = True
|
||||
self.button.set_label(gtk.STOCK_MEDIA_STOP)
|
||||
|
||||
if __name__ == '__main__':
|
||||
w = Window()
|
||||
w.show()
|
||||
gtk.main()
|
||||
@@ -0,0 +1,299 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- Mode: Python -*-
|
||||
# vi:si:et:sw=4:sts=4:ts=4
|
||||
|
||||
import pygtk
|
||||
pygtk.require('2.0')
|
||||
|
||||
import sys
|
||||
|
||||
import gobject
|
||||
gobject.threads_init()
|
||||
|
||||
import pygst
|
||||
pygst.require('0.10')
|
||||
import gst
|
||||
import gst.interfaces
|
||||
import gtk
|
||||
gtk.gdk.threads_init()
|
||||
|
||||
class GstPlayer:
|
||||
def __init__(self, videowidget):
|
||||
self.playing = False
|
||||
self.player = gst.element_factory_make("playbin", "player")
|
||||
self.videowidget = videowidget
|
||||
self.on_eos = False
|
||||
|
||||
bus = self.player.get_bus()
|
||||
bus.enable_sync_message_emission()
|
||||
bus.add_signal_watch()
|
||||
bus.connect('sync-message::element', self.on_sync_message)
|
||||
bus.connect('message', self.on_message)
|
||||
|
||||
def on_sync_message(self, bus, message):
|
||||
if message.structure is None:
|
||||
return
|
||||
if message.structure.get_name() == 'prepare-xwindow-id':
|
||||
# Sync with the X server before giving the X-id to the sink
|
||||
gtk.gdk.threads_enter()
|
||||
gtk.gdk.display_get_default().sync()
|
||||
self.videowidget.set_sink(message.src)
|
||||
message.src.set_property('force-aspect-ratio', True)
|
||||
gtk.gdk.threads_leave()
|
||||
|
||||
def on_message(self, bus, message):
|
||||
t = message.type
|
||||
if t == gst.MESSAGE_ERROR:
|
||||
err, debug = message.parse_error()
|
||||
print "Error: %s" % err, debug
|
||||
if self.on_eos:
|
||||
self.on_eos()
|
||||
self.playing = False
|
||||
elif t == gst.MESSAGE_EOS:
|
||||
if self.on_eos:
|
||||
self.on_eos()
|
||||
self.playing = False
|
||||
|
||||
def set_location(self, location):
|
||||
self.player.set_property('uri', location)
|
||||
|
||||
def query_position(self):
|
||||
"Returns a (position, duration) tuple"
|
||||
try:
|
||||
position, format = self.player.query_position(gst.FORMAT_TIME)
|
||||
except:
|
||||
position = gst.CLOCK_TIME_NONE
|
||||
|
||||
try:
|
||||
duration, format = self.player.query_duration(gst.FORMAT_TIME)
|
||||
except:
|
||||
duration = gst.CLOCK_TIME_NONE
|
||||
|
||||
return (position, duration)
|
||||
|
||||
def seek(self, location):
|
||||
"""
|
||||
@param location: time to seek to, in nanoseconds
|
||||
"""
|
||||
gst.debug("seeking to %r" % location)
|
||||
event = gst.event_new_seek(1.0, gst.FORMAT_TIME,
|
||||
gst.SEEK_FLAG_FLUSH | gst.SEEK_FLAG_ACCURATE,
|
||||
gst.SEEK_TYPE_SET, location,
|
||||
gst.SEEK_TYPE_NONE, 0)
|
||||
|
||||
res = self.player.send_event(event)
|
||||
if res:
|
||||
gst.info("setting new stream time to 0")
|
||||
self.player.set_new_stream_time(0L)
|
||||
else:
|
||||
gst.error("seek to %r failed" % location)
|
||||
|
||||
def pause(self):
|
||||
gst.info("pausing player")
|
||||
self.player.set_state(gst.STATE_PAUSED)
|
||||
self.playing = False
|
||||
|
||||
def play(self):
|
||||
gst.info("playing player")
|
||||
self.player.set_state(gst.STATE_PLAYING)
|
||||
self.playing = True
|
||||
|
||||
def stop(self):
|
||||
self.player.set_state(gst.STATE_NULL)
|
||||
gst.info("stopped player")
|
||||
|
||||
def get_state(self, timeout=1):
|
||||
return self.player.get_state(timeout=timeout)
|
||||
|
||||
def is_playing(self):
|
||||
return self.playing
|
||||
|
||||
class VideoWidget(gtk.DrawingArea):
|
||||
def __init__(self):
|
||||
gtk.DrawingArea.__init__(self)
|
||||
self.imagesink = None
|
||||
self.unset_flags(gtk.DOUBLE_BUFFERED)
|
||||
|
||||
def do_expose_event(self, event):
|
||||
if self.imagesink:
|
||||
self.imagesink.expose()
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
def set_sink(self, sink):
|
||||
assert self.window.xid
|
||||
self.imagesink = sink
|
||||
self.imagesink.set_xwindow_id(self.window.xid)
|
||||
|
||||
class PlayerWindow(gtk.Window):
|
||||
UPDATE_INTERVAL = 500
|
||||
def __init__(self):
|
||||
gtk.Window.__init__(self)
|
||||
self.set_default_size(410, 325)
|
||||
|
||||
self.create_ui()
|
||||
|
||||
self.player = GstPlayer(self.videowidget)
|
||||
|
||||
def on_eos():
|
||||
self.player.seek(0L)
|
||||
self.play_toggled()
|
||||
self.player.on_eos = lambda *x: on_eos()
|
||||
|
||||
self.update_id = -1
|
||||
self.changed_id = -1
|
||||
self.seek_timeout_id = -1
|
||||
|
||||
self.p_position = gst.CLOCK_TIME_NONE
|
||||
self.p_duration = gst.CLOCK_TIME_NONE
|
||||
|
||||
def on_delete_event():
|
||||
self.player.stop()
|
||||
gtk.main_quit()
|
||||
self.connect('delete-event', lambda *x: on_delete_event())
|
||||
|
||||
def load_file(self, location):
|
||||
self.player.set_location(location)
|
||||
|
||||
def create_ui(self):
|
||||
vbox = gtk.VBox()
|
||||
self.add(vbox)
|
||||
|
||||
self.videowidget = VideoWidget()
|
||||
vbox.pack_start(self.videowidget)
|
||||
|
||||
hbox = gtk.HBox()
|
||||
vbox.pack_start(hbox, fill=False, expand=False)
|
||||
|
||||
self.pause_image = gtk.image_new_from_stock(gtk.STOCK_MEDIA_PAUSE,
|
||||
gtk.ICON_SIZE_BUTTON)
|
||||
self.pause_image.show()
|
||||
self.play_image = gtk.image_new_from_stock(gtk.STOCK_MEDIA_PLAY,
|
||||
gtk.ICON_SIZE_BUTTON)
|
||||
self.play_image.show()
|
||||
self.button = button = gtk.Button()
|
||||
button.add(self.play_image)
|
||||
button.set_property('can-default', True)
|
||||
button.set_focus_on_click(False)
|
||||
button.show()
|
||||
hbox.pack_start(button, False)
|
||||
button.set_property('has-default', True)
|
||||
button.connect('clicked', lambda *args: self.play_toggled())
|
||||
|
||||
self.adjustment = gtk.Adjustment(0.0, 0.00, 100.0, 0.1, 1.0, 1.0)
|
||||
hscale = gtk.HScale(self.adjustment)
|
||||
hscale.set_digits(2)
|
||||
hscale.set_update_policy(gtk.UPDATE_CONTINUOUS)
|
||||
hscale.connect('button-press-event', self.scale_button_press_cb)
|
||||
hscale.connect('button-release-event', self.scale_button_release_cb)
|
||||
hscale.connect('format-value', self.scale_format_value_cb)
|
||||
hbox.pack_start(hscale)
|
||||
self.hscale = hscale
|
||||
|
||||
self.videowidget.connect_after('realize',
|
||||
lambda *x: self.play_toggled())
|
||||
|
||||
def play_toggled(self):
|
||||
self.button.remove(self.button.child)
|
||||
if self.player.is_playing():
|
||||
self.player.pause()
|
||||
self.button.add(self.play_image)
|
||||
else:
|
||||
self.player.play()
|
||||
if self.update_id == -1:
|
||||
self.update_id = gobject.timeout_add(self.UPDATE_INTERVAL,
|
||||
self.update_scale_cb)
|
||||
self.button.add(self.pause_image)
|
||||
|
||||
def scale_format_value_cb(self, scale, value):
|
||||
if self.p_duration == -1:
|
||||
real = 0
|
||||
else:
|
||||
real = value * self.p_duration / 100
|
||||
|
||||
seconds = real / gst.SECOND
|
||||
|
||||
return "%02d:%02d" % (seconds / 60, seconds % 60)
|
||||
|
||||
def scale_button_press_cb(self, widget, event):
|
||||
# see seek.c:start_seek
|
||||
gst.debug('starting seek')
|
||||
|
||||
self.button.set_sensitive(False)
|
||||
self.was_playing = self.player.is_playing()
|
||||
if self.was_playing:
|
||||
self.player.pause()
|
||||
|
||||
# don't timeout-update position during seek
|
||||
if self.update_id != -1:
|
||||
gobject.source_remove(self.update_id)
|
||||
self.update_id = -1
|
||||
|
||||
# make sure we get changed notifies
|
||||
if self.changed_id == -1:
|
||||
self.changed_id = self.hscale.connect('value-changed',
|
||||
self.scale_value_changed_cb)
|
||||
|
||||
def scale_value_changed_cb(self, scale):
|
||||
# see seek.c:seek_cb
|
||||
real = long(scale.get_value() * self.p_duration / 100) # in ns
|
||||
gst.debug('value changed, perform seek to %r' % real)
|
||||
self.player.seek(real)
|
||||
# allow for a preroll
|
||||
self.player.get_state(timeout=50*gst.MSECOND) # 50 ms
|
||||
|
||||
def scale_button_release_cb(self, widget, event):
|
||||
# see seek.cstop_seek
|
||||
widget.disconnect(self.changed_id)
|
||||
self.changed_id = -1
|
||||
|
||||
self.button.set_sensitive(True)
|
||||
if self.seek_timeout_id != -1:
|
||||
gobject.source_remove(self.seek_timeout_id)
|
||||
self.seek_timeout_id = -1
|
||||
else:
|
||||
gst.debug('released slider, setting back to playing')
|
||||
if self.was_playing:
|
||||
self.player.play()
|
||||
|
||||
if self.update_id != -1:
|
||||
self.error('Had a previous update timeout id')
|
||||
else:
|
||||
self.update_id = gobject.timeout_add(self.UPDATE_INTERVAL,
|
||||
self.update_scale_cb)
|
||||
|
||||
def update_scale_cb(self):
|
||||
self.p_position, self.p_duration = self.player.query_position()
|
||||
if self.p_position != gst.CLOCK_TIME_NONE:
|
||||
value = self.p_position * 100.0 / self.p_duration
|
||||
self.adjustment.set_value(value)
|
||||
|
||||
return True
|
||||
|
||||
def main(args):
|
||||
def usage():
|
||||
sys.stderr.write("usage: %s URI-OF-MEDIA-FILE\n" % args[0])
|
||||
sys.exit(1)
|
||||
|
||||
# Need to register our derived widget types for implicit event
|
||||
# handlers to get called.
|
||||
gobject.type_register(PlayerWindow)
|
||||
gobject.type_register(VideoWidget)
|
||||
|
||||
w = PlayerWindow()
|
||||
|
||||
if len(args) != 2:
|
||||
usage()
|
||||
|
||||
if not gst.uri_is_valid(args[1]):
|
||||
sys.stderr.write("Error: Invalid URI: %s\n" % args[1])
|
||||
sys.exit(1)
|
||||
|
||||
w.load_file(args[1])
|
||||
w.show_all()
|
||||
|
||||
gtk.main()
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main(sys.argv))
|
||||
@@ -0,0 +1,68 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
import pygtk
|
||||
pygtk.require ("2.0")
|
||||
import gobject
|
||||
gobject.threads_init()
|
||||
|
||||
import pygst
|
||||
pygst.require('0.10')
|
||||
import gst
|
||||
|
||||
class PyIdentity(gst.Element):
|
||||
_sinkpadtemplate = gst.PadTemplate ("sink",
|
||||
gst.PAD_SINK,
|
||||
gst.PAD_ALWAYS,
|
||||
gst.caps_new_any())
|
||||
_srcpadtemplate = gst.PadTemplate ("src",
|
||||
gst.PAD_SRC,
|
||||
gst.PAD_ALWAYS,
|
||||
gst.caps_new_any())
|
||||
|
||||
def __init__(self):
|
||||
gst.Element.__init__(self)
|
||||
|
||||
self.sinkpad = gst.Pad(self._sinkpadtemplate, "sink")
|
||||
self.sinkpad.set_chain_function(self.chainfunc)
|
||||
self.sinkpad.set_event_function(self.eventfunc)
|
||||
self.sinkpad.set_getcaps_function(gst.Pad.proxy_getcaps)
|
||||
self.sinkpad.set_setcaps_function(gst.Pad.proxy_setcaps)
|
||||
self.add_pad (self.sinkpad)
|
||||
|
||||
self.srcpad = gst.Pad(self._srcpadtemplate, "src")
|
||||
|
||||
self.srcpad.set_event_function(self.srceventfunc)
|
||||
self.srcpad.set_query_function(self.srcqueryfunc)
|
||||
self.srcpad.set_getcaps_function(gst.Pad.proxy_getcaps)
|
||||
self.srcpad.set_setcaps_function(gst.Pad.proxy_setcaps)
|
||||
self.add_pad (self.srcpad)
|
||||
|
||||
def chainfunc(self, pad, buffer):
|
||||
gst.log ("Passing buffer with ts %d" % (buffer.timestamp))
|
||||
return self.srcpad.push (buffer)
|
||||
|
||||
def eventfunc(self, pad, event):
|
||||
return self.srcpad.push_event (event)
|
||||
|
||||
def srcqueryfunc (self, pad, query):
|
||||
return self.sinkpad.query (query)
|
||||
def srceventfunc (self, pad, event):
|
||||
return self.sinkpad.push_event (event)
|
||||
|
||||
gobject.type_register(PyIdentity)
|
||||
|
||||
pipe = gst.Pipeline()
|
||||
vt = gst.element_factory_make ("videotestsrc")
|
||||
i1 = PyIdentity()
|
||||
color = gst.element_factory_make ("ffmpegcolorspace")
|
||||
scale = gst.element_factory_make ("videoscale")
|
||||
q1 = gst.element_factory_make ("queue")
|
||||
i2 = PyIdentity()
|
||||
sink = gst.element_factory_make ("autovideosink")
|
||||
|
||||
pipe.add (vt, i1, q1, i2, color, scale, sink)
|
||||
gst.element_link_many (vt, i1, q1, i2, color, scale, sink)
|
||||
|
||||
pipe.set_state (gst.STATE_PLAYING)
|
||||
|
||||
gobject.MainLoop().run()
|
||||
@@ -0,0 +1,840 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- Mode: Python -*-
|
||||
# vi:si:et:sw=4:sts=4:ts=4
|
||||
|
||||
import pygtk
|
||||
pygtk.require('2.0')
|
||||
|
||||
import sys
|
||||
|
||||
import gobject
|
||||
gobject.threads_init()
|
||||
|
||||
import pygst
|
||||
pygst.require('0.10')
|
||||
import gst
|
||||
import gst.interfaces
|
||||
import gtk
|
||||
gtk.gdk.threads_init()
|
||||
|
||||
class GstPlayer:
|
||||
def __init__(self, videowidget):
|
||||
self.playing = False
|
||||
self.player = gst.element_factory_make("playbin", "player")
|
||||
self.videowidget = videowidget
|
||||
|
||||
bus = self.player.get_bus()
|
||||
bus.enable_sync_message_emission()
|
||||
bus.add_signal_watch()
|
||||
bus.connect('sync-message::element', self.on_sync_message)
|
||||
bus.connect('message', self.on_message)
|
||||
|
||||
def on_sync_message(self, bus, message):
|
||||
if message.structure is None:
|
||||
return
|
||||
if message.structure.get_name() == 'prepare-xwindow-id':
|
||||
# Sync with the X server before giving the X-id to the sink
|
||||
gtk.gdk.threads_enter()
|
||||
gtk.gdk.display_get_default().sync()
|
||||
self.videowidget.set_sink(message.src)
|
||||
message.src.set_property('force-aspect-ratio', True)
|
||||
gtk.gdk.threads_leave()
|
||||
|
||||
def on_message(self, bus, message):
|
||||
t = message.type
|
||||
if t == gst.MESSAGE_ERROR:
|
||||
err, debug = message.parse_error()
|
||||
print "Error: %s" % err, debug
|
||||
if self.on_eos:
|
||||
self.on_eos()
|
||||
self.playing = False
|
||||
elif t == gst.MESSAGE_EOS:
|
||||
if self.on_eos:
|
||||
self.on_eos()
|
||||
self.playing = False
|
||||
|
||||
def set_location(self, location):
|
||||
self.player.set_state(gst.STATE_NULL)
|
||||
self.player.set_property('uri', location)
|
||||
|
||||
def get_location(self):
|
||||
return self.player.get_property('uri')
|
||||
|
||||
def query_position(self):
|
||||
"Returns a (position, duration) tuple"
|
||||
try:
|
||||
position, format = self.player.query_position(gst.FORMAT_TIME)
|
||||
except:
|
||||
position = gst.CLOCK_TIME_NONE
|
||||
|
||||
try:
|
||||
duration, format = self.player.query_duration(gst.FORMAT_TIME)
|
||||
except:
|
||||
duration = gst.CLOCK_TIME_NONE
|
||||
|
||||
return (position, duration)
|
||||
|
||||
def seek(self, location):
|
||||
"""
|
||||
@param location: time to seek to, in nanoseconds
|
||||
"""
|
||||
gst.debug("seeking to %r" % location)
|
||||
event = gst.event_new_seek(1.0, gst.FORMAT_TIME,
|
||||
gst.SEEK_FLAG_FLUSH,
|
||||
gst.SEEK_TYPE_SET, location,
|
||||
gst.SEEK_TYPE_NONE, 0)
|
||||
|
||||
res = self.player.send_event(event)
|
||||
if res:
|
||||
gst.info("setting new stream time to 0")
|
||||
self.player.set_new_stream_time(0L)
|
||||
else:
|
||||
gst.error("seek to %r failed" % location)
|
||||
|
||||
def pause(self):
|
||||
gst.info("pausing player")
|
||||
self.player.set_state(gst.STATE_PAUSED)
|
||||
self.playing = False
|
||||
|
||||
def play(self):
|
||||
gst.info("playing player")
|
||||
self.player.set_state(gst.STATE_PLAYING)
|
||||
self.playing = True
|
||||
|
||||
def stop(self):
|
||||
self.player.set_state(gst.STATE_NULL)
|
||||
gst.info("stopped player")
|
||||
|
||||
def get_state(self, timeout=1):
|
||||
return self.player.get_state(timeout=timeout)
|
||||
|
||||
def is_playing(self):
|
||||
return self.playing
|
||||
|
||||
class VideoWidget(gtk.DrawingArea):
|
||||
def __init__(self):
|
||||
gtk.DrawingArea.__init__(self)
|
||||
self.imagesink = None
|
||||
self.unset_flags(gtk.DOUBLE_BUFFERED)
|
||||
|
||||
def do_expose_event(self, event):
|
||||
if self.imagesink:
|
||||
self.imagesink.expose()
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
def set_sink(self, sink):
|
||||
assert self.window.xid
|
||||
self.imagesink = sink
|
||||
self.imagesink.set_xwindow_id(self.window.xid)
|
||||
|
||||
class TimeControl(gtk.HBox):
|
||||
# all labels same size
|
||||
sizegroup = gtk.SizeGroup(gtk.SIZE_GROUP_HORIZONTAL)
|
||||
__gproperties__ = {'time': (gobject.TYPE_UINT64, 'Time', 'Time',
|
||||
# not actually usable: see #335854
|
||||
# kept for .notify() usage
|
||||
0L, (1<<63)-1, 0L,
|
||||
gobject.PARAM_READABLE)}
|
||||
|
||||
def __init__(self, window, label):
|
||||
gtk.HBox.__init__(self)
|
||||
self.pwindow = window
|
||||
self.label = label
|
||||
self.create_ui()
|
||||
|
||||
def get_property(self, param, pspec):
|
||||
if param == 'time':
|
||||
return self.get_time()
|
||||
else:
|
||||
assert param in self.__gproperties__, \
|
||||
'Unknown property: %s' % param
|
||||
|
||||
def create_ui(self):
|
||||
label = gtk.Label(self.label + ": ")
|
||||
label.show()
|
||||
a = gtk.Alignment(1.0, 0.5)
|
||||
a.add(label)
|
||||
a.set_padding(0, 0, 12, 0)
|
||||
a.show()
|
||||
self.sizegroup.add_widget(a)
|
||||
self.pack_start(a, True, False, 0)
|
||||
|
||||
self.minutes = minutes = gtk.Entry(5)
|
||||
minutes.set_width_chars(5)
|
||||
minutes.set_alignment(1.0)
|
||||
minutes.connect('changed', lambda *x: self.notify('time'))
|
||||
minutes.connect_after('activate', lambda *x: self.activated())
|
||||
label2 = gtk.Label(":")
|
||||
self.seconds = seconds = gtk.Entry(2)
|
||||
seconds.set_width_chars(2)
|
||||
seconds.set_alignment(1.0)
|
||||
seconds.connect('changed', lambda *x: self.notify('time'))
|
||||
seconds.connect_after('activate', lambda *x: self.activated())
|
||||
label3 = gtk.Label(".")
|
||||
self.milliseconds = milliseconds = gtk.Entry(3)
|
||||
milliseconds.set_width_chars(3)
|
||||
milliseconds.set_alignment(0.0)
|
||||
milliseconds.connect('changed', lambda *x: self.notify('time'))
|
||||
milliseconds.connect_after('activate', lambda *x: self.activated())
|
||||
set = gtk.Button('Set')
|
||||
goto = gtk.Button('Go')
|
||||
goto.set_property('image',
|
||||
gtk.image_new_from_stock(gtk.STOCK_JUMP_TO,
|
||||
gtk.ICON_SIZE_BUTTON))
|
||||
for w in minutes, label2, seconds, label3, milliseconds:
|
||||
w.show()
|
||||
self.pack_start(w, False)
|
||||
set.show()
|
||||
self.pack_start(set, False, False, 6)
|
||||
goto.show()
|
||||
self.pack_start(goto, False, False, 0)
|
||||
set.connect('clicked', lambda *x: self.set_now())
|
||||
goto.connect('clicked', lambda *x: self.activated())
|
||||
pad = gtk.Label("")
|
||||
pad.show()
|
||||
self.pack_start(pad, True, False, 0)
|
||||
|
||||
def get_time(self):
|
||||
time = 0
|
||||
for w, multiplier in ((self.minutes, gst.SECOND*60),
|
||||
(self.seconds, gst.SECOND),
|
||||
(self.milliseconds, gst.MSECOND)):
|
||||
text = w.get_text()
|
||||
try:
|
||||
val = int(text)
|
||||
except ValueError:
|
||||
val = 0
|
||||
w.set_text(val and str(val) or '0')
|
||||
time += val * multiplier
|
||||
return time
|
||||
|
||||
def set_time(self, time):
|
||||
if time == gst.CLOCK_TIME_NONE:
|
||||
print "Can't set '%s' (invalid time)" % self.label
|
||||
return
|
||||
self.freeze_notify()
|
||||
for w, multiplier in ((self.minutes, gst.SECOND*60),
|
||||
(self.seconds, gst.SECOND),
|
||||
(self.milliseconds, gst.MSECOND)):
|
||||
val = time // multiplier
|
||||
w.set_text(str(val))
|
||||
time -= val * multiplier
|
||||
self.thaw_notify()
|
||||
|
||||
def set_now(self):
|
||||
time, dur = self.pwindow.player.query_position()
|
||||
self.set_time(time)
|
||||
|
||||
def activated(self):
|
||||
time = self.get_time()
|
||||
if self.pwindow.player.is_playing():
|
||||
self.pwindow.play_toggled()
|
||||
self.pwindow.player.seek(time)
|
||||
self.pwindow.player.get_state(timeout=gst.MSECOND * 200)
|
||||
|
||||
class ProgressDialog(gtk.Dialog):
|
||||
def __init__(self, title, description, task, parent, flags, buttons):
|
||||
gtk.Dialog.__init__(self, title, parent, flags, buttons)
|
||||
self._create_ui(title, description, task)
|
||||
|
||||
def _create_ui(self, title, description, task):
|
||||
self.set_border_width(6)
|
||||
self.set_resizable(False)
|
||||
self.set_has_separator(False)
|
||||
|
||||
vbox = gtk.VBox()
|
||||
vbox.set_border_width(6)
|
||||
vbox.show()
|
||||
self.vbox.pack_start(vbox, False)
|
||||
|
||||
label = gtk.Label('<big><b>%s</b></big>' % title)
|
||||
label.set_use_markup(True)
|
||||
label.set_alignment(0.0, 0.0)
|
||||
label.show()
|
||||
vbox.pack_start(label, False)
|
||||
|
||||
label = gtk.Label(description)
|
||||
label.set_use_markup(True)
|
||||
label.set_alignment(0.0, 0.0)
|
||||
label.set_line_wrap(True)
|
||||
label.set_padding(0, 12)
|
||||
label.show()
|
||||
vbox.pack_start(label, False)
|
||||
|
||||
self.progress = progress = gtk.ProgressBar()
|
||||
progress.show()
|
||||
vbox.pack_start(progress, False)
|
||||
|
||||
self.progresstext = label = gtk.Label('')
|
||||
label.set_line_wrap(True)
|
||||
label.set_use_markup(True)
|
||||
label.set_alignment(0.0, 0.0)
|
||||
label.show()
|
||||
vbox.pack_start(label)
|
||||
self.set_task(task)
|
||||
|
||||
def set_task(self, task):
|
||||
self.progresstext.set_markup('<i>%s</i>' % task)
|
||||
|
||||
UNKNOWN = 0
|
||||
SUCCESS = 1
|
||||
FAILURE = 2
|
||||
CANCELLED = 3
|
||||
|
||||
class RemuxProgressDialog(ProgressDialog):
|
||||
def __init__(self, parent, start, stop, fromname, toname):
|
||||
ProgressDialog.__init__(self,
|
||||
"Writing to disk",
|
||||
('Writing the selected segment of <b>%s</b> '
|
||||
'to <b>%s</b>. This may take some time.'
|
||||
% (fromname, toname)),
|
||||
'Starting media pipeline',
|
||||
parent,
|
||||
gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
|
||||
(gtk.STOCK_CANCEL, CANCELLED,
|
||||
gtk.STOCK_CLOSE, SUCCESS))
|
||||
self.start = start
|
||||
self.stop = stop
|
||||
self.update_position(start)
|
||||
self.set_completed(False)
|
||||
|
||||
def update_position(self, pos):
|
||||
pos = min(max(pos, self.start), self.stop)
|
||||
remaining = self.stop - pos
|
||||
minutes = remaining // (gst.SECOND * 60)
|
||||
seconds = (remaining - minutes * gst.SECOND * 60) // gst.SECOND
|
||||
self.progress.set_text('%d:%02d of video remaining' % (minutes, seconds))
|
||||
self.progress.set_fraction(1.0 - float(remaining) / (self.stop - self.start))
|
||||
|
||||
def set_completed(self, completed):
|
||||
self.set_response_sensitive(CANCELLED, not completed)
|
||||
self.set_response_sensitive(SUCCESS, completed)
|
||||
|
||||
def set_connection_blocked_async_marshalled(pads, proc, *args, **kwargs):
|
||||
def clear_list(l):
|
||||
while l:
|
||||
l.pop()
|
||||
|
||||
to_block = list(pads)
|
||||
to_relink = [(x, x.get_peer()) for x in pads]
|
||||
|
||||
def on_pad_blocked_sync(pad, is_blocked):
|
||||
if pad not in to_block:
|
||||
# can happen after the seek and before unblocking -- racy,
|
||||
# but no prob, bob.
|
||||
return
|
||||
to_block.remove(pad)
|
||||
if not to_block:
|
||||
# marshal to main thread
|
||||
gobject.idle_add(on_pads_blocked)
|
||||
|
||||
def on_pads_blocked():
|
||||
for src, sink in to_relink:
|
||||
src.link(sink)
|
||||
proc(*args, **kwargs)
|
||||
for src, sink in to_relink:
|
||||
src.set_blocked_async(False, lambda *x: None)
|
||||
clear_list(to_relink)
|
||||
|
||||
for src, sink in to_relink:
|
||||
src.unlink(sink)
|
||||
src.set_blocked_async(True, on_pad_blocked_sync)
|
||||
|
||||
class Remuxer(gst.Pipeline):
|
||||
|
||||
__gsignals__ = {'done': (gobject.SIGNAL_RUN_LAST, None, (int,))}
|
||||
|
||||
def __init__(self, fromuri, touri, start, stop):
|
||||
# HACK: should do Pipeline.__init__, but that doesn't do what we
|
||||
# want; there's a bug open aboooot that
|
||||
self.__gobject_init__()
|
||||
|
||||
assert start >= 0
|
||||
assert stop > start
|
||||
|
||||
self.fromuri = fromuri
|
||||
self.touri = None
|
||||
self.start_time = start
|
||||
self.stop_time = stop
|
||||
|
||||
self.src = self.remuxbin = self.sink = None
|
||||
self.resolution = UNKNOWN
|
||||
|
||||
self.window = None
|
||||
self.pdialog = None
|
||||
|
||||
self._query_id = -1
|
||||
|
||||
def do_setup_pipeline(self):
|
||||
self.src = gst.element_make_from_uri(gst.URI_SRC, self.fromuri)
|
||||
self.remuxbin = RemuxBin(self.start_time, self.stop_time)
|
||||
self.sink = gst.element_make_from_uri(gst.URI_SINK, self.touri)
|
||||
self.resolution = UNKNOWN
|
||||
|
||||
if gobject.signal_lookup('allow-overwrite', self.sink.__class__):
|
||||
self.sink.connect('allow-overwrite', lambda *x: True)
|
||||
|
||||
self.add(self.src, self.remuxbin, self.sink)
|
||||
|
||||
self.src.link(self.remuxbin)
|
||||
self.remuxbin.link(self.sink)
|
||||
|
||||
def do_get_touri(self):
|
||||
chooser = gtk.FileChooserDialog('Save as...',
|
||||
self.window,
|
||||
action=gtk.FILE_CHOOSER_ACTION_SAVE,
|
||||
buttons=(gtk.STOCK_CANCEL,
|
||||
CANCELLED,
|
||||
gtk.STOCK_SAVE,
|
||||
SUCCESS))
|
||||
chooser.set_uri(self.fromuri) # to select the folder
|
||||
chooser.unselect_all()
|
||||
chooser.set_do_overwrite_confirmation(True)
|
||||
name = self.fromuri.split('/')[-1][:-4] + '-remuxed.ogg'
|
||||
chooser.set_current_name(name)
|
||||
resp = chooser.run()
|
||||
uri = chooser.get_uri()
|
||||
chooser.destroy()
|
||||
|
||||
if resp == SUCCESS:
|
||||
return uri
|
||||
else:
|
||||
return None
|
||||
|
||||
def _start_queries(self):
|
||||
def do_query():
|
||||
try:
|
||||
# HACK: self.remuxbin.query() should do the same
|
||||
# (requires implementing a vmethod, dunno how to do that
|
||||
# although i think it's possible)
|
||||
# HACK: why does self.query_position(..) not give useful
|
||||
# answers?
|
||||
pad = self.remuxbin.get_pad('src')
|
||||
pos, duration = pad.query_position(gst.FORMAT_TIME)
|
||||
if pos != gst.CLOCK_TIME_NONE:
|
||||
self.pdialog.update_position(pos)
|
||||
except:
|
||||
# print 'query failed'
|
||||
pass
|
||||
return True
|
||||
if self._query_id == -1:
|
||||
self._query_id = gobject.timeout_add(100, # 10 Hz
|
||||
do_query)
|
||||
|
||||
def _stop_queries(self):
|
||||
if self._query_id != -1:
|
||||
gobject.source_remove(self._query_id)
|
||||
self._query_id = -1
|
||||
|
||||
def _bus_watch(self, bus, message):
|
||||
if message.type == gst.MESSAGE_ERROR:
|
||||
print 'error', message
|
||||
self._stop_queries()
|
||||
m = gtk.MessageDialog(self.window,
|
||||
gtk.DIALOG_MODAL|gtk.DIALOG_DESTROY_WITH_PARENT,
|
||||
gtk.MESSAGE_ERROR,
|
||||
gtk.BUTTONS_CLOSE,
|
||||
"Error processing file")
|
||||
gerror, debug = message.parse_error()
|
||||
txt = ('There was an error processing your file: %s\n\n'
|
||||
'Debug information:\n%s' % (gerror, debug))
|
||||
m.format_secondary_text(txt)
|
||||
m.run()
|
||||
m.destroy()
|
||||
self.response(FAILURE)
|
||||
elif message.type == gst.MESSAGE_WARNING:
|
||||
print 'warning', message
|
||||
elif message.type == gst.MESSAGE_EOS:
|
||||
# print 'eos, woot', message.src
|
||||
name = self.touri
|
||||
if name.startswith('file://'):
|
||||
name = name[7:]
|
||||
self.pdialog.set_task('Finished writing %s' % name)
|
||||
self.pdialog.update_position(self.stop_time)
|
||||
self._stop_queries()
|
||||
self.pdialog.set_completed(True)
|
||||
elif message.type == gst.MESSAGE_STATE_CHANGED:
|
||||
if message.src == self:
|
||||
old, new, pending = message.parse_state_changed()
|
||||
if ((old, new, pending) ==
|
||||
(gst.STATE_READY, gst.STATE_PAUSED,
|
||||
gst.STATE_VOID_PENDING)):
|
||||
self.pdialog.set_task('Processing file')
|
||||
self.pdialog.update_position(self.start_time)
|
||||
self._start_queries()
|
||||
self.set_state(gst.STATE_PLAYING)
|
||||
|
||||
def response(self, response):
|
||||
assert self.resolution == UNKNOWN
|
||||
self.resolution = response
|
||||
self.set_state(gst.STATE_NULL)
|
||||
self.pdialog.destroy()
|
||||
self.pdialog = None
|
||||
self.window.set_sensitive(True)
|
||||
self.emit('done', response)
|
||||
|
||||
def start(self, main_window):
|
||||
self.window = main_window
|
||||
self.touri = self.do_get_touri()
|
||||
if not self.touri:
|
||||
return False
|
||||
self.do_setup_pipeline()
|
||||
bus = self.get_bus()
|
||||
bus.add_signal_watch()
|
||||
bus.connect('message', self._bus_watch)
|
||||
if self.window:
|
||||
# can be None if we are debugging...
|
||||
self.window.set_sensitive(False)
|
||||
fromname = self.fromuri.split('/')[-1]
|
||||
toname = self.touri.split('/')[-1]
|
||||
self.pdialog = RemuxProgressDialog(main_window, self.start_time,
|
||||
self.stop_time, fromname, toname)
|
||||
self.pdialog.show()
|
||||
self.pdialog.connect('response', lambda w, r: self.response(r))
|
||||
|
||||
self.set_state(gst.STATE_PAUSED)
|
||||
return True
|
||||
|
||||
def run(self, main_window):
|
||||
if self.start(main_window):
|
||||
loop = gobject.MainLoop()
|
||||
self.connect('done', lambda *x: gobject.idle_add(loop.quit))
|
||||
loop.run()
|
||||
else:
|
||||
self.resolution = CANCELLED
|
||||
return self.resolution
|
||||
|
||||
class RemuxBin(gst.Bin):
|
||||
def __init__(self, start_time, stop_time):
|
||||
self.__gobject_init__()
|
||||
|
||||
self.parsefactories = self._find_parsers()
|
||||
self.parsers = []
|
||||
|
||||
self.demux = gst.element_factory_make('oggdemux')
|
||||
self.mux = gst.element_factory_make('oggmux')
|
||||
|
||||
self.add(self.demux, self.mux)
|
||||
|
||||
self.add_pad(gst.GhostPad('sink', self.demux.get_pad('sink')))
|
||||
self.add_pad(gst.GhostPad('src', self.mux.get_pad('src')))
|
||||
|
||||
self.demux.connect('pad-added', self._new_demuxed_pad)
|
||||
self.demux.connect('no-more-pads', self._no_more_pads)
|
||||
|
||||
self.start_time = start_time
|
||||
self.stop_time = stop_time
|
||||
|
||||
def _find_parsers(self):
|
||||
registry = gst.registry_get_default()
|
||||
ret = {}
|
||||
for f in registry.get_feature_list(gst.ElementFactory):
|
||||
if f.get_klass().find('Parser') >= 0:
|
||||
for t in f.get_static_pad_templates():
|
||||
if t.direction == gst.PAD_SINK:
|
||||
for s in t.get_caps():
|
||||
ret[s.get_name()] = f.get_name()
|
||||
break
|
||||
return ret
|
||||
|
||||
def _new_demuxed_pad(self, element, pad):
|
||||
format = pad.get_caps()[0].get_name()
|
||||
|
||||
if format not in self.parsefactories:
|
||||
self.async_error("Unsupported media type: %s", format)
|
||||
return
|
||||
|
||||
queue = gst.element_factory_make('queue', None);
|
||||
queue.set_property('max-size-buffers', 1000)
|
||||
parser = gst.element_factory_make(self.parsefactories[format])
|
||||
self.add(queue)
|
||||
self.add(parser)
|
||||
queue.set_state(gst.STATE_PAUSED)
|
||||
parser.set_state(gst.STATE_PAUSED)
|
||||
pad.link(queue.get_compatible_pad(pad))
|
||||
queue.link(parser)
|
||||
parser.link(self.mux)
|
||||
self.parsers.append(parser)
|
||||
|
||||
def _do_seek(self):
|
||||
flags = gst.SEEK_FLAG_FLUSH
|
||||
# HACK: self.seek should work, should try that at some point
|
||||
return self.demux.seek(1.0, gst.FORMAT_TIME, flags,
|
||||
gst.SEEK_TYPE_SET, self.start_time,
|
||||
gst.SEEK_TYPE_SET, self.stop_time)
|
||||
|
||||
def _no_more_pads(self, element):
|
||||
pads = [x.get_pad('src') for x in self.parsers]
|
||||
set_connection_blocked_async_marshalled(pads,
|
||||
self._do_seek)
|
||||
|
||||
|
||||
class PlayerWindow(gtk.Window):
|
||||
UPDATE_INTERVAL = 500
|
||||
def __init__(self):
|
||||
gtk.Window.__init__(self)
|
||||
self.set_default_size(600, 425)
|
||||
|
||||
self.create_ui()
|
||||
|
||||
self.player = GstPlayer(self.videowidget)
|
||||
|
||||
def on_eos():
|
||||
self.player.seek(0L)
|
||||
self.play_toggled()
|
||||
self.player.on_eos = lambda *x: on_eos()
|
||||
|
||||
self.update_id = -1
|
||||
self.changed_id = -1
|
||||
self.seek_timeout_id = -1
|
||||
|
||||
self.p_position = gst.CLOCK_TIME_NONE
|
||||
self.p_duration = gst.CLOCK_TIME_NONE
|
||||
|
||||
def on_delete_event():
|
||||
self.player.stop()
|
||||
gtk.main_quit()
|
||||
self.connect('delete-event', lambda *x: on_delete_event())
|
||||
|
||||
def load_file(self, location):
|
||||
filename = location.split('/')[-1]
|
||||
self.set_title('%s munger' % filename)
|
||||
self.player.set_location(location)
|
||||
if self.videowidget.flags() & gtk.REALIZED:
|
||||
self.play_toggled()
|
||||
else:
|
||||
self.videowidget.connect_after('realize',
|
||||
lambda *x: self.play_toggled())
|
||||
|
||||
def create_ui(self):
|
||||
vbox = gtk.VBox()
|
||||
vbox.show()
|
||||
self.add(vbox)
|
||||
|
||||
self.videowidget = VideoWidget()
|
||||
self.videowidget.show()
|
||||
vbox.pack_start(self.videowidget)
|
||||
|
||||
hbox = gtk.HBox()
|
||||
hbox.show()
|
||||
vbox.pack_start(hbox, fill=False, expand=False)
|
||||
|
||||
self.adjustment = gtk.Adjustment(0.0, 0.00, 100.0, 0.1, 1.0, 1.0)
|
||||
hscale = gtk.HScale(self.adjustment)
|
||||
hscale.set_digits(2)
|
||||
hscale.set_update_policy(gtk.UPDATE_CONTINUOUS)
|
||||
hscale.connect('button-press-event', self.scale_button_press_cb)
|
||||
hscale.connect('button-release-event', self.scale_button_release_cb)
|
||||
hscale.connect('format-value', self.scale_format_value_cb)
|
||||
hbox.pack_start(hscale)
|
||||
hscale.show()
|
||||
self.hscale = hscale
|
||||
|
||||
table = gtk.Table(2,3)
|
||||
table.show()
|
||||
vbox.pack_start(table, fill=False, expand=False, padding=6)
|
||||
|
||||
self.button = button = gtk.Button(stock=gtk.STOCK_MEDIA_PLAY)
|
||||
button.set_property('can-default', True)
|
||||
button.set_focus_on_click(False)
|
||||
button.show()
|
||||
|
||||
# problem: play and paused are of different widths and cause the
|
||||
# window to re-layout
|
||||
# "solution": add more buttons to a vbox so that the horizontal
|
||||
# width is enough
|
||||
bvbox = gtk.VBox()
|
||||
bvbox.add(button)
|
||||
bvbox.add(gtk.Button(stock=gtk.STOCK_MEDIA_PLAY))
|
||||
bvbox.add(gtk.Button(stock=gtk.STOCK_MEDIA_PAUSE))
|
||||
sizegroup = gtk.SizeGroup(gtk.SIZE_GROUP_HORIZONTAL)
|
||||
for kid in bvbox.get_children():
|
||||
sizegroup.add_widget(kid)
|
||||
bvbox.show()
|
||||
table.attach(bvbox, 0, 1, 0, 2, gtk.FILL, gtk.FILL)
|
||||
|
||||
# can't set this property before the button has a window
|
||||
button.set_property('has-default', True)
|
||||
button.connect('clicked', lambda *args: self.play_toggled())
|
||||
|
||||
self.cutin = cut = TimeControl(self, "Cut in time")
|
||||
cut.show()
|
||||
table.attach(cut, 1, 2, 0, 1, gtk.EXPAND, 0, 12)
|
||||
|
||||
self.cutout = cut = TimeControl(self, "Cut out time")
|
||||
cut.show()
|
||||
table.attach(cut, 1, 2, 1, 2, gtk.EXPAND, 0, 12)
|
||||
|
||||
button = gtk.Button("_Open other movie...")
|
||||
button.show()
|
||||
button.connect('clicked', lambda *x: self.do_choose_file())
|
||||
table.attach(button, 2, 3, 0, 1, gtk.FILL, gtk.FILL)
|
||||
|
||||
button = gtk.Button("_Write to disk")
|
||||
button.set_property('image',
|
||||
gtk.image_new_from_stock(gtk.STOCK_SAVE_AS,
|
||||
gtk.ICON_SIZE_BUTTON))
|
||||
button.connect('clicked', lambda *x: self.do_remux())
|
||||
button.show()
|
||||
table.attach(button, 2, 3, 1, 2, gtk.FILL, gtk.FILL)
|
||||
|
||||
#self.cutin.connect('notify::time', lambda *x: self.check_cutout())
|
||||
#self.cutout.connect('notify::time', lambda *x: self.check_cutin())
|
||||
|
||||
def do_remux(self):
|
||||
if self.player.is_playing():
|
||||
self.play_toggled()
|
||||
in_uri = self.player.get_location()
|
||||
out_uri = in_uri[:-4] + '-remuxed.ogg'
|
||||
r = Remuxer(in_uri, out_uri,
|
||||
self.cutin.get_time(), self.cutout.get_time())
|
||||
r.run(self)
|
||||
|
||||
def do_choose_file(self):
|
||||
if self.player.is_playing():
|
||||
self.play_toggled()
|
||||
chooser = gtk.FileChooserDialog('Choose a movie to cut cut cut',
|
||||
self,
|
||||
buttons=(gtk.STOCK_CANCEL,
|
||||
CANCELLED,
|
||||
gtk.STOCK_OPEN,
|
||||
SUCCESS))
|
||||
chooser.set_local_only(False)
|
||||
chooser.set_select_multiple(False)
|
||||
f = gtk.FileFilter()
|
||||
f.set_name("All files")
|
||||
f.add_pattern("*")
|
||||
chooser.add_filter(f)
|
||||
f = gtk.FileFilter()
|
||||
f.set_name("Ogg files")
|
||||
f.add_pattern("*.og[gvax]") # as long as this is the only thing we
|
||||
# support...
|
||||
chooser.add_filter(f)
|
||||
chooser.set_filter(f)
|
||||
|
||||
prev = self.player.get_location()
|
||||
if prev:
|
||||
chooser.set_uri(prev)
|
||||
|
||||
resp = chooser.run()
|
||||
uri = chooser.get_uri()
|
||||
chooser.destroy()
|
||||
|
||||
if resp == SUCCESS and uri != None:
|
||||
self.load_file(uri)
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def check_cutout(self):
|
||||
if self.cutout.get_time() <= self.cutin.get_time():
|
||||
pos, dur = self.player.query_position()
|
||||
self.cutout.set_time(dur)
|
||||
|
||||
def check_cutin(self):
|
||||
if self.cutin.get_time() >= self.cutout.get_time():
|
||||
self.cutin.set_time(0)
|
||||
|
||||
def play_toggled(self):
|
||||
if self.player.is_playing():
|
||||
self.player.pause()
|
||||
self.button.set_label(gtk.STOCK_MEDIA_PLAY)
|
||||
else:
|
||||
self.player.play()
|
||||
if self.update_id == -1:
|
||||
self.update_id = gobject.timeout_add(self.UPDATE_INTERVAL,
|
||||
self.update_scale_cb)
|
||||
self.button.set_label(gtk.STOCK_MEDIA_PAUSE)
|
||||
|
||||
def scale_format_value_cb(self, scale, value):
|
||||
if self.p_duration == -1:
|
||||
real = 0
|
||||
else:
|
||||
real = value * self.p_duration / 100
|
||||
|
||||
seconds = real / gst.SECOND
|
||||
|
||||
return "%02d:%02d" % (seconds / 60, seconds % 60)
|
||||
|
||||
def scale_button_press_cb(self, widget, event):
|
||||
# see seek.c:start_seek
|
||||
gst.debug('starting seek')
|
||||
|
||||
self.button.set_sensitive(False)
|
||||
self.was_playing = self.player.is_playing()
|
||||
if self.was_playing:
|
||||
self.player.pause()
|
||||
|
||||
# don't timeout-update position during seek
|
||||
if self.update_id != -1:
|
||||
gobject.source_remove(self.update_id)
|
||||
self.update_id = -1
|
||||
|
||||
# make sure we get changed notifies
|
||||
if self.changed_id == -1:
|
||||
self.changed_id = self.hscale.connect('value-changed',
|
||||
self.scale_value_changed_cb)
|
||||
|
||||
def scale_value_changed_cb(self, scale):
|
||||
# see seek.c:seek_cb
|
||||
real = long(scale.get_value() * self.p_duration / 100) # in ns
|
||||
gst.debug('value changed, perform seek to %r' % real)
|
||||
self.player.seek(real)
|
||||
# allow for a preroll
|
||||
self.player.get_state(timeout=50*gst.MSECOND) # 50 ms
|
||||
|
||||
def scale_button_release_cb(self, widget, event):
|
||||
# see seek.cstop_seek
|
||||
widget.disconnect(self.changed_id)
|
||||
self.changed_id = -1
|
||||
|
||||
self.button.set_sensitive(True)
|
||||
if self.seek_timeout_id != -1:
|
||||
gobject.source_remove(self.seek_timeout_id)
|
||||
self.seek_timeout_id = -1
|
||||
else:
|
||||
gst.debug('released slider, setting back to playing')
|
||||
if self.was_playing:
|
||||
self.player.play()
|
||||
|
||||
if self.update_id != -1:
|
||||
self.error('Had a previous update timeout id')
|
||||
else:
|
||||
self.update_id = gobject.timeout_add(self.UPDATE_INTERVAL,
|
||||
self.update_scale_cb)
|
||||
|
||||
def update_scale_cb(self):
|
||||
had_duration = self.p_duration != gst.CLOCK_TIME_NONE
|
||||
self.p_position, self.p_duration = self.player.query_position()
|
||||
if self.p_position != gst.CLOCK_TIME_NONE:
|
||||
value = self.p_position * 100.0 / self.p_duration
|
||||
self.adjustment.set_value(value)
|
||||
if not had_duration:
|
||||
self.cutin.set_time(0)
|
||||
return True
|
||||
|
||||
def main(args):
|
||||
def usage():
|
||||
sys.stderr.write("usage: %s [URI-OF-MEDIA-FILE]\n" % args[0])
|
||||
return 1
|
||||
|
||||
w = PlayerWindow()
|
||||
w.show()
|
||||
|
||||
if len(args) == 1:
|
||||
if not w.do_choose_file():
|
||||
return 1
|
||||
elif len(args) == 2:
|
||||
if not gst.uri_is_valid(args[1]):
|
||||
sys.stderr.write("Error: Invalid URI: %s\n" % args[1])
|
||||
return 1
|
||||
w.load_file(args[1])
|
||||
else:
|
||||
return usage()
|
||||
|
||||
gtk.main()
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main(sys.argv))
|
||||
+198
@@ -0,0 +1,198 @@
|
||||
#! /usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Segments.py
|
||||
# Copyright (C) 2006 Artem Popov <artfwo@gmail.com>
|
||||
#
|
||||
# This example demonstrates segment seeking
|
||||
# and seamless looping within playbin.
|
||||
|
||||
import pygst
|
||||
pygst.require ("0.10")
|
||||
import gst
|
||||
|
||||
import pygtk
|
||||
pygtk.require ("2.0")
|
||||
import gobject
|
||||
|
||||
class Looper (gobject.GObject):
|
||||
__gproperties__ = {
|
||||
"loop": (gobject.TYPE_BOOLEAN,
|
||||
"loop",
|
||||
"Whether to loop the segment",
|
||||
False,
|
||||
gobject.PARAM_READWRITE),
|
||||
"start-pos": (gobject.TYPE_UINT64,
|
||||
"start position",
|
||||
"The segment start marker",
|
||||
0,
|
||||
0xfffffffffffffff, # max long possible
|
||||
0,
|
||||
gobject.PARAM_READWRITE),
|
||||
"stop-pos": (gobject.TYPE_UINT64,
|
||||
"stop position",
|
||||
"The segment stop marker",
|
||||
0,
|
||||
0xfffffffffffffff, # max long possible
|
||||
0,
|
||||
gobject.PARAM_READWRITE),
|
||||
} # __gproperties__
|
||||
|
||||
__gsignals__ = {
|
||||
"stopped": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()),
|
||||
"position-updated": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_FLOAT,)),
|
||||
"error": (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,))
|
||||
} # __gsignals__
|
||||
|
||||
def __init__ (self, location = None):
|
||||
gobject.GObject.__init__ (self)
|
||||
|
||||
self.__playbin = gst.element_factory_make ("playbin")
|
||||
self.__playbin.props.video_sink = gst.element_factory_make ("fakesink")
|
||||
|
||||
bus = self.__playbin.get_bus ()
|
||||
bus.add_watch (self.__on_bus_message)
|
||||
|
||||
self.__loop = False
|
||||
self.__start_pos = 0
|
||||
self.__stop_pos = 0
|
||||
|
||||
self.__timeout_id = 0
|
||||
|
||||
if location:
|
||||
self.load (location)
|
||||
|
||||
def load (self, location):
|
||||
self.__playbin.props.uri = location
|
||||
self.__start_position = 0
|
||||
self.__stop_position = 0
|
||||
|
||||
def set_segment (self, start, stop):
|
||||
self.props.start_pos = start
|
||||
self.props.stop_pos = stop
|
||||
|
||||
def play (self):
|
||||
if not (self.__start_pos or self.__stop_pos):
|
||||
raise RuntimeError, "Cannot start playback, segment was not set!"
|
||||
|
||||
self.__playbin.set_state (gst.STATE_PLAYING)
|
||||
|
||||
def stop (self, silent = False):
|
||||
self.__playbin.set_state (gst.STATE_NULL)
|
||||
if not silent:
|
||||
self.emit ("stopped")
|
||||
|
||||
def do_get_property (self, property):
|
||||
if property.name == "loop":
|
||||
return self.__loop
|
||||
elif property.name == "start-pos":
|
||||
return self.__start_pos
|
||||
elif property.name == "stop-pos":
|
||||
return self.__stop_pos
|
||||
else:
|
||||
raise AttributeError, "Unknown property %s" % property.name
|
||||
|
||||
def do_set_property (self, property, value):
|
||||
if property.name == "loop":
|
||||
self.__loop = value
|
||||
elif property.name == "start-pos":
|
||||
self.__start_pos = value
|
||||
elif property.name == "stop-pos":
|
||||
self.__stop_pos = value
|
||||
else:
|
||||
raise AttributeError, "Unknown property %s" % property.name
|
||||
|
||||
def do_stopped (self):
|
||||
if self.__timeout_id:
|
||||
gobject.source_remove (self.__timeout_id)
|
||||
self.__timeout_id = 0
|
||||
|
||||
def __seek (self, start, stop, flush):
|
||||
flags = gst.SEEK_FLAG_SEGMENT | gst.SEEK_FLAG_ACCURATE
|
||||
if flush:
|
||||
flags = flags | gst.SEEK_FLAG_FLUSH
|
||||
self.__playbin.seek (1.0, gst.FORMAT_TIME, flags,
|
||||
gst.SEEK_TYPE_SET, start,
|
||||
gst.SEEK_TYPE_SET, stop)
|
||||
|
||||
def __on_timeout (self):
|
||||
position = self.__playbin.query_position (gst.FORMAT_TIME) [0]
|
||||
self.emit ("position-updated", float (position))
|
||||
return True
|
||||
|
||||
def __on_bus_message (self, bus, message):
|
||||
if message.type == gst.MESSAGE_ERROR:
|
||||
error, debug = message.parse_error ()
|
||||
self.stop () # this looks neccessary here
|
||||
self.emit ("error", (error, debug))
|
||||
|
||||
elif message.type == gst.MESSAGE_NEW_CLOCK:
|
||||
# we connect the timeout handler here to be sure that further queries succeed
|
||||
interval = int ((self.__stop_position - self.__start_position) / (2 * gst.SECOND) + 50)
|
||||
self.__timeout_id = gobject.timeout_add (interval, self.__on_timeout)
|
||||
|
||||
elif message.type == gst.MESSAGE_STATE_CHANGED:
|
||||
old_state, new_state, pending = message.parse_state_changed ()
|
||||
if old_state == gst.STATE_READY and new_state == gst.STATE_PAUSED and message.src == self.__playbin:
|
||||
self.__seek (self.__start_pos, self.__stop_pos, True)
|
||||
|
||||
elif message.type == gst.MESSAGE_SEGMENT_DONE:
|
||||
if self.__loop:
|
||||
self.__seek (self.__start_pos, self.__stop_pos, False)
|
||||
else:
|
||||
src = self.__playbin.get_property ("source")
|
||||
pad = src.get_pad ('src')
|
||||
pad.push_event (gst.event_new_eos ())
|
||||
|
||||
# this is the good old way:
|
||||
#
|
||||
# pads = src.src_pads ()
|
||||
# while True:
|
||||
# try:
|
||||
# pad = pads.next ()
|
||||
# pad.push_event (gst.event_new_eos ())
|
||||
# except:
|
||||
# break
|
||||
|
||||
elif message.type == gst.MESSAGE_EOS:
|
||||
self.stop ()
|
||||
|
||||
return True
|
||||
|
||||
mainloop = gobject.MainLoop ()
|
||||
|
||||
def on_looper_stopped (looper):
|
||||
mainloop.quit ()
|
||||
|
||||
def on_looper_pos_updated (looper, position):
|
||||
print round (position / gst.SECOND, 2)
|
||||
|
||||
def on_looper_error (looper, error_tuple):
|
||||
error, debug = error_tuple
|
||||
print "\n\n%s\n\n%s\n\n" % (error, debug)
|
||||
mainloop.quit ()
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
if len (sys.argv) != 5:
|
||||
print "Usage: %s <filename|uri> <start_seconds> <stop_seconds> <loop = 0|1>" % sys.argv [0]
|
||||
sys.exit (1)
|
||||
|
||||
if "://" in sys.argv [1]:
|
||||
uri = sys.argv [1]
|
||||
else:
|
||||
import os.path
|
||||
uri = "file://" + os.path.abspath (sys.argv [1])
|
||||
|
||||
looper = Looper (uri)
|
||||
|
||||
looper.props.start_pos = long (sys.argv [2]) * gst.SECOND
|
||||
looper.props.stop_pos = long (sys.argv [3]) * gst.SECOND
|
||||
looper.props.loop = int (sys.argv [4])
|
||||
|
||||
looper.connect ("stopped", on_looper_stopped)
|
||||
looper.connect ("position-updated", on_looper_pos_updated)
|
||||
looper.connect ("error", on_looper_error)
|
||||
|
||||
looper.play ()
|
||||
mainloop.run ()
|
||||
@@ -0,0 +1,69 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- Mode: Python -*-
|
||||
# vi:si:et:sw=4:sts=4:ts=4
|
||||
|
||||
# sinkelement.py
|
||||
# (c) 2005 Edward Hervey <edward@fluendo.com>
|
||||
# (c) 2007 Jan Schmidt <jan@fluendo.com>
|
||||
# Licensed under LGPL
|
||||
#
|
||||
# Small test application to show how to write a sink element
|
||||
# in 20 lines in python and place into the gstreamer registry
|
||||
# so it can be autoplugged or used from parse_launch.
|
||||
#
|
||||
# Run this script with GST_DEBUG=python:5 to see the debug
|
||||
# messages
|
||||
|
||||
import pygst
|
||||
pygst.require('0.10')
|
||||
import gst
|
||||
import gobject
|
||||
gobject.threads_init ()
|
||||
|
||||
#
|
||||
# Simple Sink element created entirely in python
|
||||
#
|
||||
|
||||
class MySink(gst.Element):
|
||||
__gstdetails__ = ('CustomSink','Sink', \
|
||||
'Custom test sink element', 'Edward Hervey')
|
||||
|
||||
_sinkpadtemplate = gst.PadTemplate ("sinkpadtemplate",
|
||||
gst.PAD_SINK,
|
||||
gst.PAD_ALWAYS,
|
||||
gst.caps_new_any())
|
||||
|
||||
def __init__(self):
|
||||
gst.Element.__init__(self)
|
||||
gst.info('creating sinkpad')
|
||||
self.sinkpad = gst.Pad(self._sinkpadtemplate, "sink")
|
||||
gst.info('adding sinkpad to self')
|
||||
self.add_pad(self.sinkpad)
|
||||
|
||||
gst.info('setting chain/event functions')
|
||||
self.sinkpad.set_chain_function(self.chainfunc)
|
||||
self.sinkpad.set_event_function(self.eventfunc)
|
||||
|
||||
def chainfunc(self, pad, buffer):
|
||||
self.info("%s timestamp(buffer):%d" % (pad, buffer.timestamp))
|
||||
return gst.FLOW_OK
|
||||
|
||||
def eventfunc(self, pad, event):
|
||||
self.info("%s event:%r" % (pad, event.type))
|
||||
return True
|
||||
|
||||
gobject.type_register(MySink)
|
||||
|
||||
# Register the element into this process' registry.
|
||||
gst.element_register (MySink, 'mysink', gst.RANK_MARGINAL)
|
||||
|
||||
print "Use --gst-debug=python:3 to see output from this example"
|
||||
|
||||
#
|
||||
# Code to test the MySink class
|
||||
#
|
||||
gst.info('About to create MySink')
|
||||
pipeline = gst.parse_launch ("fakesrc ! mysink")
|
||||
pipeline.set_state(gst.STATE_PLAYING)
|
||||
|
||||
gobject.MainLoop().run()
|
||||
@@ -0,0 +1,68 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- Mode: Python -*-
|
||||
# vi:si:et:sw=4:sts=4:ts=4
|
||||
|
||||
# sinkelement.py
|
||||
# (c) 2005 Edward Hervey <edward@fluendo.com>
|
||||
# Licensed under LGPL
|
||||
#
|
||||
# Small test application to show how to write a sink element
|
||||
# in 20 lines in python
|
||||
#
|
||||
# Run this script with GST_DEBUG=python:5 to see the debug
|
||||
# messages
|
||||
|
||||
import pygst
|
||||
pygst.require('0.10')
|
||||
import gst
|
||||
import gobject
|
||||
gobject.threads_init ()
|
||||
|
||||
#
|
||||
# Simple Sink element created entirely in python
|
||||
#
|
||||
|
||||
class MySink(gst.Element):
|
||||
|
||||
_sinkpadtemplate = gst.PadTemplate ("sinkpadtemplate",
|
||||
gst.PAD_SINK,
|
||||
gst.PAD_ALWAYS,
|
||||
gst.caps_new_any())
|
||||
|
||||
def __init__(self):
|
||||
gst.Element.__init__(self)
|
||||
gst.info('creating sinkpad')
|
||||
self.sinkpad = gst.Pad(self._sinkpadtemplate, "sink")
|
||||
gst.info('adding sinkpad to self')
|
||||
self.add_pad(self.sinkpad)
|
||||
|
||||
gst.info('setting chain/event functions')
|
||||
self.sinkpad.set_chain_function(self.chainfunc)
|
||||
self.sinkpad.set_event_function(self.eventfunc)
|
||||
|
||||
def chainfunc(self, pad, buffer):
|
||||
self.info("%s timestamp(buffer):%d" % (pad, buffer.timestamp))
|
||||
return gst.FLOW_OK
|
||||
|
||||
def eventfunc(self, pad, event):
|
||||
self.info("%s event:%r" % (pad, event.type))
|
||||
return True
|
||||
|
||||
gobject.type_register(MySink)
|
||||
|
||||
#
|
||||
# Code to test the MySink class
|
||||
#
|
||||
|
||||
src = gst.element_factory_make('fakesrc')
|
||||
gst.info('About to create MySink')
|
||||
sink = MySink()
|
||||
|
||||
pipeline = gst.Pipeline()
|
||||
pipeline.add(src, sink)
|
||||
|
||||
src.link(sink)
|
||||
|
||||
pipeline.set_state(gst.STATE_PLAYING)
|
||||
|
||||
gobject.MainLoop().run()
|
||||
+192
@@ -0,0 +1,192 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- Mode: Python -*-
|
||||
# vi:si:et:sw=4:sts=4:ts=4
|
||||
|
||||
import pygtk
|
||||
pygtk.require('2.0')
|
||||
|
||||
import sys
|
||||
|
||||
import gobject
|
||||
gobject.threads_init()
|
||||
|
||||
import pygst
|
||||
pygst.require('0.10')
|
||||
import gst
|
||||
import gst.interfaces
|
||||
import gtk
|
||||
gtk.gdk.threads_init()
|
||||
|
||||
class SwitchTest:
|
||||
def __init__(self, videowidget):
|
||||
self.playing = False
|
||||
pipestr = ('videotestsrc pattern=0 ! queue ! s.sink0'
|
||||
' videotestsrc pattern=1 ! queue ! s.sink1'
|
||||
' input-selector name=s ! autovideosink')
|
||||
self.pipeline = gst.parse_launch(pipestr)
|
||||
self.videowidget = videowidget
|
||||
|
||||
bus = self.pipeline.get_bus()
|
||||
bus.enable_sync_message_emission()
|
||||
bus.add_signal_watch()
|
||||
bus.connect('sync-message::element', self.on_sync_message)
|
||||
bus.connect('message', self.on_message)
|
||||
|
||||
def on_sync_message(self, bus, message):
|
||||
if message.structure is None:
|
||||
return
|
||||
if message.structure.get_name() == 'prepare-xwindow-id':
|
||||
# Sync with the X server before giving the X-id to the sink
|
||||
gtk.gdk.threads_enter()
|
||||
gtk.gdk.display_get_default().sync()
|
||||
self.videowidget.set_sink(message.src)
|
||||
message.src.set_property('force-aspect-ratio', True)
|
||||
gtk.gdk.threads_leave()
|
||||
|
||||
def on_message(self, bus, message):
|
||||
t = message.type
|
||||
if t == gst.MESSAGE_ERROR:
|
||||
err, debug = message.parse_error()
|
||||
print "Error: %s" % err, debug
|
||||
if self.on_eos:
|
||||
self.on_eos()
|
||||
self.playing = False
|
||||
elif t == gst.MESSAGE_EOS:
|
||||
if self.on_eos:
|
||||
self.on_eos()
|
||||
self.playing = False
|
||||
|
||||
def play(self):
|
||||
self.playing = True
|
||||
gst.info("playing player")
|
||||
self.pipeline.set_state(gst.STATE_PLAYING)
|
||||
|
||||
def stop(self):
|
||||
self.pipeline.set_state(gst.STATE_NULL)
|
||||
gst.info("stopped player")
|
||||
self.playing = False
|
||||
|
||||
def get_state(self, timeout=1):
|
||||
return self.pipeline.get_state(timeout=timeout)
|
||||
|
||||
def is_playing(self):
|
||||
return self.playing
|
||||
|
||||
def switch(self, padname):
|
||||
switch = self.pipeline.get_by_name('s')
|
||||
stop_time = switch.emit('block')
|
||||
newpad = switch.get_static_pad(padname)
|
||||
start_time = newpad.get_property('running-time')
|
||||
|
||||
gst.warning('stop time = %d' % (stop_time,))
|
||||
gst.warning('stop time = %s' % (gst.TIME_ARGS(stop_time),))
|
||||
|
||||
gst.warning('start time = %d' % (start_time,))
|
||||
gst.warning('start time = %s' % (gst.TIME_ARGS(start_time),))
|
||||
|
||||
gst.warning('switching from %r to %r'
|
||||
% (switch.get_property('active-pad'), padname))
|
||||
switch.emit('switch', newpad, stop_time, start_time)
|
||||
|
||||
class VideoWidget(gtk.DrawingArea):
|
||||
def __init__(self):
|
||||
gtk.DrawingArea.__init__(self)
|
||||
self.imagesink = None
|
||||
self.unset_flags(gtk.DOUBLE_BUFFERED)
|
||||
|
||||
def do_expose_event(self, event):
|
||||
if self.imagesink:
|
||||
self.imagesink.expose()
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
def set_sink(self, sink):
|
||||
assert self.window.xid
|
||||
self.imagesink = sink
|
||||
self.imagesink.set_xwindow_id(self.window.xid)
|
||||
|
||||
class SwitchWindow(gtk.Window):
|
||||
UPDATE_INTERVAL = 500
|
||||
def __init__(self):
|
||||
gtk.Window.__init__(self)
|
||||
self.set_default_size(410, 325)
|
||||
|
||||
self.create_ui()
|
||||
self.player = SwitchTest(self.videowidget)
|
||||
self.populate_combobox()
|
||||
|
||||
self.update_id = -1
|
||||
self.changed_id = -1
|
||||
self.seek_timeout_id = -1
|
||||
|
||||
self.p_position = gst.CLOCK_TIME_NONE
|
||||
self.p_duration = gst.CLOCK_TIME_NONE
|
||||
|
||||
def on_delete_event():
|
||||
self.player.stop()
|
||||
gtk.main_quit()
|
||||
self.connect('delete-event', lambda *x: on_delete_event())
|
||||
|
||||
def load_file(self, location):
|
||||
self.player.set_location(location)
|
||||
|
||||
def play(self):
|
||||
self.player.play()
|
||||
|
||||
def populate_combobox(self):
|
||||
switch = self.player.pipeline.get_by_name('s')
|
||||
for i, pad in enumerate([p for p in switch.pads()
|
||||
if p.get_direction() == gst.PAD_SINK]):
|
||||
self.combobox.append_text(pad.get_name())
|
||||
if switch.get_property('active-pad') == pad.get_name():
|
||||
self.combobox.set_active(i)
|
||||
if self.combobox.get_active() == -1:
|
||||
self.combobox.set_active(0)
|
||||
|
||||
def combobox_changed(self):
|
||||
model = self.combobox.get_model()
|
||||
row = model[self.combobox.get_active()]
|
||||
padname, = row
|
||||
self.player.switch(padname)
|
||||
|
||||
def create_ui(self):
|
||||
vbox = gtk.VBox()
|
||||
self.add(vbox)
|
||||
|
||||
self.videowidget = VideoWidget()
|
||||
vbox.pack_start(self.videowidget)
|
||||
|
||||
hbox = gtk.HBox()
|
||||
vbox.pack_start(hbox, fill=False, expand=False)
|
||||
|
||||
self.combobox = combobox = gtk.combo_box_new_text()
|
||||
combobox.show()
|
||||
hbox.pack_start(combobox)
|
||||
|
||||
self.combobox.connect('changed',
|
||||
lambda *x: self.combobox_changed())
|
||||
|
||||
self.videowidget.connect_after('realize',
|
||||
lambda *x: self.play())
|
||||
|
||||
def main(args):
|
||||
def usage():
|
||||
sys.stderr.write("usage: %s\n" % args[0])
|
||||
return 1
|
||||
|
||||
# Need to register our derived widget types for implicit event
|
||||
# handlers to get called.
|
||||
gobject.type_register(SwitchWindow)
|
||||
gobject.type_register(VideoWidget)
|
||||
|
||||
if len(args) != 1:
|
||||
return usage()
|
||||
|
||||
w = SwitchWindow()
|
||||
w.show_all()
|
||||
gtk.main()
|
||||
return 0
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main(sys.argv))
|
||||
+820
@@ -0,0 +1,820 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- Mode: Python -*-
|
||||
# vi:si:et:sw=4:sts=4:ts=4
|
||||
|
||||
import pygtk
|
||||
pygtk.require('2.0')
|
||||
|
||||
import sys
|
||||
|
||||
import gobject
|
||||
gobject.threads_init()
|
||||
|
||||
import pygst
|
||||
pygst.require('0.10')
|
||||
import gst
|
||||
import gst.interfaces
|
||||
import gtk
|
||||
gtk.gdk.threads_init()
|
||||
|
||||
class GstPlayer:
|
||||
def __init__(self, videowidget):
|
||||
self.playing = False
|
||||
self.player = gst.element_factory_make("playbin", "player")
|
||||
self.videowidget = videowidget
|
||||
|
||||
bus = self.player.get_bus()
|
||||
bus.enable_sync_message_emission()
|
||||
bus.add_signal_watch()
|
||||
bus.connect('sync-message::element', self.on_sync_message)
|
||||
bus.connect('message', self.on_message)
|
||||
|
||||
def on_sync_message(self, bus, message):
|
||||
if message.structure is None:
|
||||
return
|
||||
if message.structure.get_name() == 'prepare-xwindow-id':
|
||||
# Sync with the X server before giving the X-id to the sink
|
||||
gtk.gdk.threads_enter()
|
||||
gtk.gdk.display_get_default().sync()
|
||||
self.videowidget.set_sink(message.src)
|
||||
message.src.set_property('force-aspect-ratio', True)
|
||||
gtk.gdk.threads_leave()
|
||||
|
||||
def on_message(self, bus, message):
|
||||
t = message.type
|
||||
if t == gst.MESSAGE_ERROR:
|
||||
err, debug = message.parse_error()
|
||||
print "Error: %s" % err, debug
|
||||
if self.on_eos:
|
||||
self.on_eos()
|
||||
self.playing = False
|
||||
elif t == gst.MESSAGE_EOS:
|
||||
if self.on_eos:
|
||||
self.on_eos()
|
||||
self.playing = False
|
||||
|
||||
def set_location(self, location):
|
||||
self.player.set_state(gst.STATE_NULL)
|
||||
self.player.set_property('uri', location)
|
||||
|
||||
def get_location(self):
|
||||
return self.player.get_property('uri')
|
||||
|
||||
def query_position(self):
|
||||
"Returns a (position, duration) tuple"
|
||||
try:
|
||||
position, format = self.player.query_position(gst.FORMAT_TIME)
|
||||
except:
|
||||
position = gst.CLOCK_TIME_NONE
|
||||
|
||||
try:
|
||||
duration, format = self.player.query_duration(gst.FORMAT_TIME)
|
||||
except:
|
||||
duration = gst.CLOCK_TIME_NONE
|
||||
|
||||
return (position, duration)
|
||||
|
||||
def seek(self, location):
|
||||
"""
|
||||
@param location: time to seek to, in nanoseconds
|
||||
"""
|
||||
gst.debug("seeking to %r" % location)
|
||||
event = gst.event_new_seek(1.0, gst.FORMAT_TIME,
|
||||
gst.SEEK_FLAG_FLUSH,
|
||||
gst.SEEK_TYPE_SET, location,
|
||||
gst.SEEK_TYPE_NONE, 0)
|
||||
|
||||
res = self.player.send_event(event)
|
||||
if res:
|
||||
gst.info("setting new stream time to 0")
|
||||
self.player.set_new_stream_time(0L)
|
||||
else:
|
||||
gst.error("seek to %r failed" % location)
|
||||
|
||||
def pause(self):
|
||||
gst.info("pausing player")
|
||||
self.player.set_state(gst.STATE_PAUSED)
|
||||
self.playing = False
|
||||
|
||||
def play(self):
|
||||
gst.info("playing player")
|
||||
self.player.set_state(gst.STATE_PLAYING)
|
||||
self.playing = True
|
||||
|
||||
def stop(self):
|
||||
self.player.set_state(gst.STATE_NULL)
|
||||
gst.info("stopped player")
|
||||
|
||||
def get_state(self, timeout=1):
|
||||
return self.player.get_state(timeout=timeout)
|
||||
|
||||
def is_playing(self):
|
||||
return self.playing
|
||||
|
||||
class VideoWidget(gtk.DrawingArea):
|
||||
def __init__(self):
|
||||
gtk.DrawingArea.__init__(self)
|
||||
self.imagesink = None
|
||||
self.unset_flags(gtk.DOUBLE_BUFFERED)
|
||||
|
||||
def do_expose_event(self, event):
|
||||
if self.imagesink:
|
||||
self.imagesink.expose()
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
def set_sink(self, sink):
|
||||
assert self.window.xid
|
||||
self.imagesink = sink
|
||||
self.imagesink.set_xwindow_id(self.window.xid)
|
||||
|
||||
class SyncPoints(gtk.VBox):
|
||||
def __init__(self, window):
|
||||
gtk.VBox.__init__(self)
|
||||
self.pwindow = window
|
||||
self.create_ui()
|
||||
|
||||
def get_time_as_str(self, iter, i):
|
||||
value = self.model.get_value(iter, i)
|
||||
ret = ''
|
||||
for div, sep, mod, pad in ((gst.SECOND*60, '', 0, 0),
|
||||
(gst.SECOND, ':', 60, 2),
|
||||
(gst.MSECOND, '.', 1000, 3)):
|
||||
n = value // div
|
||||
if mod:
|
||||
n %= mod
|
||||
ret += sep + ('%%0%dd' % pad) % n
|
||||
return ret
|
||||
|
||||
def create_ui(self):
|
||||
self.model = model = gtk.ListStore(gobject.TYPE_UINT64,
|
||||
gobject.TYPE_UINT64)
|
||||
self.view = view = gtk.TreeView(self.model)
|
||||
|
||||
renderer = gtk.CellRendererText()
|
||||
column = gtk.TreeViewColumn("Audio time", renderer)
|
||||
def time_to_text(column, cell, method, iter, i):
|
||||
cell.set_property('text', self.get_time_as_str(iter, i))
|
||||
column.set_cell_data_func(renderer, time_to_text, 0)
|
||||
column.set_expand(True)
|
||||
column.set_clickable(True)
|
||||
view.append_column(column)
|
||||
|
||||
renderer = gtk.CellRendererText()
|
||||
column = gtk.TreeViewColumn("Video time", renderer)
|
||||
column.set_cell_data_func(renderer, time_to_text, 1)
|
||||
column.set_expand(True)
|
||||
view.append_column(column)
|
||||
|
||||
view.show()
|
||||
self.pack_start(view, True, True, 6)
|
||||
|
||||
hbox = gtk.HBox(False, 0)
|
||||
hbox.show()
|
||||
self.pack_start(hbox, False, False, 0)
|
||||
|
||||
add = gtk.Button(stock=gtk.STOCK_ADD)
|
||||
add.show()
|
||||
def add_and_select(*x):
|
||||
iter = model.append()
|
||||
self.view.get_selection().select_iter(iter)
|
||||
self.changed()
|
||||
add.connect("clicked", add_and_select)
|
||||
hbox.pack_end(add, False, False, 0)
|
||||
|
||||
remove = gtk.Button(stock=gtk.STOCK_REMOVE)
|
||||
remove.show()
|
||||
def remove_selected(*x):
|
||||
model, iter = self.view.get_selection().get_selected()
|
||||
model.remove(iter)
|
||||
self.changed()
|
||||
remove.connect("clicked", remove_selected)
|
||||
hbox.pack_end(remove, False, False, 0)
|
||||
|
||||
pad = gtk.Label(' ')
|
||||
pad.show()
|
||||
hbox.pack_end(pad)
|
||||
|
||||
label = gtk.Label("Set: ")
|
||||
label.show()
|
||||
hbox.pack_start(label)
|
||||
|
||||
a = gtk.Button("A_udio")
|
||||
a.show()
|
||||
a.connect("clicked", lambda *x: self.set_selected_audio_now())
|
||||
hbox.pack_start(a)
|
||||
|
||||
l = gtk.Label(" / ")
|
||||
l.show()
|
||||
hbox.pack_start(l)
|
||||
|
||||
v = gtk.Button("_Video")
|
||||
v.show()
|
||||
v.connect("clicked", lambda *x: self.set_selected_video_now())
|
||||
hbox.pack_start(v)
|
||||
|
||||
def get_sync_points(self):
|
||||
def get_value(row, i):
|
||||
return self.model.get_value(row.iter, i)
|
||||
pairs = [(get_value(row, 1), get_value(row, 0)) for row in self.model]
|
||||
pairs.sort()
|
||||
ret = []
|
||||
maxdiff = 0
|
||||
for pair in pairs:
|
||||
maxdiff = max(maxdiff, abs(pair[1] - pair[0]))
|
||||
ret.extend(pair)
|
||||
return ret, maxdiff
|
||||
|
||||
def changed(self):
|
||||
print 'Sync times now:'
|
||||
for index, row in enumerate(self.model):
|
||||
print 'A/V %d: %s -- %s' % (index,
|
||||
self.get_time_as_str(row.iter, 0),
|
||||
self.get_time_as_str(row.iter, 1))
|
||||
|
||||
|
||||
def set_selected_audio(self, time):
|
||||
sel = self.view.get_selection()
|
||||
model, iter = sel.get_selected()
|
||||
if iter:
|
||||
model.set_value(iter, 0, time)
|
||||
self.changed()
|
||||
|
||||
def set_selected_video(self, time):
|
||||
sel = self.view.get_selection()
|
||||
model, iter = sel.get_selected()
|
||||
if iter:
|
||||
model.set_value(iter, 1, time)
|
||||
self.changed()
|
||||
|
||||
def set_selected_audio_now(self):
|
||||
time, dur = self.pwindow.player.query_position()
|
||||
self.set_selected_audio(time)
|
||||
|
||||
def set_selected_video_now(self):
|
||||
# pause and preroll first
|
||||
if self.pwindow.player.is_playing():
|
||||
self.pwindow.play_toggled()
|
||||
self.pwindow.player.get_state(timeout=gst.MSECOND * 200)
|
||||
|
||||
time, dur = self.pwindow.player.query_position()
|
||||
self.set_selected_video(time)
|
||||
|
||||
def seek_and_pause(self, time):
|
||||
if self.pwindow.player.is_playing():
|
||||
self.pwindow.play_toggled()
|
||||
self.pwindow.player.seek(time)
|
||||
if self.pwindow.player.is_playing():
|
||||
self.pwindow.play_toggled()
|
||||
self.pwindow.player.get_state(timeout=gst.MSECOND * 200)
|
||||
|
||||
class ProgressDialog(gtk.Dialog):
|
||||
def __init__(self, title, description, task, parent, flags, buttons):
|
||||
gtk.Dialog.__init__(self, title, parent, flags, buttons)
|
||||
self._create_ui(title, description, task)
|
||||
|
||||
def _create_ui(self, title, description, task):
|
||||
self.set_border_width(6)
|
||||
self.set_resizable(False)
|
||||
self.set_has_separator(False)
|
||||
|
||||
vbox = gtk.VBox()
|
||||
vbox.set_border_width(6)
|
||||
vbox.show()
|
||||
self.vbox.pack_start(vbox, False)
|
||||
|
||||
label = gtk.Label('<big><b>%s</b></big>' % title)
|
||||
label.set_use_markup(True)
|
||||
label.set_alignment(0.0, 0.0)
|
||||
label.show()
|
||||
vbox.pack_start(label, False)
|
||||
|
||||
label = gtk.Label(description)
|
||||
label.set_use_markup(True)
|
||||
label.set_alignment(0.0, 0.0)
|
||||
label.set_line_wrap(True)
|
||||
label.set_padding(0, 12)
|
||||
label.show()
|
||||
vbox.pack_start(label, False)
|
||||
|
||||
self.progress = progress = gtk.ProgressBar()
|
||||
progress.show()
|
||||
vbox.pack_start(progress, False)
|
||||
|
||||
self.progresstext = label = gtk.Label('')
|
||||
label.set_line_wrap(True)
|
||||
label.set_use_markup(True)
|
||||
label.set_alignment(0.0, 0.0)
|
||||
label.show()
|
||||
vbox.pack_start(label)
|
||||
self.set_task(task)
|
||||
|
||||
def set_task(self, task):
|
||||
self.progresstext.set_markup('<i>%s</i>' % task)
|
||||
|
||||
UNKNOWN = 0
|
||||
SUCCESS = 1
|
||||
FAILURE = 2
|
||||
CANCELLED = 3
|
||||
|
||||
class RemuxProgressDialog(ProgressDialog):
|
||||
def __init__(self, parent, fromname, toname):
|
||||
ProgressDialog.__init__(self,
|
||||
"Writing to disk",
|
||||
('Writing the newly synchronized <b>%s</b> '
|
||||
'to <b>%s</b>. This may take some time.'
|
||||
% (fromname, toname)),
|
||||
'Starting media pipeline',
|
||||
parent,
|
||||
gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
|
||||
(gtk.STOCK_CANCEL, CANCELLED,
|
||||
gtk.STOCK_CLOSE, SUCCESS))
|
||||
self.set_completed(False)
|
||||
|
||||
def update_position(self, pos, dur):
|
||||
remaining = dur - pos
|
||||
minutes = remaining // (gst.SECOND * 60)
|
||||
seconds = (remaining - minutes * gst.SECOND * 60) // gst.SECOND
|
||||
self.progress.set_text('%d:%02d of video remaining' % (minutes, seconds))
|
||||
self.progress.set_fraction(1.0 - float(remaining) / dur)
|
||||
|
||||
def set_completed(self, completed):
|
||||
self.set_response_sensitive(CANCELLED, not completed)
|
||||
self.set_response_sensitive(SUCCESS, completed)
|
||||
|
||||
class Resynchronizer(gst.Pipeline):
|
||||
|
||||
__gsignals__ = {'done': (gobject.SIGNAL_RUN_LAST, None, (int,))}
|
||||
|
||||
def __init__(self, fromuri, touri, (syncpoints, maxdiff)):
|
||||
# HACK: should do Pipeline.__init__, but that doesn't do what we
|
||||
# want; there's a bug open aboooot that
|
||||
self.__gobject_init__()
|
||||
|
||||
self.fromuri = fromuri
|
||||
self.touri = None
|
||||
self.syncpoints = syncpoints
|
||||
self.maxdiff = maxdiff
|
||||
|
||||
self.src = self.resyncbin = self.sink = None
|
||||
self.resolution = UNKNOWN
|
||||
|
||||
self.window = None
|
||||
self.pdialog = None
|
||||
|
||||
self._query_id = -1
|
||||
|
||||
def do_setup_pipeline(self):
|
||||
self.src = gst.element_make_from_uri(gst.URI_SRC, self.fromuri)
|
||||
self.resyncbin = ResyncBin(self.syncpoints, self.maxdiff)
|
||||
self.sink = gst.element_make_from_uri(gst.URI_SINK, self.touri)
|
||||
self.resolution = UNKNOWN
|
||||
|
||||
if gobject.signal_lookup('allow-overwrite', self.sink.__class__):
|
||||
self.sink.connect('allow-overwrite', lambda *x: True)
|
||||
|
||||
self.add(self.src, self.resyncbin, self.sink)
|
||||
|
||||
self.src.link(self.resyncbin)
|
||||
self.resyncbin.link(self.sink)
|
||||
|
||||
def do_get_touri(self):
|
||||
chooser = gtk.FileChooserDialog('Save as...',
|
||||
self.window,
|
||||
action=gtk.FILE_CHOOSER_ACTION_SAVE,
|
||||
buttons=(gtk.STOCK_CANCEL,
|
||||
CANCELLED,
|
||||
gtk.STOCK_SAVE,
|
||||
SUCCESS))
|
||||
chooser.set_uri(self.fromuri) # to select the folder
|
||||
chooser.unselect_all()
|
||||
chooser.set_do_overwrite_confirmation(True)
|
||||
name = self.fromuri.split('/')[-1][:-4] + '-remuxed.ogg'
|
||||
chooser.set_current_name(name)
|
||||
resp = chooser.run()
|
||||
uri = chooser.get_uri()
|
||||
chooser.destroy()
|
||||
|
||||
if resp == SUCCESS:
|
||||
return uri
|
||||
else:
|
||||
return None
|
||||
|
||||
def _start_queries(self):
|
||||
def do_query():
|
||||
try:
|
||||
# HACK: self.remuxbin.query() should do the same
|
||||
# (requires implementing a vmethod, dunno how to do that
|
||||
# although i think it's possible)
|
||||
# HACK: why does self.query_position(..) not give useful
|
||||
# answers?
|
||||
pad = self.resyncbin.get_pad('src')
|
||||
pos, format = pad.query_position(gst.FORMAT_TIME)
|
||||
dur, format = pad.query_duration(gst.FORMAT_TIME)
|
||||
if pos != gst.CLOCK_TIME_NONE:
|
||||
self.pdialog.update_position(pos, duration)
|
||||
except:
|
||||
# print 'query failed'
|
||||
pass
|
||||
return True
|
||||
if self._query_id == -1:
|
||||
self._query_id = gobject.timeout_add(100, # 10 Hz
|
||||
do_query)
|
||||
|
||||
def _stop_queries(self):
|
||||
if self._query_id != -1:
|
||||
gobject.source_remove(self._query_id)
|
||||
self._query_id = -1
|
||||
|
||||
def _bus_watch(self, bus, message):
|
||||
if message.type == gst.MESSAGE_ERROR:
|
||||
print 'error', message
|
||||
self._stop_queries()
|
||||
m = gtk.MessageDialog(self.window,
|
||||
gtk.DIALOG_MODAL|gtk.DIALOG_DESTROY_WITH_PARENT,
|
||||
gtk.MESSAGE_ERROR,
|
||||
gtk.BUTTONS_CLOSE,
|
||||
"Error processing file")
|
||||
gerror, debug = message.parse_error()
|
||||
txt = ('There was an error processing your file: %s\n\n'
|
||||
'Debug information:\n%s' % (gerror, debug))
|
||||
m.format_secondary_text(txt)
|
||||
m.run()
|
||||
m.destroy()
|
||||
self.response(FAILURE)
|
||||
elif message.type == gst.MESSAGE_WARNING:
|
||||
print 'warning', message
|
||||
elif message.type == gst.MESSAGE_EOS:
|
||||
# print 'eos, woot', message.src
|
||||
name = self.touri
|
||||
if name.startswith('file://'):
|
||||
name = name[7:]
|
||||
self.pdialog.set_task('Finished writing %s' % name)
|
||||
self.pdialog.update_position(1,1)
|
||||
self._stop_queries()
|
||||
self.pdialog.set_completed(True)
|
||||
elif message.type == gst.MESSAGE_STATE_CHANGED:
|
||||
if message.src == self:
|
||||
old, new, pending = message.parse_state_changed()
|
||||
if ((old, new, pending) ==
|
||||
(gst.STATE_READY, gst.STATE_PAUSED,
|
||||
gst.STATE_VOID_PENDING)):
|
||||
self.pdialog.set_task('Processing file')
|
||||
self._start_queries()
|
||||
self.set_state(gst.STATE_PLAYING)
|
||||
|
||||
def response(self, response):
|
||||
assert self.resolution == UNKNOWN
|
||||
self.resolution = response
|
||||
self.set_state(gst.STATE_NULL)
|
||||
self.pdialog.destroy()
|
||||
self.pdialog = None
|
||||
self.window.set_sensitive(True)
|
||||
self.emit('done', response)
|
||||
|
||||
def start(self, main_window):
|
||||
self.window = main_window
|
||||
self.touri = self.do_get_touri()
|
||||
if not self.touri:
|
||||
return False
|
||||
self.do_setup_pipeline()
|
||||
bus = self.get_bus()
|
||||
bus.add_signal_watch()
|
||||
bus.connect('message', self._bus_watch)
|
||||
if self.window:
|
||||
# can be None if we are debugging...
|
||||
self.window.set_sensitive(False)
|
||||
fromname = self.fromuri.split('/')[-1]
|
||||
toname = self.touri.split('/')[-1]
|
||||
self.pdialog = RemuxProgressDialog(main_window, fromname, toname)
|
||||
self.pdialog.show()
|
||||
self.pdialog.connect('response', lambda w, r: self.response(r))
|
||||
|
||||
self.set_state(gst.STATE_PAUSED)
|
||||
return True
|
||||
|
||||
def run(self, main_window):
|
||||
if self.start(main_window):
|
||||
loop = gobject.MainLoop()
|
||||
self.connect('done', lambda *x: gobject.idle_add(loop.quit))
|
||||
loop.run()
|
||||
else:
|
||||
self.resolution = CANCELLED
|
||||
return self.resolution
|
||||
|
||||
class ResyncBin(gst.Bin):
|
||||
def __init__(self, sync_points, maxdiff):
|
||||
self.__gobject_init__()
|
||||
|
||||
self.parsefactories = self._find_parsers()
|
||||
self.parsers = []
|
||||
|
||||
self.demux = gst.element_factory_make('oggdemux')
|
||||
self.mux = gst.element_factory_make('oggmux')
|
||||
|
||||
self.add(self.demux, self.mux)
|
||||
|
||||
self.add_pad(gst.GhostPad('sink', self.demux.get_pad('sink')))
|
||||
self.add_pad(gst.GhostPad('src', self.mux.get_pad('src')))
|
||||
|
||||
self.demux.connect('pad-added', self._new_demuxed_pad)
|
||||
|
||||
self.sync_points = sync_points
|
||||
self.maxdiff = maxdiff
|
||||
|
||||
def _find_parsers(self):
|
||||
registry = gst.registry_get_default()
|
||||
ret = {}
|
||||
for f in registry.get_feature_list(gst.ElementFactory):
|
||||
if f.get_klass().find('Parser') >= 0:
|
||||
for t in f.get_static_pad_templates():
|
||||
if t.direction == gst.PAD_SINK:
|
||||
for s in t.get_caps():
|
||||
ret[s.get_name()] = f.get_name()
|
||||
break
|
||||
return ret
|
||||
|
||||
def _new_demuxed_pad(self, element, pad):
|
||||
format = pad.get_caps()[0].get_name()
|
||||
|
||||
if format not in self.parsefactories:
|
||||
self.async_error("Unsupported media type: %s", format)
|
||||
return
|
||||
|
||||
queue = gst.element_factory_make('queue', 'queue_' + format)
|
||||
queue.set_property('max-size-buffers', 0)
|
||||
queue.set_property('max-size-bytes', 0)
|
||||
print self.maxdiff
|
||||
queue.set_property('max-size-time', int(self.maxdiff * 1.5))
|
||||
parser = gst.element_factory_make(self.parsefactories[format])
|
||||
self.add(queue)
|
||||
self.add(parser)
|
||||
queue.set_state(gst.STATE_PAUSED)
|
||||
parser.set_state(gst.STATE_PAUSED)
|
||||
pad.link(queue.get_compatible_pad(pad))
|
||||
queue.link(parser)
|
||||
parser.link(self.mux)
|
||||
self.parsers.append(parser)
|
||||
|
||||
print repr(self.sync_points)
|
||||
|
||||
if 'video' in format:
|
||||
parser.set_property('synchronization-points',
|
||||
self.sync_points)
|
||||
|
||||
class PlayerWindow(gtk.Window):
|
||||
UPDATE_INTERVAL = 500
|
||||
def __init__(self):
|
||||
gtk.Window.__init__(self)
|
||||
self.set_default_size(600, 500)
|
||||
|
||||
self.create_ui()
|
||||
|
||||
self.player = GstPlayer(self.videowidget)
|
||||
|
||||
def on_eos():
|
||||
self.player.seek(0L)
|
||||
self.play_toggled()
|
||||
self.player.on_eos = lambda *x: on_eos()
|
||||
|
||||
self.update_id = -1
|
||||
self.changed_id = -1
|
||||
self.seek_timeout_id = -1
|
||||
|
||||
self.p_position = gst.CLOCK_TIME_NONE
|
||||
self.p_duration = gst.CLOCK_TIME_NONE
|
||||
|
||||
def on_delete_event():
|
||||
self.player.stop()
|
||||
gtk.main_quit()
|
||||
self.connect('delete-event', lambda *x: on_delete_event())
|
||||
|
||||
def load_file(self, location):
|
||||
filename = location.split('/')[-1]
|
||||
self.set_title('%s munger' % filename)
|
||||
self.player.set_location(location)
|
||||
if self.videowidget.flags() & gtk.REALIZED:
|
||||
self.play_toggled()
|
||||
else:
|
||||
self.videowidget.connect_after('realize',
|
||||
lambda *x: self.play_toggled())
|
||||
|
||||
def create_ui(self):
|
||||
vbox = gtk.VBox()
|
||||
vbox.show()
|
||||
self.add(vbox)
|
||||
|
||||
self.videowidget = VideoWidget()
|
||||
self.videowidget.show()
|
||||
vbox.pack_start(self.videowidget)
|
||||
|
||||
hbox = gtk.HBox()
|
||||
hbox.show()
|
||||
vbox.pack_start(hbox, fill=False, expand=False)
|
||||
|
||||
self.adjustment = gtk.Adjustment(0.0, 0.00, 100.0, 0.1, 1.0, 1.0)
|
||||
hscale = gtk.HScale(self.adjustment)
|
||||
hscale.set_digits(2)
|
||||
hscale.set_update_policy(gtk.UPDATE_CONTINUOUS)
|
||||
hscale.connect('button-press-event', self.scale_button_press_cb)
|
||||
hscale.connect('button-release-event', self.scale_button_release_cb)
|
||||
hscale.connect('format-value', self.scale_format_value_cb)
|
||||
hbox.pack_start(hscale)
|
||||
hscale.show()
|
||||
self.hscale = hscale
|
||||
|
||||
table = gtk.Table(3,3)
|
||||
table.show()
|
||||
vbox.pack_start(table, fill=False, expand=False, padding=6)
|
||||
|
||||
self.button = button = gtk.Button(stock=gtk.STOCK_MEDIA_PLAY)
|
||||
button.set_property('can-default', True)
|
||||
button.set_focus_on_click(False)
|
||||
button.show()
|
||||
|
||||
# problem: play and paused are of different widths and cause the
|
||||
# window to re-layout
|
||||
# "solution": add more buttons to a vbox so that the horizontal
|
||||
# width is enough
|
||||
bvbox = gtk.VBox()
|
||||
bvbox.add(button)
|
||||
bvbox.add(gtk.Button(stock=gtk.STOCK_MEDIA_PLAY))
|
||||
bvbox.add(gtk.Button(stock=gtk.STOCK_MEDIA_PAUSE))
|
||||
sizegroup = gtk.SizeGroup(gtk.SIZE_GROUP_HORIZONTAL)
|
||||
for kid in bvbox.get_children():
|
||||
sizegroup.add_widget(kid)
|
||||
bvbox.show()
|
||||
table.attach(bvbox, 0, 1, 1, 3, gtk.FILL, gtk.FILL)
|
||||
|
||||
# can't set this property before the button has a window
|
||||
button.set_property('has-default', True)
|
||||
button.connect('clicked', lambda *args: self.play_toggled())
|
||||
|
||||
self.sync = sync = SyncPoints(self)
|
||||
sync.show()
|
||||
table.attach(sync, 1, 2, 0, 3, gtk.EXPAND, gtk.EXPAND|gtk.FILL, 12)
|
||||
# nasty things to get sizes
|
||||
l = gtk.Label('\n\n\n')
|
||||
l.show()
|
||||
table.attach(l, 0, 1, 0, 1, 0, 0, 0)
|
||||
l = gtk.Label('\n\n\n')
|
||||
l.show()
|
||||
table.attach(l, 2, 3, 0, 1, 0, 0, 0)
|
||||
|
||||
button = gtk.Button("_Open other movie...")
|
||||
button.show()
|
||||
button.connect('clicked', lambda *x: self.do_choose_file())
|
||||
table.attach(button, 2, 3, 1, 2, gtk.FILL, gtk.FILL)
|
||||
|
||||
button = gtk.Button("_Write to disk")
|
||||
button.set_property('image',
|
||||
gtk.image_new_from_stock(gtk.STOCK_SAVE_AS,
|
||||
gtk.ICON_SIZE_BUTTON))
|
||||
button.connect('clicked', lambda *x: self.do_remux())
|
||||
button.show()
|
||||
table.attach(button, 2, 3, 2, 3, gtk.FILL, gtk.FILL)
|
||||
|
||||
def do_remux(self):
|
||||
if self.player.is_playing():
|
||||
self.play_toggled()
|
||||
in_uri = self.player.get_location()
|
||||
out_uri = in_uri[:-4] + '-remuxed.ogg'
|
||||
r = Resynchronizer(in_uri, out_uri, self.sync.get_sync_points())
|
||||
r.run(self)
|
||||
|
||||
def do_choose_file(self):
|
||||
if self.player.is_playing():
|
||||
self.play_toggled()
|
||||
chooser = gtk.FileChooserDialog('Choose a movie to bork bork bork',
|
||||
self,
|
||||
buttons=(gtk.STOCK_CANCEL,
|
||||
CANCELLED,
|
||||
gtk.STOCK_OPEN,
|
||||
SUCCESS))
|
||||
chooser.set_local_only(False)
|
||||
chooser.set_select_multiple(False)
|
||||
f = gtk.FileFilter()
|
||||
f.set_name("All files")
|
||||
f.add_pattern("*")
|
||||
chooser.add_filter(f)
|
||||
f = gtk.FileFilter()
|
||||
f.set_name("Ogg files")
|
||||
f.add_pattern("*.ogg") # as long as this is the only thing we
|
||||
# support...
|
||||
chooser.add_filter(f)
|
||||
chooser.set_filter(f)
|
||||
|
||||
prev = self.player.get_location()
|
||||
if prev:
|
||||
chooser.set_uri(prev)
|
||||
|
||||
resp = chooser.run()
|
||||
uri = chooser.get_uri()
|
||||
chooser.destroy()
|
||||
|
||||
if resp == SUCCESS:
|
||||
self.load_file(uri)
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def play_toggled(self):
|
||||
if self.player.is_playing():
|
||||
self.player.pause()
|
||||
self.button.set_label(gtk.STOCK_MEDIA_PLAY)
|
||||
else:
|
||||
self.player.play()
|
||||
if self.update_id == -1:
|
||||
self.update_id = gobject.timeout_add(self.UPDATE_INTERVAL,
|
||||
self.update_scale_cb)
|
||||
self.button.set_label(gtk.STOCK_MEDIA_PAUSE)
|
||||
|
||||
def scale_format_value_cb(self, scale, value):
|
||||
if self.p_duration == -1:
|
||||
real = 0
|
||||
else:
|
||||
real = value * self.p_duration / 100
|
||||
|
||||
seconds = real / gst.SECOND
|
||||
|
||||
return "%02d:%02d" % (seconds / 60, seconds % 60)
|
||||
|
||||
def scale_button_press_cb(self, widget, event):
|
||||
# see seek.c:start_seek
|
||||
gst.debug('starting seek')
|
||||
|
||||
self.button.set_sensitive(False)
|
||||
self.was_playing = self.player.is_playing()
|
||||
if self.was_playing:
|
||||
self.player.pause()
|
||||
|
||||
# don't timeout-update position during seek
|
||||
if self.update_id != -1:
|
||||
gobject.source_remove(self.update_id)
|
||||
self.update_id = -1
|
||||
|
||||
# make sure we get changed notifies
|
||||
if self.changed_id == -1:
|
||||
self.changed_id = self.hscale.connect('value-changed',
|
||||
self.scale_value_changed_cb)
|
||||
|
||||
def scale_value_changed_cb(self, scale):
|
||||
# see seek.c:seek_cb
|
||||
real = long(scale.get_value() * self.p_duration / 100) # in ns
|
||||
gst.debug('value changed, perform seek to %r' % real)
|
||||
self.player.seek(real)
|
||||
# allow for a preroll
|
||||
self.player.get_state(timeout=50*gst.MSECOND) # 50 ms
|
||||
|
||||
def scale_button_release_cb(self, widget, event):
|
||||
# see seek.cstop_seek
|
||||
widget.disconnect(self.changed_id)
|
||||
self.changed_id = -1
|
||||
|
||||
self.button.set_sensitive(True)
|
||||
if self.seek_timeout_id != -1:
|
||||
gobject.source_remove(self.seek_timeout_id)
|
||||
self.seek_timeout_id = -1
|
||||
else:
|
||||
gst.debug('released slider, setting back to playing')
|
||||
if self.was_playing:
|
||||
self.player.play()
|
||||
|
||||
if self.update_id != -1:
|
||||
self.error('Had a previous update timeout id')
|
||||
else:
|
||||
self.update_id = gobject.timeout_add(self.UPDATE_INTERVAL,
|
||||
self.update_scale_cb)
|
||||
|
||||
def update_scale_cb(self):
|
||||
had_duration = self.p_duration != gst.CLOCK_TIME_NONE
|
||||
self.p_position, self.p_duration = self.player.query_position()
|
||||
if self.p_position != gst.CLOCK_TIME_NONE:
|
||||
value = self.p_position * 100.0 / self.p_duration
|
||||
self.adjustment.set_value(value)
|
||||
return True
|
||||
|
||||
def main(args):
|
||||
def usage():
|
||||
sys.stderr.write("usage: %s [URI-OF-MEDIA-FILE]\n" % args[0])
|
||||
return 1
|
||||
|
||||
w = PlayerWindow()
|
||||
w.show()
|
||||
|
||||
if len(args) == 1:
|
||||
if not w.do_choose_file():
|
||||
return 1
|
||||
elif len(args) == 2:
|
||||
if not gst.uri_is_valid(args[1]):
|
||||
sys.stderr.write("Error: Invalid URI: %s\n" % args[1])
|
||||
return 1
|
||||
w.load_file(args[1])
|
||||
else:
|
||||
return usage()
|
||||
|
||||
gtk.main()
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main(sys.argv))
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- Mode: Python -*-
|
||||
# vi:si:et:sw=4:sts=4:ts=4
|
||||
|
||||
# gst-python
|
||||
# Copyright (C) 2009 Stefan Kost <ensonic@user.sf.net>
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU Library General Public
|
||||
# License as published by the Free Software Foundation; either
|
||||
# version 2 of the License, or (at your option) any later version.
|
||||
#
|
||||
# This library 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
|
||||
# Library General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Library General Public
|
||||
# License along with this library; if not, write to the
|
||||
# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
||||
# Boston, MA 02110-1301, USA.
|
||||
#
|
||||
|
||||
import sys
|
||||
|
||||
import gobject
|
||||
gobject.threads_init()
|
||||
|
||||
import pygst
|
||||
pygst.require('0.10')
|
||||
import gst
|
||||
|
||||
|
||||
mainloop = gobject.MainLoop()
|
||||
|
||||
def on_eos(bus, msg):
|
||||
mainloop.quit()
|
||||
|
||||
def main(args):
|
||||
"Tagsetter test, test result with:"
|
||||
"gst-launch -t playbin uri=file://$PWD/test.avi"
|
||||
|
||||
# create a new bin to hold the elements
|
||||
bin = gst.parse_launch('audiotestsrc num-buffers=100 ! ' +
|
||||
'lame ! ' +
|
||||
'avimux name=mux ! ' +
|
||||
'filesink location=test.avi')
|
||||
|
||||
mux = bin.get_by_name('mux')
|
||||
|
||||
bus = bin.get_bus()
|
||||
bus.add_signal_watch()
|
||||
bus.connect('message::eos', on_eos)
|
||||
|
||||
# prepare
|
||||
bin.set_state(gst.STATE_READY)
|
||||
|
||||
# send tags
|
||||
l = gst.TagList()
|
||||
l[gst.TAG_ARTIST] = "Unknown Genius"
|
||||
l[gst.TAG_TITLE] = "Unnamed Artwork"
|
||||
mux.merge_tags(l, gst.TAG_MERGE_APPEND)
|
||||
|
||||
# start playing
|
||||
bin.set_state(gst.STATE_PLAYING)
|
||||
|
||||
try:
|
||||
mainloop.run()
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
|
||||
# stop the bin
|
||||
bin.set_state(gst.STATE_NULL)
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main(sys.argv))
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- Mode: Python -*-
|
||||
# vi:si:et:sw=4:sts=4:ts=4
|
||||
|
||||
# videomixer-controller.py
|
||||
# (c) 2008 Stefan Kost <ensonic@users.sf.net>
|
||||
# Test case for the GstController using videomixer and videotestsrc
|
||||
|
||||
import pygst
|
||||
pygst.require('0.10')
|
||||
import gst
|
||||
import time
|
||||
|
||||
def main():
|
||||
pipeline = gst.Pipeline("videocontroller")
|
||||
src = gst.element_factory_make("videotestsrc", "src")
|
||||
mix = gst.element_factory_make("videomixer", "mix")
|
||||
conv = gst.element_factory_make("ffmpegcolorspace", "conv")
|
||||
sink = gst.element_factory_make("autovideosink", "sink")
|
||||
pipeline.add(src, mix, conv, sink)
|
||||
|
||||
spad = src.get_static_pad('src')
|
||||
dpad = mix.get_request_pad('sink_%d')
|
||||
|
||||
spad.link(dpad)
|
||||
mix.link(conv)
|
||||
conv.link(sink)
|
||||
|
||||
control = gst.Controller(dpad, "xpos", "ypos")
|
||||
control.set_interpolation_mode("xpos", gst.INTERPOLATE_LINEAR)
|
||||
control.set_interpolation_mode("ypos", gst.INTERPOLATE_LINEAR)
|
||||
|
||||
control.set("xpos", 0, 0)
|
||||
control.set("xpos", 5 * gst.SECOND, 200)
|
||||
|
||||
control.set("ypos", 0, 0)
|
||||
control.set("ypos", 5 * gst.SECOND, 200)
|
||||
|
||||
pipeline.set_state(gst.STATE_PLAYING)
|
||||
|
||||
time.sleep(7)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- Mode: Python -*-
|
||||
# vi:si:et:sw=4:sts=4:ts=4
|
||||
|
||||
# gst-python
|
||||
# Copyright (C) 2005 Andy Wingo <wingo@pobox.com>
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU Library General Public
|
||||
# License as published by the Free Software Foundation; either
|
||||
# version 2 of the License, or (at your option) any later version.
|
||||
#
|
||||
# This library 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
|
||||
# Library General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Library General Public
|
||||
# License along with this library; if not, write to the
|
||||
# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
||||
# Boston, MA 02110-1301, USA.
|
||||
|
||||
|
||||
# A test more of gst-plugins than of gst-python.
|
||||
|
||||
|
||||
import pygtk
|
||||
pygtk.require('2.0')
|
||||
import gtk
|
||||
import gobject
|
||||
|
||||
import pygst
|
||||
pygst.require('0.10')
|
||||
import gst
|
||||
|
||||
import fvumeter
|
||||
|
||||
|
||||
def clamp(x, min, max):
|
||||
if x < min:
|
||||
return min
|
||||
elif x > max:
|
||||
return max
|
||||
return x
|
||||
|
||||
|
||||
class Window(gtk.Dialog):
|
||||
def __init__(self):
|
||||
gtk.Dialog.__init__(self, 'Volume Level')
|
||||
self.prepare_ui()
|
||||
|
||||
def prepare_ui(self):
|
||||
self.set_default_size(200,60)
|
||||
self.set_title('Volume Level')
|
||||
self.connect('delete-event', lambda *x: gtk.main_quit())
|
||||
self.vus = []
|
||||
self.vus.append(fvumeter.FVUMeter())
|
||||
self.vus.append(fvumeter.FVUMeter())
|
||||
self.vbox.add(self.vus[0])
|
||||
self.vbox.add(self.vus[1])
|
||||
self.vus[0].show()
|
||||
self.vus[1].show()
|
||||
|
||||
def error(self, message, secondary=None):
|
||||
m = gtk.MessageDialog(self,
|
||||
gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
|
||||
gtk.MESSAGE_ERROR,
|
||||
gtk.BUTTONS_OK,
|
||||
message)
|
||||
if secondary:
|
||||
m.format_secondary_text(secondary)
|
||||
m.run()
|
||||
|
||||
def on_message(self, bus, message):
|
||||
if message.structure.get_name() == 'level':
|
||||
s = message.structure
|
||||
for i in range(0, len(s['peak'])):
|
||||
self.vus[i].freeze_notify()
|
||||
decay = clamp(s['decay'][i], -90.0, 0.0)
|
||||
peak = clamp(s['peak'][i], -90.0, 0.0)
|
||||
if peak > decay:
|
||||
print "ERROR: peak bigger than decay!"
|
||||
|
||||
self.vus[i].set_property('decay', decay)
|
||||
self.vus[i].set_property('peak', peak)
|
||||
return True
|
||||
|
||||
def run(self):
|
||||
try:
|
||||
self.set_sensitive(False)
|
||||
s = 'alsasrc ! level message=true ! fakesink'
|
||||
pipeline = gst.parse_launch(s)
|
||||
self.set_sensitive(True)
|
||||
pipeline.get_bus().add_signal_watch()
|
||||
i = pipeline.get_bus().connect('message::element', self.on_message)
|
||||
pipeline.set_state(gst.STATE_PLAYING)
|
||||
gtk.Dialog.run(self)
|
||||
pipeline.get_bus().disconnect(i)
|
||||
pipeline.get_bus().remove_signal_watch()
|
||||
pipeline.set_state(gst.STATE_NULL)
|
||||
except gobject.GError, e:
|
||||
self.set_sensitive(True)
|
||||
self.error('Could not create pipeline', e.__str__)
|
||||
|
||||
if __name__ == '__main__':
|
||||
w = Window()
|
||||
w.show_all()
|
||||
w.run()
|
||||
Reference in New Issue
Block a user