diff --git a/.gitignore b/.gitignore index 69c4a8f9..530928db 100644 --- a/.gitignore +++ b/.gitignore @@ -66,13 +66,7 @@ doc/reference/html/ # Ignore external command flumotion/extern/command.revision -flumotion/extern/command/ChangeLog -flumotion/extern/command/README -flumotion/extern/command/__init__.py -flumotion/extern/command/command.py -flumotion/extern/command/help2man.py -flumotion/extern/command/manholecmd.py -flumotion/extern/command/test_command.py +flumotion/extern/command/* # Misc ignores data/flumotion-admin.desktop diff --git a/.gitmodules b/.gitmodules index eaef5b1c..f4960781 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ [submodule "common"] path = common - url = git://code.flumotion.com/flumotion-common.git + url = https://github.com/Flumotion/flumotion-common.git diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..6a644a9d --- /dev/null +++ b/.travis.yml @@ -0,0 +1,33 @@ +language: python +python: + - "2.7" +virtualenv: + system_site_packages: true +# installing missing packages +before_install: + - sudo add-apt-repository ppa:gstreamer-developers/ppa -y + - sudo apt-get update -qq + - "export DISPLAY=:99.0" + - "sh -e /etc/init.d/xvfb start" +install: + - sudo apt-get install -qq subversion autopoint python-gst0.10 gstreamer0.10* python-gi python3-gi python-gobject-dev gstreamer1.0* gir1.2-gstreamer-1.0 gir1.2-gst-plugins-base-1.0 libglib2.0-dev gir1.2-glib-2.0 libgirepository1.0-dev libglib2.0-0 gir1.2-gtk-3 libxml-parser-perl python-twisted python-gtk2 python-glade2 python-kiwi pkg-config libglib2.0-dev liborc-0.4-dev bison flex + - sudo pip install icalendar==2.2 pyparsing python-dateutil + - wget https://launchpadlibrarian.net/170386464/gst-python1.0_1.2.0.orig.tar.gz + - tar xvzf gst-python1.0_1.2.0.orig.tar.gz + - cd gst-python-1.2.0/ + - ./configure + - make + - sudo make install + - cd .. +# command to run tests +script: + - ./autogen.sh + - make + - ./run_tests.sh +# specified which branches to test +branches: + only: + - porting-to-gst1.0 +# parallelizing the builds +env: + - TEST_TYPE=test diff --git a/README b/README index 9aed0942..e3abad47 100644 --- a/README +++ b/README @@ -1,129 +1,122 @@ -Flumotion - your streaming media server +Flumotion - Your streaming media server +======================================= -WHAT IT IS ----------- +[![Build Status](https://travis-ci.org/aps-sids/flumotion-orig.svg?branch=porting-to-gst1.0)](https://travis-ci.org/aps-sids/flumotion-orig) Flumotion is a streaming media server based on GStreamer and Twisted. -This is the first public development series of Flumotion. -It will probably still contain bugs, be difficult to install (since -you need recent dependencies), and cause you some headaches. +This is the first public development series of **Flumotion ported to Gstreamer 1.x API**. +It is still a **work in progress** and will probably still contain bugs, be difficult to install (since some parts still need to be ported), and cause you some headaches. On the other hand, when it works and you are capturing your Firewire camera on one machine, encoding to Theora on a second (with an overlay, of course), encoding the audio to Vorbis on a third, muxing to Ogg on a fourth, and then streaming both audio and video from a fifth, audio only from a sixth, video only from a seventh, and capturing all three streams to disk from -an eigth, you feel very good about yourself. +an eighth, you feel very good about yourself. And you also have too many computers. -REQUIREMENTS +Requirements ------------ -You need: +* Gstreamer 1.2.4 or higher +* Gstreamer plugins - good, bad and ugly +* Python bindings GObject Introspection Libraries +* Twisted 13 or higher +* and some other python packages -Gstreamer: you need GStreamer version 0.10.10 or higher. 0.10.11 or higher is -strongly recommended. +**Note:** You might have to use Gstreamer Developers PPA to get the latest packages -For 0.10.x: - GStreamer: 0.10.10 or higher - GStreamer plugins base 0.10.10 or higher - GStreamer plugins good 0.10.1 or higher - GStreamer python bindings 0.10.1 or higher +To install system requirements on Ubuntu, run follow commands: -PyGTK - 2.8.3 or higher -Python - 2.4 or higher -Twisted - 2.5.0 or higher -Kiwi - 1.9.13 or higher -PyCairo- 1.2.0 or higher +```bash +$ sudo add-apt-repository ppa:gstreamer-developers/ppa +$ sudo apt-get update +$ sudo apt-get install libtool autopoint autoconf libxml-parser-perl python-gi python-gobject-dev python-gst-1.0 gir1.2-gstreamer-1.0 gir1.2-gst-plugins-base-1.0 libglib2.0-dev gir1.2-glib-2.0 libgirepository1.0-dev libglib2.0-0 gir1.2-gtk-3 pkg-config libglib2.0-dev liborc-0.4-dev python-twisted python-pyparsing python-kiwi python-dateutil + +# we can't use latest python-icalendar till a few things are fixed + +$ sudo pip install icalendar==2.2 +``` And depending on what codecs you want to use: -libvorbis 1.0.1 or higher -libogg 1.1 or higher -libtheora 1.0alpha3 or higher +* libvorbis 1.0.1 or higher +* libogg 1.1 or higher +* libtheora 1.0alpha3 or higher And if you want to build documentation: -epydoc +* epydoc And if you want support for java applets: -cortado +* cortado -ISSUES ------- +Quick Start +----------- -Some issues have been brought to our attention. Please take note if you -run into them: -- locales with a comma as a decimal point trigger problems in the XML parsing. - Run with LANG=C if you run into this -- When encoding to Vorbis, prefer to use quality instead of absolute bitrate. - For absolute bitrate, for each sample rate a limited range of bitrates is - allowed. -- You may see memory leaks if you use the soundcard or firewire source - components, and you use PyGTK prior to 0.8.5, or gst-python prior to 0.10.3, - so upgrading to these versions is strongly recommended. - -A FIRST TEST ------------- +```bash +# Build source +$ ./autogen.sh +$ make -Once everything is built and installed, -you can try this to start the server: +# To run the tests +$ ./run_tests.sh -terminal 1: - flumotion-manager -v -T tcp conf/managers/default/planet.xml +# in terminal 1 +$ ./env bash +$ flumotion-manager -v -T tcp test_config.xml -terminal 2: - flumotion-worker -v -T tcp -u user -p test +# in terminal 2 +$ ./env bash +$ flumotion-worker -v -T tcp -u user -p test -terminal 3: - flumotion-admin - Unselect 'Secure connection via SSL', press Next, then enter "user" and - "test" in the dialog. +# in terminal 3 +$ gst-launch playbin2 uri=http://localhost:8800/ogg-audio-video/ +``` - Go through the wizard, chosing defaults. +Some systems may not have `gst-launch`, but only `gst-launch-0.10` or `gst-launch-1.0`. In that case you can substitute either of those commands. - Double-click the http component after the wizard has completed +Alternatively, you can use any theora-enabled network player or web browser to see the stream. -terminal 4: - gst-launch playbin2 uri=http://localhost:8800/ogg-audio-video/ +```bash +# in terminal 4 +$ for a in `seq 1 9`; do ( flumotion-tester http://localhost:8800/ogg-audio-video/ & ); done +``` - Some systems may not have gst-launch, but only gst-launch-0.8 or - gst-launch-0.10, in that case you can substitute either of those commands. +This will throw 9 processes with 100 clients each at the server. Hopefully, all of them will return success at the end of their run! - Alternatively, you can use any theora-enabled network player to see the - stream. +We use 900 clients rather than a nice round number such as 1000 because, with a standard unix system, you'll usually be limited to slightly under 1000 clients by default due to limits on open file descriptors (each client requires a file descriptor). This limit is changeable, but it's simpler to just test with slightly fewer clients. -terminal 5: - for a in `seq 1 9`; do ( flumotion-tester http://localhost:8800/ogg-audio-video/ & ); done +Issues +------ - This will throw 9 processes with 100 clients each at the server. Hopefully, - all of them will return success at the end of their run! +Take a look at the Github issues. Please create any new issues you encounter. - We use 900 clients rather than a nice round number such as 1000 because, with - a standard unix system, you'll usually be limited to slightly under 1000 - clients by default due to limits on open file descriptors (each client - requires a file descriptor). This limit is changable, but it's simpler to just - test with slightly fewer clients. +Hacking +------- -DOCUMENTATION -------------- +The user manual is available [here](http://www.flumotion.net/doc/flumotion/manual/en/trunk/html/), but it's probably outdated since currently there is no admin GUI to work with. Still, it might be helpful if you are new to Flumotion. -A manual is being written. You can access the current work in progress at -http://www.flumotion.net/doc/flumotion/manual/en/trunk/html/ +The developer documentation is available [here](https://code.flumotion.com/trac/wiki/Documentation/DeveloperIntroduction). More information will be added as the work progresses. -Flumotion also comes with API documentation. You need epydoc to build them. +Contributing +------------ -HACKING -------- +The project had been quite dead for a while. This port was done as a [GSoC 2014](https://www.google-melange.com/gsoc/homepage/google/gsoc2014) project under [TimVideos](http://code.timvideos.us/). + +You can subscribe to the official development list. Information is at +http://lists.fluendo.com/mailman/listinfo/flumotion-devel -You can use jhbuild with the flu.modules and jhbuildrc file provided in misc/ +You can visit us on IRC: [#fluendo](irc://irc.freenode.net/#fluendo) and [#timvideos](irc://irc.freenode.net/#timvideos) on irc.freenode.org -This will set up everything except for Twisted for you from source. +You can visit our trac installation for the Wiki, source browsing and +ticket tracking: +https://core.fluendo.com/flumotion/trac -SECURITY +Security -------- Read the security chapter in the aforementioned manual to get started. @@ -142,20 +135,8 @@ sure you change the configuration before moving it into production. Remove the host entry from the sample planet.xml file to allow other hosts to connect. -ABOVE AND BEYOND ----------------- - -You can subscribe to our development list. Information is at -http://lists.fluendo.com/mailman/listinfo/flumotion-devel - -You can visit us on IRC: #fluendo on irc.freenode.org - -You can visit our trac installation for the Wiki, source browsing and -ticket tracking: -https://core.fluendo.com/flumotion/trac - -LICENSING ---------- +Licence +------- This version of the Flumotion Streaming Server is licensed under the the terms of the GNU Lesser General Public License version 2.1 as published @@ -163,11 +144,3 @@ by the Free Software Foundation. See LICENSE.LGPL If the conditions of this licensing strategy are not clear to you, please contact Thomas Vander Stichele (thomas@fluendo.com). - -TESTING -------- -For testing purposes of your streams, you can run some GStreamer pipelines -to verify if things work as they should. - -- multipart/jpeg stream (video only): - gst-launch -v gnomevfssrc location=http://localhost:8802 ! multipartdemux ! jpegdec ! xvimagesink diff --git a/README.md b/README.md new file mode 100644 index 00000000..4128d389 --- /dev/null +++ b/README.md @@ -0,0 +1,146 @@ +Flumotion - Your streaming media server +======================================= + +[![Build Status](https://travis-ci.org/aps-sids/flumotion-orig.svg?branch=porting-to-gst1.0)](https://travis-ci.org/aps-sids/flumotion-orig) + +Flumotion is a streaming media server based on GStreamer and Twisted. + +This is the first public development series of **Flumotion ported to Gstreamer 1.x API**. +It is still a **work in progress** and will probably still contain bugs, be difficult to install (since some parts still need to be ported), and cause you some headaches. + +On the other hand, when it works and you are capturing your Firewire camera +on one machine, encoding to Theora on a second (with an overlay, of course), +encoding the audio to Vorbis on a third, muxing to Ogg on a fourth, and then +streaming both audio and video from a fifth, audio only from a sixth, +video only from a seventh, and capturing all three streams to disk from +an eighth, you feel very good about yourself. + +And you also have too many computers. + +Requirements +------------ + +* Gstreamer 1.2.4 or higher +* Gstreamer plugins - good, bad and ugly +* Python bindings GObject Introspection Libraries +* Twisted 13 or higher +* and some other python packages + +**Note:** You might have to use Gstreamer Developers PPA to get the latest packages + +To install system requirements on Ubuntu, run follow commands: + +```bash +$ sudo add-apt-repository ppa:gstreamer-developers/ppa +$ sudo apt-get update +$ sudo apt-get install subversion libtool autopoint autoconf libxml-parser-perl python-gi python-gobject-dev python-gst-1.0 gir1.2-gstreamer-1.0 gir1.2-gst-plugins-base-1.0 libglib2.0-dev gir1.2-glib-2.0 libgirepository1.0-dev libglib2.0-0 gir1.2-gtk-3 pkg-config libglib2.0-dev liborc-0.4-dev python-twisted python-pyparsing python-kiwi python-dateutil + +# we can't use latest python-icalendar till a few things are fixed + +$ sudo pip install icalendar==2.2 +``` + +And depending on what codecs you want to use: + +* libvorbis 1.0.1 or higher +* libogg 1.1 or higher +* libtheora 1.0alpha3 or higher + +And if you want to build documentation: + +* epydoc + +And if you want support for java applets: + +* cortado + +Quick Start +----------- + +```bash +# Build source +$ ./autogen.sh +$ make + +# To run the tests +$ ./run_tests.sh + +# in terminal 1 +$ ./env bash +$ flumotion-manager -v -T tcp test_config.xml + +# in terminal 2 +$ ./env bash +$ flumotion-worker -v -T tcp -u user -p test + +# in terminal 3 +$ gst-launch playbin2 uri=http://localhost:8800/ogg-audio-video/ +``` + +Some systems may not have `gst-launch`, but only `gst-launch-0.10` or `gst-launch-1.0`. In that case you can substitute either of those commands. + +Alternatively, you can use any theora-enabled network player or web browser to see the stream. + +```bash +# in terminal 4 +$ for a in `seq 1 9`; do ( flumotion-tester http://localhost:8800/ogg-audio-video/ & ); done +``` + +This will throw 9 processes with 100 clients each at the server. Hopefully, all of them will return success at the end of their run! + +We use 900 clients rather than a nice round number such as 1000 because, with a standard unix system, you'll usually be limited to slightly under 1000 clients by default due to limits on open file descriptors (each client requires a file descriptor). This limit is changeable, but it's simpler to just test with slightly fewer clients. + +Issues +------ + +Take a look at the Github issues. Please create any new issues you encounter. + +Hacking +------- + +The user manual is available [here](http://www.flumotion.net/doc/flumotion/manual/en/trunk/html/), but it's probably outdated since currently there is no admin GUI to work with. Still, it might be helpful if you are new to Flumotion. + +The developer documentation is available [here](https://code.flumotion.com/trac/wiki/Documentation/DeveloperIntroduction). More information will be added as the work progresses. + +Contributing +------------ + +The project had been quite dead for a while. This port was done as a [GSoC 2014](https://www.google-melange.com/gsoc/homepage/google/gsoc2014) project under [TimVideos](http://code.timvideos.us/). + +You can subscribe to the official development list. Information is at +http://lists.fluendo.com/mailman/listinfo/flumotion-devel + +You can visit us on IRC: [#fluendo](irc://irc.freenode.net/#fluendo) and [#timvideos](irc://irc.freenode.net/#timvideos) on irc.freenode.org + +You can visit our trac installation for the Wiki, source browsing and +ticket tracking: +https://core.fluendo.com/flumotion/trac + +Security +-------- + +Read the security chapter in the aforementioned manual to get started. + +Flumotion uses SSL transport by default. For this you need a PEM certificate +file. For security reasons you should generate this yourself when installing +from source. + +The sample configuration file for the manager contains some htpasswd-style +crypted credentials, for a user named "user" and a password "test". Use +these only for testing; make sure you change them before putting Flumotion +into production. + +The sample configuration also only allows localhost connections, to make +sure you change the configuration before moving it into production. +Remove the host entry from the sample planet.xml file to allow other hosts +to connect. + +Licence +------- + +This version of the Flumotion Streaming Server is licensed under the the +terms of the GNU Lesser General Public License version 2.1 as published +by the Free Software Foundation. See LICENSE.LGPL + +If the conditions of this licensing strategy are not clear to you, please +contact Thomas Vander Stichele (thomas@fluendo.com). diff --git a/common b/common index c852bca1..19d17592 160000 --- a/common +++ b/common @@ -1 +1 @@ -Subproject commit c852bca17320de824f7c3b110011714505954a02 +Subproject commit 19d175927fe86e9383dbbfc37c11f98dd44b0615 diff --git a/configure.ac b/configure.ac index 37be3ca2..ecdc2d43 100644 --- a/configure.ac +++ b/configure.ac @@ -181,6 +181,7 @@ flumotion/component/consumers/fgdp/Makefile flumotion/component/consumers/icystreamer/Makefile flumotion/component/consumers/httpstreamer/Makefile flumotion/component/consumers/hlsstreamer/Makefile +flumotion/component/consumers/justintv/Makefile flumotion/component/consumers/pipeline/Makefile flumotion/component/consumers/preview/Makefile flumotion/component/consumers/shout2/Makefile diff --git a/doc/random/developer-introduction.rst b/doc/random/developer-introduction.rst index 13d2de49..c9f41e9c 100644 --- a/doc/random/developer-introduction.rst +++ b/doc/random/developer-introduction.rst @@ -1,12 +1,12 @@ .. contents:: Table of Contents -.. _Open a new Ticket: https://code.flumotion.com/trac/newticket +.. _Open a new Ticket: https://code.flumotion.com/trac/newticket .. _Wiki: https://code.flumotion.com/trac/wiki -.. _Code Browser: https://code.flumotion.com/trac/browser +.. _Code Browser: https://code.flumotion.com/trac/browser .. _Timeline: https://code.flumotion.com/trac/timeline .. _Style guide: https://code.flumotion.com/trac/browser/flumotion/doc/random/styleguide -.. _Existing tickets: https://code.flumotion.com/trac/report +.. _Existing tickets: https://code.flumotion.com/trac/report .. _Buildbot: http://build.fluendo.com:8070/ .. _Trial: http://twistedmatrix.com/trac/wiki/TwistedTrial .. _Twisted: http://twistedmatrix.com/ @@ -35,8 +35,7 @@ contribute to the Flumotion project. Getting started =============== -This section describes how you'll get started as a developer. It means fetching the sources, -building, and running. +This section describes how you'll get started as a developer. It means fetching the sources, building, and running. Getting your git environment configured --------------------------------------- @@ -74,9 +73,17 @@ You should also configure the following options and aliases to help you in the d Getting your development environment installed ---------------------------------------------- -Once you have gstreamer installed on an uninstalled directory, you need to install flumotion the -same way. This time though, you get the code from git directly as this is the most -up to date code. So, let's start. create a folder and check it out:: +Once you have gstreamer installed on an uninstalled directory, you need to +install flumotion the same way. This time though, you get the code from git +directly as this is the most up to date code. So, let's start. Create a folder +and check it out. + +If you don't have a git account with us:: + + git clone git://code.flumotion.com/flumotion.git + + +If you do have a git account with us:: git clone git@code.flumotion.com:flumotion.git @@ -84,7 +91,8 @@ First the build environment needs to be prepared:: ./autogen.sh -Autogen might fail if you miss some dependencies. Normally you need the following: +Autogen might fail if you miss some dependencies. Normally you need the +following: - C compiler - make @@ -103,8 +111,9 @@ When the autogen script runs, you're almost ready, you just need to type:: make -This will do a bunch of stuff, one of them is creating a script called **env** that -is a small shell script which prepares the environment to run flumotion properly. +This will do a bunch of stuff, one of them is creating a script called **env** +that is a small shell script which prepares the environment to run flumotion +properly. So, once make is finished, type:: @@ -608,6 +617,25 @@ to run cannot be run, you need to specify the reactor, for instance:: trial -r gtk2 flumotion.test.test_component_disker +Unit test coverage +------------------ + +You can get coverage data for the unit tests:: + + make coverage + +This will give you a breakdown per module, showing coverage percentage as well +as lines covered. At the bottom, you'll see the total coverage. + + +You can also see which lines of each file get covered. +Under _trial_coverage/coverage, one file is generated per module. + +For example: + less _trial_coverage/coverage/flumotion.common.formatting.cover + +Lines marked with >>>>>>>> are not covered by the tests. + Debugging --------- @@ -657,3 +685,40 @@ From here you can access the vishnu in the manager; for example:: Deferred #0 called back: ([7700, 7701, 7702, 7703, 7704, 7705, 7706, 7707, 7708], None) >>> + +Additional topics +================= + +Admin UI's +---------- + +Flumotion uses a view architecture to display the UI for components. +When you click on a component in flumotion-admin, it loads bundles for +the component's GTK admin view on the fly and loads it. + +As a consequence, you can make changes to the code and see the result of +those changes immediately without restarting Flumotion. + +As an example, set up a flow that includes the videotest-producer, and click +on its tab. + +Open up flumotion/component/producers/videotest/admin_gtk.py and change the +label called 'Pattern' to something else. Click again on the component +to reload the UI and note that its label has changed. + + +Internationalization +-------------------- + +Flumotion handles translations lazily, translating messages generated on +managers and workers only when displaying them in admin clients. + +See flumotion.common.i18n for where this is defined, and +flumotion.component.producers.videotest.admin_gtk for an example use +(look for the example messages). + +To test translations, run the admin in a different locale; for example:: + + LANG=nl_NL flumotion-admin + +See `Updating translation`_ for more information. diff --git a/doc/random/styleguide b/doc/random/styleguide index ff50a9ed..2fd42d9a 100644 --- a/doc/random/styleguide +++ b/doc/random/styleguide @@ -18,10 +18,24 @@ Other specific code guidelines: [1]: http://www.python.org/peps/pep-0008.html -ChangeLog ---------- -All checkins should add an appropriate entry in the ChangeLog. -Exceptions for repository ignore files and metadata. +IMPORT +------ + +In Flumotion code, don't import objects directly; import the module name. +This makes it easier to see which names are local to the current module, +and which come from another module. This matches Twisted's style. + +For example, use + + from flumotion.component import component + + c = component.BaseComponent(...) + +Instead of doing + + from flumotion.component.component import BaseComponent + + c = BaseComponent(...) UI -- diff --git a/flumotion/__init__.py b/flumotion/__init__.py index b8d7ec43..0b977e97 100644 --- a/flumotion/__init__.py +++ b/flumotion/__init__.py @@ -14,3 +14,7 @@ # See "LICENSE.LGPL" in the source distribution for more information. # # Headers in this file shall remain intact. + +from gi.repository import Gst + +Gst.CLOCK_TIME_NONE = 18446744073709551615L \ No newline at end of file diff --git a/flumotion/admin/command/component.py b/flumotion/admin/command/component.py index 203ea7ff..3150f4f0 100644 --- a/flumotion/admin/command/component.py +++ b/flumotion/admin/command/component.py @@ -18,13 +18,13 @@ """ component commands """ +from twisted.internet import defer +from flumotion.admin.command import common from flumotion.common import errors, planet, log from flumotion.monitor.nagios import util from flumotion.common.planet import moods -from flumotion.admin.command import common - __version__ = "$Rev: 6562 $" @@ -197,6 +197,53 @@ def doCallback(self, args): self.parentCommand.print_components(comps, workers) +class DownstreamList(common.AdminCommand): + description = """List a component and its downstream components along +with types and worker hosts.""" + + components = [] + + def doCallback(self, args): + p = self.parentCommand + s = p.workerHeavenState + workers = s.get('workers') + + if not p.componentId: + common.errorRaise("Please specify a component id " + "with 'component -i [component-id]'") + + self.stdout.write('Downstream Components:\n') + + d = defer.maybeDeferred(self.getUIState, p.componentState) + d.addCallback(self.parentCommand.print_components, workers) + return d + + def getUIState(self, state): + p = self.parentCommand + admin = p.parentCommand.medium + + self.components.append(state) + d = admin.componentCallRemote(state, 'getUIState') + d.addCallback(self.gotUIState) + return d + + def gotUIState(self, state): + p = self.parentCommand + + dList = [] + for f in state.get('feeders'): + for c in f['clients']: + feeder_id = c['client-id'].split(':')[0] + compState = util.findComponent(p.planetState, feeder_id) + if compState is None or compState in self.components: + continue + d = defer.maybeDeferred(self.getUIState, compState) + dList.append(d) + d = defer.DeferredList(dList) + d.addCallback(lambda result: self.components) + return d + + class Mood(common.AdminCommand): description = "Check the mood of a component." @@ -364,8 +411,8 @@ class Component(util.LogCommand): description = "Act on a component." usage = "-i [component id]" - subCommandClasses = [Delete, Invoke, List, DetailedList, - UpstreamList, Mood, Property, Start, Stop] + subCommandClasses = [Delete, Invoke, List, DetailedList, UpstreamList, + DownstreamList, Mood, Property, Start, Stop] componentId = None componentState = None @@ -376,9 +423,13 @@ def addOptions(self): self.parser.add_option('-i', '--component-id', action="store", dest="componentId", help="component id of the component") + self.parser.add_option('-e', '--extra-attrs', + action="store", dest="extraAttributes", default="", + help="comma-separated extra attributes for the component list") def handleOptions(self, options): self.componentId = options.componentId + self.extraAttributes = options.extraAttributes.split(",") # call our callback after connecting self.getRootCommand().loginDeferred.addCallback(self._callback) @@ -438,5 +489,9 @@ def print_components(self, components, workers): host = w.get('host') break moodName = planet.moods.get(c.get('mood')).name - comps.append((c.get('name'), c.get('type'), host, moodName)) + componentAttrs = [c.get('name'), c.get('type'), host, moodName] + for attr in self.extraAttributes: + if c.hasKey(attr): + componentAttrs.append(str(c.get(attr))) + comps.append(componentAttrs) self.pprint(comps) diff --git a/flumotion/common/boot.py b/flumotion/common/boot.py index be6a1e33..75eb2bd6 100644 --- a/flumotion/common/boot.py +++ b/flumotion/common/boot.py @@ -26,8 +26,7 @@ # Keep in sync with configure.ac PYGTK_REQ = (2, 10, 0) KIWI_REQ = (1, 9, 13) -GST_REQ = {'0.10': {'gstreamer': (0, 10, 10), - 'gst-python': (0, 10, 4)}} +GST_REQ = {'1.0': {'gstreamer': (1, 2, 0)}} USE_GOPTION_PARSER = False USE_GTK = False USE_GST = True @@ -39,18 +38,14 @@ def init_gobject(): SystemExit exception to be raised. """ try: - import pygtk - pygtk.require('2.0') + import gi + gi.require_version('Gtk', '3.0') - import gobject + from gi.repository import GObject except ImportError: raise SystemExit('ERROR: PyGTK could not be found') - if gobject.pygtk_version < PYGTK_REQ: - raise SystemExit('ERROR: PyGTK %s or higher is required' - % '.'.join(map(str, PYGTK_REQ))) - - gobject.threads_init() + GObject.threads_init() def _init_gst_version(gst_majorminor): @@ -62,25 +57,20 @@ def tup2version(tup): raise SystemExit('ERROR: Invalid FLU_GST_VERSION: %r (expected ' 'one of %r)' % (gst_majorminor, GST_REQ.keys())) - pygst_req = GST_REQ[gst_majorminor]['gst-python'] gst_req = GST_REQ[gst_majorminor]['gstreamer'] try: - import pygst - pygst.require(gst_majorminor) - import gst + from gi.repository import Gst except ImportError: return False except AssertionError: return False try: - gst_version = gst.get_gst_version() - pygst_version = gst.get_pygst_version() + gst_version = Gst.version() except AttributeError: # get_foo_version() added in 0.10.4, fall back - gst_version = gst.gst_version - pygst_version = gst.pygst_version + gst_version = Gst.gst_version if gst_req[:2] != gst_version[:2]: raise SystemExit( @@ -92,20 +82,15 @@ def tup2version(tup): 'ERROR: GStreamer %s too old; install %s or newer' % (tup2version(gst_version), tup2version(gst_req))) - if pygst_version < pygst_req: - raise SystemExit( - 'ERROR: gst-python %s too old; install %s or newer' - % (tup2version(pygst_version), tup2version(pygst_req))) - return True def init_gst(): """ - Initialize pygst. A missing or too-old pygst will cause a + Initialize gst. A missing or too-old gst will cause a SystemExit exception to be raised. """ - assert 'gobject' in sys.modules, "Run init_gobject() first" + assert 'gi.repository.GObject' in sys.modules, "Run init_gobject() first" gst_majorminor = os.getenv('FLU_GST_VERSION') @@ -129,7 +114,7 @@ def init_gst(): def init_kiwi(): - import gobject + from gi.repository import GObject try: from kiwi.__version__ import version as kiwi_version @@ -139,13 +124,12 @@ def init_kiwi(): if kiwi_version < KIWI_REQ: raise SystemExit('ERROR: Kiwi %s or higher is required' % '.'.join(map(str, KIWI_REQ))) - elif gobject.pygobject_version > (2, 26, 0): + elif GObject.pygobject_version > (2, 26, 0): # Kiwi is not compatible yet with the changes introduced in # http://git.gnome.org/browse/pygobject/commit/?id=84d614 # Basically, what we do is to revert the changes in _type_register of # GObjectMeta at least until kiwi works properly with new pygobject - from gobject._gobject import type_register - + def _type_register(cls, namespace): ## don't register the class if already registered if '__gtype__' in namespace: @@ -161,9 +145,9 @@ def _type_register(cls, namespace): if cls.__module__.startswith('gi.overrides.'): return - type_register(cls, namespace.get('__gtype_name__')) + GObject.type_register(cls, namespace.get('__gtype_name__')) - gobject.GObjectMeta._type_register = _type_register + GObject.GObjectMeta._type_register = _type_register return True @@ -178,8 +162,8 @@ def init_option_parser(gtk, gst): if not gtk and not gst: USE_GOPTION_PARSER = False else: - import gobject - if getattr(gobject, 'pygobject_version', ()) >= (2, 15, 0): + from gi.repository import GObject + if getattr(GObject, 'pygobject_version', ()) >= (2, 15, 0): USE_GOPTION_PARSER = True else: USE_GOPTION_PARSER = False @@ -253,6 +237,7 @@ def boot(path, gtk=False, gst=True, installReactor=True): if gst: from flumotion.configure import configure configure.gst_version = init_gst() + from gi.repository import GstNet global USE_GTK, USE_GST USE_GTK=gtk @@ -261,9 +246,9 @@ def boot(path, gtk=False, gst=True, installReactor=True): # installing the reactor could override our packager's import hooks ... if installReactor: - from twisted.internet import gtk2reactor + from twisted.internet import gtk3reactor try: - gtk2reactor.install(useGtk=gtk) + gtk3reactor.install() except RuntimeError, e: safeprintf(sys.stderr, 'ERROR: %s\n', e) sys.exit(1) diff --git a/flumotion/common/gstreamer.py b/flumotion/common/gstreamer.py index 9185afa6..4c0ae754 100644 --- a/flumotion/common/gstreamer.py +++ b/flumotion/common/gstreamer.py @@ -22,19 +22,23 @@ # moving this down causes havoc when running this file directly for some reason from flumotion.common import errors, log -import gobject -import gst +import gi +gi.require_version('Gst', '1.0') +from gi.repository import GObject, Gst + +GObject.threads_init() +Gst.init(None) __version__ = "$Rev$" def caps_repr(caps): """ - Represent L{gst.Caps} as a string. + Represent L{Gst.Caps} as a string. @rtype: string """ - value = str(caps) + value = caps.to_string() pos = value.find('streamheader') if pos != -1: return 'streamheader=<...>' @@ -47,13 +51,13 @@ def verbose_deep_notify_cb(object, orig, pspec, component): A default deep-notify signal handler for pipelines. """ value = orig.get_property(pspec.name) - if pspec.value_type == gobject.TYPE_BOOLEAN: + if pspec.value_type == GObject.TYPE_BOOLEAN: if value: value = 'TRUE' else: value = 'FALSE' output = value - elif pspec.value_type == gst.Caps.__gtype__: + elif pspec.value_type == Gst.Caps.__gtype__: output = caps_repr(value) else: output = value @@ -87,8 +91,8 @@ def element_factory_has_property(element_factory, property_name): @rtype: boolean """ # FIXME: find a better way than instantiating one - e = gst.element_factory_make(element_factory) - for pspec in gobject.list_properties(e): + e = Gst.ElementFactory.make(element_factory, None) + for pspec in GObject.list_properties(e): if pspec.name == property_name: return True return False @@ -102,7 +106,7 @@ def element_factory_has_property_value(element_factory, property_name, value): @rtype: boolean """ # FIXME: find a better way than instantiating one - e = gst.element_factory_make(element_factory) + e = Gst.ElementFactory.make(element_factory, None) try: e.set_property(property_name, value) except TypeError: @@ -117,8 +121,8 @@ def element_factory_exists(name): @rtype: boolean """ - registry = gst.registry_get_default() - factory = registry.find_feature(name, gst.TYPE_ELEMENT_FACTORY) + registry = Gst.Registry.get() + factory = registry.lookup_feature(name) if factory: return True @@ -133,7 +137,7 @@ def get_plugin_version(plugin_name): @rtype: tuple of (major, minor, micro, nano), or None if it could not be found or determined """ - plugin = gst.registry_get_default().find_plugin(plugin_name) + plugin = Gst.Registry.get().find_plugin(plugin_name) if not plugin: return None @@ -147,29 +151,31 @@ def get_plugin_version(plugin_name): def get_state_change(old, new): - table = {(gst.STATE_NULL, gst.STATE_READY): - gst.STATE_CHANGE_NULL_TO_READY, - (gst.STATE_READY, gst.STATE_PAUSED): - gst.STATE_CHANGE_READY_TO_PAUSED, - (gst.STATE_PAUSED, gst.STATE_PLAYING): - gst.STATE_CHANGE_PAUSED_TO_PLAYING, - (gst.STATE_PLAYING, gst.STATE_PAUSED): - gst.STATE_CHANGE_PLAYING_TO_PAUSED, - (gst.STATE_PAUSED, gst.STATE_READY): - gst.STATE_CHANGE_PAUSED_TO_READY, - (gst.STATE_READY, gst.STATE_NULL): - gst.STATE_CHANGE_READY_TO_NULL} + table = {(Gst.State.NULL, Gst.State.READY): + Gst.StateChange.NULL_TO_READY, + (Gst.State.READY, Gst.State.PAUSED): + Gst.StateChange.READY_TO_PAUSED, + (Gst.State.PAUSED, Gst.State.PLAYING): + Gst.StateChange.PAUSED_TO_PLAYING, + (Gst.State.PLAYING, Gst.State.PAUSED): + Gst.StateChange.PLAYING_TO_PAUSED, + (Gst.State.PAUSED, Gst.State.READY): + Gst.StateChange.PAUSED_TO_READY, + (Gst.State.READY, Gst.State.NULL): + Gst.StateChange.READY_TO_NULL} return table.get((old, new), 0) def flumotion_reset_event(): ''' Helper method to create a 'flumotion-reset' event ''' - return gst.event_new_custom(gst.EVENT_CUSTOM_DOWNSTREAM, - gst.Structure('flumotion-reset')) + return Gst.event_new_custom(Gst.EVENT_CUSTOM_DOWNSTREAM, + Gst.Structure('flumotion-reset')) def event_is_flumotion_reset(event): ''' Helper method to check if an event is a 'flumotion-reset' event ''' + if event.get_structure() is None: + return False return event.get_structure().get_name() == 'flumotion-reset' @@ -201,13 +207,13 @@ def state_changed(self, old, new): def have_error(self, curstate, message): # if we have a state change defer that has not yet # fired, we should errback it - changes = [gst.STATE_CHANGE_NULL_TO_READY, - gst.STATE_CHANGE_READY_TO_PAUSED, - gst.STATE_CHANGE_PAUSED_TO_PLAYING] + changes = [Gst.StateChange.NULL_TO_READY, + Gst.StateChange.READY_TO_PAUSED, + Gst.StateChange.PAUSED_TO_PLAYING] - extras = ((gst.STATE_PAUSED, gst.STATE_CHANGE_PLAYING_TO_PAUSED), - (gst.STATE_READY, gst.STATE_CHANGE_PAUSED_TO_READY), - (gst.STATE_NULL, gst.STATE_CHANGE_READY_TO_NULL)) + extras = ((Gst.State.PAUSED, Gst.StateChange.PLAYING_TO_PAUSED), + (Gst.State.READY, Gst.StateChange.PAUSED_TO_READY), + (Gst.State.NULL, Gst.StateChange.READY_TO_NULL)) for state, change in extras: if curstate <= state: changes.append(change) diff --git a/flumotion/common/package.py b/flumotion/common/package.py index 5dbb05c7..d41c04ae 100644 --- a/flumotion/common/package.py +++ b/flumotion/common/package.py @@ -31,29 +31,6 @@ __version__ = "$Rev$" -class _PatchedModuleImporter(ihooks.ModuleImporter): - """ - I am overriding ihook's ModuleImporter's import_module() method to - accept (and ignore) the 'level' keyword argument that appeared in - the built-in __import__() function in python2.5. - - While no built-in modules in python2.5 seem to use that keyword - argument, 'encodings' module in python2.6 does and so it breaks if - used together with ihooks. - - I make no attempt to properly support the 'level' argument - - ihooks didn't make it into py3k, and the only use in python2.6 - we've seen so far, in 'encodings', serves as a performance hint - and it seems that can be ignored with no difference in behaviour. - """ - - def import_module(self, name, globals=None, locals=None, fromlist=None, - level=-1): - # all we do is drop 'level' as ihooks don't support it, anyway - return ihooks.ModuleImporter.import_module(self, name, globals, - locals, fromlist) - - class PackageHooks(ihooks.Hooks): """ I am an import Hooks object that makes sure that every package that gets @@ -106,12 +83,7 @@ def install(self): self.debug('installing custom importer') self._hooks = PackageHooks() self._hooks.packager = self - if sys.version_info < (2, 6): - self._importer = ihooks.ModuleImporter() - else: - self.debug('python2.6 or later detected - using patched' - ' ModuleImporter') - self._importer = _PatchedModuleImporter() + self._importer = ihooks.ModuleImporter() self._importer.set_hooks(self._hooks) self._importer.install() diff --git a/flumotion/common/pygobject.py b/flumotion/common/pygobject.py index 6ee2544d..bdca28d7 100644 --- a/flumotion/common/pygobject.py +++ b/flumotion/common/pygobject.py @@ -23,7 +23,7 @@ import sys -import gobject +from gi.repository import GObject __version__ = "$Rev$" @@ -32,11 +32,11 @@ def gobject_set_property(object, property, value): """ Set the given property to the given value on the given object. - @type object: L{gobject.GObject} + @type object: L{GObject.GObject} @type property: string @param value: value to set property to """ - for pspec in gobject.list_properties(object): + for pspec in GObject.list_properties(object): if pspec.name == property: break else: @@ -44,8 +44,8 @@ def gobject_set_property(object, property, value): "Property '%s' in element '%s' does not exist" % ( property, object.get_property('name'))) - if pspec.value_type in (gobject.TYPE_INT, gobject.TYPE_UINT, - gobject.TYPE_INT64, gobject.TYPE_UINT64): + if pspec.value_type in (GObject.TYPE_INT, GObject.TYPE_UINT, + GObject.TYPE_INT64, GObject.TYPE_UINT64): try: value = int(value) except ValueError: @@ -53,16 +53,16 @@ def gobject_set_property(object, property, value): property, object.get_property('name')) raise errors.PropertyError(msg) - elif pspec.value_type == gobject.TYPE_BOOLEAN: + elif pspec.value_type == GObject.TYPE_BOOLEAN: if value == 'False': value = False elif value == 'True': value = True else: value = bool(value) - elif pspec.value_type in (gobject.TYPE_DOUBLE, gobject.TYPE_FLOAT): + elif pspec.value_type in (GObject.TYPE_DOUBLE, GObject.TYPE_FLOAT): value = float(value) - elif pspec.value_type == gobject.TYPE_STRING: + elif pspec.value_type == GObject.TYPE_STRING: value = str(value) # FIXME: this is superevil ! we really need to find a better way # of checking if this property is a param enum @@ -92,7 +92,7 @@ def gsignal(name, *args): else: _dict = _locals['__gsignals__'] - _dict[name] = (gobject.SIGNAL_RUN_FIRST, None, args) + _dict[name] = (GObject.SignalFlags.RUN_FIRST, None, args) PARAM_CONSTRUCT = 1<<9 @@ -138,13 +138,13 @@ def _do_set_property(self, prop, value): if k == 'construct': flags |= PARAM_CONSTRUCT elif k == 'construct_only': - flags |= gobject.PARAM_CONSTRUCT_ONLY + flags |= GObject.PARAM_CONSTRUCT_ONLY elif k == 'readable': - flags |= gobject.PARAM_READABLE + flags |= GObject.PARAM_READABLE elif k == 'writable': - flags |= gobject.PARAM_WRITABLE + flags |= GObject.PARAM_WRITABLE elif k == 'lax_validation': - flags |= gobject.PARAM_LAX_VALIDATION + flags |= GObject.PARAM_LAX_VALIDATION else: raise Exception('Invalid GObject property flag: %r=%r' % (k, v)) @@ -155,4 +155,4 @@ def type_register(klass): if klass.__gtype__.pytype is not klass: # all subclasses will at least have a __gtype__ from their # parent, make sure it corresponds to the exact class - gobject.type_register(klass) + GObject.type_register(klass) diff --git a/flumotion/common/testsuite.py b/flumotion/common/testsuite.py index 48956408..64d5cdc2 100644 --- a/flumotion/common/testsuite.py +++ b/flumotion/common/testsuite.py @@ -19,7 +19,7 @@ """ from twisted.spread import pb -from twisted.internet import reactor, defer, selectreactor +from twisted.internet import reactor, defer, selectreactor, pollreactor from twisted.scripts import trial from twisted.trial import unittest, util @@ -57,7 +57,7 @@ class TestCase(unittest.TestCase, log.Loggable): # A sequence of reactors classes that this test supports, can be # overridden in subclasses. You can also set this to an empty # sequence, which means "any reactor" - supportedReactors = [selectreactor.SelectReactor] + supportedReactors = [selectreactor.SelectReactor, pollreactor.PollReactor] # TestCase in Twisted 2.0 doesn't define failUnlessFailure method. if not hasattr(unittest.TestCase, 'failUnlessFailure'): @@ -74,6 +74,7 @@ def _eb(failure): return deferred.addCallbacks(_cb, _eb) assertFailure = failUnlessFailure + # notice the two spaces and read the following comment def __init__(self, methodName=' impossible-name '): @@ -82,6 +83,7 @@ def __init__(self, methodName=' impossible-name '): # reactor.__class__ rather than type(reactor), because in old # Twisted the reactor was not a new-style class and # type(reactor) returns 'instance' + reactor.killed = False if (self.supportedReactors and reactor.__class__ not in self.supportedReactors): # Set the 'skip' attribute on the class rather than on the diff --git a/flumotion/component/common/avproducer/avproducer.py b/flumotion/component/common/avproducer/avproducer.py index f9771f1e..9ccd4223 100644 --- a/flumotion/component/common/avproducer/avproducer.py +++ b/flumotion/component/common/avproducer/avproducer.py @@ -15,7 +15,7 @@ # # Headers in this file shall remain intact. -import gst +from gi.repository import Gst from twisted.internet import defer from flumotion.common import errors, messages @@ -78,10 +78,10 @@ def get_pipeline_string(self, props): self.add_borders = props.get('add-borders', True) self.deintMode = props.get('deinterlace-mode', 'auto') self.deintMethod = props.get('deinterlace-method', 'ffmpeg') - self.kuinterval = props.get('keyunits-interval', 0) * gst.MSECOND + self.kuinterval = props.get('keyunits-interval', 0) * Gst.MSECOND self.volume_level = props.get('volume', 1) fr = props.get('framerate', None) - self.framerate = fr and gst.Fraction(fr[0], fr[1]) or None + self.framerate = fr and Gst.Fraction(fr[0], fr[1]) or None self._parse_aditional_properties(props) return self.get_pipeline_template(props) @@ -158,7 +158,7 @@ def _add_audio_effects(self, pipeline): # Add audio converter audioconverter = audioconvert.Audioconvert('audioconvert', - comp_level.get_pad("src"), pipeline, tolerance=40 * gst.MSECOND) + comp_level.get_pad("src"), pipeline, tolerance=40 * Gst.MSECOND) self.addEffect(audioconverter) audioconverter.plug() diff --git a/flumotion/component/common/fgdp/fgdp.py b/flumotion/component/common/fgdp/fgdp.py index 2115b723..06e6f300 100644 --- a/flumotion/component/common/fgdp/fgdp.py +++ b/flumotion/component/common/fgdp/fgdp.py @@ -15,8 +15,9 @@ # # Headers in this file shall remain intact. -import gobject -import gst +from gi.repository import GObject +from gi.repository import Gst +from gi.repository import GLib from twisted.internet import reactor @@ -25,12 +26,13 @@ GDP_TYPE_PRODUCER = "producer-type" GDP_TYPE_CONSUMER = "consumer-type" +Gst.init(None) class FDHandler(object): """ Base class for elements handling file descriptors - @type fdelement: L{gst.Element} + @type fdelement: L{Gst.Element} """ def __init__(self, fdelement): @@ -73,10 +75,10 @@ def __init__(self, fdelement): FDHandler.__init__(self, fdelement) def _check_eos(self, pad, event): - if event.type == gst.EVENT_EOS: + if event.get_event().type == Gst.EventType.EOS: # EOS are triggered after a disconnection, when the read in the # socket is 0 Bytes. Remove the handler and close the connection - pad.remove_event_probe(self._handler_id) + pad.remove_probe(self._handler_id) if self.protocol is not None: reactor.callFromThread(self.protocol.loseConnection) return False @@ -87,14 +89,14 @@ def connectFd(self, fd): # state. Add the fd and an event probe to detect disconnections self.fdelement.set_locked_state(False) self.fdelement.set_property('fd', fd) - srcpad = self.fdelement.get_pad("src") - self._handler_id = srcpad.add_event_probe(self._check_eos) - self.fdelement.set_state(gst.STATE_PLAYING) + srcpad = self.fdelement.get_static_pad("src") + self._handler_id = srcpad.add_probe(Gst.PadProbeType.EVENT_BOTH, self._check_eos, None) + self.fdelement.set_state(Gst.State.PLAYING) def disconnectFd(self, _): # Set back the element to the READY state, in which a fd can be # added/changed and lock the state of the element - self.fdelement.set_state(gst.STATE_READY) + self.fdelement.set_state(Gst.State.READY) self.fdelement.get_state(0) self.fdelement.set_locked_state(True) @@ -170,17 +172,17 @@ def stop(self): self._connector.disconnect() def _start_push(self): - self.info("Starting fgdp client") + #self.info("Starting fgdp client") factory = fgdp.FGDPClientFactory(self) self._connector = reactor.connectTCP(self.host, self.port, factory) def _start_pull(self): - self.info("Starting fgdp server") + #self.info("Starting fgdp server") factory = fgdp.FGDPServerFactory(self) self._listener = reactor.listenTCP(self.port, factory) -class FGDPBase(gst.Bin, _ProtocolMixin): +class FGDPBase(Gst.Bin, _ProtocolMixin): """ Base class for gstreamer elements using the FGDP protocol """ @@ -194,54 +196,54 @@ class FGDPBase(gst.Bin, _ProtocolMixin): version = '0.1' __gproperties__ = { - 'mode': (gobject.TYPE_STRING, 'mode', + 'mode': (GObject.TYPE_STRING, 'mode', "Connection mode: 'pull' or 'push'", - 'pull', gobject.PARAM_READWRITE), - 'host': (gobject.TYPE_STRING, 'host', + 'pull', GObject.PARAM_READWRITE), + 'host': (GObject.TYPE_STRING, 'host', 'Name of the host to connect (in push mode)', - 'localhost', gobject.PARAM_READWRITE), - 'port': (gobject.TYPE_INT, 'port', + 'localhost', GObject.PARAM_READWRITE), + 'port': (GObject.TYPE_INT, 'port', 'Connection port', - 1, 64000, 15000, gobject.PARAM_READWRITE), - 'username': (gobject.TYPE_STRING, 'user name', + 1, 64000, 15000, GObject.PARAM_READWRITE), + 'username': (GObject.TYPE_STRING, 'user name', 'Username for the authentication', - 'user', gobject.PARAM_READWRITE), - 'password': (gobject.TYPE_STRING, 'password', + 'user', GObject.PARAM_READWRITE), + 'password': (GObject.TYPE_STRING, 'password', 'Password for the authentication', - 'test', gobject.PARAM_READWRITE), - 'version': (gobject.TYPE_STRING, 'version', + 'test', GObject.PARAM_READWRITE), + 'version': (GObject.TYPE_STRING, 'version', 'Protocol version', - '0.1', gobject.PARAM_READWRITE), - 'max-reconnection-delay': (gobject.TYPE_FLOAT, + '0.1', GObject.PARAM_READWRITE), + 'max-reconnection-delay': (GObject.TYPE_FLOAT, 'maximum delay between reconnections in seconds', 'Maximum delay between reconnections in seconds (for push mode)', - 1, 100, 5, gobject.PARAM_READWRITE)} + 1, 100, 5, GObject.PARAM_READWRITE)} - __gsignals__ = {"connected": (gobject.SIGNAL_RUN_LAST,\ - gobject.TYPE_NONE, []), - "disconnected": (gobject.SIGNAL_RUN_LAST,\ - gobject.TYPE_NONE, - (gobject.TYPE_STRING, ))} + __gsignals__ = {"connected": (GObject.SignalFlags.RUN_LAST,\ + None, []), + "disconnected": (GObject.SignalFlags.RUN_LAST,\ + None, + (GObject.TYPE_STRING, ))} def _handle_error(self, message): - err = gst.GError(gst.RESOURCE_ERROR, - gst.RESOURCE_ERROR_FAILED, message) - m = gst.message_new_error(self, err, message) + err = GLib.GError(Gst.resource_error_quark(), + Gst.ResourceError.FAILED, message) + m = Gst.Message.new_error(self, err, message) self.post_message(m) self.error(message) def do_change_state(self, transition): - if transition == gst.STATE_CHANGE_READY_TO_PAUSED: + if transition == Gst.StateChange.READY_TO_PAUSED: try: self.prepare() self.start() except Exception, e: self._handle_error(str(e)) self.stop() - return gst.STATE_CHANGE_FAILURE - elif transition == gst.STATE_CHANGE_PAUSED_TO_READY: + return Gst.StateChange.FAILURE + elif transition == Gst.StateChange.PAUSED_TO_READY: self.stop() - return gst.Bin.do_change_state(self, transition) + return Gst.Bin.do_change_state(self, transition) def do_set_property(self, prop, value): if prop.name in ['mode', 'host', 'username', 'password', 'port', @@ -282,13 +284,12 @@ class FGDPSink(FGDPBase, MultiFDSink): def __init__(self): FGDPBase.__init__(self) # Create elements - gdppay = gst.element_factory_make('gdppay') - self.fdelement = gst.element_factory_make('multifdsink') + gdppay = Gst.ElementFactory.make('gdppay') + self.fdelement = Gst.ElementFactory.make('multifdsink') # Set default properties self.fdelement.set_property('sync', False) - self.fdelement.set_property('unit-type', 2) - self.fdelement.set_property('units-max', 1 * gst.SECOND) - self.fdelement.set_property('units-soft-max', 700 * gst.MSECOND) + self.fdelement.set_property('units-max', 1 * Gst.SECOND) + self.fdelement.set_property('units-soft-max', 700 * Gst.MSECOND) self.fdelement.set_property('recover-policy', 1) # Create fd handler proxy MultiFDSink.__init__(self, self.fdelement) @@ -296,7 +297,7 @@ def __init__(self): self.add(gdppay, self.fdelement) gdppay.link(self.fdelement) # Create sink pads - self._sink_pad = gst.GhostPad('sink', gdppay.get_pad('sink')) + self._sink_pad = Gst.GhostPad.new('sink', gdppay.get_static_pad('sink')) self.add_pad(self._sink_pad) @@ -314,15 +315,15 @@ class FGDPSrc(FGDPBase, FDSrc): def __init__(self): FGDPBase.__init__(self) # Create elements - self.fdelement = gst.element_factory_make('fdsrc') - gdpdepay = gst.element_factory_make('gdpdepay') + self.fdelement = Gst.ElementFactory.make('fdsrc') + gdpdepay = Gst.ElementFactory.make('gdpdepay') # Add elements to the bin and link them self.add(self.fdelement, gdpdepay) self.fdelement.link(gdpdepay) # Create fd handler proxy FDSrc.__init__(self, self.fdelement) # Create sink pads - self._src_pad = gst.GhostPad('src', gdpdepay.get_pad('src')) + self._src_pad = Gst.GhostPad.new('src', gdpdepay.get_static_pad('src')) self.add_pad(self._src_pad) def prepare(self): @@ -331,7 +332,38 @@ def prepare(self): self.fdelement.set_locked_state(True) -gobject.type_register(FGDPSink) -gst.element_register(FGDPSink, "fgdpsink", gst.RANK_MARGINAL) -gobject.type_register(FGDPSrc) -gst.element_register(FGDPSrc, "fgdpsrc", gst.RANK_MARGINAL) +def plugin_init(plugin, userarg): + name = plugin.get_name() + pluginType = GObject.type_register(userarg) + Gst.Element.register(plugin, name, Gst.Rank.MARGINAL, pluginType) + return True + +version = Gst.version() + +Gst.Plugin.register_static_full( + version[0], # GST_VERSION_MAJOR + version[1], # GST_VERSION_MINOR + 'fgdpsink', + 'fgdp sink plugin', + plugin_init, + '12.06', + 'LGPL', + 'fgdp', + 'fgdp', + '', + FGDPSink, +) + +Gst.Plugin.register_static_full( + version[0], # GST_VERSION_MAJOR + version[1], # GST_VERSION_MINOR + 'fgdpsrc', + 'fgdp src plugin', + plugin_init, + '12.06', + 'LGPL', + 'fgdp', + 'fgdp', + '', + FGDPSrc, +) \ No newline at end of file diff --git a/flumotion/component/common/streamer/fragmentedresource.py b/flumotion/component/common/streamer/fragmentedresource.py index 33612fc8..60feafc8 100644 --- a/flumotion/component/common/streamer/fragmentedresource.py +++ b/flumotion/component/common/streamer/fragmentedresource.py @@ -167,7 +167,7 @@ def _handleNotReady(self, request): def _getExtraLogArgs(self, request): uid = request.session and request.session.uid or None - return {'uid': uid} + return {'session-id': uid} def _checkSession(self, request): """ diff --git a/flumotion/component/common/streamer/mfdsresources.py b/flumotion/component/common/streamer/mfdsresources.py index 5220cfcb..bf0f7cfb 100644 --- a/flumotion/component/common/streamer/mfdsresources.py +++ b/flumotion/component/common/streamer/mfdsresources.py @@ -21,7 +21,7 @@ import fcntl import string -import gst +from gi.repository import Gst from twisted.web import server, resource as web_resource from twisted.internet import reactor, defer @@ -53,8 +53,8 @@ def clientRemoved(self, sink, fd, reason, stats): def _logWrite(self, request, stats): if stats: - bytes_sent = stats[0] - time_connected = int(stats[3] / gst.SECOND) + bytes_sent = stats["bytes-sent"] + time_connected = int(stats["connect-duration"] / Gst.SECOND) else: bytes_sent = -1 time_connected = -1 diff --git a/flumotion/component/common/streamer/multifdsinkstreamer.py b/flumotion/component/common/streamer/multifdsinkstreamer.py index 262a6003..df1b52dc 100644 --- a/flumotion/component/common/streamer/multifdsinkstreamer.py +++ b/flumotion/component/common/streamer/multifdsinkstreamer.py @@ -15,7 +15,7 @@ # # Headers in this file shall remain intact. -import gst +from gi.repository import Gst from twisted.internet import reactor @@ -72,19 +72,19 @@ def setup_burst_mode(self, sink): sink.set_property('sync-method', 4) # burst-keyframe sink.set_property('burst-unit', 2) # time sink.set_property('burst-value', - long(self.burst_time * gst.SECOND)) + long(self.burst_time * Gst.SECOND)) # We also want to ensure that we have sufficient data available # to satisfy this burst; and an appropriate maximum, all # specified in units of time. sink.set_property('time-min', - long((self.burst_time + 5) * gst.SECOND)) + long((self.burst_time + 5) * Gst.SECOND)) sink.set_property('unit-type', 2) # time sink.set_property('units-soft-max', - long((self.burst_time + 8) * gst.SECOND)) + long((self.burst_time + 8) * Gst.SECOND)) sink.set_property('units-max', - long((self.burst_time + 10) * gst.SECOND)) + long((self.burst_time + 10) * Gst.SECOND)) elif self.burst_size: self.debug("Configuring burst mode for %d kB burst", self.burst_size) @@ -242,7 +242,7 @@ def _client_removed_handler(self, sink, fd, reason, stats): def _notify_caps_cb(self, element, pad, param): # We store caps in sink objects as # each sink might (and will) serve different content-type - caps = pad.get_negotiated_caps() + caps = pad.get_current_caps() if caps == None: return diff --git a/flumotion/component/common/streamer/streamer.py b/flumotion/component/common/streamer/streamer.py index cc6591f3..e71535eb 100644 --- a/flumotion/component/common/streamer/streamer.py +++ b/flumotion/component/common/streamer/streamer.py @@ -16,8 +16,9 @@ # Headers in this file shall remain intact. import time - -import gst +import gi +gi.require_version('Gst', '1.0') +from gi.repository import Gst from twisted.cred import credentials from twisted.internet import reactor, error, defer @@ -36,6 +37,8 @@ from flumotion.common.i18n import N_, gettexter +Gst.init(None) + __all__ = ['HTTPMedium'] __version__ = "$Rev$" @@ -405,7 +408,7 @@ def parseProperties(self, properties): self.resource.setLogFilter(logFilter) if 'timeout' in properties: - self.timeout = properties['timeout'] * gst.SECOND + self.timeout = properties['timeout'] * Gst.SECOND self.type = properties.get('type', 'master') if self.type == 'slave': @@ -561,9 +564,9 @@ def updatePorterDetails(self, path, username, password): self._pbclient.stopTrying() # Stop trying to connect with the # old connector. self._pbclient.resetDelay() - reactor.connectWith( - fdserver.FDConnector, self._porterPath, - self._pbclient, 10, checkPID=False) + c = fdserver.FDConnector(self._porterPath, self._pbclient, + 10, checkPID=False, reactor=reactor) + c.connect() else: raise errors.WrongStateError( "Can't specify porter details in master mode") @@ -624,9 +627,9 @@ def do_setup(self): self.info("Starting porter login at \"%s\"", self._porterPath) # This will eventually cause d to fire - reactor.connectWith( - fdserver.FDConnector, self._porterPath, - self._pbclient, 10, checkPID=False) + c = fdserver.FDConnector(self._porterPath, self._pbclient, + 10, checkPID=False, reactor=reactor) + c.connect() else: # Streamer is standalone. try: diff --git a/flumotion/component/consumers/Makefile.am b/flumotion/component/consumers/Makefile.am index dc2a1818..d7ed6839 100644 --- a/flumotion/component/consumers/Makefile.am +++ b/flumotion/component/consumers/Makefile.am @@ -15,6 +15,7 @@ SUBDIRS = \ icystreamer \ hlsstreamer \ httpstreamer \ + justintv \ pipeline \ preview \ shout2 diff --git a/flumotion/component/consumers/disker/disker.py b/flumotion/component/consumers/disker/disker.py index 9e97f5d1..5b94e138 100644 --- a/flumotion/component/consumers/disker/disker.py +++ b/flumotion/component/consumers/disker/disker.py @@ -22,7 +22,7 @@ import datetime as dt import bisect -import gst +from gi.repository import Gst from twisted.internet import reactor @@ -404,7 +404,7 @@ class Disker(feedcomponent.ParseLaunchComponent, log.Loggable): componentMediumClass = DiskerMedium checkOffset = True - pipe_template = 'multifdsink name=fdsink sync-method=2 mode=1 sync=false' + pipe_template = 'multifdsink name=fdsink sync-method=2 sync=false' file = None directory = None location = None @@ -585,7 +585,7 @@ def missingModule(moduleName): else: self.debug("resend-streamheader property not available, " "resending streamheader when it changes in the caps") - sink.get_pad('sink').connect('notify::caps', self._notify_caps_cb) + sink.get_static_pad('sink').connect('notify::caps', self._notify_caps_cb) # connect to client-removed so we can detect errors in file writing sink.connect('client-removed', self._client_removed_cb) @@ -597,7 +597,7 @@ def missingModule(moduleName): self._markerPrefix = pfx if self.reactToMarks or self.writeIndex or self.syncOnTdt: - sink.get_pad("sink").add_data_probe(self._src_pad_probe) + sink.get_static_pad("sink").add_data_probe(self._src_pad_probe) ### our methods @@ -645,7 +645,7 @@ def _updateIndex(self, offset, timestamp, isKeyframe, tdt=0): # Very unlikely, but if we are not synced yet, # add this entry to the index because it's going # to be the sync point, and continue - if stats[6] == gst.CLOCK_TIME_NONE: + if stats[6] == Gst.CLOCK_TIME_NONE: index.addEntry(offset, timestamp, isKeyframe, tdt, False) continue # if we know when the client was synced, trim the index. @@ -774,8 +774,8 @@ def changeFilename(self, filenameTemplate=None, datetime=None): self.location, self.last_tstamp, True) sink = self.get_element('fdsink') - if sink.get_state() == gst.STATE_NULL: - sink.set_state(gst.STATE_READY) + if sink.get_state(0)[1] == Gst.State.NULL: + sink.set_state(Gst.State.READY) filename = "" if not filenameTemplate: @@ -850,8 +850,8 @@ def stopRecording(self): def _stopRecordingFull(self, handle, location, lastTstamp, delayedStop): sink = self.get_element('fdsink') - if sink.get_state() == gst.STATE_NULL: - sink.set_state(gst.STATE_READY) + if sink.get_state(0)[1] == Gst.State.NULL: + sink.set_state(Gst.State.READY) if handle: handle.flush() @@ -890,7 +890,7 @@ def _updateHeadersSize(self): # START OF THREAD AWARE METHODS def _notify_caps_cb(self, pad, param): - caps = pad.get_negotiated_caps() + caps = pad.get_current_caps() if caps == None: return @@ -937,7 +937,8 @@ def _client_removed_cb(self, element, arg0, client_status): del self._clients[arg0] def _handle_event(self, event): - if event.type != gst.EVENT_CUSTOM_DOWNSTREAM: + import IPython; IPython.embed() + if event.type != Gst.EVENT_CUSTOM_DOWNSTREAM: return True struct = event.get_structure() @@ -959,21 +960,21 @@ def _handle_event(self, event): def _handle_buffer(self, buf): # IN_CAPS Buffers - if buf.flag_is_set(gst.BUFFER_FLAG_IN_CAPS): + if buf.flag_is_set(Gst.BUFFER_FLAG_IN_CAPS): self._headers_size += buf.size reactor.callFromThread(self._updateHeadersSize) return True # re-timestamp buffers without timestamp, so that we can get from # multifdsink's client stats the first and last buffer received - if buf.timestamp == gst.CLOCK_TIME_NONE: + if buf.timestamp == Gst.CLOCK_TIME_NONE: buf.timestamp = self._clock.get_time() if self.syncOnTdt: if self._nextIsKF: # That's the first buffer after a 'tdt'. we mark it as a # keyframe and the sink will start streaming from it. - buf.flag_unset(gst.BUFFER_FLAG_DELTA_UNIT) + buf.flag_unset(Gst.BUFFER_FLAG_DELTA_UNIT) self._nextIsKF = False reactor.callFromThread(self._updateIndex, self._offset, buf.timestamp, False, int(self._lastTdt)) @@ -982,9 +983,9 @@ def _handle_buffer(self, buf): self._startFilenameTemplate, self._startTime) self._firstTdt = False else: - buf.flag_set(gst.BUFFER_FLAG_DELTA_UNIT) + buf.flag_set(Gst.BUFFER_FLAG_DELTA_UNIT) # if we don't sync on TDT and this is a keyframe, add it to the index - elif not buf.flag_is_set(gst.BUFFER_FLAG_DELTA_UNIT): + elif not buf.flag_is_set(Gst.BUFFER_FLAG_DELTA_UNIT): reactor.callFromThread(self._updateIndex, self._offset, buf.timestamp, True, time.time()) self._offset += buf.size @@ -992,7 +993,7 @@ def _handle_buffer(self, buf): def _src_pad_probe(self, pad, data): # Events - if type(data) is gst.Event: + if type(data) is Gst.Event: if self.reactToMarks or self.syncOnTdt: return self._handle_event(data) # Buffers diff --git a/flumotion/component/consumers/hlsstreamer/hlsring.py b/flumotion/component/consumers/hlsstreamer/hlsring.py index b91c6d49..7c95c7fd 100644 --- a/flumotion/component/consumers/hlsstreamer/hlsring.py +++ b/flumotion/component/consumers/hlsstreamer/hlsring.py @@ -72,7 +72,7 @@ def _autoUpdate(self, count): if self._counter == count: self._isAutoUpdate = True self._dummyFragments.append(self._getFragmentName(count)) - self._addPlaylistFragment(count, self._duration, False) + self._addPlaylistFragment(count, self._getTargetDuration(), False) def _addPlaylistFragment(self, sequenceNumber, duration, encrypted): # Add the fragment to the playlist if it wasn't added before diff --git a/flumotion/component/consumers/icystreamer/icystreamer.xml b/flumotion/component/consumers/icystreamer/icystreamer.xml index ff8355cd..0e3e288f 100644 --- a/flumotion/component/consumers/icystreamer/icystreamer.xml +++ b/flumotion/component/consumers/icystreamer/icystreamer.xml @@ -54,9 +54,19 @@ - + + + + + + + + + + + @@ -65,6 +75,7 @@ + @@ -72,6 +83,7 @@ + diff --git a/flumotion/component/consumers/justintv/Makefile.am b/flumotion/component/consumers/justintv/Makefile.am new file mode 100644 index 00000000..e99ed9e3 --- /dev/null +++ b/flumotion/component/consumers/justintv/Makefile.am @@ -0,0 +1,10 @@ +include $(top_srcdir)/common/python.mk + +component_PYTHON = __init__.py justintv.py +componentdir = $(libdir)/flumotion/python/flumotion/component/consumers/justintv +component_DATA = justintv.xml + +clean-local: + rm -rf *.pyc *.pyo + +EXTRA_DIST = $(component_DATA) diff --git a/flumotion/component/consumers/justintv/__init__.py b/flumotion/component/consumers/justintv/__init__.py new file mode 100644 index 00000000..2157e846 --- /dev/null +++ b/flumotion/component/consumers/justintv/__init__.py @@ -0,0 +1,18 @@ +# -*- Mode: Python -*- +# vi:si:et:sw=4:sts=4:ts=4 + +# Flumotion - a streaming media server +# Copyright (C) 2004,2005,2006,2007,2008,2009 Fluendo, S.L. +# Copyright (C) 2010,2011 Flumotion Services, S.A. +# All rights reserved. +# +# This file may be distributed and/or modified under the terms of +# the GNU Lesser General Public License version 2.1 as published by +# the Free Software Foundation. +# This file is distributed without any warranty; without even the implied +# warranty of merchantability or fitness for a particular purpose. +# See "LICENSE.LGPL" in the source distribution for more information. +# +# Headers in this file shall remain intact. + +__version__ = "$Rev: 5969 $" diff --git a/flumotion/component/consumers/justintv/justintv.py b/flumotion/component/consumers/justintv/justintv.py new file mode 100644 index 00000000..1d8da5f4 --- /dev/null +++ b/flumotion/component/consumers/justintv/justintv.py @@ -0,0 +1,60 @@ +# -*- Mode: Python -*- +# vi:si:et:sw=4:sts=4:ts=4 + +# Flumotion - a streaming media server +# Copyright (C) 2004,2005,2006,2007,2008,2009 Fluendo, S.L. +# Copyright (C) 2010,2011 Flumotion Services, S.A. +# All rights reserved. +# +# This file may be distributed and/or modified under the terms of +# the GNU Lesser General Public License version 2.1 as published by +# the Free Software Foundation. +# This file is distributed without any warranty; without even the implied +# warranty of merchantability or fitness for a particular purpose. +# See "LICENSE.LGPL" in the source distribution for more information. +# +# Headers in this file shall remain intact. + +from flumotion.component import feedcomponent + +__all__ = ['Consumer'] +__version__ = "$Rev: 7162 $" + + +class JustinTV(feedcomponent.MultiInputParseLaunchComponent): + LINK_MUXER=False + + def get_pipeline_string(self, properties): + self._justintv_user = properties['justintv-user'] + self._justintv_key = properties['justintv-key'] + + assert 'video' in self.eaters + assert 'audio' in self.eaters + + # These settings from from + # http://apiwiki.justin.tv/mediawiki/index.php/VLC_Broadcasting_API + + #video/x-raw-yuv, width= ! + #x264{keyint=60,idrint=2},vcodec=h264,vb=300 + videopipe = """\ +@ eater:video @ ! +videoscale ! +x264enc bframes=0 key-int-max=2 bitrate=300 ! +muxer. +""" + # acodec=mp4a,ab=32,channels=2,samplerate=2205 + audiopipe = """\ +@ eater:audio @ ! +audioconvert ! +audioresample ! +audio/x-raw-int,channels=1,samplerate=44100 ! +lamemp3enc bitrate=64 mono=1 target=bitrate cbr=1 ! +muxer. +""" + # gst-launch-0.10 -v videotestsrc ! ffenc_flv ! flvmux ! rtmpsink location='rtmp://live.justin.tv/app/live_22076196_raZAJ55hsNU4Z9LN130N1GL71KJok6 live=1' + outputpipe = """\ +flvmux name=muxer streamable=true ! +rtmpsink location="rtmp://live.justin.tv/app/%s live=1" +""" % (self._justintv_key) + + return videopipe + "\n\n" + audiopipe + "\n\n" + outputpipe diff --git a/flumotion/component/consumers/justintv/justintv.xml b/flumotion/component/consumers/justintv/justintv.xml new file mode 100644 index 00000000..ece0f92f --- /dev/null +++ b/flumotion/component/consumers/justintv/justintv.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/flumotion/component/decodercomponent.py b/flumotion/component/decodercomponent.py index ae3b2e35..16609b01 100644 --- a/flumotion/component/decodercomponent.py +++ b/flumotion/component/decodercomponent.py @@ -19,8 +19,7 @@ Decoder component, participating in the stream """ -import gst -import gst.interfaces +from gi.repository import Gst from flumotion.common.i18n import N_, gettexter from flumotion.common import errors, messages, gstreamer @@ -44,7 +43,7 @@ class DecoderComponent(fc.ReconfigurableComponent): def configure_pipeline(self, pipeline, properties): # Handle decoder dynamic pads decoder = self.pipeline.get_by_name("decoder") - decoder.connect('new-decoded-pad', self._new_decoded_pad_cb) + decoder.connect('pad-added', self._pad_added_cb) self._add_video_effects() self._add_audio_effects() @@ -62,21 +61,21 @@ def _add_video_effects(self): width = props.get('width', None) height = props.get('height', None) # Expressed in ms - interval = props.get('keyunits-interval', 10000) * gst.MSECOND + interval = props.get('keyunits-interval', 10000) * Gst.MSECOND fr = props.get('framerate', (25, 2)) - framerate = gst.Fraction(fr[0], fr[1]) + framerate = Gst.Fraction(fr[0], fr[1]) self.vr = videorate.Videorate('videorate', None, self.pipeline, framerate) self.addEffect(self.vr) - #self.vr.effectBin.set_state(gst.STATE_PLAYING) + #self.vr.effectBin.set_state(Gst.State.PLAYING) self.debug("Videorate added") self.videoscaler = videoscale.Videoscale('videoscale', self, None, self.pipeline, width, height, is_square, add_borders) self.addEffect(self.videoscaler) - #self.videoscaler.effectBin.set_state(gst.STATE_PLAYING) + #self.videoscaler.effectBin.set_state(Gst.State.PLAYING) self.debug("Videoscaler added") self.vkuscheduler = kuscheduler.KeyUnitsScheduler('keyunits-scheduler', @@ -90,7 +89,7 @@ def _add_audio_effects(self): props = self.config['properties'] samplerate = props.get('samplerate', 44100) channels = props.get('channels', 2) - interval = props.get('keyunits-interval', 10000) * gst.MSECOND + interval = props.get('keyunits-interval', 10000) * Gst.MSECOND self.ar = audioconvert.Audioconvert('audioconvert', None, self.pipeline, channels=channels, @@ -102,18 +101,18 @@ def _add_audio_effects(self): self.addEffect(self.akuscheduler) self.debug("KeyUnitsScheduler added") - def _new_decoded_pad_cb(self, decoder, pad, last): + def _pad_added_cb(self, decoder, pad): self.log("Decoder %s got new decoded pad %s", decoder, pad) - new_caps = pad.get_caps() + new_caps = pad.query_caps() # Select a compatible output element for outelem in self.get_output_elements(): - output_pad = outelem.get_pad('sink') + output_pad = outelem.get_static_pad('sink') if output_pad.is_linked(): continue - pad_caps = output_pad.get_caps() + pad_caps = output_pad.query_caps() if not new_caps.is_subset(pad_caps): continue @@ -135,13 +134,13 @@ def _new_decoded_pad_cb(self, decoder, pad, last): def _plug_video_effects(self, pad): self.vr.sourcePad = pad self.vr.plug() - self.videoscaler.sourcePad = self.vr.effectBin.get_pad("src") + self.videoscaler.sourcePad = self.vr.effectBin.get_static_pad("src") self.videoscaler.plug() - self.vkuscheduler.sourcePad = self.videoscaler.effectBin.get_pad("src") + self.vkuscheduler.sourcePad = self.videoscaler.effectBin.get_static_pad("src") self.vkuscheduler.plug() def _plug_audio_effects(self, pad): self.ar.sourcePad = pad self.ar.plug() - self.akuscheduler.sourcePad = self.ar.effectBin.get_pad("src") + self.akuscheduler.sourcePad = self.ar.effectBin.get_static_pad("src") self.akuscheduler.plug() diff --git a/flumotion/component/decoders/generic/generic.py b/flumotion/component/decoders/generic/generic.py index c4dd3083..5edea55a 100644 --- a/flumotion/component/decoders/generic/generic.py +++ b/flumotion/component/decoders/generic/generic.py @@ -15,8 +15,8 @@ # # Headers in this file shall remain intact. -import gst -import gobject +from gi.repository import Gst +from gi.repository import GObject import threading from flumotion.component import decodercomponent as dc @@ -27,8 +27,8 @@ __version__ = "$Rev: 7162 $" -BASIC_AUDIO_CAPS = "audio/x-raw-int;audio/x-raw-float" -BASIC_VIDEO_CAPS = "video/x-raw-yuv;video/x-raw-rgb" +BASIC_AUDIO_CAPS = "audio/x-raw;audio/x-raw" +BASIC_VIDEO_CAPS = "video/x-raw;video/x-raw" # FIXME: The GstAutoplugSelectResult enum has no bindings in gst-python. # Replace this when the enum is exposed in the bindings. @@ -44,47 +44,47 @@ def __init__(self, name, caps, linked=False): self.caps = caps -class SyncKeeper(gst.Element): - __gstdetails__ = ('SyncKeeper', 'Generic', +class SyncKeeper(Gst.Element): + __gstmetadata__ = ('SyncKeeper', 'Generic', 'Retimestamp the output to be contiguous and maintain ' 'the sync', 'Xavier Queralt') - _audiosink = gst.PadTemplate("audio-in", - gst.PAD_SINK, - gst.PAD_ALWAYS, - gst.caps_from_string(BASIC_AUDIO_CAPS)) - _videosink = gst.PadTemplate("video-in", - gst.PAD_SINK, - gst.PAD_ALWAYS, - gst.caps_from_string(BASIC_VIDEO_CAPS)) - _audiosrc = gst.PadTemplate("audio-out", - gst.PAD_SRC, - gst.PAD_ALWAYS, - gst.caps_from_string(BASIC_AUDIO_CAPS)) - _videosrc = gst.PadTemplate("video-out", - gst.PAD_SRC, - gst.PAD_ALWAYS, - gst.caps_from_string(BASIC_VIDEO_CAPS)) + _audiosink = Gst.PadTemplate.new("audio-in", + Gst.PadDirection.SINK, + Gst.PadPresence.ALWAYS, + Gst.Caps.from_string(BASIC_AUDIO_CAPS)) + _videosink = Gst.PadTemplate.new("video-in", + Gst.PadDirection.SINK, + Gst.PadPresence.ALWAYS, + Gst.Caps.from_string(BASIC_VIDEO_CAPS)) + _audiosrc = Gst.PadTemplate.new("audio-out", + Gst.PadDirection.SRC, + Gst.PadPresence.ALWAYS, + Gst.Caps.from_string(BASIC_AUDIO_CAPS)) + _videosrc = Gst.PadTemplate.new("video-out", + Gst.PadDirection.SRC, + Gst.PadPresence.ALWAYS, + Gst.Caps.from_string(BASIC_VIDEO_CAPS)) def __init__(self): - gst.Element.__init__(self) + Gst.Element.__init__(self) # create source pads - self.audiosrc = gst.Pad(self._audiosrc, "audio-out") + self.audiosrc = Gst.Pad.new_from_template(self._audiosrc, "audio-out") self.add_pad(self.audiosrc) - self.videosrc = gst.Pad(self._videosrc, "video-out") + self.videosrc = Gst.Pad.new_from_template(self._videosrc, "video-out") self.add_pad(self.videosrc) # create the sink pads and set the chain and event function - self.audiosink = gst.Pad(self._audiosink, "audio-in") - self.audiosink.set_chain_function(lambda pad, buffer: + self.audiosink = Gst.Pad.new_from_template(self._audiosink, "audio-in") + self.audiosink.set_chain_function_full(lambda pad, buffer: self.chainfunc(pad, buffer, self.audiosrc)) - self.audiosink.set_event_function(lambda pad, buffer: + self.audiosink.set_event_function_full(lambda pad, buffer: self.eventfunc(pad, buffer, self.audiosrc)) self.add_pad(self.audiosink) - self.videosink = gst.Pad(self._videosink, "video-in") - self.videosink.set_chain_function(lambda pad, buffer: + self.videosink = Gst.Pad.new_from_template(self._videosink, "video-in") + self.videosink.set_chain_function_full(lambda pad, buffer: self.chainfunc(pad, buffer, self.videosrc)) - self.videosink.set_event_function(lambda pad, buffer: + self.videosink.set_event_function_full(lambda pad, buffer: self.eventfunc(pad, buffer, self.videosrc)) self.add_pad(self.videosink) @@ -99,7 +99,7 @@ def __init__(self): def _send_new_segment(self): for pad in [self.videosrc, self.audiosrc]: pad.push_event( - gst.event_new_new_segment(True, 1.0, gst.FORMAT_TIME, + Gst.Event.new_segment(True, 1.0, Gst.Format.TIME, self._syncTimestamp, -1, 0)) self._sendNewSegment = False @@ -115,14 +115,14 @@ def _update_sync_point(self, start, position): self._syncOffset = start self._resetReceived = False self.info("Update sync point to % r, offset to %r" % - (gst.TIME_ARGS(self._syncTimestamp), - (gst.TIME_ARGS(self._syncOffset)))) + (Gst.TIME_ARGS(self._syncTimestamp), + (Gst.TIME_ARGS(self._syncOffset)))) def chainfunc(self, pad, buf, srcpad): self.log("Input %s timestamp: %s, %s" % (srcpad is self.audiosrc and 'audio' or 'video', - gst.TIME_ARGS(buf.timestamp), - gst.TIME_ARGS(buf.duration))) + Gst.TIME_ARGS(buf.pts), + Gst.TIME_ARGS(buf.duration))) if not self._sendNewSegment: self._send_new_segment() @@ -130,35 +130,35 @@ def chainfunc(self, pad, buf, srcpad): try: self._lock.acquire() # Discard buffers outside the configured segment - if buf.timestamp < self._syncOffset: + if buf.pts < self._syncOffset: self.warning("Could not clip buffer to segment") - return gst.FLOW_OK - if buf.timestamp == gst.CLOCK_TIME_NONE: - return gst.FLOW_OK + return Gst.FlowReturn.OK + if buf.pts == Gst.CLOCK_TIME_NONE: + return Gst.FlowReturn.OK # Get the input stream time of the buffer - buf.timestamp -= self._syncOffset + buf.pts -= self._syncOffset # Set the accumulated stream time - buf.timestamp += self._syncTimestamp + buf.pts += self._syncTimestamp duration = 0 - if buf.duration != gst.CLOCK_TIME_NONE: + if buf.duration != Gst.CLOCK_TIME_NONE: duration = buf.duration - self._totalTime = max(buf.timestamp + duration, self._totalTime) + self._totalTime = max(buf.pts + duration, self._totalTime) self.log("Output %s timestamp: %s, %s" % (srcpad is self.audiosrc and 'audio' or 'video', - gst.TIME_ARGS(buf.timestamp), - gst.TIME_ARGS(buf.duration))) + Gst.TIME_ARGS(buf.pts), + Gst.TIME_ARGS(buf.duration))) finally: self._lock.release() srcpad.push(buf) - return gst.FLOW_OK + return Gst.FlowReturn.OK def eventfunc(self, pad, event, srcpad): self.debug("Received event %r from %s" % (event, event.src)) try: self._lock.acquire() - if event.type == gst.EVENT_NEWSEGMENT: + if event.type == Gst.EventType.SEGMENT: u, r, f, start, s, position = event.parse_new_segment() self._update_sync_point(start, position) if gstreamer.event_is_flumotion_reset(event): @@ -168,17 +168,36 @@ def eventfunc(self, pad, event, srcpad): self._lock.release() # forward all the events except the new segment events - if event.type != gst.EVENT_NEWSEGMENT: + if event.type != Gst.EventType.SEGMENT: return srcpad.push_event(event) return True -gobject.type_register(SyncKeeper) -gst.element_register(SyncKeeper, "synckeeper", gst.RANK_MARGINAL) +def plugin_init(plugin, userarg): + name = plugin.get_name() + pluginType = GObject.type_register(userarg) + Gst.Element.register(plugin, name, Gst.Rank.MARGINAL, pluginType) + return True + +version = Gst.version() + +Gst.Plugin.register_static_full( + version[0], # GST_VERSION_MAJOR + version[1], # GST_VERSION_MINOR + 'synckeeper', + 'sync keeper plugin', + plugin_init, + '12.06', + 'LGPL', + 'synckeeper', + 'synckeeper', + '', + SyncKeeper, +) class GenericDecoder(dc.DecoderComponent): """ - Generic decoder component using decodebin2. + Generic decoder component using decodebin. It listen to the custom gstreamer event flumotion-reset, and reset the decoding pipeline by removing the old one @@ -232,7 +251,7 @@ def configure_pipeline(self, pipeline, properties): ### Protected Methods ## def _get_base_pipeline_string(self): - return 'decodebin2 name=decoder' + return 'decodebin name=decoder' def _get_feeders_info(self): """ @@ -290,4 +309,4 @@ def _get_feeders_info(self): FeederInfo('video', BASIC_VIDEO_CAPS)) def _get_base_pipeline_string(self): - return 'decodebin2 name=decoder synckeeper name=sync' + return 'decodebin name=decoder synckeeper name=sync' diff --git a/flumotion/component/effects/audioconvert/audioconvert.py b/flumotion/component/effects/audioconvert/audioconvert.py index 6daef402..d00d7097 100644 --- a/flumotion/component/effects/audioconvert/audioconvert.py +++ b/flumotion/component/effects/audioconvert/audioconvert.py @@ -17,8 +17,8 @@ import sys -import gobject -import gst +from gi.repository import GObject +from gi.repository import Gst from flumotion.common.i18n import gettexter from flumotion.component import feedcomponent @@ -30,7 +30,7 @@ DEFAULT_TOLERANCE = 20000000 # 20ms -class AudioconvertBin(gst.Bin): +class AudioconvertBin(Gst.Bin): """ I am a GStreamer bin that can convert an an audio stream, changing its samplerate and the number of channels @@ -38,45 +38,45 @@ class AudioconvertBin(gst.Bin): logCategory = "audiorate" RATE_CAPS = ', rate=%d' CHANNELS_CAPS = ', channels=%d' - CAPS_TEMPLATE = ("audio/x-raw-int %(extra_caps)s ;" - "audio/x-raw-float %(extra_caps)s") + CAPS_TEMPLATE = ("audio/x-raw %(extra_caps)s ;" + "audio/x-raw %(extra_caps)s") __gproperties__ = { - 'channels': (gobject.TYPE_UINT, 'channels', + 'channels': (GObject.TYPE_UINT, 'channels', 'Audio channels', 1, 8, 2, - gobject.PARAM_READWRITE), - 'samplerate': (gobject.TYPE_UINT, 'samplerate', + GObject.PARAM_READWRITE), + 'samplerate': (GObject.TYPE_UINT, 'samplerate', 'Audio samplerate', 1, 200000, 44100, - gobject.PARAM_READWRITE), - 'tolerance': (gobject.TYPE_UINT, 'tolerance', + GObject.PARAM_READWRITE), + 'tolerance': (GObject.TYPE_UINT, 'tolerance', 'Correct imperfect timestamps when it exeeds the ' 'tolerance', 0, sys.maxint, DEFAULT_TOLERANCE, - gobject.PARAM_READWRITE)} + GObject.PARAM_READWRITE)} def __init__(self, channels=None, samplerate=None, tolerance=DEFAULT_TOLERANCE): - gst.Bin.__init__(self) + Gst.Bin.__init__(self) self._samplerate = samplerate self._samplerate_caps = '' self._channels = channels self._channels_caps = '' if self._use_audiorate(): - self._audiorate = gst.element_factory_make("audiorate") + self._audiorate = Gst.ElementFactory.make("audiorate") self._audiorate.set_property("skip-to-first", True) else: - self._audiorate = gst.element_factory_make("identity") + self._audiorate = Gst.ElementFactory.make("identity") self._audiorate.set_property("silent", True) - self._audioconv = gst.element_factory_make("audioconvert") + self._audioconv = Gst.ElementFactory.make("audioconvert") resampler = 'audioresample' if gstreamer.element_factory_exists('legacyresample'): resampler = 'legacyresample' - self._audioresample = gst.element_factory_make(resampler) + self._audioresample = Gst.ElementFactory.make(resampler) - self._capsfilter = gst.element_factory_make("capsfilter") - self._identity = gst.parse_launch("identity silent=true") + self._capsfilter = Gst.ElementFactory.make("capsfilter") + self._identity = Gst.parse_launch("identity silent=true") self.add(self._audiorate) self.add(self._audioconv) self.add(self._audioresample) @@ -89,8 +89,8 @@ def __init__(self, channels=None, samplerate=None, self._capsfilter.link(self._identity) # Create source and sink pads - self._sinkPad = gst.GhostPad('sink', self._audiorate.get_pad('sink')) - self._srcPad = gst.GhostPad('src', self._identity.get_pad('src')) + self._sinkPad = Gst.GhostPad.new('sink', self._audiorate.get_static_pad('sink')) + self._srcPad = Gst.GhostPad.new('src', self._identity.get_static_pad('src')) self.add_pad(self._sinkPad) self.add_pad(self._srcPad) @@ -111,14 +111,14 @@ def _setChannels(self, channels): self._channels_caps = '' if self._channels is not None: self._channels_caps = self.CHANNELS_CAPS % channels - self._capsfilter.set_property('caps', gst.Caps(self._getCapsString())) + self._capsfilter.set_property('caps', Gst.Caps(self._getCapsString())) def _setSamplerate(self, samplerate): self._samplerate = samplerate self._samplerate_caps = '' if self._samplerate is not None: self._samplerate_caps = self.RATE_CAPS % samplerate - self._capsfilter.set_property('caps', gst.Caps(self._getCapsString())) + self._capsfilter.set_property('caps', Gst.Caps(self._getCapsString())) def _setTolerance(self, tolerance): self._tolerance = tolerance diff --git a/flumotion/component/effects/colorbalance/colorbalance.py b/flumotion/component/effects/colorbalance/colorbalance.py index 6e43244b..baf87542 100644 --- a/flumotion/component/effects/colorbalance/colorbalance.py +++ b/flumotion/component/effects/colorbalance/colorbalance.py @@ -15,8 +15,7 @@ # # Headers in this file shall remain intact. -import gst -import gst.interfaces +from gi.repository import Gst from flumotion.component import feedcomponent @@ -40,7 +39,7 @@ def __init__(self, name, element, hue, saturation, brightness, contrast, @param contrast: the colorbalance contrast, as a percentage @type contrast: float @param pipeline: the pipeline - @type pipeline: L{gst.Pipeline} + @type pipeline: L{Gst.Pipeline} """ self.debug("colorbalance init") feedcomponent.Effect.__init__(self, name) @@ -68,10 +67,10 @@ def _bus_message_received_cb(self, bus, message, hue, saturation, @param message: the message received """ t = message.type - if t == gst.MESSAGE_STATE_CHANGED and message.src == self._element: + if t == Gst.MessageType.STATE_CHANGED and message.src == self._element: (old, new, pending) = message.parse_state_changed() # we have a state change - if old == gst.STATE_READY and new == gst.STATE_PAUSED: + if old == Gst.State.READY and new == Gst.State.PAUSED: self._setInitialColorBalance(hue, saturation, brightness, contrast) @@ -113,8 +112,8 @@ def effect_setColorBalanceProperty(self, which, value): return value def _setInitialColorBalance(self, hue, saturation, brightness, contrast): - self._channels = self._element.list_colorbalance_channels() - self.debug('colorbalance channels: %d' % len(self._channels)) + #self._channels = self._element.list_colorbalance_channels() + #self.debug('colorbalance channels: %d' % len(self._channels)) self.effect_setColorBalanceProperty('Hue', hue) self.effect_setColorBalanceProperty('Saturation', saturation) self.effect_setColorBalanceProperty('Brightness', brightness) diff --git a/flumotion/component/effects/deinterlace/deinterlace.py b/flumotion/component/effects/deinterlace/deinterlace.py index f35ef9f5..28a772ee 100644 --- a/flumotion/component/effects/deinterlace/deinterlace.py +++ b/flumotion/component/effects/deinterlace/deinterlace.py @@ -15,11 +15,12 @@ # # Headers in this file shall remain intact. -import gst -import gobject +from gi.repository import Gst +from gi.repository import GObject from twisted.internet import reactor from flumotion.component import feedcomponent +from flumotion.common import gstreamer __version__ = "$Rev$" @@ -48,7 +49,7 @@ "ffmpeg": FF_DEINTERLACER} -class DeinterlaceBin(gst.Bin): +class DeinterlaceBin(Gst.Bin): """ I am a GStreamer bin that can deinterlace a video stream from its source pad using different methods. @@ -58,31 +59,31 @@ class DeinterlaceBin(gst.Bin): DEFAULT_METHOD = 'ffmpeg' __gproperties__ = { - 'keep-framerate': (gobject.TYPE_BOOLEAN, 'keeps the input framerate', + 'keep-framerate': (GObject.TYPE_BOOLEAN, 'keeps the input framerate', 'keeps in the output the same framerate as in the output ' 'even if the deinterlacer changes it', - True, gobject.PARAM_READWRITE), - 'mode': (gobject.TYPE_STRING, 'deinterlace mode', + True, GObject.PARAM_READWRITE), + 'mode': (GObject.TYPE_STRING, 'deinterlace mode', 'mode used to deinterlace incoming frames', - 'auto', gobject.PARAM_READWRITE), - 'method': (gobject.TYPE_STRING, 'deinterlace method', + 'auto', GObject.PARAM_READWRITE), + 'method': (GObject.TYPE_STRING, 'deinterlace method', 'method/algorithm used to deinterlace incoming frames', - 'ffmpeg', gobject.PARAM_READWRITE)} + 'ffmpeg', GObject.PARAM_READWRITE)} def __init__(self, mode, method): - gst.Bin.__init__(self) + Gst.Bin.__init__(self) self.keepFR = True self.deinterlacerName = PASSTHROUGH_DEINTERLACER self._interlaced = False # Create elements - self._colorspace = gst.element_factory_make("ffmpegcolorspace") - self._colorfilter = gst.element_factory_make("capsfilter") - self._deinterlacer = gst.element_factory_make(PASSTHROUGH_DEINTERLACER) + self._colorspace = Gst.ElementFactory.make("videoconvert") + self._colorfilter = Gst.ElementFactory.make("capsfilter") + self._deinterlacer = Gst.ElementFactory.make(PASSTHROUGH_DEINTERLACER) self._deinterlacer.set_property('silent', True) - self._videorate = gst.element_factory_make("videorate") - self._ratefilter = gst.element_factory_make("capsfilter") + self._videorate = Gst.ElementFactory.make("videorate") + self._ratefilter = Gst.ElementFactory.make("capsfilter") # Add elements to the bin self.add(self._colorspace, self._colorfilter, self._deinterlacer, @@ -93,8 +94,11 @@ def __init__(self, mode, method): # is different and the ffmpeg deinterlacer is added after the # negotiation happened in a different colorspace. This makes this # element not-passthrough. - self._colorfilter.set_property('caps', gst.Caps( - 'video/x-raw-yuv, format=(fourcc)I420')) + self._colorfilter.set_property('caps', Gst.Caps( + 'video/x-raw, format=(string)I420')) + + if gstreamer.element_has_property(self._videorate, 'skip-to-first'): + self._videorate.set_property('skip-to-first', True) # Link elements self._colorspace.link(self._colorfilter) @@ -103,17 +107,14 @@ def __init__(self, mode, method): self._videorate.link(self._ratefilter) # Create source and sink pads - self._sinkPad = gst.GhostPad('sink', self._colorspace.get_pad('sink')) - self._srcPad = gst.GhostPad('src', self._ratefilter.get_pad('src')) + self._sinkPad = Gst.GhostPad.new('sink', self._colorspace.get_static_pad('sink')) + self._srcPad = Gst.GhostPad.new('src', self._ratefilter.get_static_pad('src')) self.add_pad(self._sinkPad) self.add_pad(self._srcPad) # Store deinterlacer's sink and source peer pads - self._sinkPeerPad = self._colorspace.get_pad('src') - self._srcPeerPad = self._videorate.get_pad('sink') - - # Add setcaps callback in the sink pad - self._sinkPad.set_setcaps_function(self._sinkSetCaps) + self._sinkPeerPad = self._colorfilter.get_static_pad('src') + self._srcPeerPad = self._videorate.get_static_pad('sink') # Set the mode and method in the deinterlacer self._setMethod(method) @@ -129,21 +130,19 @@ def _sinkSetCaps(self, pad, caps): try: framerate = struct['framerate'] except KeyError: - framerate = gst.Fraction(25, 1) + framerate = Gst.Fraction(25, 1) fr = '%s/%s' % (framerate.num, framerate.denom) - self._ratefilter.set_property('caps', gst.Caps( - 'video/x-raw-yuv, framerate=%s;' - 'video/x-raw-rgb, framerate=%s' % (fr, fr))) - # Detect if it's an interlaced stream using the 'interlaced' field + self._ratefilter.set_property('caps', Gst.Caps( + 'video/x-raw, framerate=%s;' + 'video/x-raw, framerate=%s' % (fr, fr))) + # Detect if it's an interlaced stream using the 'interlace-mode' field try: - interlaced = struct['interlaced'] + interlaced = struct.has_field("interlace-mode") except KeyError: interlaced = False if interlaced == self._interlaced: return True else: - self.debug("Input is%sinterlaced" % - (interlaced and " " or " not ")) self._interlaced = interlaced # If we are in 'auto' mode and the interlaced field has changed, # switch to the appropiate deinterlacer @@ -160,18 +159,18 @@ def _replaceDeinterlacer(self, blockPad, deinterlacerName): def unlinkAndReplace(Pad, blocked, deinterlacerName): oldDeinterlacer = self._deinterlacer - self._deinterlacer = gst.element_factory_make(deinterlacerName) + self._deinterlacer = Gst.ElementFactory.make(deinterlacerName) if deinterlacerName == GST_DEINTERLACER: self._deinterlacer.set_property("method", self.method) elif deinterlacerName == PASSTHROUGH_DEINTERLACER: self._deinterlacer.set_property("silent", True) - self._deinterlacer.set_state(gst.STATE_PLAYING) + self._deinterlacer.set_state(Gst.State.PLAYING) self.add(self._deinterlacer) # unlink the sink and source pad of the old deinterlacer self._colorfilter.unlink(oldDeinterlacer) oldDeinterlacer.unlink(self._videorate) # remove the old deinterlacer from the bin - oldDeinterlacer.set_state(gst.STATE_NULL) + oldDeinterlacer.set_state(Gst.State.NULL) self.remove(oldDeinterlacer) self._colorfilter.link(self._deinterlacer) self._deinterlacer.link(self._videorate) @@ -257,6 +256,7 @@ class Deinterlace(feedcomponent.PostProcEffect): I am an effect that can be added to any component that has a deinterlacer component and a way of changing the deinterlace method. """ + logCategory = "deinterlace" def __init__(self, name, sourcePad, pipeline, mode, method): diff --git a/flumotion/component/effects/kuscheduler/kuscheduler.py b/flumotion/component/effects/kuscheduler/kuscheduler.py index 61171d33..a2970d02 100644 --- a/flumotion/component/effects/kuscheduler/kuscheduler.py +++ b/flumotion/component/effects/kuscheduler/kuscheduler.py @@ -16,8 +16,8 @@ # Headers in this file shall remain intact. -import gobject -import gst +from gi.repository import GObject +from gi.repository import Gst from flumotion.common import gstreamer from flumotion.common.i18n import gettexter @@ -27,39 +27,39 @@ T_ = gettexter() -DEFAULT_INTERVAL = 10 * gst.SECOND +DEFAULT_INTERVAL = 10 * Gst.SECOND -class GstKeyUnitsScheduler(gst.Element): +class GstKeyUnitsScheduler(Gst.Element): __gproperties__ = { - 'interval': (gobject.TYPE_UINT64, + 'interval': (GObject.TYPE_UINT64, 'Key Unit Interval', 'Key Unit interval in ns', - 0, gst.CLOCK_TIME_NONE, DEFAULT_INTERVAL, - gobject.PARAM_READWRITE)} + 0, Gst.CLOCK_TIME_NONE, DEFAULT_INTERVAL, + GObject.PARAM_READWRITE)} - __gstdetails__ = ('FluKeyUnitsScheduler', 'Converter', + __gstmetadata__ = ('FluKeyUnitsScheduler', 'Converter', 'Key Units scheduler for flumotion', 'Flumotion Dev Team') - _sinkpadtemplate = gst.PadTemplate("sink", - gst.PAD_SINK, - gst.PAD_ALWAYS, - gst.caps_new_any()) + _sinkpadtemplate = Gst.PadTemplate.new("sink", + Gst.PadDirection.SINK, + Gst.PadPresence.ALWAYS, + Gst.Caps.new_any()) - _srcpadtemplate = gst.PadTemplate("src", - gst.PAD_SRC, - gst.PAD_ALWAYS, - gst.caps_new_any()) + _srcpadtemplate = Gst.PadTemplate.new("src", + Gst.PadDirection.SRC, + Gst.PadPresence.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) + Gst.Element.__init__(self) + self.sinkpad = Gst.Pad.new_from_template(self._sinkpadtemplate, "sink") + self.sinkpad.set_chain_function_full(self.chainfunc) self.add_pad(self.sinkpad) - self.srcpad = gst.Pad(self._srcpadtemplate, "src") + self.srcpad = Gst.Pad.new_from_template(self._srcpadtemplate, "src") self.add_pad(self.srcpad) self._last_ts = 0L @@ -72,17 +72,17 @@ def _send_event(self, timestamp): running_time = clock.get_time() - self.get_base_time() else: running_time = 0 - s = gst.Structure("GstForceKeyUnit") + s = Gst.Structure("GstForceKeyUnit") s.set_value('timestamp', timestamp, 'uint64') s.set_value('stream-time', timestamp, 'uint64') s.set_value('running-time', running_time, 'uint64') s.set_value('all-headers', True) s.set_value('count', self._count) return self.srcpad.push_event( - gst.event_new_custom(gst.EVENT_CUSTOM_DOWNSTREAM, s)) + Gst.event_new_custom(Gst.EVENT_CUSTOM_DOWNSTREAM, s)) def chainfunc(self, pad, buf): - if self.interval == 0 or buf.timestamp == gst.CLOCK_TIME_NONE: + if self.interval == 0 or buf.timestamp == Gst.CLOCK_TIME_NONE: pass elif self._last_ts == 0 or \ buf.timestamp >= self._last_ts + self.interval: @@ -93,10 +93,10 @@ def chainfunc(self, pad, buf): return self.srcpad.push(buf) def do_change_state(self, transition): - if transition == gst.STATE_CHANGE_PAUSED_TO_READY: + if transition == Gst.StateChange.PAUSED_TO_READY: self._last_ts = 0L self._count = 0 - return gst.Element.do_change_state(self, transition) + return Gst.Element.do_change_state(self, transition) def do_set_property(self, property, value): if property.name == 'interval': @@ -133,7 +133,7 @@ def get_kuscheduler(self, interval): if not gstreamer.element_factory_exists('keyunitsscheduler'): register() - kubin = gst.parse_bin_from_description('keyunitsscheduler interval=%s ' + kubin = Gst.parse_bin_from_description('keyunitsscheduler interval=%s ' 'name=scheduler' % interval, True) self._kuscheduler = kubin.get_by_name('scheduler') return kubin @@ -147,6 +147,25 @@ def effect_getInterval(self): def register(): - gobject.type_register(GstKeyUnitsScheduler) - gst.element_register(GstKeyUnitsScheduler, 'keyunitsscheduler', - gst.RANK_MARGINAL) + + def plugin_init(plugin, userarg): + name = plugin.get_name() + pluginType = GObject.type_register(userarg) + Gst.Element.register(plugin, name, Gst.Rank.MARGINAL, pluginType) + return True + + version = Gst.version() + + Gst.Plugin.register_static_full( + version[0], # GST_VERSION_MAJOR + version[1], # GST_VERSION_MINOR + 'keyunitsscheduler', + 'key units scheduler plugin', + plugin_init, + '12.06', + 'LGPL', + 'keyunitsscheduler', + 'keyunitsscheduler', + '', + GstKeyUnitsScheduler, + ) diff --git a/flumotion/component/effects/videorate/videorate.py b/flumotion/component/effects/videorate/videorate.py index b177d79b..b5ad76c1 100644 --- a/flumotion/component/effects/videorate/videorate.py +++ b/flumotion/component/effects/videorate/videorate.py @@ -15,45 +15,44 @@ # # Headers in this file shall remain intact. -from twisted.internet import reactor -import gobject -import gst +from gi.repository import GObject +from gi.repository import Gst -from flumotion.common.i18n import N_, gettexter from flumotion.component import feedcomponent - -# register serializables -from flumotion.common import messages +from flumotion.common import gstreamer __version__ = "$Rev$" -T_ = gettexter() -class VideorateBin(gst.Bin): +class VideorateBin(Gst.Bin): """ I am a GStreamer bin that can change the framerate of a video stream. """ logCategory = "videosrate" - CAPS_TEMPLATE = "video/x-raw-yuv%(fr)s;"\ - "video/x-raw-rgb%(fr)s" + CAPS_TEMPLATE = "video/x-raw%(fr)s;"\ + "video/x-raw%(fr)s" __gproperties__ = { - 'framerate': (gobject.TYPE_OBJECT, 'framerate', - 'Video framerate', gobject.PARAM_READWRITE)} + 'framerate': (GObject.TYPE_OBJECT, 'framerate', + 'Video framerate', GObject.PARAM_READWRITE)} - def __init__(self, framerate=gst.Fraction(25, 1)): - gst.Bin.__init__(self) + def __init__(self, framerate=Gst.Fraction(25, 1)): + Gst.Bin.__init__(self) self._framerate = framerate - self._videorate = gst.element_factory_make("videorate") - self._capsfilter = gst.element_factory_make("capsfilter") + self._videorate = Gst.ElementFactory.make("videorate") + self._capsfilter = Gst.ElementFactory.make("capsfilter") self.add(self._videorate, self._capsfilter) self._videorate.link(self._capsfilter) + # Set properties + if gstreamer.element_has_property(self._videorate, 'skip-to-first'): + self._videorate.set_property('skip-to-first', True) + # Create source and sink pads - self._sinkPad = gst.GhostPad('sink', self._videorate.get_pad('sink')) - self._srcPad = gst.GhostPad('src', self._capsfilter.get_pad('src')) + self._sinkPad = Gst.GhostPad.new('sink', self._videorate.get_static_pad('sink')) + self._srcPad = Gst.GhostPad.new('src', self._capsfilter.get_static_pad('src')) self.add_pad(self._sinkPad) self.add_pad(self._srcPad) @@ -62,7 +61,7 @@ def __init__(self, framerate=gst.Fraction(25, 1)): def _setFramerate(self, framerate): self._framerate = framerate self._capsfilter.set_property('caps', - gst.Caps(self.CAPS_TEMPLATE % dict(fr=self.framerateToString()))) + Gst.Caps(self.CAPS_TEMPLATE % dict(fr=self.framerateToString()))) def do_set_property(self, property, value): if property.name == 'framerate': diff --git a/flumotion/component/effects/videoscale/videoscale.py b/flumotion/component/effects/videoscale/videoscale.py index bcd7c3cd..37bc4824 100644 --- a/flumotion/component/effects/videoscale/videoscale.py +++ b/flumotion/component/effects/videoscale/videoscale.py @@ -16,8 +16,8 @@ # Headers in this file shall remain intact. from twisted.internet import reactor -import gobject -import gst +from gi.repository import GObject +from gi.repository import Gst from flumotion.common import errors, messages, gstreamer from flumotion.common.i18n import N_, gettexter @@ -28,35 +28,35 @@ T_ = gettexter() -class VideoscaleBin(gst.Bin): +class VideoscaleBin(Gst.Bin): """ I am a GStreamer bin that can scale a video stream from its source pad. """ logCategory = "videoscale" __gproperties__ = { - 'width': (gobject.TYPE_UINT, 'width', + 'width': (GObject.TYPE_UINT, 'width', 'Output width', - 1, 10000, 100, gobject.PARAM_READWRITE), - 'height': (gobject.TYPE_UINT, 'height', + 1, 10000, 100, GObject.PARAM_READWRITE), + 'height': (GObject.TYPE_UINT, 'height', 'Output height', - 1, 10000, 100, gobject.PARAM_READWRITE), - 'width-correction': (gobject.TYPE_UINT, 'width correction', + 1, 10000, 100, GObject.PARAM_READWRITE), + 'width-correction': (GObject.TYPE_UINT, 'width correction', 'Corrects with to be a multiple of this value', - 0, 64, 8, gobject.PARAM_READWRITE), - 'height-correction': (gobject.TYPE_UINT, 'height correction', + 0, 64, 8, GObject.PARAM_READWRITE), + 'height-correction': (GObject.TYPE_UINT, 'height correction', 'Corrects height to be a multiple of this value', - 0, 64, 0, gobject.PARAM_READWRITE), - 'is-square': (gobject.TYPE_BOOLEAN, 'PAR 1/1', + 0, 64, 0, GObject.PARAM_READWRITE), + 'is-square': (GObject.TYPE_BOOLEAN, 'PAR 1/1', 'Output with PAR 1/1', - False, gobject.PARAM_READWRITE), - 'add-borders': (gobject.TYPE_BOOLEAN, 'Add borders', + False, GObject.PARAM_READWRITE), + 'add-borders': (GObject.TYPE_BOOLEAN, 'Add borders', 'Add black borders to keep DAR if needed', - False, gobject.PARAM_READWRITE)} + False, GObject.PARAM_READWRITE)} def __init__(self, width, height, is_square, add_borders, width_correction=8, height_correction=0): - gst.Bin.__init__(self) + Gst.Bin.__init__(self) self._width = width self._height = height self._width_correction = width_correction @@ -68,10 +68,10 @@ def __init__(self, width, height, is_square, add_borders, self._inwidth = None self._inheight = None - self._identity = gst.element_factory_make("identity") - self._videoscaler = gst.element_factory_make("videoscale") - self._capsfilter = gst.element_factory_make("capsfilter") - self._videobox = gst.element_factory_make("videobox") + self._identity = Gst.ElementFactory.make("identity") + self._videoscaler = Gst.ElementFactory.make("videoscale") + self._capsfilter = Gst.ElementFactory.make("capsfilter") + self._videobox = Gst.ElementFactory.make("videobox") self.add(self._identity, self._videoscaler, self._capsfilter, self._videobox) @@ -80,33 +80,32 @@ def __init__(self, width, height, is_square, add_borders, self._capsfilter.link(self._videobox) # Create source and sink pads - self._sinkPad = gst.GhostPad('sink', self._identity.get_pad('sink')) - self._srcPad = gst.GhostPad('src', self._videobox.get_pad('src')) + self._sinkPad = Gst.GhostPad.new('sink', self._identity.get_static_pad('sink')) + self._srcPad = Gst.GhostPad.new('src', self._videobox.get_static_pad('src')) self.add_pad(self._sinkPad) self.add_pad(self._srcPad) self._configureOutput() self._identity.set_property('silent', True) - # Add the setcaps function in the sink pad - self._sinkPad.set_setcaps_function(self._sinkSetCaps) + # Add a callback for caps changes in the videoscaler source pad # to recalculate the scale correction - self._videoscaler.get_pad('src').connect( + self._videoscaler.get_static_pad('src').connect( 'notify::caps', self._applyScaleCorrection) def _updateFilter(self, blockPad): def unlinkAndReplace(pad, blocked): - self._videoscaler.set_state(gst.STATE_NULL) - self._capsfilter.set_state(gst.STATE_NULL) - self._videobox.set_state(gst.STATE_NULL) + self._videoscaler.set_state(Gst.State.NULL) + self._capsfilter.set_state(Gst.State.NULL) + self._videobox.set_state(Gst.State.NULL) self._configureOutput() - self._videobox.set_state(gst.STATE_PLAYING) - self._videoscaler.set_state(gst.STATE_PLAYING) - self._capsfilter.set_state(gst.STATE_PLAYING) + self._videobox.set_state(Gst.State.PLAYING) + self._videoscaler.set_state(Gst.State.PLAYING) + self._capsfilter.set_state(Gst.State.PLAYING) # unlink the sink and source pad of the old deinterlacer reactor.callFromThread(blockPad.set_blocked, False) @@ -126,9 +125,8 @@ def _configureOutput(self): p = "%s,width=(int)%d" % (p, self._width) if self._height: p = "%s,height=(int)%d" % (p, self._height) - p = "video/x-raw-yuv%s;video/x-raw-rgb%s" % (p, p) - self.info("out:%s" % p) - caps = gst.Caps(p) + p = "video/x-raw%s;video/x-raw%s" % (p, p) + caps = Gst.Caps(p) self._capsfilter.set_property("caps", caps) if gstreamer.element_has_property(self._videoscaler, 'add-borders'): @@ -142,7 +140,7 @@ def _applyScaleCorrection(self, pad, param): # to 4:3 or 16:9 # FIXME: Add the option to strech or reduce the image instead of # padding with a black line - c = pad.get_negotiated_caps() + c = pad.get_current_caps() if c is None: return diff --git a/flumotion/component/effects/volume/volume.py b/flumotion/component/effects/volume/volume.py index 2dd9eed9..ca16f3c9 100644 --- a/flumotion/component/effects/volume/volume.py +++ b/flumotion/component/effects/volume/volume.py @@ -64,8 +64,8 @@ def _bus_message_received_cb(self, bus, message): @param bus: the message bus sending the message @param message: the message received """ - if message.structure.get_name() == 'level': - s = message.structure + if message.get_structure().get_name() == 'level': + s = message.get_structure() peak = list(s['peak']) decay = list(s['decay']) rms = list(s['rms']) diff --git a/flumotion/component/encoders/dirac/dirac.py b/flumotion/component/encoders/dirac/dirac.py index 5afb8d61..ab54969e 100644 --- a/flumotion/component/encoders/dirac/dirac.py +++ b/flumotion/component/encoders/dirac/dirac.py @@ -47,7 +47,7 @@ def check_schroenc_bug(result, component): return d.addCallback(check_schroenc_bug, self) def get_pipeline_string(self, properties): - return "ffmpegcolorspace ! schroenc name=encoder" + return "videoconvert ! schroenc name=encoder" def configure_pipeline(self, pipeline, properties): element = pipeline.get_by_name('encoder') diff --git a/flumotion/component/encoders/lamemp3/Makefile.am b/flumotion/component/encoders/lamemp3/Makefile.am new file mode 100644 index 00000000..6258d054 --- /dev/null +++ b/flumotion/component/encoders/lamemp3/Makefile.am @@ -0,0 +1,10 @@ +include $(top_srcdir)/common/python.mk + +component_PYTHON = __init__.py lamemp3.py wizard_gtk.py +componentdir = $(libdir)/flumotion/python/flumotion/component/encoders/lamemp3 +component_DATA = lamemp3.xml + +clean-local: + rm -rf *.pyc *.pyo + +EXTRA_DIST = $(component_DATA) diff --git a/flumotion/component/encoders/lamemp3/__init__.py b/flumotion/component/encoders/lamemp3/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/flumotion/component/encoders/lamemp3/lamemp3.py b/flumotion/component/encoders/lamemp3/lamemp3.py new file mode 100644 index 00000000..5ce75c20 --- /dev/null +++ b/flumotion/component/encoders/lamemp3/lamemp3.py @@ -0,0 +1,33 @@ +# -*- Mode: Python -*- +# vi:si:et:sw=4:sts=4:ts=4 +# +# flumotion-ugly - components for the Flumotion streaming media server +# Copyright (C) 2010 Zaheer Abbas Merali +# Some portions may be Copyright (C) 2004-2010 Fluendo, S.L. (www.fluendo.com). +# All rights reserved. + +# This file may be distributed and/or modified under the terms of +# the GNU General Public License version 2 as published by +# the Free Software Foundation. +# This file is distributed without any warranty; without even the implied +# warranty of merchantability or fitness for a particular purpose. +# See "LICENSE.GPL" in the source distribution for more information. + +# Headers in this file shall remain intact. + +from flumotion.component import feedcomponent + +__version__ = "$Rev: 8561 $" + + +class LameMp3(feedcomponent.EncoderComponent): + + def get_pipeline_string(self, properties): + return 'audioconvert ! audioresample ! lamemp3enc name=encoder' + \ + ' ! mpegaudioparse' + + def configure_pipeline(self, pipeline, properties): + element = pipeline.get_by_name('encoder') + if 'bitrate' in properties: + element.set_property('target', 'bitrate') + element.set_property('bitrate', properties['bitrate'] / 1000) diff --git a/flumotion/component/encoders/lamemp3/lamemp3.xml b/flumotion/component/encoders/lamemp3/lamemp3.xml new file mode 100644 index 00000000..2192bf93 --- /dev/null +++ b/flumotion/component/encoders/lamemp3/lamemp3.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/flumotion/component/encoders/lamemp3/wizard_gtk.py b/flumotion/component/encoders/lamemp3/wizard_gtk.py new file mode 100644 index 00000000..3e165d5a --- /dev/null +++ b/flumotion/component/encoders/lamemp3/wizard_gtk.py @@ -0,0 +1,76 @@ +# -*- Mode: Python -*- +# vi:si:et:sw=4:sts=4:ts=4 +# +# flumotion-ugly - components for the Flumotion streaming media server +# Copyright (C) 2010 Zaheer Abbas Merali +# Some portions may be Copyright (C) 2004-2010 Fluendo, S.L. (www.fluendo.com). +# All rights reserved. + +# This file may be distributed and/or modified under the terms of +# the GNU General Public License version 2 as published by +# the Free Software Foundation. +# This file is distributed without any warranty; without even the implied +# warranty of merchantability or fitness for a particular purpose. +# See "LICENSE.GPL" in the source distribution for more information. + +# Headers in this file shall remain intact. + +import gettext + +from zope.interface import implements + +from flumotion.admin.assistant.interfaces import IEncoderPlugin +from flumotion.admin.assistant.models import AudioEncoder +from flumotion.admin.gtk.basesteps import AudioEncoderStep + +__version__ = "$Rev: 7268 $" +_ = gettext.gettext + + +class LameMp3AudioEncoder(AudioEncoder): + componentType = 'lamemp3-encoder' + + def __init__(self): + super(LameMp3AudioEncoder, self).__init__() + + self.properties.bitrate = 128 + + def getProperties(self): + properties = super(LameMp3AudioEncoder, self).getProperties() + properties.bitrate *= 1000 + return properties + + +class LameMp3Step(AudioEncoderStep): + name = 'Lame Mp3 encoder' + title = _('Lame Mp3 Encoder') + sidebarName = _('Lame mp3') + componentType = 'lamemp3' + docSection = 'help-configuration-assistant-encoder-lamemp3' + docAnchor = '' + docVersion = 'local' + + # WizardStep + + def setup(self): + self.bitrate.set_range(16, 256) + self.bitrate.set_value(128) + + self.bitrate.data_type = int + + self.add_proxy(self.model.properties, ['bitrate']) + + def workerChanged(self, worker): + self.model.worker = worker + self.wizard.requireElements(worker, 'lamemp3enc') + + +class LameMp3WizardPlugin(object): + implements(IEncoderPlugin) + + def __init__(self, wizard): + self.wizard = wizard + self.model = LameMp3AudioEncoder() + + def getConversionStep(self): + return LameMp3Step(self.wizard, self.model) diff --git a/flumotion/component/encoders/theora/theora.py b/flumotion/component/encoders/theora/theora.py index 40c51c42..da875e0f 100644 --- a/flumotion/component/encoders/theora/theora.py +++ b/flumotion/component/encoders/theora/theora.py @@ -46,7 +46,7 @@ def check_properties(self, props, addMessage): raise errors.ConfigError(msg) def get_pipeline_string(self, properties): - return "ffmpegcolorspace ! theoraenc name=encoder" + return "videoconvert ! theoraenc name=encoder" def configure_pipeline(self, pipeline, properties): element = pipeline.get_by_name('encoder') diff --git a/flumotion/component/encoders/voaac/Makefile.am b/flumotion/component/encoders/voaac/Makefile.am new file mode 100644 index 00000000..4f9ae584 --- /dev/null +++ b/flumotion/component/encoders/voaac/Makefile.am @@ -0,0 +1,10 @@ +include $(top_srcdir)/common/python.mk + +component_PYTHON = __init__.py voaac.py wizard_gtk.py +componentdir = $(libdir)/flumotion/python/flumotion/component/encoders/voaac +component_DATA = voaac.xml + +clean-local: + rm -rf *.pyc *.pyo + +EXTRA_DIST = $(component_DATA) diff --git a/flumotion/component/encoders/voaac/__init__.py b/flumotion/component/encoders/voaac/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/flumotion/component/encoders/voaac/voaac.py b/flumotion/component/encoders/voaac/voaac.py new file mode 100644 index 00000000..3a942e8f --- /dev/null +++ b/flumotion/component/encoders/voaac/voaac.py @@ -0,0 +1,31 @@ +# -*- Mode: Python -*- +# vi:si:et:sw=4:sts=4:ts=4 +# +# flumotion-ugly - components for the Flumotion streaming media server +# Copyright (C) 2010 Zaheer Abbas Merali +# Some portions may be Copyright (C) 2004-2010 Fluendo, S.L. (www.fluendo.com). +# All rights reserved. + +# This file may be distributed and/or modified under the terms of +# the GNU General Public License version 2 as published by +# the Free Software Foundation. +# This file is distributed without any warranty; without even the implied +# warranty of merchantability or fitness for a particular purpose. +# See "LICENSE.GPL" in the source distribution for more information. + +# Headers in this file shall remain intact. + +from flumotion.component import feedcomponent + +__version__ = "$Rev: 8561 $" + + +class Voaac(feedcomponent.EncoderComponent): + + def get_pipeline_string(self, properties): + return ("audioconvert ! audioresample ! voaacenc name=encoder") + + def configure_pipeline(self, pipeline, properties): + element = pipeline.get_by_name('encoder') + if 'bitrate' in properties: + element.set_property('bitrate', properties['bitrate']) diff --git a/flumotion/component/encoders/voaac/voaac.xml b/flumotion/component/encoders/voaac/voaac.xml new file mode 100644 index 00000000..92f95c9d --- /dev/null +++ b/flumotion/component/encoders/voaac/voaac.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/flumotion/component/encoders/voaac/wizard_gtk.py b/flumotion/component/encoders/voaac/wizard_gtk.py new file mode 100644 index 00000000..aa1f4c15 --- /dev/null +++ b/flumotion/component/encoders/voaac/wizard_gtk.py @@ -0,0 +1,79 @@ +# -*- Mode: Python -*- +# vi:si:et:sw=4:sts=4:ts=4 +# +# flumotion-ugly - components for the Flumotion streaming media server +# Copyright (C) 2010 Zaheer Abbas Merali +# Some portions may be Copyright (C) 2004-2010 Fluendo, S.L. (www.fluendo.com). +# All rights reserved. + +# This file may be distributed and/or modified under the terms of +# the GNU General Public License version 2 as published by +# the Free Software Foundation. +# This file is distributed without any warranty; without even the implied +# warranty of merchantability or fitness for a particular purpose. +# See "LICENSE.GPL" in the source distribution for more information. + +# Headers in this file shall remain intact. + +import gettext + +from zope.interface import implements + +from flumotion.admin.assistant.interfaces import IEncoderPlugin +from flumotion.admin.assistant.models import AudioEncoder +from flumotion.admin.gtk.basesteps import AudioEncoderStep + +__version__ = "$Rev: 7268 $" +_ = gettext.gettext + + +class VoaacAudioEncoder(AudioEncoder): + componentType = 'voaac-encoder' + + def __init__(self): + super(VoaacAudioEncoder, self).__init__() + + self.properties.bitrate = 128 + + def getProperties(self): + properties = super(VoaacAudioEncoder, self).getProperties() + properties.bitrate *= 1000 + return properties + + +class VoaacStep(AudioEncoderStep): + name = 'Voaac AAC encoder' + title = _('Voaac AAC Encoder') + sidebarName = _('Voaac AAC') + componentType = 'voaac' + docSection = 'help-configuration-assistant-encoder-voaac' + docAnchor = '' + docVersion = 'local' + + # WizardStep + + def setup(self): + self.bitrate.set_range(16, 256) + self.bitrate.set_value(128) + + self.bitrate.data_type = int + + self.add_proxy(self.model.properties, ['bitrate']) + + if self.wizard.getStep('Encoding').getMuxerFormat() == 'aac': + self.model.properties.adts = True + + def workerChanged(self, worker): + self.model.worker = worker + self.wizard.requireElements(worker, 'voaac') + + +class VoaacWizardPlugin(object): + implements(IEncoderPlugin) + + def __init__(self, wizard): + self.wizard = wizard + self.model = VoaacAudioEncoder() + + def getConversionStep(self): + return VoaacStep(self.wizard, self.model) diff --git a/flumotion/component/encoders/vorbis/vorbis.py b/flumotion/component/encoders/vorbis/vorbis.py index 60c013b9..6291efe8 100644 --- a/flumotion/component/encoders/vorbis/vorbis.py +++ b/flumotion/component/encoders/vorbis/vorbis.py @@ -15,7 +15,7 @@ # # Headers in this file shall remain intact. -import gst +from gi.repository import Gst from vorbis010 import Vorbis diff --git a/flumotion/component/encoders/vorbis/vorbis010.py b/flumotion/component/encoders/vorbis/vorbis010.py index cd569e8f..5bef21e5 100644 --- a/flumotion/component/encoders/vorbis/vorbis010.py +++ b/flumotion/component/encoders/vorbis/vorbis010.py @@ -15,7 +15,7 @@ # # Headers in this file shall remain intact. -import gst +from gi.repository import Gst from flumotion.common import gstreamer from flumotion.component import feedcomponent @@ -49,10 +49,10 @@ def get_pipeline_string(self, properties): if gstreamer.element_factory_exists('legacyresample'): resampler = 'legacyresample' return ('%s name=ar ! audioconvert ! capsfilter name=cf ' - '! vorbisenc name=enc' % resampler) + '! vorbisenc name=encoder' % resampler) def configure_pipeline(self, pipeline, properties): - enc = pipeline.get_by_name('enc') + enc = pipeline.get_by_name('encoder') cf = pipeline.get_by_name('cf') ar = pipeline.get_by_name('ar') @@ -63,13 +63,13 @@ def configure_pipeline(self, pipeline, properties): else: enc.set_property('quality', self.quality) - pad = ar.get_pad('sink') + pad = ar.get_static_pad('sink') handle = None - def buffer_probe(pad, buffer): + def buffer_probe(pad, buffer, user_data): # this comes from another thread - caps = buffer.get_caps() - in_rate = caps[0]['rate'] + caps = pad.get_current_caps() + in_rate = caps.get_structure(0).get_int('rate')[1] # now do necessary filtercaps self.rate = in_rate @@ -84,14 +84,14 @@ def buffer_probe(pad, buffer): in_rate, maxsamplerate, self.bitrate, self.rate)) - caps_str = 'audio/x-raw-float, rate=%d, channels=%d' % (self.rate, + caps_str = 'audio/x-raw, rate=%d, channels=%d' % (self.rate, self.channels) cf.set_property('caps', - gst.caps_from_string(caps_str)) - pad.remove_buffer_probe(handle) + Gst.Caps.from_string(caps_str)) + pad.remove_probe(handle) return True - handle = pad.add_buffer_probe(buffer_probe) + handle = pad.add_probe(Gst.PadProbeType.BUFFER, buffer_probe, None) def modify_property_Bitrate(self, value): if not self.checkPropertyType('bitrate', value, int): diff --git a/flumotion/component/encoders/vp8/vp8.py b/flumotion/component/encoders/vp8/vp8.py index d9b5260e..d70e9cac 100644 --- a/flumotion/component/encoders/vp8/vp8.py +++ b/flumotion/component/encoders/vp8/vp8.py @@ -46,16 +46,15 @@ def check_limit(prop_name, lower_limit, upper_limit): check_limit('threads', 1, 64) def get_pipeline_string(self, properties): - return "ffmpegcolorspace ! vp8enc name=encoder" + return "videoconvert ! vp8enc name=encoder" def configure_pipeline(self, pipeline, properties): element = pipeline.get_by_name('encoder') - props = (('bitrate', 'bitrate', 400), - ('quality', 'quality', None), - ('speed', 'speed', 2), + props = (('bitrate', 'target-bitrate', 400), + ('quality', 'cq-level', None), ('threads', 'threads', 4), - ('keyframe-maxdistance', 'max-keyframe-distance', 50)) + ('keyframe-maxdistance', 'keyframe-max-dist', 50)) for pproperty, eproperty, default in props: if eproperty is None: diff --git a/flumotion/component/encoders/x264/Makefile.am b/flumotion/component/encoders/x264/Makefile.am new file mode 100644 index 00000000..e0a9d2ae --- /dev/null +++ b/flumotion/component/encoders/x264/Makefile.am @@ -0,0 +1,10 @@ +include $(top_srcdir)/common/python.mk + +component_PYTHON = __init__.py x264.py wizard_gtk.py +componentdir = $(libdir)/flumotion/python/flumotion/component/encoders/x264 +component_DATA = x264.xml wizard.glade + +clean-local: + rm -rf *.pyc *.pyo + +EXTRA_DIST = $(component_DATA) diff --git a/flumotion/component/encoders/x264/__init__.py b/flumotion/component/encoders/x264/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/flumotion/component/encoders/x264/wizard.glade b/flumotion/component/encoders/x264/wizard.glade new file mode 100644 index 00000000..c1441095 --- /dev/null +++ b/flumotion/component/encoders/x264/wizard.glade @@ -0,0 +1,195 @@ + + + + + + + + True + vertical + 12 + + + True + 2 + 3 + 6 + 6 + + + True + 0 + kbit/s + + + 2 + 3 + + + + + + True + True + + 0 0 4000 1 10 10 + 1 + + + 1 + 2 + + + + + + + True + Bitrate: + + + GTK_FILL + + + + + + True + True + + 0 0 10 1 10 10 + 1 + + + 1 + 2 + 1 + 2 + + + + + + + + + + True + Quality: + + + 1 + 2 + GTK_FILL + + + + + + False + 0 + + + + + True + True + + + True + 2 + 3 + 6 + 6 + + + True + 0 + seconds + + + 2 + 3 + + + + + + True + True + A shorter time between key frames reduces playback latency at the cost of a higher bit rate or lower quality. + + 1.9999999552949999 0 60 0.039999999105899998 1 1 + 1 + 3 + True + + + 1 + 2 + GTK_SHRINK | GTK_FILL + + + + + + True + 0 + Time between _key frames: + True + + + GTK_FILL + + + + + + True + 0 + Option string: + True + + + 1 + 2 + GTK_FILL + + + + + + True + True + + 15 + + + 1 + 3 + 1 + 2 + + + + + + + + True + Advanced settings + + + label_item + + + + + False + 1 + + + + + + diff --git a/flumotion/component/encoders/x264/wizard_gtk.py b/flumotion/component/encoders/x264/wizard_gtk.py new file mode 100644 index 00000000..450e7f05 --- /dev/null +++ b/flumotion/component/encoders/x264/wizard_gtk.py @@ -0,0 +1,106 @@ +# -*- Mode: Python -*- +# vi:si:et:sw=4:sts=4:ts=4 +# +# flumotion-ugly - components for the Flumotion streaming media server +# Copyright (C) 2010 Zaheer Abbas Merali +# Some portions may be Copyright (C) 2004-2010 Fluendo, S.L. (www.fluendo.com). +# All rights reserved. + +# This file may be distributed and/or modified under the terms of +# the GNU General Public License version 2 as published by +# the Free Software Foundation. +# This file is distributed without any warranty; without even the implied +# warranty of merchantability or fitness for a particular purpose. +# See "LICENSE.GPL" in the source distribution for more information. + +# Headers in this file shall remain intact. + +import gettext +import os + +from zope.interface import implements + +from flumotion.admin.assistant.interfaces import IEncoderPlugin +from flumotion.admin.assistant.models import VideoEncoder +from flumotion.common.fraction import fractionAsFloat +from flumotion.admin.gtk.basesteps import VideoEncoderStep + +__version__ = "$Rev$" +_ = gettext.gettext + + +class X264VideoEncoder(VideoEncoder): + """ + @ivar framerate: number of frames per second; to be set by view + @type framerate: float + """ + componentType = 'x264-encoder' + + def __init__(self): + super(X264VideoEncoder, self).__init__() + self.framerate = 25.0 + + self.properties.keyframe_delta = 2.0 + self.properties.bitrate = 400 + self.properties.quality = 6 + + def getProperties(self): + properties = super(X264VideoEncoder, self).getProperties() + properties.bitrate *= 1000 + + # convert the human-friendly delta to maxdistance + properties.keyframe_maxdistance = int(properties.keyframe_delta * + self.framerate) + del properties.keyframe_delta + + return properties + + +class X264Step(VideoEncoderStep): + name = 'X264Encoder' + title = _('X264 H.264 Encoder') + sidebarName = _('X264 H.264 Encoder') + gladeFile = os.path.join(os.path.dirname(os.path.abspath(__file__)), + 'wizard.glade') + componentType = 'x264' + docSection = 'help-configuration-assistant-encoder-x264' + docAnchor = '' + docVersion = 'local' + + # WizardStep + + def setup(self): + self.bitrate.data_type = int + self.quality.data_type = int + self.keyframe_delta.data_type = float + self.option_string.data_type = str + + self.add_proxy(self.model.properties, + ['bitrate', 'quality', 'keyframe_delta', + 'option_string']) + + # we specify keyframe_delta in seconds, but x264 expects + # a number of frames, so we need the framerate and calculate + # we need to go through the Step (which is the view) because models + # don't have references to other models + producer = self.wizard.getScenario().getVideoProducer(self.wizard) + self.model.framerate = fractionAsFloat(producer.getFramerate()) + self.debug('Framerate of video producer: %r' % self.model.framerate) + step = 1 / self.model.framerate + page = 1.0 + self.keyframe_delta.set_increments(step, page) + + def workerChanged(self, worker): + self.model.worker = worker + self.wizard.requireElements(worker, 'x264enc') + + +class X264WizardPlugin(object): + implements(IEncoderPlugin) + + def __init__(self, wizard): + self.wizard = wizard + self.model = X264VideoEncoder() + + def getConversionStep(self): + return X264Step(self.wizard, self.model) diff --git a/flumotion/component/encoders/x264/x264.py b/flumotion/component/encoders/x264/x264.py new file mode 100644 index 00000000..9768951d --- /dev/null +++ b/flumotion/component/encoders/x264/x264.py @@ -0,0 +1,44 @@ +# -*- Mode: Python -*- +# vi:si:et:sw=4:sts=4:ts=4 +# +# flumotion-ugly - components for the Flumotion streaming media server +# Copyright (C) 2010 Zaheer Abbas Merali +# Some portions may be Copyright (C) 2004-2010 Fluendo, S.L. (www.fluendo.com). +# All rights reserved. + +# This file may be distributed and/or modified under the terms of +# the GNU General Public License version 2 as published by +# the Free Software Foundation. +# This file is distributed without any warranty; without even the implied +# warranty of merchantability or fitness for a particular purpose. +# See "LICENSE.GPL" in the source distribution for more information. + +# Headers in this file shall remain intact. + +from flumotion.common import messages +from flumotion.common.i18n import N_, gettexter +from flumotion.component import feedcomponent +from flumotion.worker.checks import check + + +__version__ = "$Rev$" +T_ = gettexter() + + +class X264(feedcomponent.EncoderComponent): + checkTimestamp = True + checkOffset = True + + def get_pipeline_string(self, properties): + return "videoconvert ! x264enc name=encoder %s ! video/x-h264, stream-format=(string)avc" % ( + properties.get('append-string', ''),) + + def configure_pipeline(self, pipeline, properties): + element = pipeline.get_by_name('encoder') + if 'bitrate' in properties: + element.set_property('bitrate', properties['bitrate'] / 1000) + if 'keyframe-maxdistance' in properties: + element.set_property('key-int-max', + properties['keyframe-maxdistance']) + if 'option-string' in properties: + element.set_property('option-string', properties['option-string']) diff --git a/flumotion/component/encoders/x264/x264.xml b/flumotion/component/encoders/x264/x264.xml new file mode 100644 index 00000000..cbeda0c1 --- /dev/null +++ b/flumotion/component/encoders/x264/x264.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/flumotion/component/feed.py b/flumotion/component/feed.py index 2f23ac2b..9646975b 100644 --- a/flumotion/component/feed.py +++ b/flumotion/component/feed.py @@ -135,8 +135,10 @@ def startConnecting(self, host, port, authenticator, timeout=30, """ assert self._factory is None self._factory = FeedClientFactory(self) - reactor.connectWith(PassableClientConnector, host, port, - self._factory, timeout, bindAddress) + c = PassableClientConnector(host, port, self._factory, timeout, + bindAddress, reactor=reactor) + c.connect() + return self._factory.login(authenticator) def requestFeed(self, host, port, authenticator, fullFeedId): diff --git a/flumotion/component/feedcomponent.py b/flumotion/component/feedcomponent.py index 08ad974b..7768a16c 100644 --- a/flumotion/component/feedcomponent.py +++ b/flumotion/component/feedcomponent.py @@ -21,9 +21,11 @@ import os -import gst -import gst.interfaces -import gobject +import gi +gi.require_version('Gst', '1.0') +from gi.repository import Gst +#import Gst.interfaces +from gi.repository import GObject from twisted.internet import reactor, defer from twisted.spread import pb @@ -98,12 +100,12 @@ def remote_setGstDebug(self, debug): if glob: try: # value has to be an integer - gst.debug_set_threshold_for_name(glob, value) + Gst.debug_set_threshold_for_name(glob, value) except TypeError: self.warning("Cannot set glob %s to value %s" % ( glob, value)) else: - gst.debug_set_default_threshold(value) + Gst.debug_set_default_threshold(value) self.comp.uiState.set('gst-debug', debug) @@ -338,8 +340,8 @@ def create_pipeline(self): self.pipeline_string = self.parse_pipeline(unparsed) try: - pipeline = gst.parse_launch(self.pipeline_string) - except gobject.GError, e: + pipeline = Gst.parse_launch(self.pipeline_string) + except GObject.GError, e: self.warning('Could not parse pipeline: %s' % e.message) m = messages.Error(T_(N_( "GStreamer error: could not parse component pipeline.")), @@ -486,14 +488,14 @@ def get_eater_srcpad(self, eaterAlias): Method that returns the source pad of the final element in an eater. @returns: the GStreamer source pad of the final element in an eater - @rtype: L{gst.Pad} + @rtype: L{Gst.Pad} """ e = self.eaters[eaterAlias] identity = self.get_element(e.elementName + '-identity') depay = self.get_element(e.depayName) - srcpad = depay.get_pad("src") + srcpad = depay.get_static_pad("src") if identity: - srcpad = identity.get_pad("src") + srcpad = identity.get_static_pad("src") return srcpad def get_feeder_sinkpad(self, feederAlias): @@ -501,7 +503,7 @@ def get_feeder_sinkpad(self, feederAlias): Method that returns the sink pad of the first element in a feeder @returns: the GStreamer sink pad of the first element in a feeder - @rtype: L{gst.Pad} + @rtype: L{Gst.Pad} """ e = self.feeders[feederAlias] gdppay = self.get_element(e.elementName + '-pay') @@ -602,12 +604,12 @@ def plug(self): peerSinkPad.unlink(peerSrcPad) # Add the deinterlacer bin to the pipeline - self.effectBin.set_state(gst.STATE_PLAYING) + self.effectBin.set_state(Gst.State.PLAYING) self.pipeline.add(self.effectBin) # link it with the element src pad and its peer's sink pad - peerSinkPad.link(self.effectBin.get_pad('sink')) - self.effectBin.get_pad('src').link(peerSrcPad) + peerSinkPad.link(self.effectBin.get_static_pad('sink')) + self.effectBin.get_static_pad('src').link(peerSrcPad) self.plugged = True @@ -676,14 +678,14 @@ def _underrun_cb(element): # Called from a streaming thread. The queue element does not hold # the queue lock when this is called, so we block our sinkpad, # then re-check the current level. - pad = element.get_pad("sink") - pad.set_blocked_async(True, _block_cb) + pad = element.get_static_pad("sink") + blocked_probe = pad.add_probe(Gst.PadProbeType.BLOCK, _block_cb, None) level = element.get_property("current-level-buffers") if level < self.QUEUE_SIZE_BUFFERS: element.set_property('max-size-buffers', self.QUEUE_SIZE_BUFFERS) element.disconnect(signalid) - pad.set_blocked_async(False, _block_cb) + pad.remove_probe(blocked_probe) signalid = queue.connect("underrun", _underrun_cb) @@ -727,7 +729,7 @@ def get_base_pipeline_string(self): def get_eater_srcpad(self, eaterAlias): e = self.eaters[eaterAlias] inputq = self.get_element('input-' + e.elementName) - return inputq.get_pad('src') + return inputq.get_static_pad('src') # Private methods @@ -740,7 +742,7 @@ def _install_changes_probes(self): # FIXME: Add documentation def output_reset_event(pad, event): - if event.type != gst.EVENT_FLUSH_START: + if event.type != Gst.EventType.FLUSH_START: return True self.debug('RESET: out reset event received on output pad %r', pad) @@ -759,7 +761,7 @@ def output_reset_event(pad, event): return False def got_new_caps(pad, args): - caps = pad.get_negotiated_caps() + caps = pad.get_current_caps() if not caps: self.debug("RESET: Caps unset! Looks like we're stopping") return @@ -786,7 +788,12 @@ def got_new_buffer(pad, buff, element): self.info("INCAPS: Got buffer but we're still disconnected.") return True - if not buff.flag_is_set(gst.BUFFER_FLAG_IN_CAPS): + buff = buff.get_buffer() + + ''' + #FIXME(aps-sids): Not able to figure out what this code is supposed to do + + if not buff.flag_is_set(Gst.BUFFER_FLAG_IN_CAPS): return True self.info("INCAPS: Got buffer with caps of len %d", buff.size) @@ -794,35 +801,41 @@ def got_new_buffer(pad, buff, element): newcaps = buff.caps[0].copy() resets = self.uiState.get('reset-count') newcaps['count'] = resets - buff.set_caps(gst.Caps(newcaps)) + buff.set_caps(Gst.Caps(newcaps)) + ''' return True self.log('RESET: installing event probes for detecting changes') # Listen for incoming flumotion-reset events on eaters for elem in self.get_input_elements(): self.debug('RESET: Add caps monitor for %s', elem.get_name()) - sink = elem.get_pad('sink') - sink.get_peer().add_buffer_probe(got_new_buffer, elem) + sink = elem.get_static_pad('sink') + sink.get_peer().add_probe(Gst.PadProbeType.BUFFER, + got_new_buffer, elem) sink.connect("notify::caps", got_new_caps) for elem in self.get_output_elements(): self.debug('RESET: adding event probe for %s', elem.get_name()) - elem.get_pad('sink').add_event_probe(output_reset_event) + elem.get_static_pad('sink').add_probe(Gst.PadProbeType.EVENT_BOTH, + output_reset_event) + global blocked_eater_probes + blocked_eater_probes = [] def _block_eaters(self): """ Function that blocks all the identities of the eaters """ for elem in self.get_input_elements(): - pad = elem.get_pad('src') - self.debug("RESET: Blocking pad %s", pad) - pad.set_blocked_async(True, self._on_eater_blocked) + pad = elem.get_static_pad('src') + self.debug("RESET: Blocking eater pad %s", pad) + blocked_eater_probes.append(pad.add_probe( + Gst.PadProbeType.BLOCK, self._on_eater_blocked, None)) def _unblock_eaters(self): - for elem in self.get_input_elements(): - pad = elem.get_pad('src') + for i, elem in enumerate(self.get_input_elements()): + pad = elem.get_static_pad('src') self.debug("RESET: Unblocking pad %s", pad) - pad.set_blocked_async(False, self._on_eater_blocked) + pad.remove_probe(blocked_eater_probes[i]) def _unlink_pads(self, element, directions): for pad in element.pads(): @@ -830,11 +843,11 @@ def _unlink_pads(self, element, directions): if not ppad: continue if (pad.get_direction() in directions and - pad.get_direction() == gst.PAD_SINK): + pad.get_direction() == Gst.PAD_SINK): self.debug('RESET: unlink %s with %s', pad, ppad) ppad.unlink(pad) elif (pad.get_direction() in directions and - pad.get_direction() == gst.PAD_SRC): + pad.get_direction() == Gst.PAD_SRC): self.debug('RESET: unlink %s with %s', pad, ppad) pad.unlink(ppad) @@ -858,7 +871,7 @@ def _remove_pipeline(self, pipeline, element, end, done=None): element.unlink(peer) self.log("RESET: removing old element %s from pipeline", element) - element.set_state(gst.STATE_NULL) + element.set_state(Gst.State.NULL) pipeline.remove(element) def _rebuild_pipeline(self): @@ -873,7 +886,7 @@ def _rebuild_pipeline(self): # Place a fakesrc element so we can know from where to start # rebuilding the pipeline. fake_pipeline = 'fakesrc name=start ! %s' % base_pipe - pipeline = gst.parse_launch(fake_pipeline) + pipeline = Gst.parse_launch(fake_pipeline) def move_element(element, orig, dest): if not element: @@ -893,7 +906,7 @@ def move_element(element, orig, dest): move_element(to_link[-1], orig, dest) - self._unlink_pads(element, [gst.PAD_SRC, gst.PAD_SINK]) + self._unlink_pads(element, [Gst.PAD_SRC, Gst.PAD_SINK]) orig.remove(element) dest.add(element) @@ -903,7 +916,7 @@ def move_element(element, orig, dest): element.link(peer) done = [] - start = pipeline.get_by_name('start').get_pad('src').get_peer() + start = pipeline.get_by_name('start').get_static_pad('src').get_peer() move_element(start.get_parent(), pipeline, self.pipeline) # Link eaters to the first element in the pipeline @@ -923,7 +936,7 @@ def move_element(element, orig, dest): done[-1].link(elem) self.configure_pipeline(self.pipeline, self.config['properties']) - self.pipeline.set_state(gst.STATE_PLAYING) + self.pipeline.set_state(Gst.State.PLAYING) self._unblock_eaters() resets = self.uiState.get('reset-count') @@ -935,13 +948,14 @@ def _on_pad_blocked(self, pad, blocked): self.log("RESET: Pad %s %s", pad, (blocked and "blocked") or "unblocked") - def _on_eater_blocked(self, pad, blocked): + def _on_eater_blocked(self, pad, blocked, *args): self._on_pad_blocked(pad, blocked) if blocked: peer = pad.get_peer() - peer.send_event(gst.event_new_flush_start()) - #peer.send_event(gst.event_new_eos()) - #self._unlink_pads(pad.get_parent(), [gst.PAD_SRC]) + # FIXME(aps-sids): Next line seems to create problems since it returns false + peer.send_event(Gst.Event.new_flush_start()) + #peer.send_event(Gst.event_new_eos()) + #self._unlink_pads(pad.get_parent(), [Gst.PAD_SRC]) def _on_pipeline_drained(self): self.debug('RESET: Proceed to unlink pipeline') @@ -949,7 +963,7 @@ def _on_pipeline_drained(self): end = self.get_output_elements() done = [] for element in start: - element = element.get_pad('src').get_peer().get_parent() + element = element.get_static_pad('src').get_peer().get_parent() self._remove_pipeline(self.pipeline, element, end, done) self._rebuild_pipeline() @@ -959,7 +973,22 @@ class EncoderComponent(ParseLaunchComponent): Component that is reconfigured when new changes arrive through the flumotion-reset event (sent by the fms producer). """ - pass + + def setup_completed(self): + ParseLaunchComponent.setup_completed(self) + + encoder = self.get_element('encoder') + encoder.get_static_pad('sink').add_probe(Gst.PadProbeType.EVENT_BOTH, + self.handle_reset_event, None) + + def handle_reset_event(self, pad, info, user_data): + event=info.get_event() + if gstreamer.event_is_flumotion_reset(event): + self.debug("Got reset event in the encoder... reseting it!") + encoder = self.get_element('encoder') + encoder.set_state(Gst.State.READY) + self.try_start_pipeline(force=True) + return True class MuxerComponent(MultiInputParseLaunchComponent): @@ -974,9 +1003,12 @@ class MuxerComponent(MultiInputParseLaunchComponent): def get_link_pad(self, muxer, srcpad, caps): return muxer.get_compatible_pad(srcpad, caps) - def buffer_probe_cb(self, pad, buffer, depay, eaterAlias): - pad = depay.get_pad("src") - caps = pad.get_negotiated_caps() + global blocked_probes + blocked_probes = [] + + def buffer_probe_cb(self, pad, buffer, (depay, eaterAlias)): + pad = depay.get_static_pad("src") + caps = pad.get_current_caps() if not caps: return False srcpad_to_link = self.get_eater_srcpad(eaterAlias) @@ -992,24 +1024,28 @@ def buffer_probe_cb(self, pad, buffer, depay, eaterAlias): self.addMessage(m) # this is the streaming thread, cannot set state here # so we do it in the mainloop - reactor.callLater(0, self.pipeline.set_state, gst.STATE_NULL) + reactor.callLater(0, self.pipeline.set_state, Gst.State.NULL) return True self.debug("Got link pad %r", linkpad) srcpad_to_link.link(linkpad) - depay.get_pad("src").remove_buffer_probe(self._probes[eaterAlias]) + depay.get_static_pad("src").remove_probe(self._probes[eaterAlias]) if srcpad_to_link.is_blocked(): self.is_blocked_cb(srcpad_to_link, True) else: - srcpad_to_link.set_blocked_async(True, self.is_blocked_cb) + blocked_probes.append(srcpad_to_link.add_probe( + Gst.PadProbeType.BLOCK, self.is_blocked_cb, eaterAlias)) return True - def event_probe_cb(self, pad, event, depay, eaterAlias): - caps = pad.get_negotiated_caps() + def event_probe_cb(self, pad, event, (depay, eaterAlias)): + event = event.get_event() + caps = pad.get_current_caps() if caps is None: return True # if this pad doesn't push audio, remove the probe if 'audio' not in caps[0].to_string(): - depay.get_pad("src").remove_buffer_probe(self._eprobes[eaterAlias]) + depay.get_static_pad("src").remove_probe(self._eprobes[eaterAlias]) + if event.get_structure() is None: + return True if event.get_structure().get_name() == 'GstForceKeyUnit': return False return True @@ -1022,28 +1058,29 @@ def configure_pipeline(self, pipeline, properties): # sink pads with input data # gone are the days when we know we only have one pad template in # muxers - self.fired_eaters = 0 + self.fired_eaters = [] self._probes = {} # depay element -> id self._eprobes = {} # depay element -> id for e in self.eaters: depay = self.get_element(self.eaters[e].depayName) self._probes[e] = \ - depay.get_pad("src").add_buffer_probe( - self.buffer_probe_cb, depay, e) + depay.get_static_pad("src").add_probe(Gst.PadProbeType.BUFFER, + self.buffer_probe_cb, (depay, e)) # Add an event probe to drop GstForceKeyUnit events # in audio pads if self.dropAudioKuEvents: self._eprobes[e] = \ - depay.get_pad("src").add_event_probe( - self.event_probe_cb, depay, e) + depay.get_static_pad("src").add_probe(Gst.PadProbeType.EVENT_BOTH, + self.event_probe_cb, (depay, e)) - def is_blocked_cb(self, pad, is_blocked): + def is_blocked_cb(self, pad, is_blocked, eaterAlias): if is_blocked: - self.fired_eaters = self.fired_eaters + 1 - if self.fired_eaters == len(self.eaters): + if eaterAlias not in self.fired_eaters: + self.fired_eaters.append(eaterAlias) + if len(self.fired_eaters) == len(self.eaters): self.debug("All pads are now blocked") self.disconnectedPads = False - for e in self.eaters: + for i, e in enumerate(self.eaters): srcpad = self.get_eater_srcpad(e) - srcpad.set_blocked_async(False, self.is_blocked_cb) + srcpad.remove_probe(blocked_probes[i]) diff --git a/flumotion/component/feedcomponent010.py b/flumotion/component/feedcomponent010.py index 5bea6278..b3be51d5 100644 --- a/flumotion/component/feedcomponent010.py +++ b/flumotion/component/feedcomponent010.py @@ -15,8 +15,9 @@ # # Headers in this file shall remain intact. -import gst -import gobject +from gi.repository import Gst +from gi.repository import GObject +from gi.repository import GstNet import os import time @@ -129,7 +130,7 @@ def do_setup(self): self.try_start_pipeline() # no race, messages marshalled asynchronously via the bus - d = self._change_monitor.add(gst.STATE_CHANGE_PAUSED_TO_PLAYING) + d = self._change_monitor.add(Gst.StateChange.PAUSED_TO_PLAYING) d.addCallback(lambda x: self.do_pipeline_playing()) def setup_completed(self): @@ -143,7 +144,7 @@ def create_pipeline(self): """ Subclasses have to implement this method. - @rtype: L{gst.Pipeline} + @rtype: L{Gst.Pipeline} """ raise NotImplementedError( "subclass must implement create_pipeline") @@ -164,15 +165,15 @@ def attachPadMonitorToFeeder(self, feederName): if not element: raise errors.ComponentError("No such feeder %s" % feederName) - pad = element.get_pad('src') + pad = element.get_static_pad('src') self._pad_monitors.attach(pad, "%s:%s" % (self.name, elementName)) def attachPadMonitorToElement(self, elementName, setActive=None, setInactive=None): element = self.pipeline.get_by_name(elementName) if not element: - raise error.ComponentError("No such element %s" % elementName) - pad = element.get_pad('src') + raise errors.ComponentError("No such element %s" % elementName) + pad = element.get_static_pad('src') name = "%s:%s" % (self.name, elementName) self._pad_monitors.attach(pad, name) @@ -226,7 +227,7 @@ def make_message_for_gstreamer_error(self, gerror, debug): @param gerror: The GError from the error message posted on the GStreamer message bus. - @type gerror: L{gst.GError} + @type gerror: L{Gst.GError} @param debug: A string with debugging information. @type debug: str @@ -249,8 +250,8 @@ def state_changed(): old, new, pending = message.parse_state_changed() self._change_monitor.state_changed(old, new) dump_filename = "%s.%s_%s" % (self.name, - gst.element_state_get_name(old), - gst.element_state_get_name(new)) + Gst.Element.state_get_name(old), + Gst.Element.state_get_name(new)) self.dump_gstreamer_debug_dot_file(dump_filename, True) def error(): @@ -270,7 +271,7 @@ def error(): self.__class__, msg)) self.state.append('messages', m) - self._change_monitor.have_error(self.pipeline.get_state(), + self._change_monitor.have_error(self.pipeline.get_state(0), message) def eos(): @@ -284,9 +285,9 @@ def eos(): def default(): self.log('message received: %r', message) - handlers = {gst.MESSAGE_STATE_CHANGED: state_changed, - gst.MESSAGE_ERROR: error, - gst.MESSAGE_EOS: eos} + handlers = {Gst.MessageType.STATE_CHANGED: state_changed, + Gst.MessageType.ERROR: error, + Gst.MessageType.EOS: eos} t = message.type src = message.src handlers.get(t, default)() @@ -305,33 +306,33 @@ def on_element_message(bus, message): name = src.get_name() if name in eaterWatchElements: eater = eaterWatchElements[name] - s = message.structure + s = message.get_structure() def timestampDiscont(): prevTs = s["prev-timestamp"] prevDuration = s["prev-duration"] curTs = s["cur-timestamp"] - if prevTs == gst.CLOCK_TIME_NONE: + if prevTs == Gst.CLOCK_TIME_NONE: self.debug("no previous timestamp") return - if prevDuration == gst.CLOCK_TIME_NONE: + if prevDuration == Gst.CLOCK_TIME_NONE: self.debug("no previous duration") return - if curTs == gst.CLOCK_TIME_NONE: + if curTs == Gst.CLOCK_TIME_NONE: self.debug("no current timestamp") return discont = curTs - (prevTs + prevDuration) - dSeconds = discont / float(gst.SECOND) + dSeconds = discont / float(Gst.SECOND) self.debug("we have a discont on eater %s of %.9f s " "between %s and %s ", eater.eaterAlias, dSeconds, - gst.TIME_ARGS(prevTs + prevDuration), - gst.TIME_ARGS(curTs)) + Gst.TIME_ARGS(prevTs + prevDuration), + Gst.TIME_ARGS(curTs)) eater.timestampDiscont(dSeconds, - float(curTs) / float(gst.SECOND)) + float(curTs) / float(Gst.SECOND)) def offsetDiscont(): prevOffsetEnd = s["prev-offset-end"] @@ -355,10 +356,10 @@ def offsetDiscont(): def install_eater_event_probes(self, eater): - def fdsrc_event(pad, event): + def fdsrc_event(pad, event, user_data): # An event probe used to consume unwanted EOS events on eaters. # Called from GStreamer threads. - if event.type == gst.EVENT_EOS: + if event.get_event().type == Gst.EventType.EOS: self.info('End of stream for eater %s, disconnect will be ' 'triggered', eater.eaterAlias) # We swallow it because otherwise our component acts on the EOS @@ -367,11 +368,11 @@ def fdsrc_event(pad, event): return False return True - def depay_event(pad, event): + def depay_event(pad, event, user_data): # An event probe used to consume unwanted duplicate # newsegment events. # Called from GStreamer threads. - if event.type == gst.EVENT_NEWSEGMENT: + if event.type == Gst.EventType.SEGMENT: # We do this because we know gdppay/gdpdepay screw up on 2nd # newsegments (unclear what the original reason for this # was, perhaps #349204) @@ -392,9 +393,11 @@ def depay_event(pad, event): self.debug('adding event probe for eater %s', eater.eaterAlias) fdsrc = self.get_element(eater.elementName) - fdsrc.get_pad("src").add_event_probe(fdsrc_event) + fdsrc.get_static_pad("src").add_probe(Gst.PadProbeType.EVENT_BOTH, + fdsrc_event, None) depay = self.get_element(eater.depayName) - depay.get_pad("src").add_event_probe(depay_event) + depay.get_static_pad("src").add_probe(Gst.PadProbeType.EVENT_BOTH, + depay_event, None) def _setup_pipeline(self): self.debug('setup_pipeline()') @@ -405,13 +408,13 @@ def _setup_pipeline(self): bus.add_signal_watch() self.bus_signal_id = bus.connect('message', self.bus_message_received_cb) - sig_id = self.pipeline.connect('deep-notify', + sig_id = self.pipeline.connect('deep-notify::pspec', gstreamer.verbose_deep_notify_cb, self) self.pipeline_signals.append(sig_id) # set to ready so that multifdsinks can always receive fds, even # if the pipeline has a delayed start due to clock slaving - self.pipeline.set_state(gst.STATE_READY) + self.pipeline.set_state(Gst.State.READY) # start checking feeders, if we have a sufficiently recent multifdsink if self._get_stats_supported: @@ -432,7 +435,7 @@ def _setup_pipeline(self): for eater in self.eaters.values(): self.install_eater_event_probes(eater) - pad = self.get_element(eater.elementName).get_pad('src') + pad = self.get_element(eater.elementName).get_static_pad('src') name = "%s:%s" % (self.name, eater.elementName) self._pad_monitors.attach(pad, name, padmonitor.EaterPadMonitor, @@ -447,8 +450,8 @@ def stop_pipeline(self): if self.clock_provider: self.clock_provider.set_property('active', False) self.clock_provider = None - retval = self.pipeline.set_state(gst.STATE_NULL) - if retval != gst.STATE_CHANGE_SUCCESS: + retval = self.pipeline.set_state(Gst.State.NULL) + if retval != Gst.StateChangeReturn.SUCCESS: self.warning('Setting pipeline to NULL failed') def cleanup(self): @@ -484,7 +487,7 @@ def do_stop(self): def set_master_clock(self, ip, port, base_time): self.debug("Master clock set to %s:%d with base_time %s", ip, port, - gst.TIME_ARGS(base_time)) + Gst.TIME_ARGS(base_time)) assert self._clock_slaved if self._master_clock_info == (ip, port, base_time): @@ -495,10 +498,10 @@ def set_master_clock(self, ip, port, base_time): self._master_clock_info = ip, port, base_time - clock = gst.NetClientClock(None, ip, port, base_time) + clock = GstNet.NetClientClock.new("clock", ip, port, base_time) # disable the pipeline's management of base_time -- we're going # to set it ourselves. - self.pipeline.set_new_stream_time(gst.CLOCK_TIME_NONE) + self.pipeline.set_start_time(Gst.CLOCK_TIME_NONE) self.pipeline.set_base_time(base_time) self.pipeline.use_clock(clock) @@ -527,13 +530,13 @@ def pipelinePaused(r): # make sure the pipeline sticks with this clock self.pipeline.use_clock(clock) - self.clock_provider = gst.NetTimeProvider(clock, None, port) + self.clock_provider = GstNet.NetTimeProvider.new(clock, None, port) realport = self.clock_provider.get_property('port') base_time = self.pipeline.get_base_time() self.debug('provided master clock from %r, base time %s', - clock, gst.TIME_ARGS(base_time)) + clock, Gst.TIME_ARGS(base_time)) if self.medium: # FIXME: This isn't always correct. We need a more @@ -549,9 +552,9 @@ def pipelinePaused(r): assert self.pipeline assert not self._clock_slaved (ret, state, pending) = self.pipeline.get_state(0) - if state != gst.STATE_PAUSED and state != gst.STATE_PLAYING: + if state != Gst.State.PAUSED and state != Gst.State.PLAYING: self.debug("pipeline still spinning up: %r", state) - d = self._change_monitor.add(gst.STATE_CHANGE_READY_TO_PAUSED) + d = self._change_monitor.add(Gst.StateChange.READY_TO_PAUSED) d.addCallback(pipelinePaused) return d elif self.clock_provider: @@ -570,11 +573,11 @@ def dump_gstreamer_debug_dot_file(self, filename, with_timestamp=False): @param with_timestamp: if True, then timestamp will be prepended to filename """ - if hasattr(gst, "DEBUG_BIN_TO_DOT_FILE"): - method = gst.DEBUG_BIN_TO_DOT_FILE + if hasattr(Gst, "DEBUG_BIN_TO_DOT_FILE"): + method = Gst.DEBUG_BIN_TO_DOT_FILE if with_timestamp: - method = gst.DEBUG_BIN_TO_DOT_FILE_WITH_TS - method(self.pipeline, gst.DEBUG_GRAPH_SHOW_ALL, filename) + method = Gst.DEBUG_BIN_TO_DOT_FILE_WITH_TS + method(self.pipeline, Gst.DEBUG_GRAPH_SHOW_ALL, filename) ### BaseComponent interface implementation @@ -585,7 +588,7 @@ def try_start_pipeline(self, force=False): eaters have received their file descriptor to eat from. """ (ret, state, pending) = self.pipeline.get_state(0) - if state == gst.STATE_PLAYING: + if state == Gst.State.PLAYING: self.log('already PLAYING') if not force: return @@ -602,7 +605,7 @@ def try_start_pipeline(self, force=False): return self.debug("Setting pipeline %r to GST_STATE_PLAYING", self.pipeline) - self.pipeline.set_state(gst.STATE_PLAYING) + self.pipeline.set_state(Gst.State.PLAYING) def _feeder_probe_calllater(self): for feedId, feeder in self.feeders.items(): @@ -611,7 +614,7 @@ def _feeder_probe_calllater(self): # a currently disconnected client will have fd None if client.fd is not None: array = feederElement.emit('get-stats', client.fd) - if len(array) == 0: + if array.n_fields() == 0: # There is an unavoidable race here: we can't know # whether the fd has been removed from multifdsink. # However, if we call get-stats on an fd that @@ -670,13 +673,13 @@ def get_element_property(self, element_name, property): raise errors.PropertyError(msg) # param enums and enums need to be returned by integer value - if isinstance(value, gobject.GEnum): + if isinstance(value, GObject.GEnum): value = int(value) return value def modify_element_property(self, element_name, property_name, value, - mutable_state=gst.STATE_READY, + mutable_state=Gst.State.READY, needs_reset=False): ''' Sets a property on the fly on a gstreamer element @@ -687,16 +690,16 @@ def modify_element_property(self, element_name, property_name, value, @type property_name: str @param value: Value to set @param mutable_state: Minimum state required to set the property - @type mutable_state: L{gst.Enum} + @type mutable_state: L{Gst.Enum} @param needs_reset: Whether setting this property requires sending a 'flumotion-reset' event @type needs_reset: bool ''' def drop_stream_headers(pad, buf): - if buf.flag_is_set(gst.BUFFER_FLAG_IN_CAPS): + if buf.flag_is_set(Gst.BUFFER_FLAG_IN_CAPS): return False - pad.remove_buffer_probe(probes[pad]) + pad.remove_probe(probes[pad]) return True probes = {} @@ -711,9 +714,9 @@ def drop_stream_headers(pad, buf): # get the peer pad for each sink pad sink_pads = [p.get_peer() for p in element.pads() - if p.get_direction() == gst.PAD_SINK] + if p.get_direction() == Gst.PAD_SINK] src_pads = [p for p in element.pads() - if p.get_direction() == gst.PAD_SRC] + if p.get_direction() == Gst.PAD_SRC] # Iterate over all the sink pads and block them for pad in sink_pads: @@ -742,7 +745,8 @@ def drop_stream_headers(pad, buf): # re-sending the headers again) else: for pad in src_pads: - probes[pad] = pad.add_buffer_probe(drop_stream_headers) + probes[pad] = pad.add_probe(Gst.PadProbeType.BUFFER, + drop_stream_headers, None) if state > mutable_state: element.set_state(state) @@ -790,7 +794,7 @@ def feedToFD(self, feedName, fd, cleanup, eaterId=None): # We must have a pipeline in READY or above to do this. Do a # non-blocking (zero timeout) get_state. if (not self.pipeline or - self.pipeline.get_state(0)[1] == gst.STATE_NULL): + self.pipeline.get_state(0)[1] == Gst.State.NULL): self.warning('told to feed %s to fd %d, but pipeline not ' 'running yet', feedName, fd) cleanup(fd) @@ -853,7 +857,7 @@ def eatFromFD(self, eaterAlias, feedId, fd): # fdsrc only switches to the new fd in ready or below (result, current, pending) = element.get_state(0L) - pipeline_playing = current not in [gst.STATE_NULL, gst.STATE_READY] + pipeline_playing = current not in [Gst.State.NULL, Gst.State.READY] if pipeline_playing: self.debug('eater %s in state %r, kidnapping it', eaterAlias, current) @@ -864,17 +868,18 @@ def eatFromFD(self, eaterAlias, feedId, fd): # To do this safely, we first block fdsrc:src, then let the # component do any neccesary unlocking (needed for multi-input # elements) - srcpad = element.get_pad('src') + srcpad = element.get_static_pad('src') - def _block_cb(pad, blocked): + def _block_cb(pad, blocked, user_data): pass - srcpad.set_blocked_async(True, _block_cb) + blocked_probe = srcpad.add_probe(Gst.PadProbeType.BLOCK, _block_cb, None) + # add buffer probe to drop buffers that are flagged as IN_CAPS # needs to be done to gdpdepay's src pad depay = self.get_element(eater.depayName) def remove_in_caps_buffers(pad, buffer, eater): - if buffer.flag_is_set(gst.BUFFER_FLAG_IN_CAPS): + if buffer.flag_is_set(Gst.BUFFER_FLAG_IN_CAPS): self.info("We got streamheader buffer which we are " "dropping because we do not want this just " "after a reconnect because it breaks " @@ -888,7 +893,7 @@ def remove_in_caps_buffers(pad, buffer, eater): if eater.streamheaderBufferProbeHandler: self.log("Removing buffer probe on depay src pad on " "eater %r", eater) - pad.remove_buffer_probe( + pad.remove_probe( eater.streamheaderBufferProbeHandler) eater.streamheaderBufferProbeHandler = None else: @@ -905,8 +910,8 @@ def remove_in_caps_buffers(pad, buffer, eater): self.log("Adding buffer probe on depay src pad on " "eater %r", eater) eater.streamheaderBufferProbeHandler = \ - depay.get_pad("src").add_buffer_probe( - remove_in_caps_buffers, eater) + depay.get_static_pad("src").add_probe( + Gst.PadProbeType.BUFFER, remove_in_caps_buffers, eater) self.unblock_eater(eaterAlias) @@ -916,7 +921,7 @@ def remove_in_caps_buffers(pad, buffer, eater): parent = element.get_parent() parent.remove(element) self.log("setting to ready") - element.set_state(gst.STATE_READY) + element.set_state(Gst.State.READY) self.log("setting to ready complete!!!") old = element.get_property('fd') self.log("Closing old fd %d", old) @@ -924,9 +929,9 @@ def remove_in_caps_buffers(pad, buffer, eater): element.set_property('fd', fd) parent.add(element) srcpad.link(sinkpad) - element.set_state(gst.STATE_PLAYING) + element.set_state(Gst.State.PLAYING) # We're done; unblock the pad - srcpad.set_blocked_async(False, _block_cb) + srcpad.remove_probe(blocked_probe) else: element.set_property('fd', fd) diff --git a/flumotion/component/feeder.py b/flumotion/component/feeder.py index 52754995..c1c8befe 100644 --- a/flumotion/component/feeder.py +++ b/flumotion/component/feeder.py @@ -17,7 +17,9 @@ import time -import gst +import gi +gi.require_version('Gst', '1.0') +from gi.repository import Gst from twisted.internet import reactor @@ -141,16 +143,16 @@ def __init__(self, clientId): def setStats(self, stats): """ - @type stats: list + @type stats: GstStructure """ - bytesSent = stats[0] - #timeAdded = stats[1] - #timeRemoved = stats[2] - #timeActive = stats[3] - timeLastActivity = float(stats[4]) / gst.SECOND - if len(stats) > 5: + bytesSent = stats["bytes-sent"] + #timeAdded = stats["connect-time"] + #timeRemoved = stats["disconnect-time"] + #timeActive = stats["connect-duration"] + timeLastActivity = float(stats["last-activitity-time"]) / Gst.SECOND + if stats.n_fields() > 5: # added in gst-plugins-base 0.10.11 - buffersDropped = stats[5] + buffersDropped = stats["buffers-dropped"] else: # We don't know, but we cannot use None # since that would break integer addition below diff --git a/flumotion/component/misc/httpserver/httpserver.py b/flumotion/component/misc/httpserver/httpserver.py index c83b9430..5b167470 100644 --- a/flumotion/component/misc/httpserver/httpserver.py +++ b/flumotion/component/misc/httpserver/httpserver.py @@ -408,8 +408,9 @@ def do_setup(self): self._pbclient.startLogin(creds, self._pbclient.medium) self.info("Logging to porter on socketPath %s", self._porterPath) # This will eventually cause d to fire - reactor.connectWith(fdserver.FDConnector, self._porterPath, - self._pbclient, 10, checkPID=False) + c = fdserver.FDConnector(self._porterPath, self._pbclient, 10, + checkPID=False, reactor=reactor) + c.connect() else: # File Streamer is standalone. try: @@ -500,8 +501,9 @@ def _updatePath(self, path): self._pbclient.stopTrying() self._pbclient.resetDelay() - reactor.connectWith(fdserver.FDConnector, self._porterPath, - self._pbclient, 10, checkPID=False) + c = fdserver.FDConnector(self._porterPath, self._pbclient, 10, + checkPID=False, reactor=reactor) + c.connect() def _timeoutRequests(self): self._timeoutRequestsCallLater = None @@ -651,7 +653,7 @@ def getUrl(self): def getStreamData(self): socket = 'flumotion.component.plugs.streamdata.StreamDataProviderPlug' - if self.plugs[socket]: + if socket in self.plugs: plug = self.plugs[socket][-1] return plug.getStreamData() else: diff --git a/flumotion/component/misc/httpserver/ourmimetypes.py b/flumotion/component/misc/httpserver/ourmimetypes.py index 617d91d5..0e546d4d 100644 --- a/flumotion/component/misc/httpserver/ourmimetypes.py +++ b/flumotion/component/misc/httpserver/ourmimetypes.py @@ -29,6 +29,8 @@ def loadMimeTypes(): d['.flv'] = 'video/x-flv' d['.mp4'] = 'video/mp4' d['.webm'] = 'video/webm' + d['.ts'] = 'video/MP2T' + d['.m3u8'] = 'application/vnd.apple.mpegurl' return d def __init__(self): diff --git a/flumotion/component/misc/porter/porter.py b/flumotion/component/misc/porter/porter.py index 58a30e01..d76c4851 100644 --- a/flumotion/component/misc/porter/porter.py +++ b/flumotion/component/misc/porter/porter.py @@ -323,9 +323,15 @@ def do_setup(self): except OSError: pass - self._socketlistener = reactor.listenWith( - fdserver.FDPort, self._socketPath, - serverfactory, mode=self._socketMode) + # listenWith is deprecated but the function never did much anyway + # + # self._socketlistener = reactor.listenWith( + # fdserver.FDPort, self._socketPath, + # serverfactory, mode=self._socketMode) + self._socketlistener = fdserver.FDPort(self._socketPath, + serverfactory, reactor=reactor, mode=self._socketMode) + self._socketlistener.startListening() + self.info("Now listening on socketPath %s", self._socketPath) except error.CannotListenError: self.warning("Failed to create socket %s" % self._socketPath) @@ -350,9 +356,9 @@ def do_setup(self): # appropriate protocol (HTTP, RTSP, etc.) factory = PorterProtocolFactory(self, proto) try: - reactor.listenWith( - fdserver.PassableServerPort, self._port, factory, - interface=self._interface) + p = fdserver.PassableServerPort(self._port, factory, + interface=self._interface, reactor=reactor) + p.startListening() self.info("Now listening on interface %r on port %d", self._interface, self._port) except error.CannotListenError: diff --git a/flumotion/component/muxers/Makefile.am b/flumotion/component/muxers/Makefile.am index 7b0bbf3d..3faee87d 100644 --- a/flumotion/component/muxers/Makefile.am +++ b/flumotion/component/muxers/Makefile.am @@ -6,6 +6,8 @@ component_PYTHON = \ checks.py \ webm.py \ multipart.py \ + mp4.py \ + mkv.py \ ogg.py \ wizard_gtk.py diff --git a/flumotion/component/muxers/gstflv/Makefile.am b/flumotion/component/muxers/gstflv/Makefile.am new file mode 100644 index 00000000..c208a035 --- /dev/null +++ b/flumotion/component/muxers/gstflv/Makefile.am @@ -0,0 +1,11 @@ +include $(top_srcdir)/common/python.mk + +component_PYTHON = __init__.py gstflv.py wizard_gtk.py +componentdir = $(libdir)/flumotion/python/flumotion/component/muxers/gstflv +component_DATA = \ + gstflvmuxer.xml + +clean-local: + rm -rf *.pyc *.pyo + +EXTRA_DIST = $(component_DATA) diff --git a/flumotion/component/muxers/gstflv/__init__.py b/flumotion/component/muxers/gstflv/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/flumotion/component/muxers/gstflv/gstflv.py b/flumotion/component/muxers/gstflv/gstflv.py new file mode 100644 index 00000000..e3c472e3 --- /dev/null +++ b/flumotion/component/muxers/gstflv/gstflv.py @@ -0,0 +1,36 @@ +# -*- Mode: Python -*- +# vi:si:et:sw=4:sts=4:ts=4 +# +# Flumotion - a streaming media server +# Copyright (C) 2004,2005,2006,2007 Fluendo, S.L. (www.fluendo.com). +# All rights reserved. + +# This file may be distributed and/or modified under the terms of +# the GNU General Public License version 2 as published by +# the Free Software Foundation. +# This file is distributed without any warranty; without even the implied +# warranty of merchantability or fitness for a particular purpose. +# See "LICENSE.GPL" in the source distribution for more information. + +# Licensees having purchased or holding a valid Flumotion Advanced +# Streaming Server license may use this file in accordance with the +# Flumotion Advanced Streaming Server Commercial License Agreement. +# See "LICENSE.Flumotion" in the source distribution for more information. + +# Headers in this file shall remain intact. + +from flumotion.common import messages +from flumotion.common.i18n import N_, gettexter +from flumotion.component import feedcomponent +from flumotion.worker.checks import check + +__version__ = "$Rev$" +T_ = gettexter() + + +class GstFlv(feedcomponent.MuxerComponent): + checkTimestamp = True + + def get_muxer_string(self, properties): + muxer = 'flvmux name=muxer streamable=true' + return muxer diff --git a/flumotion/component/muxers/gstflv/gstflvmuxer.xml b/flumotion/component/muxers/gstflv/gstflvmuxer.xml new file mode 100644 index 00000000..58608635 --- /dev/null +++ b/flumotion/component/muxers/gstflv/gstflvmuxer.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/flumotion/component/muxers/gstflv/wizard_gtk.py b/flumotion/component/muxers/gstflv/wizard_gtk.py new file mode 100644 index 00000000..2b714940 --- /dev/null +++ b/flumotion/component/muxers/gstflv/wizard_gtk.py @@ -0,0 +1,26 @@ +# -*- Mode: Python -*- +# vi:si:et:sw=4:sts=4:ts=4 +# +# Flumotion - a streaming media server +# Copyright (C) 2004,2005,2006,2007,2008 Fluendo, S.L. (www.fluendo.com). +# All rights reserved. + +# This file may be distributed and/or modified under the terms of +# the GNU General Public License version 2 as published by +# the Free Software Foundation. +# This file is distributed without any warranty; without even the implied +# warranty of merchantability or fitness for a particular purpose. +# See "LICENSE.GPL" in the source distribution for more information. + +# Headers in this file shall remain intact. + +"""Wizard plugin for the ogg, multipart and webm muxers +""" + +from flumotion.component.muxers import base + +__version__ = "$Rev$" + + +class GstFlvWizardPlugin(base.MuxerPlugin): + requirements = ['flvmux'] diff --git a/flumotion/component/muxers/lamemp3/Makefile.am b/flumotion/component/muxers/lamemp3/Makefile.am new file mode 100644 index 00000000..f3ed3998 --- /dev/null +++ b/flumotion/component/muxers/lamemp3/Makefile.am @@ -0,0 +1,11 @@ +include $(top_srcdir)/common/python.mk + +component_PYTHON = __init__.py lamemp3.py +componentdir = $(libdir)/flumotion/python/flumotion/component/muxers/lamemp3 +component_DATA = \ + lamemp3.xml + +clean-local: + rm -rf *.pyc *.pyo + +EXTRA_DIST = $(component_DATA) diff --git a/flumotion/component/muxers/lamemp3/__init__.py b/flumotion/component/muxers/lamemp3/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/flumotion/component/muxers/lamemp3/lamemp3.py b/flumotion/component/muxers/lamemp3/lamemp3.py new file mode 100644 index 00000000..d0f0ecb5 --- /dev/null +++ b/flumotion/component/muxers/lamemp3/lamemp3.py @@ -0,0 +1,31 @@ +# -*- Mode: Python -*- +# vi:si:et:sw=4:sts=4:ts=4 +# +# Flumotion - a streaming media server +# Copyright (C) 2004,2005,2006,2007 Fluendo, S.L. (www.fluendo.com). +# All rights reserved. + +# This file may be distributed and/or modified under the terms of +# the GNU General Public License version 2 as published by +# the Free Software Foundation. +# This file is distributed without any warranty; without even the implied +# warranty of merchantability or fitness for a particular purpose. +# See "LICENSE.GPL" in the source distribution for more information. + +# Licensees having purchased or holding a valid Flumotion Advanced +# Streaming Server license may use this file in accordance with the +# Flumotion Advanced Streaming Server Commercial License Agreement. +# See "LICENSE.Flumotion" in the source distribution for more information. + +# Headers in this file shall remain intact. + +from flumotion.component import feedcomponent + +__version__ = "$Rev$" + + +class LameMp3Muxer(feedcomponent.MultiInputParseLaunchComponent): + checkOffset = True + + def get_muxer_string(self, properties): + return 'identity silent=true name=muxer' diff --git a/flumotion/component/muxers/lamemp3/lamemp3.xml b/flumotion/component/muxers/lamemp3/lamemp3.xml new file mode 100644 index 00000000..b17f6e3b --- /dev/null +++ b/flumotion/component/muxers/lamemp3/lamemp3.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/flumotion/component/muxers/mkv.py b/flumotion/component/muxers/mkv.py new file mode 100644 index 00000000..489e9664 --- /dev/null +++ b/flumotion/component/muxers/mkv.py @@ -0,0 +1,39 @@ +# -*- Mode: Python -*- +# vi:si:et:sw=4:sts=4:ts=4 + +# Flumotion - a streaming media server +# Copyright (C) 2004,2005,2006,2007,2008,2009 Fluendo, S.L. +# Copyright (C) 2010,2011 Flumotion Services, S.A. +# All rights reserved. +# +# This file may be distributed and/or modified under the terms of +# the GNU Lesser General Public License version 2.1 as published by +# the Free Software Foundation. +# This file is distributed without any warranty; without even the implied +# warranty of merchantability or fitness for a particular purpose. +# See "LICENSE.LGPL" in the source distribution for more information. +# +# Headers in this file shall remain intact. + +import gi +gi.require_version('Gst', '1.0') +from gi.repository import Gst +from twisted.internet import defer + +from flumotion.component import feedcomponent +from flumotion.worker.checks import check + +__version__ = "$Rev$" + + +class MKV(feedcomponent.MuxerComponent): + checkTimestamp = True + + def do_check(self): + return check.do_check(self, check.checkPlugin, 'matroska', + 'gst-plugins-good', (0, 10, 24)) + + def get_muxer_string(self, properties): + muxer = 'matroskamux name=muxer streamable=true' + + return muxer diff --git a/flumotion/component/muxers/mp4.py b/flumotion/component/muxers/mp4.py new file mode 100644 index 00000000..f003100b --- /dev/null +++ b/flumotion/component/muxers/mp4.py @@ -0,0 +1,36 @@ +# -*- Mode: Python -*- +# vi:si:et:sw=4:sts=4:ts=4 + +# Flumotion - a streaming media server +# Copyright (C) 2004,2005,2006,2007,2008,2009 Fluendo, S.L. +# Copyright (C) 2010,2011 Flumotion Services, S.A. +# All rights reserved. +# +# This file may be distributed and/or modified under the terms of +# the GNU Lesser General Public License version 2.1 as published by +# the Free Software Foundation. +# This file is distributed without any warranty; without even the implied +# warranty of merchantability or fitness for a particular purpose. +# See "LICENSE.LGPL" in the source distribution for more information. +# +# Headers in this file shall remain intact. + +from flumotion.common import messages +from flumotion.common.i18n import N_, gettexter +from flumotion.component import feedcomponent +from flumotion.worker.checks import check + +__version__ = "$Rev$" +T_ = gettexter() + + +class MP4(feedcomponent.MuxerComponent): + checkTimestamp = True + + def do_check(self): + return check.do_check(self, check.checkPlugin, 'isomp4', + 'gst-plugins-ugly', (0, 10, 24)) + + def get_muxer_string(self, properties): + muxer = 'mp4mux name=muxer streamable=true' + return muxer diff --git a/flumotion/component/muxers/muxers.xml b/flumotion/component/muxers/muxers.xml index d8dd5c10..6f9480a0 100644 --- a/flumotion/component/muxers/muxers.xml +++ b/flumotion/component/muxers/muxers.xml @@ -63,6 +63,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -85,6 +129,8 @@ + + diff --git a/flumotion/component/muxers/ogg.py b/flumotion/component/muxers/ogg.py index 26da0571..e2e01339 100644 --- a/flumotion/component/muxers/ogg.py +++ b/flumotion/component/muxers/ogg.py @@ -15,7 +15,9 @@ # # Headers in this file shall remain intact. -import gst +import gi +gi.require_version('Gst', '1.0') +from gi.repository import Gst from twisted.internet import defer from flumotion.component import feedcomponent diff --git a/flumotion/component/muxers/voaac/Makefile.am b/flumotion/component/muxers/voaac/Makefile.am new file mode 100644 index 00000000..bdaa0bf2 --- /dev/null +++ b/flumotion/component/muxers/voaac/Makefile.am @@ -0,0 +1,11 @@ +include $(top_srcdir)/common/python.mk + +component_PYTHON = __init__.py voaac.py +componentdir = $(libdir)/flumotion/python/flumotion/component/muxers/voaac +component_DATA = \ + voaac.xml + +clean-local: + rm -rf *.pyc *.pyo + +EXTRA_DIST = $(component_DATA) diff --git a/flumotion/component/muxers/voaac/__init__.py b/flumotion/component/muxers/voaac/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/flumotion/component/muxers/voaac/voaac.py b/flumotion/component/muxers/voaac/voaac.py new file mode 100644 index 00000000..f4a661b5 --- /dev/null +++ b/flumotion/component/muxers/voaac/voaac.py @@ -0,0 +1,31 @@ +# -*- Mode: Python -*- +# vi:si:et:sw=4:sts=4:ts=4 +# +# Flumotion - a streaming media server +# Copyright (C) 2004,2005,2006,2007 Fluendo, S.L. (www.fluendo.com). +# All rights reserved. + +# This file may be distributed and/or modified under the terms of +# the GNU General Public License version 2 as published by +# the Free Software Foundation. +# This file is distributed without any warranty; without even the implied +# warranty of merchantability or fitness for a particular purpose. +# See "LICENSE.GPL" in the source distribution for more information. + +# Licensees having purchased or holding a valid Flumotion Advanced +# Streaming Server license may use this file in accordance with the +# Flumotion Advanced Streaming Server Commercial License Agreement. +# See "LICENSE.Flumotion" in the source distribution for more information. + +# Headers in this file shall remain intact. + +from flumotion.component import feedcomponent + +__version__ = "$Rev$" + + +class VOAACMuxer(feedcomponent.MultiInputParseLaunchComponent): + checkOffset = True + + def get_muxer_string(self, properties): + return 'identity silent=true name=muxer' diff --git a/flumotion/component/muxers/voaac/voaac.xml b/flumotion/component/muxers/voaac/voaac.xml new file mode 100644 index 00000000..ec7b991d --- /dev/null +++ b/flumotion/component/muxers/voaac/voaac.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/flumotion/component/muxers/wizard_gtk.py b/flumotion/component/muxers/wizard_gtk.py index 9d6f7e61..c068baa9 100644 --- a/flumotion/component/muxers/wizard_gtk.py +++ b/flumotion/component/muxers/wizard_gtk.py @@ -15,7 +15,7 @@ # # Headers in this file shall remain intact. -"""Wizard plugin for the ogg, multipart and webm muxers +"""Wizard plugin for the ogg, multipart, webm, mp4 and mkv muxers """ from flumotion.component.muxers import base @@ -33,3 +33,10 @@ class MultipartWizardPlugin(base.MuxerPlugin): class WebMWizardPlugin(base.MuxerPlugin): requirements = ['webmmux'] + + +class MP4WizardPlugin(base.MuxerPlugin): + requirements = ['mp4mux'] + +class MKVWizardPlugin(base.MuxerPlugin): + requirements = ['matroskamux'] diff --git a/flumotion/component/padmonitor.py b/flumotion/component/padmonitor.py index ab3070ab..572798ed 100644 --- a/flumotion/component/padmonitor.py +++ b/flumotion/component/padmonitor.py @@ -17,12 +17,16 @@ import time -import gst +import gi +gi.require_version('Gst', '1.0') +from gi.repository import Gst from twisted.internet import reactor, defer from flumotion.common import log from flumotion.common.poller import Poller +Gst.init(None) + __version__ = "$Rev$" @@ -39,7 +43,7 @@ class PadMonitor(log.Loggable): def __init__(self, pad, name, setActive, setInactive): """ - @type pad: L{gst.Pad} + @type pad: L{Gst.Pad} @type name: str @param setActive: a callable that will be called when the pad is considered active, taking the name of the monitor. @@ -90,13 +94,13 @@ def detach(self): # actually return d, probe_id = self._probe_id.pop("id", (None, None)) if probe_id: - self._pad.remove_buffer_probe(probe_id) + self._pad.remove_probe(probe_id) d.callback(None) def _probe_timeout(self): # called every so often to install a probe callback - def probe_cb(pad, buffer): + def probe_cb(pad, buffer, user_data): """ Periodically scheduled buffer probe, that ensures that we're currently actually having dataflow through our eater @@ -104,19 +108,19 @@ def probe_cb(pad, buffer): Called from GStreamer threads. - @param pad: The gst.Pad srcpad for one eater in this + @param pad: The Gst.Pad srcpad for one eater in this component. - @param buffer: A gst.Buffer that has arrived on this pad + @param buffer: A Gst.Buffer that has arrived on this pad """ self._last_data_time = time.time() self.logMessage('buffer probe on %s has timestamp %s', self.name, - gst.TIME_ARGS(buffer.timestamp)) + Gst.TIME_ARGS(buffer.get_buffer().pts)) deferred, probe_id = self._probe_id.pop("id", (None, None)) if probe_id: # This will be None only if detach() has been called. - self._pad.remove_buffer_probe(probe_id) + self._pad.remove_probe(probe_id) reactor.callFromThread(deferred.callback, None) # Data received! Return to happy ASAP: @@ -130,7 +134,8 @@ def probe_cb(pad, buffer): d = defer.Deferred() # FIXME: this is racy: evaluate RHS, drop GIL, buffer probe # fires before __setitem__ in LHS; need a mutex - self._probe_id['id'] = (d, self._pad.add_buffer_probe(probe_cb)) + self._probe_id['id'] = (d, self._pad.add_probe( + Gst.PadProbeType.BUFFER, probe_cb, None)) return d def _check_timeout(self): diff --git a/flumotion/component/producers/audiotest/audiotest.py b/flumotion/component/producers/audiotest/audiotest.py index b0b972ff..fc652856 100644 --- a/flumotion/component/producers/audiotest/audiotest.py +++ b/flumotion/component/producers/audiotest/audiotest.py @@ -15,7 +15,7 @@ # # Headers in this file shall remain intact. -import gst +from gi.repository import Gst from twisted.internet import defer @@ -74,7 +74,7 @@ def get_pipeline_string(self, properties): return ('%s name=source wave=%s %s ! ' \ 'identity name=identity silent=TRUE ! ' \ - 'audio/x-raw-int,rate=%d ! ' \ + 'audio/x-raw,rate=%d ! ' \ 'volume name=volume volume=%f ! level name=level' % (source, wave, is_live, samplerate, volume)) diff --git a/flumotion/component/producers/checks.py b/flumotion/component/producers/checks.py index a3d74bea..801991ff 100644 --- a/flumotion/component/producers/checks.py +++ b/flumotion/component/producers/checks.py @@ -46,24 +46,9 @@ def checkTicket347(): processing (mostly affects soundcard, firewire) """ result = messages.Result() - import pygtk - pygtk.require('2.0') - import gobject - # Really, we want to check for pygobject_version, but that doesn't exist in - # all versions of pygtk, and this check is sufficient. - (major, minor, nano) = gobject.pygtk_version - if (major, minor, nano) < (2, 8, 6): - m = messages.Warning(T_( - N_("Version %d.%d.%d of the PyGTK library contains " - "a memory leak.\n"), - major, minor, nano), - mid='ticket-347') - m.add(T_(N_("The Soundcard and Firewire sources may leak a lot of " - "memory as a result, and would need to be restarted " - "frequently.\n"))) - m.add(T_(N_("Please upgrade '%s' to version %s or later."), - 'pygtk', '2.8.6')) - result.add(m) + import gi + gi.require_version('Gtk', '3.0') + from gi.repository import GObject result.succeed(None) return defer.succeed(result) @@ -71,21 +56,7 @@ def checkTicket347(): def checkTicket348(): result = messages.Result() - import pygst - pygst.require('0.10') - import gst - (major, minor, nano) = gst.pygst_version - if (major, minor, nano) < (0, 10, 3): - m = messages.Warning(T_( - N_("Version %d.%d.%d of the gst-python library contains " - "a large memory leak.\n"), - major, minor, nano), - mid='ticket-348') - m.add(T_(N_("The Soundcard and Firewire sources may leak a lot of " - "memory as a result, and need to be restarted frequently.\n"))) - m.add(T_(N_("Please upgrade '%s' to version %s or later."), - 'gst-python', '0.10.3')) - result.add(m) + from gi.repository import Gst result.succeed(None) return defer.succeed(result) diff --git a/flumotion/component/producers/dvswitch/dvswitch.py b/flumotion/component/producers/dvswitch/dvswitch.py index 64650075..ce084b69 100644 --- a/flumotion/component/producers/dvswitch/dvswitch.py +++ b/flumotion/component/producers/dvswitch/dvswitch.py @@ -19,7 +19,7 @@ # Headers in this file shall remain intact. -import gst +from gi.repository import Gst from twisted.internet import defer from flumotion.common import errors, messages, gstreamer @@ -89,7 +89,7 @@ def get_pipeline_string(self, props): fr = props.get('framerate', None) if fr is not None: - self.framerate = gst.Fraction(fr[0], fr[1]) + self.framerate = Gst.Fraction(fr[0], fr[1]) else: self.framerate = None @@ -97,15 +97,18 @@ def get_pipeline_string(self, props): # replace it with videotestsrc of the same size and PAR, so we can # unittest the pipeline # need a queue in case tcpserversink blocks somehow - template = ('dvswitchsrc %s' + template = ('dvswitchsrc name=src %s' + ' ! typefind' + ' ! tee name=t' ' ! queue leaky=2 max-size-time=1000000000' ' ! dvdemux name=demux' ' demux. ! queue ! %s name=decoder' ' ! @feeder:video@' - ' demux. ! queue ! audio/x-raw-int ' + ' demux. ! queue ! audio/x-raw ' ' ! volume name=setvolume' ' ! level name=volumelevel message=true ' - ' ! @feeder:audio@' % (uri, decoder)) + ' ! @feeder:audio@' + ' t. ! queue ! @feeder:dv@' % (uri, decoder)) return template @@ -132,18 +135,18 @@ def configure_pipeline(self, pipeline, properties): decoder.set_property('drop-factor', drop_factor) vr = videorate.Videorate('videorate', - decoder.get_pad("src"), pipeline, self.framerate) + decoder.get_static_pad("src"), pipeline, self.framerate) self.addEffect(vr) vr.plug() deinterlacer = deinterlace.Deinterlace('deinterlace', - vr.effectBin.get_pad("src"), pipeline, + vr.effectBin.get_static_pad("src"), pipeline, self.deintMode, self.deintMethod) self.addEffect(deinterlacer) deinterlacer.plug() videoscaler = videoscale.Videoscale('videoscale', self, - deinterlacer.effectBin.get_pad("src"), pipeline, + deinterlacer.effectBin.get_static_pad("src"), pipeline, self.width, self.height, self.is_square, self.add_borders) self.addEffect(videoscaler) videoscaler.plug() @@ -151,8 +154,8 @@ def configure_pipeline(self, pipeline, properties): # Setting a tolerance of 20ms should be enough (1/2 frame), but # we set it to 40ms to be more conservatives ar = audioconvert.Audioconvert('audioconvert', - comp_level.get_pad("src"), - pipeline, tolerance=40 * gst.MSECOND) + comp_level.get_static_pad("src"), + pipeline, tolerance=40 * Gst.MSECOND) self.addEffect(ar) ar.plug() diff --git a/flumotion/component/producers/fgdp/fgdp.py b/flumotion/component/producers/fgdp/fgdp.py index 970aa828..f54489a8 100644 --- a/flumotion/component/producers/fgdp/fgdp.py +++ b/flumotion/component/producers/fgdp/fgdp.py @@ -15,7 +15,7 @@ # # Headers in this file shall remain intact. -import gst +from gi.repository import Gst from flumotion.component import feedcomponent from flumotion.common import gstreamer diff --git a/flumotion/component/producers/icecast/icecast.py b/flumotion/component/producers/icecast/icecast.py index 8fb94935..0e211a6f 100644 --- a/flumotion/component/producers/icecast/icecast.py +++ b/flumotion/component/producers/icecast/icecast.py @@ -107,6 +107,7 @@ def configure_pipeline(self, pipeline, properties): self.reconnecting = False self.reconnector = RetryingDeferred(self.connect) self.reconnector.initialDelay = 1.0 + self.reconnector.maxDelay = 300 self.attemptD = None def _drop_eos(pad, event): @@ -194,5 +195,6 @@ def _src_disconnected(self, name): def _retry(self): assert self.attemptD - self.debug('Retrying connection to icecast server on %s', self.url) - self.attemptD.errback(errors.ConnectionError) + if not self.attemptD.called: + self.debug('Retrying connection to icecast server on %s', self.url) + self.attemptD.errback(errors.ConnectionError) diff --git a/flumotion/component/producers/videotest/videotest.py b/flumotion/component/producers/videotest/videotest.py index 667839d9..c2c74983 100644 --- a/flumotion/component/producers/videotest/videotest.py +++ b/flumotion/component/producers/videotest/videotest.py @@ -15,7 +15,7 @@ # # Headers in this file shall remain intact. -import gst +from gi.repository import Gst from flumotion.common import errors, gstreamer, messages from flumotion.common.i18n import N_, gettexter @@ -39,31 +39,31 @@ def init(self): self.uiState.addKey('pattern', 0) def get_pipeline_string(self, properties): - capsString = properties.get('format', 'video/x-raw-yuv') + capsString = properties.get('format', 'video/x-raw') - if capsString == 'video/x-raw-yuv': - capsString = '%s,format=(fourcc)I420' % capsString + if capsString == 'video/x-raw': + capsString = '%s,format=(string)I420' % capsString # Filtered caps - struct = gst.structure_from_string(capsString) + struct = Gst.structure_from_string(capsString)[0] for k in 'width', 'height': if k in properties: - struct[k] = properties[k] + struct.set_value(k, properties[k]) if 'framerate' in properties: framerate = properties['framerate'] - struct['framerate'] = gst.Fraction(framerate[0], framerate[1]) + struct.set_value('framerate', Gst.Fraction(framerate[0]/framerate[1])) # always set par - struct['pixel-aspect-ratio']= gst.Fraction(1, 1) + struct.set_value('pixel-aspect-ratio', Gst.Fraction(1/1)) if 'pixel-aspect-ratio' in properties: par = properties['pixel-aspect-ratio'] - struct['pixel-aspect-ratio'] = gst.Fraction(par[0], par[1]) + struct.set_value('pixel-aspect-ratio', Gst.Fraction(par[0]/par[1])) # If RGB, set something ffmpegcolorspace can convert. if capsString == 'video/x-raw-rgb': - struct['red_mask'] = 0xff00 - caps = gst.Caps(struct) + struct.set_value('red_mask', 0xff00) + caps = Gst.Caps.from_string(struct.to_string()) is_live = 'is-live=true' diff --git a/flumotion/extern/Makefile.am b/flumotion/extern/Makefile.am index 87b1a1dc..c2817b23 100644 --- a/flumotion/extern/Makefile.am +++ b/flumotion/extern/Makefile.am @@ -3,7 +3,7 @@ include $(top_srcdir)/common/python.mk all-local: command command: - svn co http://thomas.apestaart.org/moap/svn/trunk/moap/extern/command + svn co http://thomas.apestaart.org/moap/svn/trunk/moap/extern/command@509 svn info command > command.revision command/__init__.py: command diff --git a/flumotion/extern/log/log.py b/flumotion/extern/log/log.py index 6672e52d..1f8411e3 100644 --- a/flumotion/extern/log/log.py +++ b/flumotion/extern/log/log.py @@ -882,7 +882,13 @@ def getFailureMessage(failure): if len(failure.frames) == 0: return "failure %(exc)s: %(msg)s" % locals() - (func, filename, line, some, other) = failure.frames[-1] + # when using inlineCallbacks, a traceback coming from unwindGenerator + # is actually provoked one frame down + for frame in failure.frames[::-1]: + (func, filename, line, some, other) = frame + if func not in ['unwindGenerator', ]: + break + filename = scrubFilename(filename) return "failure %(exc)s at %(filename)s:%(line)s: %(func)s(): %(msg)s" \ % locals() diff --git a/flumotion/job/main.py b/flumotion/job/main.py index 41096432..2241df05 100644 --- a/flumotion/job/main.py +++ b/flumotion/job/main.py @@ -52,8 +52,9 @@ def main(args): log.info('job', 'Connecting to worker on socket %s' % (socket)) job_factory = job.JobClientFactory(avatarId) - reactor.connectWith(fdserver.FDConnector, socket, job_factory, - 10, checkPID=False) + c = fdserver.FDConnector(socket, job_factory, 10, checkPID=False, + reactor=reactor) + c.connect() reactor.addSystemEventTrigger('before', 'shutdown', job_factory.medium.shutdownHandler) diff --git a/flumotion/monitor/nagios/stream.py b/flumotion/monitor/nagios/stream.py index 60c71756..9962621b 100644 --- a/flumotion/monitor/nagios/stream.py +++ b/flumotion/monitor/nagios/stream.py @@ -29,9 +29,6 @@ import urllib2 import cookielib -import gst -import gobject - from twisted.internet import reactor, defer from flumotion.admin import admin, connections @@ -404,22 +401,31 @@ class GstInfo: error = None def __init__(self, uri, options, tmpfile): + + # if we import gst globally, it overrides our --help output + import gobject + import gst + + self.gobject = gobject + self.gst = gst + PIPELINE = '%s ! tee name = t ! \ queue ! decodebin name=dbin t. ! \ queue ! filesink location=%s' % (uri, tmpfile) if options.timeout: - gobject.timeout_add(int(options.timeout) * 1000, self.timedOut) + self.gobject.timeout_add(int(options.timeout) * 1000, + self.timedOut) self.duration = options.duration - self.mainloop = gobject.MainLoop() - self.pipeline = gst.parse_launch(PIPELINE) + self.mainloop = self.gobject.MainLoop() + self.pipeline = self.gst.parse_launch(PIPELINE) self.dbin = self.pipeline.get_by_name('dbin') self.bus = self.pipeline.get_bus() self.bus.add_watch(self.onBusMessage) self.dbin.connect('new-decoded-pad', self.demux_pad_added) def run(self): - self.pipeline.set_state(gst.STATE_PLAYING) + self.pipeline.set_state(self.gst.STATE_PLAYING) self.mainloop.run() return self.have_audio, self.have_video, self.info, self.error @@ -427,14 +433,14 @@ def run(self): def launch_eos(self): if self.have_audio and self.have_video: if self.audio_done and self.video_done: - self.bus.post(gst.message_new_eos(self.pipeline)) + self.bus.post(self.gst.message_new_eos(self.pipeline)) else: if self.audio_done or self.video_done: - self.bus.post(gst.message_new_eos(self.pipeline)) + self.bus.post(self.gst.message_new_eos(self.pipeline)) def get_audio_info_cb(self, sink, buffer, pad): '''Callback to get audio info''' - timestamp = buffer.timestamp / gst.SECOND + timestamp = buffer.timestamp / self.gst.SECOND if not self.audio_ts: self.audio_ts = timestamp if (self.audio_ts + self.duration) < timestamp: @@ -449,7 +455,7 @@ def get_audio_info_cb(self, sink, buffer, pad): def get_video_info_cb(self, sink, buffer, pad): '''Callback to get video info''' - timestamp = buffer.timestamp / gst.SECOND + timestamp = buffer.timestamp / self.gst.SECOND if not self.video_ts: self.video_ts = timestamp if (self.video_ts + self.duration) < timestamp: @@ -507,37 +513,38 @@ def demux_pad_added(self, element, pad, bool): stream_type = structure.get_name() if stream_type.startswith('video'): self.have_video = True - colorspace = gst.element_factory_make('ffmpegcolorspace') + colorspace = self.gst.element_factory_make('ffmpegcolorspace') self.pipeline.add(colorspace) - colorspace.set_state(gst.STATE_PLAYING) + colorspace.set_state(self.gst.STATE_PLAYING) pad.link(colorspace.get_pad('sink')) - self.video = gst.element_factory_make('fakesink') + self.video = self.gst.element_factory_make('fakesink') self.video.props.signal_handoffs = True self.pipeline.add(self.video) - self.video.set_state(gst.STATE_PLAYING) + self.video.set_state(self.gst.STATE_PLAYING) colorspace.link(self.video) self.video_cb = self.video.connect('handoff', self.get_video_info_cb) elif stream_type.startswith('audio'): self.have_audio = True - self.audio = gst.element_factory_make('fakesink') + self.audio = self.gst.element_factory_make('fakesink') self.audio.props.signal_handoffs = True self.pipeline.add(self.audio) - self.audio.set_state(gst.STATE_PLAYING) + self.audio.set_state(self.gst.STATE_PLAYING) pad.link(self.audio.get_pad('sink')) self.audio_cb = self.audio.connect('handoff', self.get_audio_info_cb) def quit(self): self.get_mime() - self.pipeline.set_state(gst.STATE_NULL) + self.pipeline.set_state(self.gst.STATE_NULL) self.pipeline.get_state() self.mainloop.quit() def onBusMessage(self, bus, message): - if message.src == self.pipeline and message.type == gst.MESSAGE_EOS: + if message.src == self.pipeline \ + and message.type == self.gst.MESSAGE_EOS: self.quit() - elif message.type == gst.MESSAGE_ERROR: + elif message.type == self.gst.MESSAGE_ERROR: self.error = message.parse_error() self.mainloop.quit() return True diff --git a/flumotion/test/__init__.py b/flumotion/test/__init__.py index 610e68fd..a04e4b00 100644 --- a/flumotion/test/__init__.py +++ b/flumotion/test/__init__.py @@ -24,18 +24,18 @@ from flumotion.common import log -def useGtk2Reactor(): - var = 'FLU_TEST_GTK2_REACTOR' +def useGtk3Reactor(): + var = 'FLU_TEST_GTK3_REACTOR' if var not in os.environ: return False else: return True -if useGtk2Reactor(): - log.info('check', 'using gtk2 reactor') - from twisted.internet import gtk2reactor - gtk2reactor.install() +if useGtk3Reactor(): + log.info('check', 'using gtk3 reactor') + from twisted.internet import gtk3reactor + gtk3reactor.install() else: log.info('check', 'using default reactor') @@ -65,4 +65,4 @@ def useGtk2Reactor(): fdpass.__path__.append(os.path.join(top_builddir, 'flumotion', 'extern', 'fdpass')) -del boot, flumotion, i, log, useGtk2Reactor +del boot, flumotion, i, log, useGtk3Reactor diff --git a/flumotion/test/comptest.py b/flumotion/test/comptest.py index d51c9f97..773bed4e 100644 --- a/flumotion/test/comptest.py +++ b/flumotion/test/comptest.py @@ -19,7 +19,7 @@ import os from twisted.python import failure -from twisted.internet import reactor, defer, interfaces, gtk2reactor +from twisted.internet import reactor, defer, interfaces, gtk3reactor from flumotion.common import registry, log, testsuite, common from flumotion.common.planet import moods @@ -80,7 +80,7 @@ def call_and_passthru_callback(result, callable_, *args, **kwargs): class CompTestTestCase(testsuite.TestCase): - supportedReactors = [gtk2reactor.Gtk2Reactor] + supportedReactors = [gtk3reactor.Gtk3Reactor] logCategory = 'comptest-test' diff --git a/flumotion/test/test_common_gstreamer.py b/flumotion/test/test_common_gstreamer.py index f075a454..c94d1181 100644 --- a/flumotion/test/test_common_gstreamer.py +++ b/flumotion/test/test_common_gstreamer.py @@ -15,11 +15,13 @@ # # Headers in this file shall remain intact. -import gst +import gi +gi.require_version('Gst', '1.0') +from gi.repository import Gst from twisted.trial import unittest - from flumotion.common import gstreamer +Gst.init(None) class Factory(unittest.TestCase): @@ -33,16 +35,16 @@ def testFakeSrc(self): class Caps(unittest.TestCase): def testCaps(self): - caps = gst.caps_from_string( - 'video/x-raw-yuv,width=10,framerate=5.0;video/x-raw-rgb,' + caps = Gst.caps_from_string( + 'video/x-raw,width=10,framerate=5.0;video/x-raw,' 'width=15,framerate=10.0') self.assertEquals(gstreamer.caps_repr(caps), - 'video/x-raw-yuv, width=(int)10, ' - 'framerate=(double)5; video/x-raw-rgb, ' + 'video/x-raw, width=(int)10, ' + 'framerate=(double)5; video/x-raw, ' 'width=(int)15, framerate=(double)10') def testCapsStreamheader(self): - caps = gst.caps_from_string('application/ogg,streamheader=abcd') + caps = Gst.caps_from_string('application/ogg,streamheader=abcd') self.assertEquals(gstreamer.caps_repr(caps), 'streamheader=<...>') @@ -54,16 +56,16 @@ def debug(self, string): def run_it_a_little_while(p): - p.set_state(gst.STATE_PLAYING) - m = p.get_bus().poll(gst.MESSAGE_EOS, -1) - p.set_state(gst.STATE_NULL) + p.set_state(Gst.State.PLAYING) + m = p.get_bus().poll(Gst.MESSAGE_EOS, -1) + p.set_state(Gst.State.NULL) class DeepNotify(unittest.TestCase): def testDeepNotify(self): component = FakeComponent() - pipeline = gst.parse_launch('fakesrc num-buffers=3 ! fakesink') + pipeline = Gst.parse_launch('fakesrc num-buffers=3 ! fakesink') pipeline.connect('deep-notify', gstreamer.verbose_deep_notify_cb, component) diff --git a/flumotion/test/test_common_pygobject.py b/flumotion/test/test_common_pygobject.py index 4c05c18e..919c6809 100644 --- a/flumotion/test/test_common_pygobject.py +++ b/flumotion/test/test_common_pygobject.py @@ -15,8 +15,8 @@ # # Headers in this file shall remain intact. -import gobject -import gtk +from gi.repository import GObject +from gi.repository import Gtk from flumotion.common import testsuite from flumotion.common import errors, pygobject @@ -26,7 +26,7 @@ class SetProperty(testsuite.TestCase): def testButton(self): - b = gtk.Button() + b = Gtk.Button() # string pygobject.gobject_set_property(b, 'name', 'button') @@ -53,13 +53,13 @@ class TestPyGObject(testsuite.TestCase): def testPyGObject(self): - class Foo(gobject.GObject): + class Foo(GObject.GObject): gsignal('hcf', bool, str) gproperty(bool, 'burning', 'If the object is burning', False) def __init__(xself): - gobject.GObject.__init__(xself) + GObject.GObject.__init__(xself) xself.connect('hcf', xself.on_hcf) xself.set_property('burning', False) @@ -67,7 +67,7 @@ def on_hcf(xself, again_self, x, y): self.assert_(isinstance(x, bool)) self.assert_(isinstance(y, str)) xself.set_property('burning', True) - gobject.type_register(Foo) + GObject.type_register(Foo) o = Foo() diff --git a/flumotion/test/test_component_feedcomponent.py b/flumotion/test/test_component_feedcomponent.py index 592cc938..492f7af6 100644 --- a/flumotion/test/test_component_feedcomponent.py +++ b/flumotion/test/test_component_feedcomponent.py @@ -18,7 +18,7 @@ from flumotion.common.testsuite import TestCase from flumotion.component import feedcomponent from flumotion.component.eater import Eater -from twisted.internet import defer, gtk2reactor +from twisted.internet import defer, gtk3reactor class FakeMuxerComponent(feedcomponent.MuxerComponent): @@ -47,7 +47,7 @@ def connectEater(self, eaterAlias): class TestFeedComponentMedium(TestCase): - supportedReactors = [gtk2reactor.Gtk2Reactor] + supportedReactors = [gtk3reactor.Gtk3Reactor] def setUp(self): config = {} @@ -100,7 +100,7 @@ def test1277(self): class TestMuxer(TestCase): - supportedReactors = [gtk2reactor.Gtk2Reactor] + supportedReactors = [gtk3reactor.Gtk3Reactor] def setup(self): self.fakecomp = None diff --git a/flumotion/test/test_component_feeder.py b/flumotion/test/test_component_feeder.py index 9c62a6db..8635b7b2 100644 --- a/flumotion/test/test_component_feeder.py +++ b/flumotion/test/test_component_feeder.py @@ -16,7 +16,7 @@ # Headers in this file shall remain intact. import time - +from gi.repository import Gst from flumotion.common import testsuite from twisted.internet import defer, reactor @@ -51,7 +51,10 @@ def checkClientDisconnected(): self.clientAssertStats(c, 0, 0, 10, 1, 2) # read 20 bytes, drop 2 buffers - c.setStats((20, None, None, None, time.time(), 2)) + stats = Gst.Structure.from_string('multihandlesink-stats, bytes-sent=20, \ + connect-time=None, disconnect-time=None, connect-duration=None, \ + last-activitity-time=' + str(time.time()) + ', buffers-dropped=2')[0] + c.setStats(stats) self.clientAssertStats(c, 20, 2, 30, 3, 2) d.callback(None) @@ -63,7 +66,11 @@ def checkClientDisconnected(): self.clientAssertStats(c, 0, None, 0, None, 1) # read 10 bytes, drop 1 buffer - c.setStats((10, None, None, None, time.time(), 1)) + stats = Gst.Structure.from_string('multihandlesink-stats, bytes-sent=10, \ + connect-time=None, disconnect-time=None, connect-duration=None,\ + last-activitity-time=' + str(time.time()) + ', buffers-dropped=1')[0] + + c.setStats(stats) self.clientAssertStats(c, 10, 1, 10, 1, 1) # remove client diff --git a/flumotion/test/test_component_icystreamer.py b/flumotion/test/test_component_icystreamer.py index 0ca710b8..f4e8dd79 100644 --- a/flumotion/test/test_component_icystreamer.py +++ b/flumotion/test/test_component_icystreamer.py @@ -195,7 +195,6 @@ def assertsOnStream(factory): d.addCallback(assertsOnStream) d.addCallback(lambda _: comptest.delayed_d(0.1, _)) d.addCallback(lambda _: self.assertEqual(0, self.comp.getClients())) - d.addCallback(lambda _: self.tp.stop_flow()) return d @@ -238,7 +237,7 @@ def pagePart(self, data): self.finished = True log.debug('stream-downloader',\ 'Calling callback of StreamDownloader') - self.deferred.addCallback(self.connector.transport.loseConnection) + self.connector.transport.loseConnection() self.deferred.callback(self) def pageStart(self, partialContent): diff --git a/flumotion/test/test_component_padmonitor.py b/flumotion/test/test_component_padmonitor.py index 4732ba47..8a2b1bca 100644 --- a/flumotion/test/test_component_padmonitor.py +++ b/flumotion/test/test_component_padmonitor.py @@ -15,7 +15,9 @@ # # Headers in this file shall remain intact. -import gst +import gi +gi.require_version('Gst', '1.0') +from gi.repository import Gst from twisted.internet import defer, reactor from twisted.trial import unittest @@ -31,16 +33,16 @@ class TestPadMonitor(testsuite.TestCase): slow = True def _run_pipeline(self, pipeline): - pipeline.set_state(gst.STATE_PLAYING) - pipeline.get_bus().poll(gst.MESSAGE_EOS, -1) - pipeline.set_state(gst.STATE_NULL) + pipeline.set_state(Gst.State.PLAYING) + pipeline.get_bus().poll(Gst.MessageType.EOS, 18446744073709551615L)#Gst.CLOCK_TIME_NONE + pipeline.set_state(Gst.State.NULL) def testPadMonitorActivation(self): - pipeline = gst.parse_launch( + pipeline = Gst.parse_launch( 'fakesrc num-buffers=1 ! identity name=id ! fakesink') identity = pipeline.get_by_name('id') - srcpad = identity.get_pad('src') + srcpad = identity.get_static_pad('src') monitor = padmonitor.PadMonitor(srcpad, "identity-source", lambda name: None, lambda name: None) @@ -62,11 +64,11 @@ def testPadMonitorTimeout(self): padmonitor.PadMonitor.PAD_MONITOR_PROBE_INTERVAL = 0.2 padmonitor.PadMonitor.PAD_MONITOR_CHECK_INTERVAL = 0.5 - pipeline = gst.parse_launch( + pipeline = Gst.parse_launch( 'fakesrc num-buffers=1 ! identity name=id ! fakesink') identity = pipeline.get_by_name('id') - srcpad = identity.get_pad('src') + srcpad = identity.get_static_pad('src') # Now give the reactor a chance to process the callFromThread() diff --git a/flumotion/test/test_component_video_converter.py b/flumotion/test/test_component_video_converter.py index e681be3b..b11adddd 100644 --- a/flumotion/test/test_component_video_converter.py +++ b/flumotion/test/test_component_video_converter.py @@ -18,7 +18,7 @@ import gst from twisted.python import failure -from twisted.internet import defer, reactor, interfaces, gtk2reactor +from twisted.internet import defer, reactor, interfaces, gtk3reactor from twisted.web import client, error from flumotion.common import testsuite diff --git a/flumotion/test/test_comptest.py b/flumotion/test/test_comptest.py index d0c06283..997ea335 100644 --- a/flumotion/test/test_comptest.py +++ b/flumotion/test/test_comptest.py @@ -17,7 +17,7 @@ import sys -from twisted.internet import defer, reactor, selectreactor, gtk2reactor +from twisted.internet import defer, reactor, selectreactor, gtk3reactor from flumotion.common import testsuite from flumotion.common import log, errors @@ -31,10 +31,10 @@ attr = testsuite.attr -class TestCompTestGtk2Reactorness(testsuite.TestCase): +class TestCompTestGtk3Reactorness(testsuite.TestCase): supportedReactors = [] - def testGtk2Supportness(self): + def testGtk3Supportness(self): class TestCompTestSupportedReactors(CompTestTestCase): @@ -43,8 +43,8 @@ def runTest(self): obj = TestCompTestSupportedReactors('runTest') if not isinstance(sys.modules['twisted.internet.reactor'], - gtk2reactor.Gtk2Reactor): - # not running with a gtk2reactor, the TestCompTestSupportedReactors + gtk3reactor.Gtk3Reactor): + # not running with a gtk3reactor, the TestCompTestSupportedReactors # instance should have a 'skip' attribute self.failUnless(hasattr(obj, 'skip'), "setting supportedReactors doesn't set 'skip'" diff --git a/flumotion/twisted/defer.py b/flumotion/twisted/defer.py index e9370cad..5cce56e5 100644 --- a/flumotion/twisted/defer.py +++ b/flumotion/twisted/defer.py @@ -17,11 +17,12 @@ import random +from twisted import version from twisted.internet import defer, reactor from twisted.python import reflect # FIXME: this is for HandledException - maybe it should move here instead ? -from flumotion.common import errors +from flumotion.common import errors, common __version__ = "$Rev$" @@ -106,7 +107,10 @@ def raise_error(): # exception class is in our namespace, and it only takes # one string argument. if either condition is not true, # we wrap the strings in a default Exception. - k, v = failure.parents[-1], failure.value + if common.versionStringToTuple(version.short()) >= (11, 1, 0): + k, v = failure.parents[0], failure.value + else: + k, v = failure.parents[-1], failure.value try: if isinstance(k, str): k = reflect.namedClass(k) diff --git a/flumotion/worker/base.py b/flumotion/worker/base.py index 2ed613ea..0c9e5d68 100644 --- a/flumotion/worker/base.py +++ b/flumotion/worker/base.py @@ -183,7 +183,13 @@ def listen(self): # our particular Port, which creates Transports that we know how to # pass FDs over. self.debug("Listening for FD's on unix socket %s", self._socketPath) - port = reactor.listenWith(fdserver.FDPort, self._socketPath, f) + + # listenWith is deprecated but the function never did much anyway + # + # port = reactor.listenWith(fdserver.FDPort, self._socketPath, f) + port = fdserver.FDPort(self._socketPath, f, reactor=reactor) + port.startListening() + self._port = port ### portal.IRealm method diff --git a/flumotion/worker/checks/check.py b/flumotion/worker/checks/check.py index 090fbf74..52caf4c6 100644 --- a/flumotion/worker/checks/check.py +++ b/flumotion/worker/checks/check.py @@ -16,8 +16,9 @@ # Headers in this file shall remain intact. import os - -import gst +import gi +gi.require_version('Gst', '1.0') +from gi.repository import Gst from twisted.internet import defer from flumotion.common import documentation, errors, gstreamer, log, messages @@ -45,25 +46,25 @@ def handleGStreamerDeviceError(failure, device, mid=None): gerror.message, gerror.domain, gerror.code, debug)) if gerror.domain == "gst-resource-error-quark": - if gerror.code == int(gst.RESOURCE_ERROR_OPEN_READ): + if gerror.code == int(Gst.ResourceError.OPEN_READ): m = messages.Error(T_( N_("Could not open device '%s' for reading. " "Check permissions on the device."), device), mid=mid) documentation.messageAddFixBadPermissions(m) - if gerror.code == int(gst.RESOURCE_ERROR_OPEN_WRITE): + if gerror.code == int(Gst.ResourceError.OPEN_WRITE): m = messages.Error(T_( N_("Could not open device '%s' for writing. " "Check permissions on the device."), device), mid=mid) documentation.messageAddFixBadPermissions(m) - elif gerror.code == int(gst.RESOURCE_ERROR_OPEN_READ_WRITE): + elif gerror.code == int(Gst.ResourceError.OPEN_READ_WRITE): m = messages.Error(T_( N_("Could not open device '%s'. " "Check permissions on the device."), device), mid=mid) documentation.messageAddFixBadPermissions(m) - elif gerror.code == int(gst.RESOURCE_ERROR_BUSY): + elif gerror.code == int(Gst.ResourceError.BUSY): m = messages.Error(T_( N_("Device '%s' is already in use."), device), mid=mid) - elif gerror.code == int(gst.RESOURCE_ERROR_SETTINGS): + elif gerror.code == int(Gst.ResourceError.SETTINGS): m = messages.Error(T_( N_("Device '%s' did not accept the requested settings."), device), @@ -126,7 +127,7 @@ def errbackResult(failure, result, mid, device): def errbackNotFoundResult(failure, result, mid, device): """ I am an errback to add to a do_element_check deferred - to check for RESOURCE_ERROR_NOT_FOUND, and add a message to the result. + to check for ResourceError.NOT_FOUND, and add a message to the result. @param mid: the id to set on the message """ @@ -134,7 +135,7 @@ def errbackNotFoundResult(failure, result, mid, device): source, gerror, debug = failure.value.args if gerror.domain == "gst-resource-error-quark" and \ - gerror.code == int(gst.RESOURCE_ERROR_NOT_FOUND): + gerror.code == int(Gst.ResourceError.NOT_FOUND): m = messages.Warning(T_( N_("No device found on %s."), device), mid=mid) result.add(m) @@ -164,9 +165,9 @@ def checkElements(elementNames): ret = [] for name in elementNames: try: - gst.element_factory_make(name) + Gst.ElementFactory.make(name) ret.append(name) - except gst.PluginNotFoundError: + except Gst.PluginNotFoundError: log.debug('check', 'no plugin found for element factory %s', name) pass log.debug('check', 'checkElements: returning elements names %r', ret) @@ -253,7 +254,7 @@ def discovered(dcv, ismedia): result.succeed((True, properties)) return d.callback(result) - from gst.extend import discoverer + from Gst.extend import discoverer dcv = discoverer.Discoverer(filePath) dcv.connect('discovered', discovered) dcv.discover() @@ -290,7 +291,7 @@ def checkPlugin(pluginName, packageName, minimumVersion=None, documentation.messageAddGStreamerInstall(m) result.add(m) elif featureName: - r = gst.registry_get_default() + r = Gst.Registry.get() features = r.get_feature_list_by_plugin(pluginName) byname = dict([(f.get_name(), f) for f in features]) if (featureName not in byname diff --git a/flumotion/worker/feedserver.py b/flumotion/worker/feedserver.py index c581b6ba..e0d7ee11 100644 --- a/flumotion/worker/feedserver.py +++ b/flumotion/worker/feedserver.py @@ -61,8 +61,12 @@ def listen(self, bouncer, portNum, unsafeTracebacks=0): factory = pb.PBServerFactory(portal, unsafeTracebacks=unsafeTracebacks) - tport = reactor.listenWith(fdserver.PassableServerPort, portNum, - factory) + # listenWith is deprecated but the function never did much anyway + # + # tport = reactor.listenWith(fdserver.PassableServerPort, portNum, + # factory) + tport = fdserver.PassableServerPort(portNum, factory, reactor=reactor) + tport.startListening() self._tport = tport self.debug('Listening for feed requests on TCP port %d', diff --git a/run_tests.sh b/run_tests.sh new file mode 100755 index 00000000..116eafba --- /dev/null +++ b/run_tests.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +filename=working-tests.txt +test_to_run="" + +while read line +do + tests_to_run+=flumotion.test.$line' ' +done < $filename + +trial -rselect $tests_to_run \ No newline at end of file diff --git a/test_config.xml b/test_config.xml new file mode 100644 index 00000000..c0457374 --- /dev/null +++ b/test_config.xml @@ -0,0 +1,108 @@ + + + + + user:PSfNpHTkpTx1M + + + + + + pycBUhaLQlwt + flu-KbVvOp.socket + nWbDPQzKKrMa + 8800 + + + + + + 0 + 50/10 + 320 + 240 + false + + + + + producer-video + + 400000 + + + + 1.0 + 44100 + 440 + true + + + + + producer-audio:default + + 64000 + false + + + + + encoder-video:default + encoder-audio:default + + false + + + + + muxer-audio-video:default + + 10 + True + 1024 + pycBUhaLQlwt + /ogg-audio-video/ + 8800 + slave + flu-KbVvOp.socket + nWbDPQzKKrMa + + + + \ No newline at end of file diff --git a/tools/httpdigesthasher.py b/tools/httpdigesthasher.py index 9b1ace41..1e572940 100755 --- a/tools/httpdigesthasher.py +++ b/tools/httpdigesthasher.py @@ -16,8 +16,7 @@ # # Headers in this file shall remain intact. - -import md5 +from haslib import md5 import sys diff --git a/working-tests.txt b/working-tests.txt new file mode 100644 index 00000000..cd8c0b2c --- /dev/null +++ b/working-tests.txt @@ -0,0 +1,5 @@ +test_common_gstreamer +test_common_pygobject +test_component_feeder +test_component_padmonitor +test_component_feedcomponent