Compile and deploy Python application to binary file for Windows and Linux

Hey guys,
today I had to deploy a Python application for Windows and Linux users, which have
no Python distribution installed on their system. That’s why, I had to statically link
all used Python libraries and compile my Python application to a binary file.

There have been a few traps. For saving a bit of your time, I wrote this short How-To.
Ok, let’s say we have a Python script called ‘MyFancyApplication.py‘, which we want to deploy for Linux and Windows. We start with the Windows environment.

Compile Python application in Windows environment

For this task, I use the tool py2exe.

We can install the tool via easy_install. If you have easy_install not installed, you can get it by installing the setuptools.

easy_install py2exe

The configuration of py2exe can be done by creating a file called setup.py with the following content:

from distutils.core import setup
import py2exe

excludes = []
#includes = ["scipy.sparse.csgraph._validation", "scipy.spatial.kdtree", "scipy.sparse.csgraph._shortest_path"]
includes = []

opts = {
	"py2exe": {
		"includes":includes,
		"excludes":excludes
	}
}

setup(console=['MyFancyApplication.py'], pptions=opts)

You can add included paths in the respective line.
Of course, you also have to change the name of you python script.

Then you can compile your Python application via command line:

python setup.py py2exe

The compiling process should end with creating two folders (‘build’ and ‘dist’).
You only need to deploy the content of ‘dist’.

Now you are able to deploy all your Python applications to your Windows friends! *Hooray!*

But what about your friends using Linux? Just read on!

Compile Python application in Linux environment

Install patchelf(needed for bbfreeze)

wget http://hydra.nixos.org/build/1524660/download/3/patchelf-0.6.tar.gz
tar xzf patchelf-0.6.tar.gz
cd patchelf-0.6
./configure
make
sudo make install

Install bbfreeze using easy_install (http://pypi.python.org/pypi/bbfreeze):

sudo easy_install bbfreeze

Now you can configure bbfreeze within a created script file, e.g. setup-bbfreeze.py:

from bbfreeze import Freezer

f = Freezer("MyFancyApplication", includes="_strptime",))
f.addScript("MyFancyApplication.py")
f() # starts freezing process

Now we can compile the Python application by calling:

python setup-bbfreeze.py

Now there should be a folder named ‘MyFancyApplication‘ containing the binary and all libs.

Your binary still depends on the version of your standard libs (libc, etc.). So try to build the application on a computer with old standard libs, since newer ones on other computers are backward-compatible, but not the other way round! In my case, I ran the compilation in Ubuntu 10.04, although 12.04 is up-to-date.

So long! Good luck!
Michael

Pyglet audio from numpy arrays

pyglet is a pretty usable “cross-platform windowing and multimedia library for Python”. Among other things, it can easily play back audio from a file:

import sys
import pyglet

sound = pyglet.media.load(sys.argv[1])

player = pyglet.media.Player()
player.queue(sound)
player.play()

pyglet.app.run()

Unfortunately, there is apparently no easy way to play back audio from memory, e.g. from a numpy array. However, the scarcely documented StaticMemorySource class provides just that. With a small wrapper, numpy arrays can conveniently be used as pyglet audio sources:

import pyglet

class ArraySource(pyglet.media.StaticMemorySource):
    '''A source that has been created from a numpy array.'''

    def __init__(self, rate, data):
        '''Construct an `ArraySource` for the data in `data`.

        :Parameters:
            `rate` : `int`
                The sampling rate in Hertz.
            `data` : `numpy.array`
                A c-contiguous numpy array of dimension (`num_samples`, `num_channels`).
        '''

        if data.ndim not in (1, 2):
            raise ValueError("The data array must be one- or two-dimensional.""")

        if not data.flags.c_contiguous:
            raise ValueError("The data array must be c-contiguous.""")

        num_channels = data.shape[1] if data.ndim > 1 else 1
        if num_channels not in [1, 2]:
            raise ValueError("Only mono and stereo audio are supported.""")

        num_bits = data.dtype.itemsize * 8
        if num_bits not in [8, 16]:
            raise ValueError("Only 8 and 16 bit audio are supported.""")

        audio_format = pyglet.media.AudioFormat(num_channels, num_bits, rate)

        super(ArraySource, self).__init__(data.tostring(), audio_format)

    def _get_queue_source(self):
        return self

The functionality of the wrapper is demonstrated in this somehow contrived example:

if __name__ == "__main__":
    import sys
    from scipy.io import wavfile

    sound = ArraySource(*wavfile.read(sys.argv[1]))

    player = pyglet.media.Player()
    player.queue(sound)
    player.play()

    pyglet.app.run()