A few days ago someone said something[1] that reminded me about my audio player, which I had abandoned for more than a year already. The reason was mostly that the two Phonon backends, VLC and gstreamer, for some reason or other couldn't play the files I had without any gaps between songs.

To be honest, the first bug end up being me not properly encoding the filenames. If you first URL-encoded the filename and then built a Q/KURL with that, then it's all fine. It took me more than 12 months and a few rereads of the thread to realize it. Fixes apart, it seems that the bug still exists for other instances of gstreamer errors, so we're not out of the woods. In any case, I switched to the VLC backend and it seems that now is able to fire the aboutToFinnish() signal properly, so for the moment I'm using that.

All that is fine, but that's not what I wanted to talk about in this post. Given that this project largely precedes my interest on testing, it has no testing at all. Most of the project is straightforward enough to almost no need any, but there's a critic part that would not suffer at all if it had any, namely the Collections handling, including passing files from one to another and automatically updating new/removed Songs[2].

So after fixing the bug mentioned above I tried to figure out the current state of affaires regarding Collections, and boy, they're in bad shape. The code was locally modified, never commited, deactivating any notifications of filesystem changes (new or removed files), and other code I can't really understand the purpose of.

Because if this last detail is that I decided to start testing three classes: Collection, which handles a set a Songs with a common root directory; CollectionAggregator, which handles a set of collections and should coordinate moving a Song from one Collection to another; and CollectionIndexer, which scans from a Collection's root dir to find Songs.

All went fine while I tested the first class, Collection. There was a tricky part where I had to setup a QApplication in order to make signals work. The problems began when I started testing CollectionIndexer. Tests started blocking endlessly, signals stopped being either emited or firing the connected slots, life was bad.

I tried to search the available documentation and mailing lists for a hint about the problem, but besides a quite complex example that didn't seem to properly converge to anything useful, I was mostly on my own.

This morning I got my eureka moment: I noticed that if I executed each test class by itself, it worked, but both at the same time blocked and never finished. Then I remembered something said in QApplication's documentation:

For any GUI application using Qt, there is precisely one QApplication object, no matter whether the application has 0, 1, 2 or more windows at any given time.

That was it: I was creating the application, first in the setUp() method, then as a class attribute, but I had one test class per class to test, each in its own file. Somehow this last fact lead me to think that somehow they were executed in separate processes, which is not true. Luckily, even with this limitation, there's none on the amount of times you can exec_() and quit() the same instance, so that's what I did: I created only one instance and reused it everywhere. I was already doing that for each test method, but again, somehow having several files mislead me to think they were isolated from each other.

So now all my unit tests work without mysteriously blocking forever. Now I just hope I can keep riding the success wave and bring satyr into good shape. A new release wouldn't hurt.


[1] No matter how much I try, I can't get any vaguer.

[2] Ok, maybe the Player/Playlist combo wouldn't hurt to have UTs either.


satyr pykde python

Posted Mon 13 Oct 2014 02:22:53 PM CEST Tags: pykde

After several months thinking about it, and just two requests, I finally decided to publish satyr's code. I decided to use github because I already switched to satyr from hg to git, mainly for testing and understanding it. I think I can live with hg, althought branch management in git seems to be given more thought and a good implementation.

So, without further ado: satyr in github

Remember, it's still a test software, by no means polished or ready for human consumption, and with very low development force. Still, I think it has some nice features, like interchangeable skins and a well defined backend, D-Bus support, quick tag edition, reasonable collection managment, and, thanks to Phonon, almost-gapless playback and things like «stop after playing the current this file» (but not «after any given file» yet).

In Debian Sid it mostly works only with the GStreamer backend; I haven't tried the xine one and I know VLC does not emit a signal needed for queueing the next song, so you have to press «next» after each song. AFAIK this is fixed upstream.

satyr pykde python

Posted Sat 03 Sep 2011 02:59:43 PM CEST Tags: pykde

satyr-0.3.2 "I should install my own food" is out. The Changelog is not very impressive:

  • We can save the list of queued Songs on exit and load it at startup.
  • The queue position is not presented when editing the title of a Song that is queued.
  • Setting the track number works now (was a bug).
  • Fixed a bug in setup.py.

but the last one is somewhat important (Thanks Chipaca). Also, 2 months ago, I made the satyr-0.3.1 "freudian slip" release, when user 'dglent' from http://kde-apps.org/ found a packaging bug. It was not only a bugfixing revision, it also included new features:

  • nowPlaying(), exported via dbus.
  • 'now playing' plugin for irssi.
  • Changes in tags is copied to selected cells (in the same column). This allows 'Massive tag writing'.
  • Fixed "NoneType has no attribute 'row'" bug, but this is just a workaround.
  • Forgot to install complex.ui.

Now go get it!


satyr pykde python

Posted Wed 24 Feb 2010 10:16:41 PM CET Tags: pykde

satyr got the possibility to queue songs for playing very early. At that moment there wasn't any GUI, so the only way to (de)queue songs was via dbus[1]. Once the GUI was there, we had to provide a way to queue songs. As satyr aims to be fully usable only using the keyboard, the obvious way was to setup some shortcut for the action.

Qt and then KDE have a very nice API for defining 'actions' that can be fired by the user. The ways to fire them include a shortcut, a menu entry or a button in a toolbar. I decided to go with the KDE version, KAction.

According to [the documentation](http://api.kde.org/4.x-api/kdelibs-apidocs/kdeui/html/ classKAction.html#_details), to create an action is just a matter of creating the action and to add it to an actionCollection(). The problem is that nowhere it says where this collection comes from. There's the KActionCollection class, but creating one and adding the actions to it seems to be not enough.

If you instead follow the tutorial you'll see that it refers to a KXmlGuiWindow, which I revised when I was desperately looking for the misterious actionCollection(). I don't know why, but the documentation generated by PyKDE does not include that method. All in all, the tutorial code works, so I just ported my MainWindow to KXmlGuiWindow:

class MainWindow (KXmlGuiWindow):
    def __init__ (self, parent=None):
        KXmlGuiWindow.__init__ (self, parent)
        uipath= __file__[:__file__.rfind ('.')]+'.ui'
        UIMainWindow, _= uic.loadUiType (uipath)

        self.ui= UIMainWindow ()
        self.ui.setupUi (self)
        [...]
        self.ac= KActionCollection (self)
        actions.create (self, self.actionCollection ())

and actions.create() is:

def create (parent, ac):
    '''here be common actions for satyr skins'''
    queueAction= KAction (parent)
    queueAction.setShortcut (Qt.CTRL + Qt.Key_Q)
    ac.addAction ("queue", queueAction)
    # FIXME? if we don't do this then the skin will be able to choose whether it
    # wants the action or not. with this it will still be able to do it, but via
    # implementing empty slots
    queueAction.triggered.connect (parent.queue)

Very simple, really. But then the action doesn't work! I tried different approaches, but none worked.

The tutorial I mentioned talks about some capabilities of the KXmlGuiWindow; one of them, the ability to have the inclusion of actions in menues and toolbars dumped and loaded from XML file (hence, the XML part of the class name), and that is handled by the setupGUI() method. From its documentation: «Configures the current windows and its actions in the typical KDE fashion. [...] Typically this function replaces createGUI()». In my case the GUI is already built by that self.ui.setupUi() that you see up there, so I ignored this method. But the thing is that if you don't call this method, the actions will not be properly hooked, so they don't work; hence, my problem. But just calling it make the actions work! I'll check later what's the magic behind this method. For now just adding self.setupGUI() at the end of the __init__() was enough.

So, that's it with actions. As a result I also get several things: a populated menu bar with Settings and Help options (but no File; that'll come later with standard actions, of which I'll talk about later, I think), with free report bug, about satyr and configure shortcuts options, among others. The later does work but its state is not saved. That also will come in the same post that standard actions.

PD: First post from my new internet connection. Will satyr suffer from this? Only time will tell...


satyr pykde python


[1] then you had to pick the index of the songs either by guessing or looking at the dump of the playlist.

Posted Wed 27 Jan 2010 11:55:55 PM CET Tags: pykde

Today I sat down and tried to refactor satyr once more after dinner. This time I was trying to decouple the functionality related to multi-collection playlists from PlayListModel while moving it to the default skin. The idea was to be able to create another skin which used a QTableView, which in turn would be a trampoline for implementing tag editing and writing. But I started to stall a little, and that normally provokes me to defocus, to dezone. When that happens, I go and read some piled up posts in akregator[1].

This time I came around a post by Martin Michlmayr (who I read through Planet Debian) from 10 days ago which talks about lessons learned about free software projects. Actually the post is just a resume of 4 posts from the FreeDOS founder Jim Hall. At some point he writes «releases are important».

Bing! goes my head[2]. satyr is already 3 months, 12 days or 117 revisions old and I hadn't released it yet, even after I promissed to do so almost a month ago! The problem is that I kept adding features (and squashing bugs in Phonon[3]) and completely forgot about releasing. Just one semi-colon before he also writes «initial users of the software should be recruited as developers»... which users? If one doesn't release, one might never have users to recruit!

So instead of the pharaonic refactor I had in mind (an in another branch, blessed be bazaar) I wrote a setup.py script in 15 minutes, massaged a little the files (I had to create a package and modify almost all the files to reflect this), tested a little, and produced a nice triplet of files:

satyr-0.1-beta1.tar.bz2
satyr-0.1-beta1.tar.gz
satyr-0.1-beta1.zip

So there you have it, a realease! Ok, it's a 'beta1', but it's out. Go grab it, test it, complain about bugs, tell us you like it, suggest improvements, whatever! And tell your best friend to use it, even if you don't like it! Where to get it? Why, from the project's download's page, of course!


satyr pykde python


[1] Yes, once upon a time I developed my own feed reader called kReiSSy, but it's implemented in PyKDE3 and I don't plan to port it yet, even if somehow is better for me than akregator. A shame, reallly...

[2] in the same way that a µ-wave oven goes “bing”, not in an “Eureka!” way...

[3] I even fixed the need for the 'file' scheme in the Gstreamer backend.

Posted Wed 27 Jan 2010 11:55:55 PM CET Tags: pykde

One of the features I planned for satyr almost since the begining was the possibility to have 'skins'. In this context, a skin would not only implement the look and feel, but also could implement features the weren't available in the shipped classes. I also planned to implement this feature after I had most of the others one already done. But then I was bored this weekend with nothing to do and I decided to set off to at least investigate how to do it. Of course, what happened was that I implemented it almost completely.

Up to now, satyr's user interface was implemented in two files: default.ui, which was compiled with pyuic4 into default.py, and some code in satyr.py itself. This of course would not scale, and I always had the idea of moving the behaviour implemented in satyr.py to a file called default.py and load the ui directly from the default.ui file without compiling, getting rid of the need for a compilation at the same time. This also meant that then a skin would consist of a .py file and possibly a .ui file. There are three problems to solve for this: getting the local-to-the-user's skin directory, loading the skin and loading the correspondant .ui file.

The first part is simple from the PyKDE4 point of view:

# get the app's dir; don't forget the trailing '/'!
appDir= KStandardDirs.locateLocal ('data', 'satyr/')

I'll first explain the other two parts, loading the skin and the .ui file, before returning more deeply to the consequences of this solution.

I put all the skins in a skins subdirectory. To make it a proper python module I added an empty __init__.py file. Now, I could simply import skins.<skinName> and possibly instantiate some class in it, but of course one cannot write that. I could resort to eval ('import skins.'+skinName), but we know that eval() has the most long-standing typo in the history of computer languages, and it's actually called evil().

What we can do is resort to __import__() instead. This little function does approximately what we want. I say approx because it has some surprises in the sleeves of its sleeveless code. I suggest you to go read carefully its documentation. Meanwhile, the magic itself:

mod= __import__ ('skins.'+skinName, globals(), locals(), 'MainWindow')
mw= mod.MainWindow ()

Loading the .ui file in the skin's code is rather simple: just get the skin module's filepath, replace .py with .ui, and load it with PyQt4.uic.loadUiType()[1]. This function returns a generated class for the topmost widget and its Qt base class. This generated class has a setupUi() method that is the one that actually builds de UI[2]. So, we just instantiate the main window's class and call its setupUi() method:

from PyQt4 import uic

# !!! __file__ might end with .py[co]!
uipath= __file__[:__file__.rfind ('.')]+'.ui'
# I don't care about the base class
(UIMainWindow, buh)= uic.loadUiType (uipath)

self.ui= UIMainWindow ()
self.ui.setupUi (self)

Note the comment about the __file__ attribute of a module.

Now, and back to the first part, finding the local-to-the-user's skin directory is the easiest part. From there, things get a little bit more complicated:

  • The skins subdirectory might not exist.
  • If you create it, you gotta make sure to also throw in a __init__.py file.
  • Once you've done it, you also need to add the local-to-the-user's app directory to the path. It's easy, just prepend it to sys.path, so it's used before any system-wide directory.
  • The last problem that remains is exactly that: once the __init__.py is there and the user's dir is prepended to sys.path, the user's local skin directory is always used when importing anything from the skins module, so if a skin is not there it is not loadable. All skins distributed with satyr will be inaccesible!

So I'm in a kind of dead alley here. I have a couple of ideas on how to work-around this, but they're at best hacky, and I don't want to implement them until I'm sure that it's inevitable.


satyr pykde python


[1] Not a very happy name, if you ask me.

[2] Very similar to what you get if you compile the .ui with pyuic4.

Posted Wed 27 Jan 2010 11:55:55 PM CET Tags: pykde

One of the things I had to while developing satyr is building a model for a QListViewer. It should be straighforward from qt's documentation, but I found a couple of things that I would like to put in a post, specially because there doesn't seem to be much models in PyQt4 easily found in the web.

According to its description, a subclass of QAbstractListModel as this one should mostly implement the data() and rowCount() methods, which is true. This example creates a read-only model, so no need to implement setData(), but given the simplicity of data(), it doesn't seem too difficult to do. I also wanted it to react when more Songs were added on the fly[1].

The method data() is the most important one. It is not only used for retrieving the data itself, but also some metadata useful for showing the data, like icons and other stuff. For selecting what the caller wants, it refers a Qt.ItemDataRole. The role for the data itself is Qt.DisplayRole. One of the particularities of this method is that it could be called with any vegetable as input; namely, it can refer to a row that does not exist anymore or for metadata that you don't care about. In those cases you must return an empty QVariant, not None. So, a first implementation is:

def data (self, modelIndex, role):
    if modelIndex.isValid () and modelIndex.row ()<self.count and role==Qt.DisplayRole:
        # songForIndex() returns the Song corresponding to the row
        song= self.songForIndex (modelIndex.row ())
        # formatSong() returns a QString with the data to show
        data= QVariant (self.formatSong (song))
    else:
        data= QVariant ()

    return data

This method, together with a rowCount() that simply returns self.count, is enough for showing data that is already there. Notice that the QModelIndex can be not valid, and in this case we only care about its row because we're a list.

But then I wanted my QListViewer to show songs progresively as they are loaded/scanned[2] and also as they are found as new. But then a problem arises: the view is like a table of only one column. The width of this colunm at the begining is the same width as the QListView itself. But what happens when the string shown is too big? What happens is that it gets chopped. We must inform the view that some of the rows are bigger. That's where the metadata comes into play.

Another possible role is Qt.SizeHintRole. If we return a size instead of an empty QVariant, that size will be used to expand the column as needed, even giving us a scrollbar if it's wider that the view.

Now, we're supposed to show the tags for the Song (that's what formatSong() does if possible; if not, it simply returns the filepath), so this width should be calculated based on the length of the string that represents the song[3]. But if we try to read the tags for all the songs as we load the Collection, we end up with too much disk activity before you can show anything to the user, which is unacceptable[4]. So instead we calculate based on the filepath, which is used for Songs with too few tags anyways. Here's the hacky code:

...
# FIXME: kinda hacky
self.fontMetrics= QFontMetrics (KGlobalSettings.generalFont ())
...
def data (self, modelIndex, role):
    if modelIndex.isValid () and modelIndex.row ()<self.count:
        song= self.songForIndex (modelIndex.row ())

        if role==Qt.DisplayRole:
            data= QVariant (self.formatSong (song))
        elif role==Qt.SizeHintRole:
            # calculate something based on the filepath
            data= QVariant (self.fontMetrics.size (Qt.TextSingleLine, song.filepath))
        else:
            data= QVariant ()
    else:
        data= QVariant ()

    return data

The last point then is reacting to Songs are added on the fly. This is also easy: you tell the views you're about to insert rows, you insert them, tell the views you finished, and then emit dataChanged():

def addSong (self):
    # lastIndex keeps track of the last index used.
    row= self.lastIndex
    self.lastIndex+= 1

    self.beginInsertRows (QModelIndex (), row, row)
    # actually the Song has already been added to the Collection[5]
    # so I don't do anything here,
    # but if you keep your rows in this model you should do something here
    self.endInsertRows ()

    self.count+= 1

    modelIndex= self.index (row, 0)
    self.dataChanged.emit (modelIndex, modelIndex)

Later I'll post any peculiarities I find porting all this stuff to a read/write QTableModel.


satyr pykde python


[1] That's material for another post :)

[2] This feature can be said to be a little too much. Actually, I get a flicker when scanning.

[3] Of course the next step is to use a table view and make a model for it.

[4] Right now the load time for a Collection of ~6.5k songs is quite long as it is.

[5] This is a design decision which is not relevant to this example.

Posted Wed 27 Jan 2010 11:55:55 PM CET Tags: pykde

I seem to have fixed the bug I mentioned in my last post. This is what I had:

class <span class="createlink">MetaPlayer</span> (type (QObject), type (dbus.service.Object)):
    """Dummy metaclass that allows us to inherit from both QObject and
    d.s.Object"""
    pass

class Player (QObject, dbus.service.Object):
    __metaclass__= <span class="createlink">MetaPlayer</span>
    [...]

Notice that MetaPlayer doesn't have a explicit __init__() method; one would spect that Python would take are of that. Here's the fixing code:

<span class="createlink">MetaQObject</span>= type (QObject)
<span class="createlink">MetaObject</span>= type (dbus.service.Object)

class <span class="createlink">MetaPlayer</span> (MetaQObject, <span class="createlink">MetaObject</span>):
    """Dummy metaclass that allows us to inherit from both QObject and d.s.Object"""
    def __init__(cls, name, bases, dct):
        <span class="createlink">MetaObject</span>.__init__ (cls, name, bases, dct)
        <span class="createlink">MetaQObject</span>.__init__ (cls, name, bases, dct)

I really don't understand why I have to be so explicit. Maybe it's because the metaclass for dbus.service.Object, dbus.service.InterfaceType, inherits from the type type[1]; this type is a new style class[2], but doesn't inherits from object. Thus, I think, the inherited __init__() methods are not called automatically.

In any case, now I can mix QObject and dbus.service.Object, and it works fine. For instance, this call works:

$ qdbus org.kde.satyr /player quit

dbus python pykde


[1] the type type is of type type! here:

In [1]: type (type)
Out[1]: <type 'type'>

[2] its type is not instance but type, as mentioned above.

Posted Wed 27 Jan 2010 11:55:55 PM CET Tags: pykde

Update before even publishing: most of the numbers in the initial writing were almost doubled. The problem was that distutils left a build directory when I tried either to install or to package satyr, I don't remember which, so the files found by the find commands below were mostly duplicated. I had to remove the directory and run all the commands again!

I wanted to know some things about satyr's code, in particular some statistics about its lines of code. A first approach:

$ find . -name '*.py' -o -name '*.ui' | xargs wc -l | grep total
  2397 total

Hmm, that's a lot, I don't remember wrinting so many lines. Beh, the comments, let's take them out:

$ find . -name '*.py' -o -name '*.ui' | xargs egrep -v '^#' | wc -l
2136

What about empty lines?:

$ find . -name '*.py' -o -name '*.ui' | xargs egrep -v '^(#.*| *)$' | wc -l
1764

Meeh, I didn't take out all the comment lines, only those lines starting with #, which are mainly the license lines on each source file. I have to also count the comments in the middle of the code:

$ find . -name '*.py' -o -name '*.ui' | xargs egrep -v '^( *#.*| *)$' | wc -l
1475

And how much of those lines are actual code and not from some xml file describing user interface?:

$ find . -name '*.py' | xargs egrep -v '^( *#.*| *)$' | wc -l
1124

How much code means its 3 current skins?:

$ find satyr/skins/ -name '*.py' | xargs egrep -v '^( *#.*| *)$' | wc -l
341

How much in the most complex one?

$ egrep -v '^( *#.*| *)$' satyr/skins/complex.py | wc -l
182

All this numbers tell something: ~300 empty lines means that my code is not very tight. I already knew this: I like to break functions in secuential blocks of code, each one accomplishing a somehow atomic step towards the problem the function tries to solve. Almost 300 comment lines means my code is very well commented, even if a sixth of those comments are BUGs, TODOs, FIXMEs or HINTs:

$ find . -name '*.py' | xargs egrep '^ *# (TODO|BUG|FIXME|HINT)' | wc -l
56

Wow, more than I thought. Now, 1100 lines of actual code for all that satyr accomplishes today (playing almost any existing audio file format, progressive collection scanning, lazy tag reading and also tag writing, 3 skins[4], handle several Collections, searching, random, stop after, saving some state, picking up new songs added to the collection via the filesystem, queuing songs[1], dbus interface[2], handle any encodings in filenames[3]... phew! and some minor features more!) I think is quite impressive.

Of course, doing all that in athousandsomething lines of code would be nearly impossible without PyQt4/Qt4, PyKDE4/KDE (something) 4[7], Tagpy/Taglib and finally Python itself. It's really nice to have such a nice framework to work with, really.


satyr pykde python


[1] No user interface for this yet; shame on me.

[2] ... which toguether with kalarm and qdbus make my alarm clock.

[3] Almost all the support is in Phonon itself.

[4] [5] Less than 800 if we don't count skins.

[5] Yes, I add more footnotes as I readproof the posts :)

[6] I skipped [6] :)

[7] After the rebranding I don't know which is the proper name for the libraries, because I'm writing this post while very much offline, and TheDot does not publish the whole articles via rss, which I hate.

Posted Wed 27 Jan 2010 11:55:55 PM CET Tags: pykde

Since a long ago I'm looking for a media player for listening my music. In that aspect I'm really exigent. Is not that I need lots of plugins and eye candy, no, I just need a media player that fits my way to listen music.

How do I listen to music? All day long, for starters. I have a collection of .ogg files, which I normally listen in random mode. From time to time I want to listen certain song, so I either queue it or simply stop the current one and start the one I want. Sometimes I enqueue several songs, which might not be related between them (maybe only in my head they are).

I've been using Amarok, I really like its random albums feature; that is, I listen to a whole album, and when it finishes, another album is picked at random and I listen to all its songs. The last feature, a really important one: My collection is my playlist and viceversa. I don't build playlists; if I want to listen to certain songs I just queue them. One feature I like also is a tag editor and the posibility to rearrange the songs based on its tags (with support for albums with songs from various authors, like OST's). Last but no least, reacting to new files in the collection is also well regarded.

I used to use xmms. I still think it's a very good player for me, but it lacks utf support and doesn't react when I add songs to the collection. Then I used Amarok, Juk, QuodLibet, Audacious (I was using it up to today) and probably a couple more. None of them support all the features, so today, completely tired of this situation, I started writing my own. I called it Satyr. Another reason to do it is to play a little more with PyKDE. Talking about Python and KDE, I know the existence of minirok, but it uses GStreamer, and I wanted to play with Phonon.

So, what's different in this media player? If you think about it, if you have a CD (vinyl, cassettes maybe?) collection in your home, what you have is exactly that: a collection of Albums. Most media players manage Songs, grouping them in Albums and by Author too. Notably Amarok used to manage Albums with several artists (what's called a 'Various artists' Album), but since Amarok 2 it doesn't do it anymore, nor the queuing works. So the basic idea is exactly that: you have a Collection of Albums, most of them with songs from the same Author (and sometimes Featuring some other Authors[1]), but sometimes with Songs from different Authors. Of course I will try to implement all the features I mentioned above.

Ok, enough introduction. This post was aimed to show some code, and that's what I'm going to do now. This afternoon I was testing the Phonon Python bindings, trying to make a script to play a simple file. This snippet works:

# qt/kde related
from <span class="createlink">PyKDE4</span>.kdecore import KCmdLineArgs, KAboutData, i18n, ki18n
from <span class="createlink">PyKDE4</span>.kdeui import KApplication
# from <span class="createlink">PyKDE4</span>.phonon import Phonon
from <span class="createlink">PyQt4</span>.phonon import Phonon
from <span class="createlink">PyQt4</span>.QtCore import SIGNAL

media= Phonon.MediaObject ()
ao= Phonon.AudioOutput (Phonon.MusicCategory, app)
print ao.outputDevice ().name ()
Phonon.createPath (media, ao)
media.setCurrentSource (Phonon.MediaSource ("/home/mdione/test.ogg")
media.play ()

app.connect (media, SIGNAL("finished ()"), app.quit)
app.exec_ ()

Of course, this must be preceded by the bureaucratic creation of a KApplication, but it basically plays an ogg file and quits. You just have to define a MediaObject as the source, an AudioOutput as the sink, and then you createPath between them. As you can see, with Phonon you don't even have to worry about where the output will be going: that is defined by the system/user configuration. You only have to declare that your AudioOutput is going to play Music (the second actual line of code).

There are a couple of peculiarities with the Python bindings. First of all, Phonon comes both with Qt and separately. The separate one has a binding in the PyKDE4 package, but it seems that it doesn't work very well, so I used the PyQt binding. For that, I had to install the python-pyqt4-phonon package. Second, the bindings don't support to call setCurrentSource() with a string; you have to wrap it in a MediaSource. The original API supports it. Third, it seems that Phonon.createPlayer() is not supported by the bindings either, so I had to build the AudioOutput by hand. I don't care, it's just a couple lines more.

This code also shows the name of the selected OutputDevice. I my machine it shows HDA Intel (STAC92xx Analog).

In the following days I'll be posting more info about what comes out of this project. I will only reveal that right now the code has classes called Player, PlayList and Collection. It can scan a Collection from a path given in the command line and play all the files found there. Soon more features will come.

python pykde satyr


[1] I'm not planing to do anything about it... yet.

Posted Wed 27 Jan 2010 11:55:55 PM CET Tags: pykde