Sun, 13 Mar 2011

Fedora Photobooth @ SXSW

This is the first year that Fedora will have a booth at SXSW! Sadly, I am not going to be attending since it conflicts with PyCon. However, my code will be running at our booth. Usually the Fedora booth at conferences is comprised of a bunch of flyers, media, swag, and some people to help answer questions and tell the Fedora story. However at SXSW, things are going to be a little different.

Aside from the amazing flyers that Máirín created, there will also be a Fedora Photobooth. Someone (probably Spot or Jared) will be dressed in a full Tux costume, and people can come and get their photo taken with them. Spot came to me the other day and asked if I could write some code to streamline the whole process.

An hour or so later, photobooth.py was born. There are definitely lots of improvements that can be made, but here is what it currently does in its initial incarnation:

In Action
See Mo's blog for photos of this code in action at the Fedora SXSW booth!
* SXSW Expo Day 1 from the show floor
* SXSW Expo Day 2
* A Beefy, Miraculous Day at SXSW (Expo Day 3)


The Code
I threw this in a git repo and tossed it up on GitHub:
github.com/lmacken/photobooth.py
#!/usr/bin/python
# photobooth.py - version 0.3
# Requires: python-imaging, qrencode, gphoto2, surl
# Author: Luke Macken <lmacken@redhat.com>
# License: GPLv3

import os
import surl
import Image
import subprocess

from uuid import uuid4
from os.path import join, basename, expanduser

# Where to spit out our qrcode, watermarked image, and local html
out = expanduser('~/Desktop/sxsw')

# The watermark to apply to all images
watermark_img = expanduser('~/Desktop/fedora.png')

# This assumes ssh-agent is running so we can do password-less scp
ssh_image_repo = 'fedorapeople.org:~/public_html/sxsw/'

# The public HTTP repository for uploaded images
http_image_repo = 'http://lmacken.fedorapeople.org/sxsw/'

# Size of the qrcode pixels
qrcode_size = 10

# Whether or not to delete the photo after uploading it to the remote server
delete_after_upload = True

# The camera configuration
# Use gphoto2 --list-config and --get-config for more information
gphoto_config = {
    '/main/imgsettings/imagesize': 3, # small
    '/main/imgsettings/imagequality': 0, # normal
    '/main/capturesettings/zoom': 70, # zoom factor
}

# The URL shortener to use
shortener = 'tinyurl.com'

class PhotoBooth(object):

    def initialize(self):
        """ Detect the camera and set the various settings """
        cfg = ['--set-config=%s=%s' % (k, v) for k, v in gphoto_config.items()]
        subprocess.call('gphoto2 --auto-detect ' +
                        ' '.join(cfg), shell=True)

    def capture_photo(self):
        """ Capture a photo and download it from the camera """
        filename = join(out, '%s.jpg' % str(uuid4()))
        cfg = ['--set-config=%s=%s' % (k, v) for k, v in gphoto_config.items()]
        subprocess.call('gphoto2 ' +
                        '--capture-image-and-download ' +
                        '--filename="%s" ' % filename,
                        shell=True)
        return filename

    def process_image(self, filename):
        print "Processing %s..." % filename
        print "Applying watermark..."
        image = self.watermark(filename)
        print "Uploading to remote server..."
        url = self.upload(image)
        print "Generating QRCode..."
        qrcode = self.qrencode(url)
        print "Shortening URL..."
        tiny = self.shorten(url)
        print "Generating HTML..."
        html = self.html_output(url, qrcode, tiny)
        subprocess.call('firefox "%s"' % html, shell=True)
        print "Done!"

    def watermark(self, image):
        """ Apply a watermark to an image """
        mark = Image.open(watermark_img)
        im = Image.open(image)
        if im.mode != 'RGBA':
            im = im.convert('RGBA')
        layer = Image.new('RGBA', im.size, (0,0,0,0))
        position = (im.size[0] - mark.size[0], im.size[1] - mark.size[1])
        layer.paste(mark, position)
        outfile = join(out, basename(image))
        Image.composite(layer, im, layer).save(outfile)
        return outfile

    def upload(self, image):
        """ Upload this image to a remote server """
        subprocess.call('scp "%s" %s' % (image, ssh_image_repo), shell=True)
        if delete_after_upload:
            os.unlink(image)
        return http_image_repo + basename(image)

    def qrencode(self, url):
        """ Generate a QRCode for a given URL """
        qrcode = join(out, 'qrcode.png')
        subprocess.call('qrencode -s %d -o "%s" %s' % (
            qrcode_size, qrcode, url), shell=True)
        return qrcode

    def shorten(self, url):
        """ Generate a shortened URL """
        return surl.services.supportedServices()[shortener].get({}, url)

    def html_output(self, image, qrcode, tinyurl):
        """ Output HTML with the image, qrcode, and tinyurl """
        html = """
            <html>
              <center>
                <table>
                  <tr>
                    <td colspan="2">
                        <b><a href="%(tinyurl)s">%(tinyurl)s</a></b>
                    </td>
                  </tr>
                  <tr>
                    <td><img src="%(image)s" border="0"/></td>
                    <td><img src="%(qrcode)s" border="0"/></td>
                  </tr>
                </table>
              </center>
          </html>
        """ % {'image': image, 'qrcode': qrcode, 'tinyurl': tinyurl}
        outfile = join(out, basename(image) + '.html')
        output = file(outfile, 'w')
        output.write(html)
        output.close()
        return outfile

if __name__ == "__main__":
    photobooth = PhotoBooth()
    try:
        photobooth.initialize()
        while True:
            raw_input("Press enter to capture photo.")
            filename = photobooth.capture_photo()
            photobooth.process_image(filename)
    except KeyboardInterrupt:
        print "\nExiting..."


posted at: 02:57 | link | Tags: , , , | 21 comments

Posted by emichan at Sun Mar 13 16:04:26 2011

Luke you rock! Thank you so much for making this for us. Spot will be wearing the costume primarily, and it looks great on him from the pictures I've seen so far. Giant penguin ftw! :)

Posted by Ryan Rix at Mon Mar 14 05:56:03 2011

You should pass this along to http://www.fauxbooth.com/ . They're a localish outfit that has a photobooth similar to this, I'd imagine, and they run Fedora on their setup. Probably very similar to yours, in fact.

Posted by tom Callaway at Wed Mar 16 12:33:05 2011

Luke, your code has been working fantastic for us! Thanks again.

Posted by Sylvain at Thu Mar 17 22:44:12 2011

Hi,
Sadly it doesn't work for me :/

$ python photobooth.py
Modèle  Port 
----------------------------------------------------------
Nikon DSC D5000 (PTP mode)  usb:002,011 

*** Erreur ***
L'élément graphique /main/capturesettings/zoom n'est pas configurable.

*** Erreur ***
La propriétée 'Taille de l'image' / 0x5003 n'etait pas réglée, PTP eurror code 0x02ff.

*** Erreur ***
Échec lors du positionnement de la nouvelle valeur de configuration 3 pour l'entrée de configuration /main/imgsettings/imagesize.

Any idea to help me ?
(I use Fedora 14 and install 
correctly requires: python-imaging, qrencode, gphoto2, surl)

Thanks,
Sylvain

Posted by Luke Macken at Fri Mar 18 04:25:00 2011

Sylvain,

It looks like your camera doesn't support the default `gphoto_config` options that I set in there by default.  Try commenting the imagequality/imagesize/zoom out.

Posted by Sylvain at Fri Mar 18 07:16:45 2011

OK
If i disable all the gphoto config line, my camera take a photo.
gphoto_config = {
  #'/main/imgsettings/imagesize': 3, # small
  #'/main/imgsettings/imagequality': 0, # normal
  #'/main/capturesettings/zoom': 70, # zoom factor
}
But of course the file is quite big..
I also saw the watermark, so it's better than yesterday !

I also don't kwon how to replace the line for the upload to my personnal FTP. Any idea too ?

Thanks for your help.
Sylvain

Posted by Luke Macken at Fri Mar 18 17:41:06 2011

@Sylvain: You can run `gphoto2 --list-config --auto-config` to see all of the configuration options for your specific camera; it may have a different type of sizing/resolution option.

As for FTP, this script currently only supports SCP'ing it to a remove SSH/SFTP server.  Adding support for FTP shouldn't be /too/ difficult though, if someone wants to write a patch :)

Posted by Sylvain at Sat Mar 19 22:04:03 2011

Thanks.
For SFTP it's OK, I just try and find the correct syntax to reach my provider. It work's fine :)

But I'm sorry, I don't see how can 'gphoto2 --list-config --auto-config' help me :

$ gphoto2 --list-config --auto-config
Utilisation: gphoto2 [-?valLnPTDR] [-?|--help] [--usage] [--debug]
  [--debug-logfile=FILENAME] [--quiet] [--hook-script=FILENAME]
  [--stdout] [--stdout-size] [--auto-detect] [--show-exif=CHAINE]
  [--show-info=CHAINE] [--summary] [--manual] [--about]
  [--storage-info] [--shell] [-v|--version] [--list-cameras]
  [--list-ports] [-a|--abilities] [--port=FILENAME] [--speed=SPEED]
  [--camera=MODEL] [--usbid=USBIDs] [--list-config]
  [--get-config=CHAINE] [--set-config=CHAINE]
  [--set-config-index=CHAINE] [--set-config-value=CHAINE]
  [--wait-event=COUNT] [--wait-event-and-download=COUNT]
  [--capture-preview] [-B|--bulb SECONDS] [-F|--frames COUNT]
  [-I|--interval SECONDS] [--reset-interval] [--capture-image]
  [--capture-image-and-download] [--capture-movie] [--capture-sound]
  [--capture-tethered=COUNT] [-l|--list-folders] [-L|--list-files]
  [-m|--mkdir DIRNAME] [-r|--rmdir DIRNAME] [-n|--num-files]
  [-p|--get-file RANGE] [-P|--get-all-files] [-t|--get-thumbnail RANGE]
  [-T|--get-all-thumbnails] [--get-metadata=RANGE] [--get-all-metadata]
  [--upload-metadata=CHAINE] [--get-raw-data=RANGE]
  [--get-all-raw-data] [--get-audio-data=RANGE] [--get-all-audio-data]
  [-d|--delete-file RANGE] [-D|--delete-all-files]
  [-u|--upload-file FILENAME] [--filename=FILENAME_PATTERN]
  [-f|--folder FOLDER] [-R|--recurse] [--no-recurse] [--new]
  [--force-overwrite]


But thanks for your previous help !

Posted by luke at Sun Mar 20 03:23:20 2011

@Sylvain, maybe try doing --auto-detect first, and then --list-config ?

Posted by Sylvain at Sun Mar 20 09:09:49 2011

Well, it's near perfect :)

I understand a little how to run gphoto. I'll read gPhoto Doc too (http://www.gphoto.org/doc/remote/)

For exemple, to know what parameter is supported by my camero for imagesize, this is this command line :
$ gphoto2 --get-config imagesize
Current: 4288x2848
Choice: 0 4288x2848
Choice: 1 3216x2136
Choice: 2 2144x1424

So, visibly, the lower resolution I can have is with choice 2, a litter big perhaps, but anayway I can run better photobooth :)

Now I'll try to config the HTML generate file.

Thanks for all.

Posted by Sylvain at Sun Mar 20 15:09:16 2011

Do you know if it is possible to have the code of Fedora Photobooth ? It look like very nice !

Posted by Luke at Fri Mar 25 20:17:26 2011

@Sylvain, I don't have that template, but I'll see if I can track it down.

Posted by Sylvain at Fri Mar 25 21:59:16 2011

I hope you can.
Thanks.

Posted by IncernMaync at Fri May 27 15:29:38 2011

stД›hovГЎnГ­ pЕ™erov  kontaktnГ­ ДЌoДЌky pro dД›ti  parfГ©my keton  http://praguevo1036-bioten.co.tv  - letenky granada  kontaktnГ­ ДЌoДЌky historie 
prodluЕѕovГЎnГ­ Е™as praha 1  parfГ©my yves rocher  kontaktnГ­ ДЌoДЌky druhy  letenky uganda , parfГ©my fendi  parfГ©my clinique  letenky do londГЅna  parfГ©my joop .
parfГ©my na lГ©to  letenky winnipeg  stД›hovГЎnГ­ fofr  letenky chorvatsko  parfГ©my ostrava  parfГ©my eu  parfГ©my akce 
stД›hovГЎnГ­ klavГ­rЕЇ brno  letenky washington  prodluЕѕovГЎnГ­ Е™as pГ­sek  stД›hovГЎnГ­ nonstop  parfГ©my top 10 .
parfГ©my lr , kontaktnГ­ ДЌoДЌky oasys  parfГ©my ysl .
http://letenkyvu866-bose.co.tv  - parfГ©my hugo boss  stД›hovГЎnГ­ nonstop 
http://liposukcehy1418-ofnut.co.tv  - parfГ©my oriflame  letenky varЕЎava 
kontaktnГ­ ДЌoДЌky upГ­r  kontaktnГ­ ДЌoДЌky praha  stД›hovГЎnГ­ trezorЕЇ  letenky pelikan  parfГ©my boss .
parfГ©my versace , stД›hovГЎnГ­ brno cena  kontaktnГ­ ДЌoДЌky frequency xc .

Posted by photo booth at Sun Jul 24 13:16:27 2011

Thanks for the information buddy.

Posted by marcoelgordo at Tue Mar 27 20:42:07 2012

Hi,

I installed libsurl.i686 but I get the following message  File "./photobooth.py", line 8, in <module>
  import surl
ImportError: No module named surl. Am I missing any dependencies?

Posted by Luke at Mon Apr 9 14:37:45 2012

marcoelgordo,

You need to install `surl`. https://launchpad.net/surl

Posted by Marcoelgordo at Wed May 9 12:13:46 2012

Lewk,

I have sent you an email with some suggestion to the code to:
1. integrate Arduino push button to start the sequence
2. have a preview before taking the shot using piggyphoto
3. refresh the same firefox tab (using remote add-on) to avoid having multiple tabs open.
Have you received the email or do I need to resend it?

Marc

Posted by Marcoelgordo at Wed May 9 12:31:30 2012

Lewk,

I have sent you an email with some suggestion to the code to:
1. integrate Arduino push button to start the sequence
2. have a preview before taking the shot using piggyphoto
3. refresh the same firefox tab (using remote add-on) to avoid having multiple tabs open.
Have you received the email or do I need to resend it?

Marc

Posted by Marcoelgordo at Wed May 9 12:49:41 2012

Lewk,

I have sent you an email with some suggestion to the code to:
1. integrate Arduino push button to start the sequence
2. have a preview before taking the shot using piggyphoto
3. refresh the same firefox tab (using remote add-on) to avoid having multiple tabs open.
Have you received the email or do I need to resend it?

Marc

Posted by Luke at Sat Jun 22 01:34:17 2013

Hi Marcoelgordo! I have yet to receive those emails, please send them to lewk@csh.rit.edu. Thanks!


Name:


E-mail:


URL:


Comment: