Ubuntu Opportunistic Developers Week March 2010 - SHOWCASE: Photobomb - Rick Spencer - Mar 3 2010
(03:02:05 PM) rickspencer3: hi all (03:02:07 PM) rickspencer3: soooo (03:02:17 PM) rickspencer3: I'm here to talk about photobomb (03:02:46 PM) rickspencer3: jono thought some folks might be interested in hearing about some of the technology that I used and such (03:03:25 PM) rickspencer3: to make it easier to walk through, I put some screenshots and some notes: (03:03:28 PM) rickspencer3: here: (03:03:28 PM) rickspencer3: http://theravingrick.blogspot.com/2010/03/photobomb-featured-app-for-opp-dev-week.html (03:04:06 PM) rickspencer3: shall I begin with photobomb? (03:05:18 PM) rickspencer3: I wrote the first iteration of Photobomb one Saturday afternoon. (03:05:34 PM) rickspencer3: does everyone know what it does? (03:05:43 PM) rickspencer3: it's basically a hacked together photo editor (03:06:09 PM) rickspencer3: that let's you mash up images from sources like your feeds, your computer, the web, your web cam, etc... (03:06:13 PM) rickspencer3: do some fun stuff (03:06:20 PM) rickspencer3: and then you can save the image or tweet it (03:06:34 PM) rickspencer3: I wrote this totally just for the fun of it in my spare time (03:06:42 PM) rickspencer3: during January, I set the goal of writing one new feature a night (03:06:48 PM) rickspencer3: so I got pretty far with it that way (03:06:56 PM) rickspencer3: of course, it's a bit buggy still (03:07:00 PM) rickspencer3: but it's kinda fun to use (03:07:14 PM) rickspencer3: anyone who wants to contribute to it, fork it, whatever (03:07:22 PM) rickspencer3: by all means, have a go (03:07:24 PM) rickspencer3: anyway ... (03:07:37 PM) rickspencer3: I used Quickly to get the project started, so it was just a matter of laying out some UI in Glade, and then figuring out how to manipulate images. (03:07:52 PM) rickspencer3: I used Python Imaging Library (PIL) to create a cropped version of that little squirrel, and to paste it onto the picture. (03:08:00 PM) rickspencer3: PIL is a complete and power library. It was a bit tough figuring out the masking to crop the squirrel image as desired, but I eventually managed. (03:08:16 PM) rickspencer3: I ended up moving away from PIL, but from what I saw, I was impressed (03:08:22 PM) rickspencer3: any questions do far? (03:09:12 PM) ClassBot: mhall119 asked: where can we get the code? (03:09:24 PM) rickspencer3: it's in a junk branch (03:09:27 PM) ***rickspencer3 gets (03:09:43 PM) rickspencer3: https://code.edge.launchpad.net/~rick-rickspencer3/+junk/photobomb (03:09:46 PM) rickspencer3: party on! (03:09:54 PM) rickspencer3: I think it might be in my ppa too (03:10:08 PM) rickspencer3: ok (03:10:17 PM) rickspencer3: so following along to the next screen shot (03:10:51 PM) rickspencer3: I added the ability to choose an image to stick in there (03:11:04 PM) rickspencer3: and photobomb with that little squirrel (03:11:12 PM) rickspencer3: It turned out to be tedious to write code to open an image file. (03:11:18 PM) rickspencer3: So I added a prompt to quickly.widgets to handle opening files. (03:11:23 PM) rickspencer3: Having your own library of reusable widgets is quite a luxury. (03:11:30 PM) rickspencer3: if you look closely at the screenshot ... (03:11:35 PM) rickspencer3: Note that I didn't know the gtk.FileChooser API that well, so made a couple of early mistakes, such as handling the existing file replace functionality myself. (03:11:47 PM) rickspencer3: Didn't realize that the API could handle it (03:12:02 PM) rickspencer3: but in any case, the shell of photobomb is PyGtk of course (03:12:25 PM) rickspencer3: and I had to learn a bit about some areas of Gtk that I hadn't used too much (03:12:32 PM) rickspencer3: any questions about that one? (03:13:23 PM) ClassBot: mhall119 asked: Did you already know Glade before you started this? (03:13:26 PM) rickspencer3: oh yes (03:13:27 PM) rickspencer3: quite (03:13:37 PM) rickspencer3: at first, you think Glade is your enemy (03:13:46 PM) rickspencer3: but after a while, you learn it is our friend (03:13:52 PM) rickspencer3: but Glade can be a bit hard to learn (03:14:01 PM) rickspencer3: and it isn't designed to do everything (03:14:11 PM) rickspencer3: so writing gtk.UIBuilder code is still part of hte process (03:14:24 PM) rickspencer3: quickly makes this a bit easier, by giving each window a builder function (03:14:30 PM) rickspencer3: I mean builder member (03:14:33 PM) rickspencer3: so you can go: (03:14:54 PM) rickspencer3: self.builder.get_object("hbox1").pack_start(my_widget) (03:15:16 PM) ClassBot: mhall119 asked: does photobomb run on Karmic? or just Lucid? (03:15:20 PM) rickspencer3: well (03:15:26 PM) rickspencer3: it's packaged for Lucid (03:15:36 PM) rickspencer3: because it uses quickly-widgets (03:15:43 PM) rickspencer3: which is in the quidgets project (03:15:48 PM) rickspencer3: however, if you want to run it (03:15:52 PM) rickspencer3: it shouldn't be too hard (03:15:59 PM) rickspencer3: just branch photobomb (03:16:07 PM) rickspencer3: then branch quidgets (03:16:34 PM) rickspencer3: then stick quidgets/quickly dir in photobomb/photobomb, and it might work (03:17:03 PM) rickspencer3: or just put quidgets somewhere on disk and set the python path when you run it (03:17:34 PM) rickspencer3: !PYTHONPATH="~/quidgets/quickly" python photobomb/bin/photobomb (03:17:35 PM) rickspencer3: for example (03:17:50 PM) ClassBot: mhall119 asked: Any good tutorials on learning to make Glade my friend? (03:17:55 PM) rickspencer3: mmm (03:17:58 PM) rickspencer3: not really (03:18:04 PM) rickspencer3: there are a bunch of tutorials (03:18:23 PM) rickspencer3: but many are out of date, since PyGtk moved form LibGlade to UI Builder (03:18:39 PM) rickspencer3: if you have Quickly installed, there is a bit in there to help you get started (03:18:52 PM) rickspencer3: #ubuntu-app-devel is a good place to ask specific gladey questions (03:19:12 PM) rickspencer3: unless it's about workign with a quickly template, in which case #quickly might be better (03:19:21 PM) rickspencer3: ok (03:19:26 PM) rickspencer3: so next section (03:19:40 PM) rickspencer3: When I tried to implement some captioning and other features, I came to realize that PIL was just not designed for the kind of stuff I wanted to do with it. (03:19:51 PM) rickspencer3: When I switched to GooCanvas, it became much easier to add features. (03:19:56 PM) rickspencer3: I was able to add resizing, rotation, and drag to move in a matter of an hour or so. (03:20:39 PM) rickspencer3: A couple of days later I fixed an issue with dragging rotated items that resulted from the craziness involved with items in goocanvas maintaining their own coordinate system seperate from the goocanvas they are in. (03:21:10 PM) rickspencer3: questions about PIL, GooCanvas, or anything else so far? (03:21:48 PM) ClassBot: mhall119 asked: it doesn't look like you did much with glade, do you find yourself doing most UI building directly in python? (03:21:52 PM) rickspencer3: yes (03:22:00 PM) rickspencer3: I genreally use glade to set up containers (03:22:05 PM) rickspencer3: then I target those container from code (03:22:15 PM) rickspencer3: especially for custom images and such' (03:22:23 PM) rickspencer3: also, I use subclassing a lot (03:22:36 PM) rickspencer3: and I haven't done much work to make my own widgets work within glade (03:22:54 PM) rickspencer3: ok (03:22:54 PM) rickspencer3: I could then go and the ability for users to use a pen tool in photobomb. (03:23:00 PM) rickspencer3: This required managing mouse events and creating a PolyLine object from the collected mouse points. (03:23:08 PM) rickspencer3: Later I found that a Path worked better than a PolyLine for creating the Ink. I still have not figured out how to add points to an existing Path. (03:23:12 PM) rickspencer3: As a result, Photobomb recreates the Path with each mouse move. This gets slow if the Path is not short. (03:23:35 PM) rickspencer3: I did a session on GooCanvas yesterday, but can take some quick questions on it today (03:24:43 PM) rickspencer3: I used the gtk.ColorChooserDialog for color selection. (03:24:48 PM) rickspencer3: I also added color button and ink width button. (03:24:57 PM) rickspencer3: originally, These hosted a goocanvas, and I just added a path to each and set the properties of the Path to display the selected color and width. (03:25:20 PM) rickspencer3: later I changed the color button to change it's background to show the correct color (03:25:25 PM) rickspencer3: I mean, selected color (03:25:41 PM) rickspencer3: there's a "color selection" button in Gtk, but it didn't quite suite my UI (03:25:53 PM) rickspencer3: in terms of getting settings from users (03:25:53 PM) rickspencer3: I had already created quickly.prompts to make it easy to collect a line width from the user. (quickly.prompts.integer()). (03:26:05 PM) rickspencer3: I could handle this with a GooCanvas.Widget object embedded in situ, but that's a lot of work and using quickly.prompts.string() is a one-liner. (03:26:12 PM) rickspencer3: questions so far? (03:26:54 PM) rickspencer3: following along, the next thing I implemented was adding text to the goocanvas (03:26:58 PM) rickspencer3: I added a gtk.STOCK_EDIT button to collect text from the user. (03:27:07 PM) rickspencer3: well, that was just to manage the correct icon (03:28:19 PM) rickspencer3: since i wanted buttons to be compact (03:28:40 PM) rickspencer3: I used toolbar buttons, and just setting them to stock worked well (03:28:54 PM) rickspencer3: later, I had to hack around some limitations to how "stock" works (03:29:22 PM) rickspencer3: I used a quickly.prompts.string() to collect the string to display from the user. (03:29:52 PM) rickspencer3: next I added the some feedback about the current selection (03:29:52 PM) rickspencer3: To display selection, I simply used a GooCanvas.Rect with a bit of buffer around the selected object. (03:30:00 PM) rickspencer3: I could have added stock selection points from gtk, but that sounded like a lot of work. (03:30:07 PM) rickspencer3: questions so far? (03:32:27 PM) rickspencer3: I added a bit of opacity to the selection box. This works better, as you can see what is behind the selected item (03:32:53 PM) rickspencer3: Then I added opacity to other selectable items. (03:32:58 PM) rickspencer3: I sniped some code from segphault's grabbersnap app to figure out how to set up rgba colors. (03:33:04 PM) rickspencer3: It's a bit of a hack, but seems to work well. (03:33:16 PM) rickspencer3: At this point in the project I also switched from having the user open individual images to add, to iterating through the Pictures directory and adding a button for each encountered image. (03:33:21 PM) rickspencer3: I accomplished this asynchronously by putting this on a thread, using quickly.widgets.asynch_task_progressbox() (03:33:29 PM) rickspencer3: This simplifies threaded coding a bit, but I am finding that threads + Python + PyGtk don't work out as well as I want, no matter how carefully I code it (03:33:54 PM) rickspencer3: happy to take questions now if you've gottem' (03:34:42 PM) rickspencer3: so moving on to the web cam feature (03:34:49 PM) rickspencer3: Web Cam support is an important feature for Photobomb, (03:35:00 PM) rickspencer3: but the available APIs looked daunting. (03:35:04 PM) rickspencer3: That is, until I discovered that Pygame had a simplified web cam interface. (03:35:14 PM) rickspencer3: I created a quickly.widget to handle web cam integration and added that widget to Photobomb. (03:35:20 PM) rickspencer3: Note that this means quickly.widgets depends on PyGame. (03:35:26 PM) rickspencer3: I think I shall move the webcam widget out of the default install and make it available in some other manner. (03:35:35 PM) rickspencer3: Having another source of images meant that I needed to add a gtk.Notebook to handle switching between the two sources. (03:36:10 PM) rickspencer3: I later went on to use icons for the notebook tabs, but labels worked fine when there were only 2 sources (03:36:15 PM) rickspencer3: Questions so far? (03:36:48 PM) ClassBot: w1nGNUtz asked: It seems some programs take too long to evolve. How could it be if you manage to do this so quickly? (03:36:52 PM) rickspencer3: hmm (03:36:55 PM) rickspencer3: interesting quetsion (03:37:01 PM) rickspencer3: I guess I just did one new feature a night (03:37:07 PM) rickspencer3: I refactored as I went (03:37:15 PM) rickspencer3: to support adding new features (03:37:22 PM) rickspencer3: this was a really fun project to work on (03:37:34 PM) rickspencer3: so I don't know (03:37:51 PM) rickspencer3: I guess PyGtk + GooCanvas are just really productive libraries (03:38:03 PM) ClassBot: titeuf_87 asked: is there any reasons you went with pygame instead of gstreamer? (03:38:19 PM) rickspencer3: for the simple reason that I discovered the PyGame api, and it looked like I could do it (03:38:46 PM) rickspencer3: I would switch to gstreamer if I saw sample code that convinced me it would be easy to do (03:38:57 PM) rickspencer3: I would love to break the PyGame dependency (03:39:16 PM) rickspencer3: ok (03:39:21 PM) rickspencer3: so Web image searching (03:39:25 PM) rickspencer3: I added this next (03:39:37 PM) rickspencer3: so you could search for a term, and then get back a list of related images (03:39:52 PM) rickspencer3: I ended up using the Yahoo! image search api for this (03:40:02 PM) rickspencer3: for the sole reason that the API was dead simple to use and well documented (03:40:38 PM) rickspencer3: around this time, someone picked up another program I wrote, and added good support for extracting image urls from a web page (03:41:08 PM) rickspencer3: so I sniped that code and made it so that if you entered a URL (03:41:21 PM) rickspencer3: instead of doing an image search, it would scrape images from that page (03:41:37 PM) rickspencer3: while I was at it, I also let the user type in a url directly to a photo and loaded that (03:41:48 PM) rickspencer3: I think the web image search functionality is really fun to use (03:41:59 PM) rickspencer3: like, "it would be funny if a picture of a cat were here" (03:42:06 PM) rickspencer3: type "cats" (03:42:18 PM) rickspencer3: and then you can get the cat, use the clipping tool, and drag it where you want (03:42:31 PM) rickspencer3: I'll pause to see if there are questions now (03:42:52 PM) rickspencer3: ok (03:43:05 PM) rickspencer3: so the next major change I made was from toolbar buttons to normal buttons (03:43:08 PM) rickspencer3: the motivation was this: (03:43:14 PM) rickspencer3: Changing size, rotation, and opacity required the user to click the button once for each increment. (03:43:21 PM) rickspencer3: So rotating something to be upside required the user to go "click click click click", etc... (03:43:27 PM) rickspencer3: What I wanted was the user to click the button, and hold it down as the image rotated, and then to stop when it was positioned as desired. (03:43:39 PM) rickspencer3: Sadly, there is no "key-down" event on gtk.Toolbars. (03:43:50 PM) rickspencer3: well, gtk.ToolButtons, I mean (03:43:56 PM) rickspencer3: So I had to switch Photobomb from using toolbars to using regular gtk.Button. (03:44:06 PM) rickspencer3: I achieved the layout by using a gtk.Table (03:44:21 PM) rickspencer3: I also created a new quickly.widget.PressAndHold widget to add press and hold functionality (03:44:28 PM) rickspencer3: I accomplish the asynch behavior with gobject.timeout_add(250, self.__tick). (03:44:35 PM) rickspencer3: At each tick the button emits a signal telling the consuming app to do something. (03:44:45 PM) rickspencer3: Questions? (03:45:07 PM) ClassBot: M49 asked: Does it check license metadata for pictures? (03:45:10 PM) rickspencer3: nope (03:45:21 PM) rickspencer3: that might be a nice feature to add though (03:45:30 PM) rickspencer3: especially if this is something that concerns you (03:46:08 PM) rickspencer3: but I don't think it's pertinent to the way I envisioned folks using photobomb (03:46:18 PM) rickspencer3: basically, making funny pictures from themselves and their friends (03:46:34 PM) rickspencer3: ok (03:46:43 PM) rickspencer3: last section is on the Social Network Integration (03:46:49 PM) rickspencer3: this was almost all achieved with Gwibber (03:46:58 PM) rickspencer3: Because Gwibber uses desktopcouch to store messages in Lucid, (03:47:05 PM) rickspencer3: I could add the feature to grovel through the messages and extract images coming from social feeds to display. (03:47:17 PM) rickspencer3: Of course, this will only work in Lucid ;) (03:47:25 PM) rickspencer3: I also sniped Gwibber's fb credentials from desktopcouch, and used that and some FML to get images from Facebook. (03:47:59 PM) rickspencer3: so your gwibber tab of the notebook loads up all the images in feeds you subscribe to, and also any images in facebook ... (03:48:22 PM) rickspencer3: that are tagged with any of your friends so long as the image has been modified in the last 5 days (03:48:31 PM) rickspencer3: I think this aspect of photobomb is really fun (03:48:48 PM) rickspencer3: converging your personal feeds into an app that is designed to mash 'em all up (03:48:57 PM) rickspencer3: I used the Gwibber API to add broadcasting Photobomb images. (03:49:04 PM) rickspencer3: The GooCanvas renders a png that is saved to a temp location. (03:49:13 PM) rickspencer3: Then pycurl uploads the image to imgur.com, which returns a url. (03:49:19 PM) rickspencer3: Gwibber's GwibberPosterVBox to broadcast the URL. (03:49:26 PM) rickspencer3: So that (03:49:30 PM) rickspencer3: s the photobomb showcase (03:49:49 PM) rickspencer3: I would love finish off photobomb sometime and let folks have fun with it (03:49:53 PM) rickspencer3: but I've been a bit busy (03:50:10 PM) rickspencer3: there are few bugs here and there, some of them quite annoying, as they involve frozen UI (03:50:17 PM) rickspencer3: so, 10 minutes left (03:50:27 PM) rickspencer3: happy to discuss anything now, answer any questions (03:51:13 PM) rickspencer3: ok (03:51:20 PM) rickspencer3: I'm going to call that a wrap, then (03:51:24 PM) rickspencer3: thanks all for coming! (03:51:34 PM) rickspencer3: and if anyone wants to contribute to photobomb in any way (03:51:45 PM) rickspencer3: I am totally open to that' (03:51:58 PM) rickspencer3: it's more fun working on these things together ;)