4 # FlickrTouchr - a simple python script to grab all your photos from flickr,
5 # dump into a directory - organised into folders by set -
6 # along with any favourites you have saved.
8 # You can then sync the photos to an iPod touch.
12 # Original Author: colm - AT - allcosts.net - Colm MacCarthaigh - 2008-01-21
14 # Modified by: Dan Benjamin - http://hivelogic.com
16 # License: Apache 2.0 - http://www.apache.org/licenses/LICENSE-2.0.html
19 import xml
.dom
.minidom
28 API_KEY
= "e224418b91b4af4e8cdb0564716fa9bd"
29 SHARED_SECRET
= "7cddb9c9716501a0"
32 # Utility functions for dealing with flickr authentication
34 def getText(nodelist
):
37 if node
.nodeType
== node
.TEXT_NODE
:
39 return rc
.encode("utf-8")
42 # Get the frob based on our API_KEY and shared secret
45 # Create our signing string
46 string
= SHARED_SECRET
+ "api_key" + API_KEY
+ "methodflickr.auth.getFrob"
47 hash = md5
.new(string
).digest().encode("hex")
49 # Formulate the request
50 url
= "http://api.flickr.com/services/rest/?method=flickr.auth.getFrob"
51 url
+= "&api_key=" + API_KEY
+ "&api_sig=" + hash
54 # Make the request and extract the frob
55 response
= urllib2
.urlopen(url
)
58 dom
= xml
.dom
.minidom
.parse(response
)
61 frob
= getText(dom
.getElementsByTagName("frob")[0].childNodes
)
70 raise "Could not retrieve frob"
73 # Login and get a token
75 def froblogin(frob
, perms
):
76 string
= SHARED_SECRET
+ "api_key" + API_KEY
+ "frob" + frob
+ "perms" + perms
77 hash = md5
.new(string
).digest().encode("hex")
79 # Formulate the request
80 url
= "http://api.flickr.com/services/auth/?"
81 url
+= "api_key=" + API_KEY
+ "&perms=" + perms
82 url
+= "&frob=" + frob
+ "&api_sig=" + hash
84 # Tell the user what's happening
85 print "In order to allow FlickrTouchr to read your photos and favourites"
86 print "you need to allow the application. Please press return when you've"
87 print "granted access at the following url (which should have opened"
88 print "automatically)."
92 print "Waiting for you to press return"
94 # We now have a login url, open it in a web-browser
95 webbrowser
.open_new(url
)
100 # Now, try and retrieve a token
101 string
= SHARED_SECRET
+ "api_key" + API_KEY
+ "frob" + frob
+ "methodflickr.auth.getToken"
102 hash = md5
.new(string
).digest().encode("hex")
104 # Formulate the request
105 url
= "http://api.flickr.com/services/rest/?method=flickr.auth.getToken"
106 url
+= "&api_key=" + API_KEY
+ "&frob=" + frob
107 url
+= "&api_sig=" + hash
109 # See if we get a token
111 # Make the request and extract the frob
112 response
= urllib2
.urlopen(url
)
115 dom
= xml
.dom
.minidom
.parse(response
)
117 # get the token and user-id
118 token
= getText(dom
.getElementsByTagName("token")[0].childNodes
)
119 nsid
= dom
.getElementsByTagName("user")[0].getAttribute("nsid")
124 # Return the token and userid
130 # Sign an arbitrary flickr request with a token
132 def flickrsign(url
, token
):
133 query
= urlparse
.urlparse(url
).query
134 query
+= "&api_key=" + API_KEY
+ "&auth_token=" + token
135 params
= query
.split('&')
137 # Create the string to hash
138 string
= SHARED_SECRET
140 # Sort the arguments alphabettically
143 string
+= param
.replace('=', '')
144 hash = md5
.new(string
).digest().encode("hex")
146 # Now, append the api_key, and the api_sig args
147 url
+= "&api_key=" + API_KEY
+ "&auth_token=" + token
+ "&api_sig=" + hash
149 # Return the signed url
153 # Grab the photo from the server
155 def getphoto(id, token
, filename
):
157 # Contruct a request to find the sizes
158 url
= "http://api.flickr.com/services/rest/?method=flickr.photos.getSizes"
159 url
+= "&photo_id=" + id
162 url
= flickrsign(url
, token
)
165 response
= urllib2
.urlopen(url
)
168 dom
= xml
.dom
.minidom
.parse(response
)
170 # Get the list of sizes
171 sizes
= dom
.getElementsByTagName("size")
173 # Grab the original if it exists
174 if (sizes
[-1].getAttribute("label") == "Original"):
175 imgurl
= sizes
[-1].getAttribute("source")
177 print "Failed to get original for photo id " + id
180 # Free the DOM memory
183 # Grab the image file
184 response
= urllib2
.urlopen(imgurl
)
185 data
= response
.read()
188 fh
= open(filename
, "w")
194 print "Failed to retrieve photo id " + id
196 ######## Main Application ##########
197 if __name__
== '__main__':
199 # The first, and only argument needs to be a directory
201 os
.chdir(sys
.argv
[1])
203 print "usage: %s directory" % sys
.argv
[0]
206 # First things first, see if we have a cached user and auth-token
208 cache
= open("touchr.frob.cache", "r")
209 config
= cPickle
.load(cache
)
212 # We don't - get a new one
214 (user
, token
) = froblogin(getfrob(), "read")
215 config
= { "version":1 , "user":user, "token":token }
217 # Save it for future use
218 cache
= open("touchr.frob.cache", "w")
219 cPickle
.dump(config
, cache
)
222 # Now, construct a query for the list of photo sets
223 url
= "http://api.flickr.com/services/rest/?method=flickr.photosets.getList"
224 url
+= "&user_id=" + config
["user"]
225 url
= flickrsign(url
, config
["token"])
228 response
= urllib2
.urlopen(url
)
231 dom
= xml
.dom
.minidom
.parse(response
)
233 # Get the list of Sets
234 sets
= dom
.getElementsByTagName("photoset")
236 # For each set - create a url
239 pid
= set.getAttribute("id")
240 dir = getText(set.getElementsByTagName("title")[0].childNodes
)
242 # Build the list of photos
243 url
= "http://api.flickr.com/services/rest/?method=flickr.photosets.getPhotos"
244 url
+= "&photoset_id=" + pid
246 # Append to our list of urls
247 urls
.append( (url
, dir) )
249 # Free the DOM memory
252 # Add the photos which are not in any set
253 url
= "http://api.flickr.com/services/rest/?method=flickr.photos.getNotInSet"
254 urls
.append( (url
, "No Set") )
256 # Add the user's Favourites
257 url
= "http://api.flickr.com/services/rest/?method=flickr.favorites.getList"
258 urls
.append( (url
, "Favourites") )
260 # Time to get the photos
262 for (url
, dir) in urls
:
263 # Create the directory
269 # Get 500 results per page
270 url
+= "&per_page=500"
274 request
= url
+ "&page=" + str(page
)
277 request
= flickrsign(request
, config
["token"])
280 response
= urllib2
.urlopen(request
)
283 dom
= xml
.dom
.minidom
.parse(response
)
286 pages
= int(dom
.getElementsByTagName("photo")[0].parentNode
.getAttribute("pages"))
289 for photo
in dom
.getElementsByTagName("photo"):
290 # Tell the user we're grabbing the file
291 print photo
.getAttribute("title").encode("utf8") + " ... in set ... " + dir
294 photoid
= photo
.getAttribute("id")
297 target
= dir + "/" + photoid
+ ".jpg"
299 # Skip files that exist
300 if os
.access(target
, os
.R_OK
):
303 # Look it up in our dictionary of inodes first
304 if inodes
.has_key(photoid
) and os
.access(inodes
[photoid
], os
.R_OK
):
305 # woo, we have it already, use a hard-link
306 os
.link(inodes
[photoid
], target
)
308 inodes
[photoid
] = getphoto(photo
.getAttribute("id"), config
["token"], target
)
310 # Move on the next page