Using Gstreamer with Python2.6 in Windows XP – Pt. 5 – A Simple Gstreamer Wrapper

(Original post from 31. March 2012)

Hello loyal audience, whoever you may be. As you can deduct from the last post about Gstreamer, I have decided to go back on board with it from a lack of option and the fact that it seems to support mp3 files now on both Windows (!!!) and Linux.

I haven’t quite got a hold of the guys responsible for the Gstreamer Ossbuild, but I will thank them without end… and then ask them about some strange things I encountered when trying to install the Gstreamer Python bindings.

Anyhow, enough of that idle chitchat. Since everything is working fine, I have been trawling through the Gstreamer Tutorial a bit more and Jonobacon’s Tutorial (My thanks to both authors, fantastic tutorials) to get all the necessary functionality together and I have to my surprise been able to write an entire wrapper for an Audiofile class, which I will append to the end of this text, because it is rather long. I will, however, highlight the odd bits about it and if I have a reason for it, I shall explain, if not (the majority) then you will have some working code and I may come back to this text, once I have found the answer.

First of all, I have to say that I stuck with the standard playbin2 as it does all the things I need and it is fantastic and simple to use. Why reinvent the wheel with pipelines and ghostpads (and spend hours upon hours in the process) when you can have it easy 🙂 If some things are unclear and I haven’t mentioned them, chances are they are in any of the entries, I have posted before on the topic (Check the tags for that).

I have already discussed the basics like play and stop. The message system isn’t spectacular and is also taken one to one from the tutorial (thank you, again, oh dear author of this fine piece of work).

One thing that is important to mention at the start is the role of the two commands

gobject.threads_init()

and

gobject.MainLoop().run()

As far, as I am aware, these are necessary for some, but not all processes. If you want your file to only play for say 10 seconds, you might just as well use the built-in time.sleep function. However, these commands provide the endless loop of the application which means that when the song ends this does not mean the script has ended, so always end your script explicitly.

And also, I am assuming, some processes need another thread to be started, so I suggest once it becomes more complex than play, pause, stop, you call these commands.

Two very straight forward methods are the volume handling. There really is no rocket science about this. Volume is a float between 1.0 and 0.0, the rest is self-explanatory, I would assume. Let’s move on.

    def set_volume(self, new_volume):
        """
        Sets new volume of self.audiofile instance.
        new_volume must be float between 0.0 and 1.0
        """
        
        self.player.set_property("volume", new_volume)
        
    def get_volume(self):
        """
        Gets volume of self.audiofile instance.
        new_volume must be float between 0.0 and 1.0
        """
        
        return self.player.get_property("volume")

The other important function for an audiofile is being able to fast forward are rewind. This is done by the set_position() and get_position() methods. Gstreamer handles the time in nanoseconds and as such, it is necessary to convert them accordingly. As I wanted to use the numbers in a more useful manner (and also more humanly readable), I decided to write two functions converting nanoseconds to a tuple of hour, minutes and seconds and back. This should also allow me to give a regular time update on the user interface once I managed to get that one.

Regarding the interesting gst.SEEK_FLAG_FLUSH, I rather quote the official guide than mincing my own words. I hereby quote:

Seeks with the GST_SEEK_FLAG_FLUSH should be done when the pipeline is in PAUSED or PLAYING state. The pipeline will automatically go to preroll state until the new data after the seek will cause the pipeline to preroll again. After the pipeline is prerolled, it will go back to the state (PAUSED or PLAYING) it was in when the seek was executed. You can wait (blocking) for the seek to complete with gst_element_get_state() or by waiting for the ASYNC_DONE message to appear on the bus.

Seeks without the GST_SEEK_FLAG_FLUSH should only be done when the pipeline is in the PLAYING state. Executing a non-flushing seek in the PAUSED state might deadlock because the pipeline streaming threads might be blocked in the sinks.

Don’t say, you weren’t told…

A thing that struck me as very odd was the fact that I had to add a time.sleep call at the end of it, because otherwise I would receive a gst.QueryError when calling get_position() or get_volume(). Why this is, I am not exactly certain about, but it did the job, so I am not arguing.

    def get_position(self):
        """
        Returns the position of the currently played audiofile as
        a tuple of (hour, min, sec).
        """
        
        try:
            pos_dur = self.player.query_position(gst.FORMAT_TIME, None)[0]
            pos = self.convert_ns(pos_dur)
            return pos
        
        except gst.QueryError:
            return (0, 0, 0)
        
    def set_position(self, new_pos):
        """
        Sets a new position of the currently played audiofile as 
        a tuple of (hour, min, sec)
        
        new_pos format is (hour, min, sec)
        """
        
        seek_pos = self.convert2ns(new_pos)
        self.player.seek_simple(gst.FORMAT_TIME, gst.SEEK_FLAG_FLUSH, seek_pos)
        # time.sleep seemed to be necessary for action right after to work
        # correctly. test if it can be removed when the GUI is up and running.
        time.sleep(0.2)
        
    def convert_ns(self, time):
        """
        Taken from gstreamer tutorial. Thank you very much to Sam Mason
        http://pygstdocs.berlios.de/pygst-tutorial/seeking.html
        adjusted to return a tuple (hour, min, sec)
        
        Converts nanoseconds to tuple (hour, min, sec).
        
        Used in:
        - method get_length
        """
        
        sec, nanosec = divmod(time, 1000000000)
        minute, sec = divmod(sec, 60)

        if minute < 60:
            return (0, int(minute), int(sec))
        else:
            hour, minute = divmod(minute, 60)
            return (int(hour), int(minute), int(sec))
        
    def convert2ns(self, time):
        """
        Converts a tuple of (hour, min, sec) to nanoseconds.
        """
        
        nanosec = 0
        hour, minute, sec = time
        
        if hour != 0:
            total_sec = (((hour*60)+minute)*60)+sec
            nanosec += total_sec*1000000000
            
        else:
            total_sec = (minute*60)+sec
            nanosec += total_sec*1000000000
            
        return nanosec

Ah well, that’s it. Maybe a rather boring post for you, but I enjoyed doing it. The more I use Gstreamer, the more it grows on me. This is probably, the last article on this topic, since I will now aim to incorporate the functionality into my GUI and create a working audioplayer. check out the posts tagged as “Music’N’SFX”. There you should find more. I hope you enjoyed it. Please find below the code for the entire Audiofile class.

—————————————————————————————————————–

A brief disclaimer up front. If there any major blooping mistakes then I apologise and if you also tell me about them and how to remedy them, I am more than happy to do so, because I may also learn something in the process.

#!usr/bin/python

# Standard libraries
import os
import time
import urllib

import pygst
pygst.require("0.10")
import gst
import gobject

class AudioFile:
    """
    A wrapper class for the gstreamer library.
    """
    
    def __init__(self, path, volume=1.0):
        
        self.path = path
        self.volume = volume # value between 0.0 and 1.0
        
        # The Gstreamer playbin - out of box audio/video functionality
        self.player = gst.element_factory_make("playbin2", "player")
        self.fakesink = gst.element_factory_make("fakesink", "fakesink")
        self.player.set_property("video-sink", self.fakesink)
        
        # set_current file
        uri_path = urllib.pathname2url(self.path)
        self.player.set_property("uri", "file:" + uri_path)
        
        # bus is used to report messages in playtime
        # Taken from tutorial, also the methods
        bus = self.player.get_bus()
        bus.add_signal_watch()
        bus.enable_sync_message_emission()
        bus.connect("message", self.on_message)
        
    def __str__(self):
        pathsplit = os.path.split(self.path)
        return """Properties of current audiofile class:
        Filename: {0}
        Path: {1}
        Volume: {2}
        """.format(pathsplit[-1], pathsplit[:-1], self.volume)
        
    def reload(self, path):
        self.__init__(path, self.volume, self.repeat)
        
    def play(self):
        self.player.set_state(gst.STATE_PLAYING)
        
    def stop(self):
        self.player.set_state(gst.STATE_NULL)
        
    def pause(self):
        self.player.set_state(gst.STATE_PAUSED)
        
    def reset(self):
        self.player.stop()
        self.player.play()
        
    def set_volume(self, new_volume):
        """
        Sets new volume of self.audiofile instance.
        new_volume must be float between 0.0 and 1.0
        """
        
        self.player.set_property("volume", new_volume)
        
    def get_volume(self):
        """
        Gets volume of self.audiofile instance.
        new_volume must be float between 0.0 and 1.0
        """
        
        return self.player.get_property("volume")
        
    def get_length(self):
        """
        Returns the length of the currently played audiofile as
        a tuple of (hour, min, sec).
        """
        
        try:
            time.sleep(0.2) # from tutorial. Why necessary?
            int_dur = self.player.query_duration(gst.FORMAT_TIME, None)[0]
            length = self.convert_ns(int_dur)
            
            return length
            
        except gst.QueryError:
            return (0, 0, 0) # hour, min, sec
        
    def get_position(self):
        """
        Returns the position of the currently played audiofile as
        a tuple of (hour, min, sec).
        """
        
        try:
            pos_dur = self.player.query_position(gst.FORMAT_TIME, None)[0]
            pos = self.convert_ns(pos_dur)
            return pos
        
        except gst.QueryError:
            return (0, 0, 0)
        
    def set_position(self, new_pos):
        """
        Sets a new position of the currently played audiofile as 
        a tuple of (hour, min, sec)
        
        new_pos format is (hour, min, sec)
        """
        
        seek_pos = self.convert2ns(new_pos)
        self.player.seek_simple(gst.FORMAT_TIME, gst.SEEK_FLAG_FLUSH, seek_pos)
        # time.sleep seemed to be necessary for action right after to work
        # correctly. test if it can be removed when the GUI is up and running.
        time.sleep(0.2)
        
    
    def on_message(self, bus, message):
        """
        Taken from gstreamer tutorial.
        http://pygstdocs.berlios.de/pygst-tutorial
        
        Used in:
        - connects to bus in class.
        """
        
        t = message.type
        if t == gst.MESSAGE_EOS: # track is finished
            self.player.set_state(gst.STATE_NULL) # change this to call next
        elif t == gst.MESSAGE_ERROR:
            self.player.set_state(gst.STATE_NULL)
            err, debug = message.parse_error()
            print "Error: %s" % err, debug
            
    def convert_ns(self, time):
        """
        Taken from gstreamer tutorial. Thank you very much to Sam Mason
        http://pygstdocs.berlios.de/pygst-tutorial/seeking.html
        adjusted to return a tuple (hour, min, sec)
        
        Converts nanoseconds to tuple (hour, min, sec).
        
        Used in:
        - method get_length
        """
        
        sec, nanosec = divmod(time, 1000000000)
        minute, sec = divmod(sec, 60)

        if minute < 60:
            return (0, int(minute), int(sec))
        else:
            hour, minute = divmod(minute, 60)
            return (int(hour), int(minute), int(sec))
        
    def convert2ns(self, time):
        """
        Converts a tuple of (hour, min, sec) to nanoseconds.
        """
        
        nanosec = 0
        hour, minute, sec = time
        
        if hour != 0:
            total_sec = (((hour*60)+minute)*60)+sec
            nanosec += total_sec*1000000000
            
        else:
            total_sec = (minute*60)+sec
            nanosec += total_sec*1000000000
            
        return nanosec

if __name__ == "__main__":
    
    gobject.threads_init()
    
    p = "/a/path/of/your/choosing"
    
    audio = AudioFile(p)
    
    audio.play()
    
    time.sleep(5)
    print audio.get_position()
    audio.set_position((0, 0, 10))
    
    print audio.get_position()
    print audio.get_length()
    print audio.get_volume()
    
    audio.set_volume(0.5)
    time.sleep(5)
    audio.set_volume(1.0)
    time.sleep(5)
    audio.pause()
    time.sleep(5)
    audio.play()
    time.sleep(5)
    audio.stop()
    
    gobject.MainLoop().run()
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s