#!/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 gobject gobject.threads_init() import gtk, gtk.glade, gtk.gdk gtk.gdk.threads_init() 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.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()