Code Project: upload pictures to Flickr with Python
Python is a great way to make apps quickly, and what better source of data is there than the world wide web? We've already shown you how to control Digg with Python and how to create a Twitter bot in Python, and now we turn our beady eyes towards Flickr, the home of more cat photos than I Can Haz Cheezburger knows what to do with. If you want to try your hand at uploading photos to Flickr, while learning just a smidge of PyGTK along the way, this project is for you.
If you're desperate for even more things to code, don't forget our complete code projects archive is waiting for you...
Once upon a clock event, somebody decided that the internet was all very well for passive consumers, but there ought to be something more. So, instead of limiting our collective wisdom to the words of knowledgeable seers who ran websites, it was decided everyone ought to be given write-permissions to the web. Chmod a+rwx -R http://*, if you will - dangerous stuff indeed. From that moment on, it's been nearly impossible to get a sensible search result to just about anything you type into Google, and thus the self-published web was born.
So far in our mashup series, we've been mostly concerned with harvesting data from the web and subverting it to our evil will. This time, though, we're going to give something back. The cloud has been good to us, so we're going to feed it some pictures.
It has also come to our attention that the shell could be seen as a rather arcane method of dealing with the web. Die-hards might feel like it's wrong that someone should be able to achieve something useful without learning three nested sets of command line switches, but we're going to bridge the divide this time and build a graphical user interface (GUI).
Python on the desktop
Our desktop application is going to need to integrate tightly with the underlying system. Since we have to pick a system, the number of Gnome users has won out and thus we've decided to use GTK for the GUI code here. If you use KDE, we feel your pain. There is one slight bonus, though. From our preliminary experiments, GTK seems to be able to get the job done in fewer lines than KDE/Qt. And if you want to try converting the code to run in wxWidgets, it shouldn't be too hard once you understand its functions. However, before we go any further, go grab PyGTK from your package manager.
Previously, we've built up applications line by line in the interactive Python shell, but that's harder when building a GUI app, because the application itself tends to be less linear. In fact, we're progressing from building a script to building a proper(ish) object-oriented application. There are debates about what constitutes object-oriented programming (OOP), but what we mean by this - here, at least - is that we're going to create an object that performs an action under certain conditions. If we don't do anything to the object, it won't do anything to us. If we poke it, it will do what we have told it to do if it gets poked.
Our first object is just an application window that opens on the desktop. Most of the time, it won't do anything other than sit around being a window. However, when you drag a file on to it, the window will give you the path name of that file - the first step in making our DIY Flickr client. To do this we need to load the relevant modules, create a method to do the work of putting the filename on the screen and then construct a GTK application. Here's our code for this, so load it and run it:
import pygtk import gtk def rec_cb(widg, context, x, y, selection, info, time): #received a valid object filename= selection.data[7:-2] print filename l.set_text(filename) #gtk app-building magic w = gtk.Window() w.set_size_request(200, 200) w.drag_dest_set( gtk.DEST_DEFAULT_MOTION | \ gtk.DEST_DEFAULT_HIGHLIGHT |\ gtk.DEST_DEFAULT_DROP \ , [("UTF8_STRING", 0, 0 )], \ gtk.gdk.ACTION_COPY) w.connect('drag_data_received', rec_cb) w.connect('destroy', lambda w: gtk.main_quit()) l = gtk.Label() w.add(l) w.show_all() gtk.main()
At first glance, this all this looks like magic, but that's because you're not seeing a lot of the code that makes it work. Before I explain it, let's try it out: run the code and a window opens. Drag a file from the desktop over that window to highlight the window area. Now drop the file on top, and you'll see the filename appear.
A few lines of code yield a functional GUI, but make sure you understand what's really going on.
Life will be a bit easier for you if you understand the mechanics of GTK before trying to understand PyGTK. The trouble is, all the examples are in C++, not Python, but a mix of background material is still useful.
There's a well-written guide to PyGTK available, although it's a little out of date, at www.pygtk.org/pygtk2tutorial. For a more general background look at Gnome/GTK, we've found the Official Gnome 2 Developer's Guide is still a good read (search for ISBN:978-1593270308).
If you want to have an easier time designing interfaces (at the cost of generating code you may not understand), grab Glade through your package manager and have a play with all the widgets.
You should know that Python is not limited to using GTK - in fact, there are many GUI-building tools. We'd particularly recommend PyQt/PyKDE as alternatives, especially if you want to interface with the KDE desktop. PyKDE is very similar to PyQt, although there are some differences. KDE uses the Qt toolkit extensively, but all of the objects and methods are wrapped up in an extra layer of KDE-ness, hence the existence of both PyKDE (for KDE apps) and PyQt (for standard Qt apps).
An alternative is wxWidgets. Apart from being very simple and easy to use, the main claim to fame of wxWidgets is that it's cross-platform and the libraries link in to native toolkits. So, a wxWidgets app running on Linux looks like a Gnome/GTK app, but on Windows it looks just like a standard Windows application, and on the Mac it uses the native Aqua, Cocoa, Carbon interface.
Unwrapping the code
Ignore the defined function for now and skip ahead to the line starting with w = gtk.Window(). This creates our application window object. The next line is the most crucial and perhaps the most confusing. Here, we've split the line up (using the \ continuation marker for Python) so you can see that the window is calling a drag_dest_set method with three parameters.
The first is some flags that tell GTK how this window will behave. Using the default_motion flag means we don't have to handle any motion events ourselves. The default_highlight flag means that GTK will highlight the drop area automatically if a compatible object is over it, and the Default_Drop flag means that if the object being dropped supplies the type of data we want, it will automatically be accepted, generating the drag_data_received signal. You could define your own methods to handle most of these events, but there isn't much point.
The next parameter passed is a list of tuples. We have only one tuple in the list. Each tuple specifies an accepted type of information - UTF8_STRING, in our case - starting with a string containing the type, some flags that can restrict you to using data from the same application or the same widget structure, and an identifier. The identifier is useful if you're accepting a lot of data types. The final parameter passed is the type of action accepted, again using a flag defined in the GTK module. In this case we have used the flag for a copy action, otherwise the original data is destroyed after it has been dropped.
So where did this UTF8_STRING business come from, then? Well, the other part of the code you're not seeing is the desktop itself. The Gnome desktop uses GTK actions for drag and drop functions all the time, mainly for placing files in folders. This means your desktop icons are already set up as sources for drag and drop operations, and have a number of available target types. If you want to have a look at them, change the code to replace l.set_text(filename) with:
l.set_text('\n'.join([str(t) for t in context.targets]))
This will list out the received target types, and you will see many are variations on a theme. We've used the UTF8 format, because we want to capture the filename of the object for later use, and Python likes UTF strings.
OK, so now we've set up the mechanics of what the window will accept. The next line connects the drag_data_received signal to the drag_dest_set method we defined earlier. We set it up in the previous line so that this signal is raised by GTK when something is dropped on to our window. If we don't connect it, though, nothing will happen at all.
The following line sets up an exit routine for the destroy signal, which is raised if the user clicks on the window's close button. Then we add a Label widget (to display the text) and call the show method for our window, which freezes all the elements together and lets GTK know that the object is ready to use. It isn't actually displayed until the main application loop is called, but that's the next line.
When gtk.main() is called, control of our program effectively passes over to GTK. All the actions that take place thereafter, such as moving the window, resizing, closing and so on, are performed by the GTK code, with the exception of the function we attached to our data received signal. This method is known as a callback, because it is called by the main application when some event takes place - in this case, our signal.
The code here now makes a bit more sense. The generated signal for this event comes with a shedload of information attached to it, most of which doesn't concern us. However, we do get a handle for the widget that received the signal, context information, and data on its location, the selection itself, what type of data it is (according to its ID), and the time the drop took place.
The received data is an element of the selection object we have been passed, so we can extract that, then trim off the first seven characters and the last two, which contain file:// and an end-of-line marker. This leaves us with the path name, which we can use to rewrite the data for the label in our window by calling the relevant method for our label object, l.
Using the data
Explaining GUI code takes up a lot of space, but if you want to be able to develop code like this, it is important to understand it and not treat it as magic. Now we have a filename, we can do something with it. To upload it to Flickr, you'll need to do several things first. Initially, you'll need to have an account there - if you don't already have one, sign up at www.flickr.com.
The second thing you'll need is an API key and secret combination, which you can get by registering your script as a non-commercial API user at www.flickr.com/services/api/keys/apply. Now you need to get a token that gives this application access to a specific Flickr account. This is how the application logs in to Flickr:
>>> import flickrapi >>> api_secret='xxxxyoursecretxxxx' >>> api_key='yyyyyyyourkeyyyyyyy' >>> flickr=flickrapi.FlickrAPI(api_key,api_secret) >>> (token,frob)= flickr.get_token_part_one(perms='write') >>> flickr.get_token_part_two((token, frob)) u'7215780808080-a94e70effffeebb01'
When you get to the penultimate line, a freaky thing will happen - your default browser will open and go to Flickr. There, you'll have to log in and answer some questions, to enable this app to access Flickr on your behalf. Don't forget to type the last line in, though, since this provides the token that links your Flickr account and this application.
We use this token so our application can log in for you, but you could do this inside your app if you want to. By default, the flickrapi module caches the tokens anyway, so you would only have to authorise once, but you could set the token not to be saved if you'd prefer. We're just going to use the token here, though, to avoid having to generate more GUI code.
The Flickr API and you
There's more to Flickr than just uploading pictures. Heck, there's more than writing random abuse on pictures of your friends, too (or, better still, people you don't know at all). In fact, thanks to Exif data, tags and numerous other types of data, Flickr is a rich playground for creating apps. We'll explore this later in the series with some programatically generated images, and a project that plots data from Flickr on a map.
One of the best things about the Flickr API is how well documented it is, though. As well as explaining each call, the online documentation has plenty of examples and even enables you to try out various calls through web forms. Take a gander at www.flickr.com/services/api and get inspired.
You (or your users) will have to log in to Flickr and authorise the app once. The Flickr API module will save the token, though.
Finally, it's time to upload something. The flickr object we create from the module has a method to upload images. The full list of the parameters it accepts are:
- filename The filename of the image. This is the only required parameter.
- title The title of the photo.
- description The description text.
- tags String containing list of tags, separated by spaces.
- is_public Should be 1 if the photo is public, 0 if it is private. The default is public.
- is_family Should be 1 if the private photo is visible for family, 0 if not. The default is not.
- is_friend Should be 1 if the private photo is visible for friends, 0 if not. The default is not.
- callback A method that receives two parameters: progress and done.
- format The response format.
Fortunately for us, the only thing that is required from this little lot is the actual filename; the rest of the parameters are optional and can be passed in using a key=value method, as in the following:
MyFlickrObject.upload(filename='/home/evilnick/plop.jpg', tags='plop image jpeg', title='This is Plop!")
We don't have to use all the parameters, or even remember which order they go in - things couldn't be simpler. If you're familiar with Flickr, you should be well versed in the tags and privacy options already. The only things that require any further explanation are the callback and format options. In fact, format is just there to tell the Flickr server how you want the response data formatted.
Even better, you can safely forget about it, because the Python module will take care of making sense out of the response. The callback is useful, though. This enables you to link to a method in your code that gets information from the upload process, telling you how much of the upload is completed, and setting up a flag when the upload is done. If we wanted to give the users of our app some feedback, we could make use of this.
So, with a few amendments to make the uploading work, our code now looks like this:
import pygtk, gtk, flickrapi api_key='--your-key---' api_secret='--your-secret--' api_token='--your-token--' flickr = flickrapi.FlickrAPI(api_key, api_secret, token=api_token) def rec_cb(wid, context, x, y, selection, info, time): filename= selection.data[7:-2] flickr.upload(filename=filename, is_public=0) # we broke this next line in two because it's long x=gtk.MessageDialog(parent=w, flags=gtk.DIALOG_MODAL, type=gtk.MESSAGE_INFO, \ buttons=gtk.BUTTONS_OK, message_format='file was uploaded') x.show_all() #gtk app-building magic w = gtk.Window() w.set_size_request(200, 200) w.drag_dest_set( gtk.DEST_DEFAULT_MOTION | \ gtk.DEST_DEFAULT_HIGHLIGHT |\ gtk.DEST_DEFAULT_DROP \ , [("UTF8_STRING", 0, 0 )], \ gtk.gdk.ACTION_COPY) w.connect('drag_data_received', rec_cb) w.connect('destroy', lambda w: gtk.main_quit()) l = gtk.Label() w.add(l) w.show_all() gtk.main()
We can see here that, apart from some setup code, the only bit of flickring we have to do is call the upload method in the callback for the drag and drop event. As a way of letting the user know the upload has worked, we pop open a modal dialog to tell them it's done.
So, although it wouldn't pass anyone's usability tests, there you have it - a desktop drag and drop image uploader in less than 30 lines of code. Maybe GUI programming isn't so hard after all?
It's always nice to get some feedback, even if it is just a default dialog box. You can extend the code to add more user-friendliness if you wish.
If you wanted to extend this application, it would be pretty easy to add toggle buttons to set the various privacy flags. You could also add a text entry widget to accept a title, description or tags. If you were feeling very ambitious, you could paint a thumbnail of the image in the drop zone and add a progress bar, using the callback from the flickr.upload() method to update it. All of these improvements will require knowing a lot more about GTK and PyGTK, though, so check out GTK Resources on above for more detail.
Well, now we've learned how to add user interaction to our web reworkings, which makes some of our mashup projects a little easier to use. As such, it's worth spending some more time fiddling around with PyGTK and its widgets.
Flickr is such a rich source of data that we can't leave it unexplored for long. This time, we've messed around with the basics of putting files on the service, but there are so many ways we can programatically explore the cumulative artistic output of all those megapixels. In short, we will return to Flickr soon for some heatmap fun.
First published in Linux Format magazine