Find all your DIY electronics in the MakerShed. 3D Printing, Kits, Arduino, Raspberry Pi, Books & more!
MZ_Codebox.gif

This Codebox shows how to use the Flickr API to create a Ken Burns-style movie from your photos, like this one for Maker Faire:

You’ll need to supply your own melancholy period music.

Set up the sketch

The first thing you’ll need to do to is create an account (if you don’t have one already) and sign into Flickr. Once you’re logged in, the next step is to get an API key to uniquely identify your app. (Well, the key doesn’t so much identify the app as it identifies that you are running the app.) You’ll be prompted to give the app a name (I called it “Ken Burns App”) and a description.

Once you create the app record, you’ll get back two pieces of information: an “app key,” which uniquely identifies the app, and a “secret,” which is used cryptographically sign the URL. (We won’t be using signed URLs in this example, but I can post the code if you’re interested. Just leave a comment if you want to see it.) It should look something like this:

Once you’ve got the key, fire up Processing and paste ken_burns_flickr_final.pde into the sketch window. Here’s the codebox:

You’ll need to replace the variable apiKey and sharedSecret with the values you got from Flickr. While you’re at it, you can also mess around with some of the other variables. For example, if you’d like to search for another term than “faire,” you can change the “tags” variable. (Note that you need to URL encode any values you search for, which mostly means that you need to replace spaces with “+” sign.) Or, if you’d like more (or fewer) images in the movie, you can update the numPhotos variable.

Discussion

This is one of the more complex projects in this series, so I’ve broken the discussion into the following buckets:

  • Pulling data from Flickr
  • Panning and zooming the images
  • Creating a movie

We’ll be using the XMLElement and the ArrayList classes and a good bit. If you need a quick refresher, check out Amuse yourself with Google Autocomplete and Swat An (Arraylist) Of Targets. They should give you the background you’ll need to follow the examples if you’re feeling lost.

Pulling data from Flickr

The flickr api allows you to read (and write, but we won’t be doing that here) data about the images, groups, collections, and photographs on the site. Data is returned in XML, although other formats are available.

So, let’s dive right in and take a look at flickr.groups.pools.getPhotos. This call returns a list of pool photos for a given group. It can accept a variety of parameters, such as the api_key (this is required for almost all calls), the group_id (MAKE’s group id is 69453349@N00), the tags you want the image to match, and the number of results you want on each page (called per_page).

If you scroll to the bottom of the call’s documentation page, you’ll find a handy link called API Explorer : flickr.groups.pools.getPhotos. This links to an interactive form where you can try out the various parameters and get the results of the call; each call in the API has similar functionality. (Also, I found I had to use Firefox to see the XML output. YMMV.) Select “Do not sign call” in the radio button because (as discussed earlier) we’re not going to be uses authenticated API calls.

Here’s a figure that should help explain what’s going on:

The Explorer is the best way to quickly see the XML data returned by a call. In this case, the XML looks like this:


<rsp stat="ok">
   <photos page="1" pages="13" perpage="100" total="1229">
      ...
      <photo id="5032437449" owner="37037184@N05" secret="d15af10a4e"
         server="4085" farm="5" title="Mark Frauenfelder" ispublic="1"
         isfriend="0" isfamily="0" ownername="segwaymonkey" dateadded="1285673760"/>
      ...
      <photo id="5031045207" owner="88608740@N00" secret="60be669918"
        server="4124" farm="5" title="20100926-DSC00898" ispublic="1"
       isfriend="0" isfamily="0" ownername="timmyj1138" dateadded="1285631093"/>
   </photos>
</rsp>

The photo data we’re after resides in the first child element off the root node. Our code will need to loop through all these child nodes, pull out the attributes we’re interested in, and store them in an ArrayList for later use.

Finally, you’ll find the URL required to generate this data just under the XML box. Here it is:



http://api.flickr.com/services/rest/?method=flickr.groups.pools.getPhotos

   &api_key=4bdfeb8048562c5d12d0c7cda3ae341e&group_id=69453349%40N00
   &tags=faire
   &per_page=5

So, now that we know the URL we need and the XML we’re going to get, we can write some code to parse the data. Here it is:


//Pulls out the first 100 phots in the makezin flickr pool
void getPhotosByGroup(String _groupId, String _tags) {
  // Set up the call to get the Token, as described here:
  // http://www.flickr.com/services/api/auth.howto.desktop.html
  String url = "http://api.flickr.com/services/rest/?api_key="+apiKey+"&group_id="+_groupId+"&tags="+_tags+"&method=flickr.groups.pools.getPhotos&per_page="+numPhotos;
  String[] results = loadStrings(url); //Load the URL
  XMLElement xml = new XMLElement(join(results,"\n")); //Collapse array elements into a string
  String[] errCodes = getStatus(xml); //Pull error codes (if any) from the XML
  if (errCodes[0].equals("ok")) {
   XMLElement root = xml.getChild(0);
   for (int i=0; i < root.getChildCount(); i++) {
   String id = root.getChild(i).getStringAttribute("id");
   String owner = root.getChild(i).getStringAttribute("owner");
   String title = root.getChild(i).getStringAttribute("title");
   photos.add( new Photo(id, title, owner));
   }
  } else {
   println ("Error! Here are some codes:\n" + errCodes);
  }
}

If you’ve been following the other columns in this series, nothing should look that unfamiliar. All we’re doing is creating a string that’s a template for the URL we need, pulling the contents of the page using loadString(), and then processing it with XMLElement.

Perhaps the only wrinkle is that we’re doing a bit of error checking using a procedure called getStatus() to see if something has gone wrong in the call. For example, if we passed in an invalid API key in the URL, we’d get an error code in the XML instead of useful information:


<?xml version="1.0" encoding="utf-8" ?>
<rsp stat="fail">
<err code="100" msg="Invalid API Key (Key has invalid format)" />
</rsp>

Once we’ve read in the meta data about the images, we need to use the flickr.photos.getSizes to find the URLs for various images associated with a particular photo ID. The call returns a structure with information about the various image sizes (thumbnails, square, small, large, etc.) Flickr stores for each photo. Here’s an example:


<rsp stat="ok">
   <sizes canblog="1" canprint="0" candownload="0">
      <size label="Square" width="75" height="75"
         source="http://farm5.static.flickr.com/4085/5032437449_d15af10a4e_s.jpg"
         url="http://www.flickr.com/photos/arduino/5032437449/sizes/sq/"
         media="photo"/>
      <size label="Thumbnail" width="100" height="67"
         source="http://farm5.static.flickr.com/4085/5032437449_d15af10a4e_t.jpg"
         url="http://www.flickr.com/photos/arduino/5032437449/sizes/t/"
         media="photo"/>
      <size label="Small" width="240" height="160"
         source="http://farm5.static.flickr.com/4085/5032437449_d15af10a4e_m.jpg"
         url="http://www.flickr.com/photos/arduino/5032437449/sizes/s/"
         media="photo"/>
      <size label="Medium" width="500" height="333"
         source="http://farm5.static.flickr.com/4085/5032437449_d15af10a4e.jpg"
         url="http://www.flickr.com/photos/arduino/5032437449/sizes/m/"
         media="photo"/>
      <size label="Medium 640" width="640" height="427"
         source="http://farm5.static.flickr.com/4085/5032437449_d15af10a4e_z.jpg"
         url="http://www.flickr.com/photos/arduino/5032437449/sizes/z/"
         media="photo"/>
      <size label="Large" width="1024" height="683"
         source="http://farm5.static.flickr.com/4085/5032437449_d15af10a4e_b.jpg"
         url="http://www.flickr.com/photos/arduino/5032437449/sizes/l/"
         media="photo"/>
</sizes>
</rsp>

This code is all handled in the getPhotoURL() method, which is almost identical in structure to getPhotosByGroup(). Once we have the URL, we can load the image using loadImage().

Panning and zooming

Once we’ve pulled in the image, we’re ready to start panning and zooming in the Ken Burns style. As demonstrated by this Pan a large image example from the Processing community, we can use buffering tocreate the panning effect. On each iteration of draw(), we’ll update the (x,y) coordinates of buffer so that it moves smoothly along a predetermined vector. This figure illustrates the key variables involved:

Zooming is even easier — we just increase a variable called zoom by a small percentage called zoomFactor, and then use Processing’s scale() function to do the appropriate transformation.

All the updates to the various variables are made in the draw() method.

Creating the movie

Creating the movie is slightly trickier. Well, I take that back — creating the movie file and adding frames to it is dead simple, thanks to the great MovieMaker contributed library. The library does all the messy, hard work. To use it, we reate a new MovieMaker object in setup(), add frames to it in draw(), and then call the finish() method when we’re done. The tricky part is controlling what those frames contain and how long they are displayed.

The content is fairly straightforward — it’s the copy buffer we just discussed in the pan and zoom section. Each iteration of draw() gives us a slightly different frame. Stringing the frames together creates a nice, animated image. All we need to do is write each frame in the animation into the movie file. Conveniently, MovieMaker’s addFrame() method does just that — it saves whatever is currently displayed in the sketch’s display window into the movie file. To make the movie a bit more authentic seeming — and just to show how it’s done — I added in a title bar with the picture’s title and creator. These are just done with standard Processing graphics commands.

Controlling how many frames to generate per image is the tricky part. The first thing to understand is that the frame rate of the sketch (the number of times draw() executes a second) is different than the frame rate of the movie. For example, suppose you set the movie’s frame rate to 60 frames per second (FPS). Regardless of how long it takes to generate an image on the screen, that image will be displayed in one sixtieth of a second in the movie. So, while you might have an effective frame rate of 6 frames per second in your sketch because it takes 10 seconds to render some complex image, the movie’s frame rate is constant. It will take 600 seconds of Processing time to generate the 1 second of movie time.

Managing this timing disconnect requires us to manually keep track of the number of frames we’ve added to the movie. In the Ken Burns example, this is done in the variable panFrameIdx, which increments on every pass through draw(). The sketch compares this value to a baseline that tells us how many frames we want to display per image. After experimenting a bit, I found that panning and zooming between 2 and 4 seconds gave the best results, like this:

   float MIN_PAN_SECS = 2;  // Min time to display photo
   float MAX_PAN_SECS = 4;  // Max time to display photo
   ...
   framesToDisplay = (int) (FPS * random(MIN_PAN_SECS, MAX_PAN_SECS));
   if (panFrameIdx > framesToDisplay) {
      ...

Once the sketch has generated the number of frames required for the image, it loads in the next image and repeats the process.

In the Maker Shed:

Makershedsmall

processingCover.jpg

Getting Started with Processing
Learn computer programming the easy way with Processing, a simple language that lets you use code to create drawings, animation, and interactive graphics. Programming courses usually start with theory,but this book lets you jump right into creative and fun projects. It’s ideal for anyone who wants to learn basic programming, and serves as a simple introduction to graphics for people with some programming skills.


Related
blog comments powered by Disqus

Featured Products from the MakerShed

Follow

Get every new post delivered to your Inbox.

Join 25,549 other followers