Embedding Mplayer in a PyGTK application
Recently I wanted to extract the sound track from a MP4 file.
You actually need a single command-line to do that, either using Mplayer or GStreamer.
It's amazing what you can do with GStreamer pipelines, but unfortunately I ran into several problems trying to play H.264 video files with AAC audio (the files are produced by a Sanyo camera and make Totem crash after about few seconds...) so I gave up and I took a look at MPlayer. After all I really didn't need all the GStreamer functionalities.
A single line actually does the job:
mplayer -ao pcm:file=$OUTPUT_FILE $INPUT_FILE
Couldn't be easier.
Nevertheless, I wanted to command the extraction from a GUI as it makes it more user friendly for people that don't deal with shells everyday. Additionally, I didn't want the MPlayer window to popup, I actually wanted to embed Mplayer inside my application.
Again, it turns out that a single line does the job:
mplayer -ao pcm:file=$OUTPUT_FILE -wid $WINDOW_XID
The -wid option tells Mplayer to connect to the window represented by the ID $WINDOW_XID.
This ID is actually the identifier given by the X Server to the window.
We'll see later how to retrieve it.
With this setup, the video is shown in the application, the problem is that there is no indication about the progress. What I wanted was to periodically ask to MPlayer the status of the playback, and displaying it in a progress bar. Checking the MPlayer's man page I discovered that it can be controlled by putting it into a slave mode and by sending him the commands through a named pipe. Bingo !
To start the mplayer in slave mode, I modified the command line to look like this:
mplayer -ao pcm:file=$OUTPUT_FILE -wid $WINDOW_XID -slave -idle \n -input file=$FIFO
where FIFO is the absolute path of the named pipe where the commands are sent
(for a complete list of commands, type mplayer -input cmdlist).
On the Python side, you have to embed the video in a GtkDrawingArea.
The documentation for this component just says:
The GtkDrawingArea widget is used for creating custom user interface elements.
It's essentially a blank widget; you can draw on widget->window
It is actually everything I need, as the only thing required to do is to get the X ID of the window and pass it to Mplayer. You do it like this:
xid = canvas.window.xid # where canvas is an instance of GtkDrawingArea
We have almost everything required to start Mplayer, the only missing thing is the FIFO.
In Python, you create a FIFO using
os.mkfifo("path_to_fifo")
The full code to start Mplayer in another process would then be:
command = "mplayer -ao pcm:file=%s -wid %i -slave -idle \n -input file=%s" % (outputFile,xid,fifo) command = command.split().append("input_video_file.mp4") subprocess.Popen(command, stdout=logfile, stderr=logfile)
I added the input file after the call to split because if there are whitespaces in the filename or in a parent directory, then the filename would break down in multiple tokens with the call to split.
Now that MPlayer has started, we have to tell him what to do. To do that, we write to the pipe:
mplayerClient = open(FIFO,"w") mplayerClient.write("play\n") # commands must ends with a newline
(Actually, I still haven't get MPlayer to not start automatically the playback, even if I'm using the -idle option. Please post a comment if you can shed a light on the subject...)
At this point the application is showing the video, but as we said before, we also want to update a progress bar to show the progress, and to do that we've to repeatedly ask the status to Mplayer.
The easiest way to do that, since I was using Gtk, is to use the gobject.timeout_add call, like this:
# call the myUpdateFunction each UPDATE_INTERVAL milliseconds gobject.timeout_add(UPDATE_INTERVAL, myUpdateFunction)
and myUpdateFunction is defined as:
def myUpdateFunction(): keepGoing = True mplayerClient.write("get_percent_pos\n") mplayerClient.flush() # really important, or commands won't be always sent ! line = logfile.readline() if line and line.startswith("ANS_PERCENT_POSITION"): # +1 because progress starts from 0, it would end to 99 otherwise progress = int(line[line.index('=')+1:-1]) + 1 progressBar.set_text("%s %%" % progress) progressBar.update(progress/100.0) else: keepGoing = False return keepGoing
That's it, progressBar is a normal GtkProgressBar object and logfile is where I previously redirected Mplayer stdout and stderr.
The above code won't actually works out of the box as there are some corner cases you have to deal with, for example it can happen that the update function is called before MPlayer starts to play the video (which should actually happen with the -idle option, but I couldn't get it to work). In that case line would be an empty string and the playback will immediately stop as myUpdateFunction will return False.
You can find the complete but fairly hackish code here:
AudioExtractor.zip

The program also pops up a dialog asking you if you want to burn the file with Serpentine or save it to a different location. I can't guarantee it will work for you as I've been written it mostly as an educational tool.
This is exactly what I was looking for! Thanks!
Hey Marco,
Thanks for that – I have the same problem to solve – thanks so much.
BTW, couldn’t you just write “pause” into the pipe right after starting mplayer?
Hi Kriss,
yes you could but that didn’t work for me, I can’t remember the reason why. Anyway, after writing the post I refined a bit the code and now I load the video with this line:
self.mplayerClient.write(“loadfile ‘%s’ 0\npause” % self.videoFile)
I’ve uploaded a new version of the file at http://www.dinointeractive.com/code/audioextractor.zip , however I don’t maintain it anymore so there may be bugs.
Hi Marco,
I just found the problem. When reading out the percentage mplayer resumes playback. Actually self.mplayerClient.write(“pausing_keep get_percent_pos\n”) should work, but doesn’t – Anyway your new code works, so I’ll look into it.
Hi,
I’m looking for something like that!
I’ve wrote a piece of code of embed several mplayer into a pygtk GUI.
But i’ve got an issue with the resize, when I resize the main window, streams doesn’t resize. But in your example stream follow the size of the window ? How do you did it? I assume I don’t use glade.
Thanks for any answer.