#!/usr/bin/python2.4
## -*- coding: utf-8 -*-
# Copyright (c) 2006 INdT - Instituto Nokia de Tecnologia, Elvis Pfutzenreuter.
# All rights reserved. This software is released under the LGPL licence.
import gtk, gtk.glade
import gobject
import pygst
import gnome, gnomevfs
import gst
import time
import Image
import os, glob
from os.path import join, dirname, exists, abspath
GLADE = gtk.glade.XML(join(dirname(__file__), "camera.glade"))
PHOTOBOOTH_DIR = join(dirname(__file__), "photobooth")
try:
os.mkdir(PHOTOBOOTH_DIR)
except:
pass
WIDTH, HEIGHT = 640, 480
THUMB_WIDTH, THUMB_HEIGHT = WIDTH/5, HEIGHT/5
EFFECTS = {
"Quark": ("quarktv",),
"Rev": ("revtv",),
"Vertigo": ("vertigotv",),
"Shagadelic": ("shagadelictv",),
"Warp": ("warptv",),
"Dice": ("dicetv",),
"Aging": ("agingtv",),
"Edge": ("edgetv",),
"Color1": ("videobalance", {"saturation": 0.5, "hue": -0.5}),
"Color2": ("videobalance", {"saturation": 0.5, "hue": 0}),
"Color3": ("videobalance", {"saturation": 0.5, "hue": +0.5}),
"Color4": ("videobalance", {"saturation": 1.5, "hue": -0.5}),
"Color5": ("videobalance", {"saturation": 1.5, "hue": 0}),
"Color6": ("videobalance", {"saturation": 1.5, "hue": +0.5}),
"Color7": ("videobalance", {"saturation": 0, "contrast": 0.5}),
"Color8": ("videobalance", {"saturation": 0, "contrast": 2}),
"Color9": ("videobalance", {"saturation": 0.5, "contrast": 2}),
"Color10": ("videobalance", {"saturation": 1, "contrast": 2}),
"Color11": ("videobalance", {"saturation": 2, "contrast": 2}),
"Color12": ("videobalance", {"saturation": 0}),
}
class PhotoboothEngine(gobject.GObject):
__gsignals__ = {
"photo-taken" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, [gobject.TYPE_STRING]),
}
def __init__(self):
gobject.GObject.__init__(self)
self.pipeline = gst.Pipeline("photobooth")
self.do_grab_frame = False
self.xwindow = 0
# Alternate sources to run outside of Internet Tablet
#source = gst.element_factory_make("videotestsrc", "src")
#source.set_property("is-live", True)
source = gst.element_factory_make("gconfvideosrc", "src")
self.pipeline.add(source)
self.csp1 = gst.element_factory_make("ffmpegcolorspace", "csp1")
self.pipeline.add(self.csp1)
self.effect = gst.element_factory_make(EFFECTS[EFFECTS.keys()[0]][0], "effect")
self.pipeline.add(self.effect)
self.csp2 = gst.element_factory_make("ffmpegcolorspace", "csp2")
self.pipeline.add(self.csp2)
caps1 = gst.element_factory_make("capsfilter", "caps1")
self.pipeline.add(caps1)
tee = gst.element_factory_make("tee", "tee")
self.pipeline.add(tee)
queuevid = gst.element_factory_make("queue", "queuevid")
self.pipeline.add(queuevid)
csp3 = gst.element_factory_make("ffmpegcolorspace", "csp3")
self.pipeline.add(csp3)
self.sink = gst.element_factory_make("xvimagesink", "sink")
self.pipeline.add(self.sink)
queueimg = gst.element_factory_make("queue", "queueimg")
self.pipeline.add(queueimg)
fakesink = gst.element_factory_make("fakesink", "fakesink")
self.pipeline.add(fakesink)
fakesink.set_property("signal-handoffs", True)
fakesink.connect("handoff", self.on_buffer)
caps1.set_property('caps', gst.caps_from_string("video/x-raw-rgb,width=%d,height=%d,bpp=24,depth=24,framerate=30/1" % (WIDTH, HEIGHT)))
source.link(self.csp1)
self.csp1.link(self.effect)
self.effect.link(self.csp2)
self.csp2.link(caps1)
caps1.link(tee)
tee.link(queuevid)
queuevid.link(csp3)
csp3.link(self.sink)
tee.link(queueimg)
queueimg.link(fakesink)
def on_buffer(self, element, buffer, pad):
if self.do_grab_frame:
self.save_image(buffer)
self.do_grab_frame = False
return True
def save_image(self, buffer):
img = Image.frombuffer("RGB", (WIDTH, HEIGHT), buffer, 'raw', 'RGB', 0, 1)
i = 0
f = join(PHOTOBOOTH_DIR, "img-%02d.jpg" % i)
while exists(f):
i += 1
f = join(PHOTOBOOTH_DIR, "img-%02d.jpg" % i)
img.save(f)
self.emit("photo-taken", f)
print '...Saved: %s' % f
def stop(self):
self.pipeline.set_state(gst.STATE_NULL)
def pause(self):
self.pipeline.set_state(gst.STATE_PAUSED)
def play(self):
self.pipeline.set_state(gst.STATE_PLAYING)
def set_window(self, window):
self.xwindow = window
self.sink.set_xwindow_id(window)
def grab_frame(self):
self.do_grab_frame = True
def set_effect(self, effect, params={}):
self.stop()
self.csp1.unlink(self.effect)
self.effect.unlink(self.csp2)
self.pipeline.remove(self.effect)
print 'Creating effect:', effect, params
self.effect = gst.element_factory_make(effect, "effect")
for key, val in params.items():
self.effect.set_property(key, val)
self.pipeline.add(self.effect)
self.csp1.link(self.effect)
self.effect.link(self.csp2)
self.play()
self.set_window(self.xwindow)
class PhotoboothModel(gtk.ListStore):
def __init__(self, engine):
gtk.ListStore.__init__(self, gtk.gdk.Pixbuf, str)
self.engine = engine
self.engine.connect("photo-taken", self.on_photo_taken)
self.load_existing()
def append_photo(self, f):
pix = gtk.gdk.pixbuf_new_from_file(f)
pix = pix.scale_simple(THUMB_WIDTH, THUMB_HEIGHT, gtk.gdk.INTERP_BILINEAR)
self.append([pix, f])
def load_existing(self):
for f in glob.glob(join(PHOTOBOOTH_DIR, "*.jpg")):
self.append_photo(f)
def on_photo_taken(self, engine, f):
self.append_photo(f)
class PhotoboothWindow:
def __init__(self, engine):
self.engine = engine
self.window = GLADE.get_widget("photobooth")
self.timer_id = 0
self.window.set_title("Python Mirror")
self.window.connect("delete_event", self.on_delete_event)
self.window.connect("destroy", self.on_destroy)
self.screen = GLADE.get_widget("screen")
self.screen.set_size_request(500, 380)
self.screen.add_events(gtk.gdk.BUTTON_PRESS_MASK)
self.screen.connect("expose-event", self.on_expose);
self.take_picture = GLADE.get_widget("take_picture")
self.take_picture.connect("clicked", self.on_take_picture)
self.status = GLADE.get_widget("status")
self.previews = GLADE.get_widget("previews")
self.preview_model = PhotoboothModel(self.engine)
self.previews.connect("item-activated", self.on_photo_activated)
self.previews.set_model(self.preview_model)
self.previews.set_property("columns", 1000000)
self.previews.set_property("pixbuf-column", 0)
self.previews.set_size_request(WIDTH, THUMB_HEIGHT)
self.effects = GLADE.get_widget("effects")
for effect in EFFECTS.keys():
self.effects.append_text(effect)
self.effects.connect("changed", self.on_effects_changed)
self.effects.set_active(0)
self.engine.connect("photo-taken", self.on_photo_taken)
self.on_photo_taken(self.engine, None)
self.window.show_all()
self.status.hide()
def on_delete_event(self, widget, event):
return False
def on_destroy(self, widget):
engine.stop()
gtk.main_quit()
def on_expose(self, dummy1, dummy2):
self.engine.set_window(self.screen.window.xid)
def on_take_picture(self, button):
print 'Smile !'
if self.timer_id != 0:
return
def foobar():
self.status.set_markup("...Smile!")
self.engine.grab_frame()
self.take_picture.hide()
self.status.show()
self.status.set_markup("3...")
gobject.timeout_add(1000, lambda: self.status.set_markup("...2..."))
gobject.timeout_add(2000, lambda: self.status.set_markup("...1..."))
self.timer_id = gobject.timeout_add(3000, foobar)
def on_photo_taken(self, engine, f):
def foobar():
if len(self.preview_model) == 0:
return
iter = self.preview_model.get_iter_from_string(str(len(self.preview_model)-1))
path = self.preview_model.get_path(iter)
self.previews.scroll_to_path(path, True, 1.0, 0.5)
self.timer_id = 0
self.status.hide()
self.take_picture.show()
gobject.idle_add(foobar)
def on_effects_changed(self, combo):
effect_desc = EFFECTS[self.effects.get_active_text()]
self.engine.set_effect(*effect_desc)
def on_photo_activated(self, effects, path):
gnome.url_show("file://%s" % abspath(self.preview_model[path][1]))
class PhotoboothUploader:
def __init__(self, engine):
self.engine = engine
self.engine.connect("photo-taken", self.on_photo_taken)
def on_photo_taken(self, engine, f):
def foobar():
os.system("./photobooth-upload '%s' &" % abspath(f))
gobject.idle_add(foobar)
engine = PhotoboothEngine()
window = PhotoboothWindow(engine)
uploader = PhotoboothUploader(engine)
engine.play()
gtk.main()