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
[-2].getAttribute("label") == "Original"):
175 imgurl
= sizes
[-2].getAttribute("source")
177 imgurl
= sizes
[-1].getAttribute("source")
179 # Free the DOM memory
182 # Grab the image file
183 response
= urllib2
.urlopen(imgurl
)
184 data
= response
.read()
187 fh
= open(filename
, "w")
193 print "Failed to retrieve photo id " + id
195 ######## Main Application ##########
196 if __name__
== '__main__':
198 # The first, and only argument needs to be a directory
200 os
.chdir(sys
.argv
[1])
202 print "usage: %s directory" % sys
.argv
[0]
205 # First things first, see if we have a cached user and auth-token
207 cache
= open("touchr.frob.cache", "r")
208 config
= cPickle
.load(cache
)
211 # We don't - get a new one
213 (user
, token
) = froblogin(getfrob(), "read")
214 config
= { "version":1 , "user":user, "token":token }
216 # Save it for future use
217 cache
= open("touchr.frob.cache", "w")
218 cPickle
.dump(config
, cache
)
221 # Now, construct a query for the list of photo sets
222 url
= "http://api.flickr.com/services/rest/?method=flickr.photosets.getList"
223 url
+= "&user_id=" + config
["user"]
224 url
= flickrsign(url
, config
["token"])
227 response
= urllib2
.urlopen(url
)
230 dom
= xml
.dom
.minidom
.parse(response
)
232 # Get the list of Sets
233 sets
= dom
.getElementsByTagName("photoset")
235 # For each set - create a url
238 pid
= set.getAttribute("id")
239 dir = getText(set.getElementsByTagName("title")[0].childNodes
)
241 # Build the list of photos
242 url
= "http://api.flickr.com/services/rest/?method=flickr.photosets.getPhotos"
243 url
+= "&photoset_id=" + pid
245 # Append to our list of urls
246 urls
.append( (url
, dir) )
248 # Free the DOM memory
251 # Add the photos which are not in any set
252 url
= "http://api.flickr.com/services/rest/?method=flickr.photos.getNotInSet"
253 urls
.append( (url
, "No Set") )
255 # Add the user's Favourites
256 url
= "http://api.flickr.com/services/rest/?method=flickr.favorites.getList"
257 urls
.append( (url
, "Favourites") )
259 # Time to get the photos
261 for (url
, dir) in urls
:
262 # Create the directory
268 # Get 500 results per page
269 url
+= "&per_page=500"
273 request
= url
+ "&page=" + str(page
)
276 request
= flickrsign(request
, config
["token"])
279 response
= urllib2
.urlopen(request
)
282 dom
= xml
.dom
.minidom
.parse(response
)
285 pages
= int(dom
.getElementsByTagName("photo")[0].parentNode
.getAttribute("pages"))
288 for photo
in dom
.getElementsByTagName("photo"):
289 # Tell the user we're grabbing the file
290 print photo
.getAttribute("title").encode("utf8") + " ... in set ... " + dir
293 photoid
= photo
.getAttribute("id")
296 target
= dir + "/" + photoid
+ ".jpg"
298 # Skip files that exist
299 if os
.access(target
, os
.R_OK
):
302 # Look it up in our dictionary of inodes first
303 if inodes
.has_key(photoid
) and os
.access(inodes
[photoid
], os
.R_OK
):
304 # woo, we have it already, use a hard-link
305 os
.link(inodes
[photoid
], target
)
307 inodes
[photoid
] = getphoto(photo
.getAttribute("id"), config
["token"], target
)
309 # Move on the next page