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
29 API_KEY
= "e224418b91b4af4e8cdb0564716fa9bd"
30 SHARED_SECRET
= "7cddb9c9716501a0"
33 # Utility functions for dealing with flickr authentication
35 def getText(nodelist
):
38 if node
.nodeType
== node
.TEXT_NODE
:
40 return rc
.encode("utf-8")
43 # Get the frob based on our API_KEY and shared secret
46 # Create our signing string
47 string
= SHARED_SECRET
+ "api_key" + API_KEY
+ "methodflickr.auth.getFrob"
48 hash = md5
.new(string
).digest().encode("hex")
50 # Formulate the request
51 url
= "http://api.flickr.com/services/rest/?method=flickr.auth.getFrob"
52 url
+= "&api_key=" + API_KEY
+ "&api_sig=" + hash
55 # Make the request and extract the frob
56 response
= urllib2
.urlopen(url
)
59 dom
= xml
.dom
.minidom
.parse(response
)
62 frob
= getText(dom
.getElementsByTagName("frob")[0].childNodes
)
71 raise "Could not retrieve frob"
74 # Login and get a token
76 def froblogin(frob
, perms
):
77 string
= SHARED_SECRET
+ "api_key" + API_KEY
+ "frob" + frob
+ "perms" + perms
78 hash = md5
.new(string
).digest().encode("hex")
80 # Formulate the request
81 url
= "http://api.flickr.com/services/auth/?"
82 url
+= "api_key=" + API_KEY
+ "&perms=" + perms
83 url
+= "&frob=" + frob
+ "&api_sig=" + hash
85 # Tell the user what's happening
86 print "In order to allow FlickrTouchr to read your photos and favourites"
87 print "you need to allow the application. Please press return when you've"
88 print "granted access at the following url (which should have opened"
89 print "automatically)."
93 print "Waiting for you to press return"
95 # We now have a login url, open it in a web-browser
96 webbrowser
.open_new(url
)
101 # Now, try and retrieve a token
102 string
= SHARED_SECRET
+ "api_key" + API_KEY
+ "frob" + frob
+ "methodflickr.auth.getToken"
103 hash = md5
.new(string
).digest().encode("hex")
105 # Formulate the request
106 url
= "http://api.flickr.com/services/rest/?method=flickr.auth.getToken"
107 url
+= "&api_key=" + API_KEY
+ "&frob=" + frob
108 url
+= "&api_sig=" + hash
110 # See if we get a token
112 # Make the request and extract the frob
113 response
= urllib2
.urlopen(url
)
116 dom
= xml
.dom
.minidom
.parse(response
)
118 # get the token and user-id
119 token
= getText(dom
.getElementsByTagName("token")[0].childNodes
)
120 nsid
= dom
.getElementsByTagName("user")[0].getAttribute("nsid")
125 # Return the token and userid
131 # Sign an arbitrary flickr request with a token
133 def flickrsign(url
, token
):
134 query
= urlparse
.urlparse(url
).query
135 query
+= "&api_key=" + API_KEY
+ "&auth_token=" + token
136 params
= query
.split('&')
138 # Create the string to hash
139 string
= SHARED_SECRET
141 # Sort the arguments alphabettically
144 string
+= param
.replace('=', '')
145 hash = md5
.new(string
).digest().encode("hex")
147 # Now, append the api_key, and the api_sig args
148 url
+= "&api_key=" + API_KEY
+ "&auth_token=" + token
+ "&api_sig=" + hash
150 # Return the signed url
154 # Grab the photo from the server
156 def getphoto(imgurl
, filename
):
157 # Grab the image file
158 response
= urllib2
.urlopen(imgurl
)
159 data
= response
.read()
162 fh
= open(filename
, "w")
168 ######## Main Application ##########
169 if __name__
== '__main__':
171 # The first, and only argument needs to be a directory
173 os
.chdir(sys
.argv
[1])
175 print "usage: %s directory" % sys
.argv
[0]
178 # First things first, see if we have a cached user and auth-token
180 cache
= open("touchr.frob.cache", "r")
181 config
= cPickle
.load(cache
)
184 # We don't - get a new one
186 (user
, token
) = froblogin(getfrob(), "read")
187 config
= { "version":1 , "user":user, "token":token }
189 # Save it for future use
190 cache
= open("touchr.frob.cache", "w")
191 cPickle
.dump(config
, cache
)
194 # Now, construct a query for the list of photo sets
195 url
= "http://api.flickr.com/services/rest/?method=flickr.photosets.getList"
196 url
+= "&user_id=" + config
["user"]
197 url
= flickrsign(url
, config
["token"])
200 response
= urllib2
.urlopen(url
)
203 dom
= xml
.dom
.minidom
.parse(response
)
205 # Get the list of Sets
206 sets
= dom
.getElementsByTagName("photoset")
208 # For each set - create a url
211 pid
= set.getAttribute("id")
212 dir = getText(set.getElementsByTagName("title")[0].childNodes
)
213 dir = unicodedata
.normalize('NFKD', dir.decode("utf-8", "ignore")).encode('ASCII', 'ignore') # Normalize to ASCII
215 # Build the list of photos
216 url
= "http://api.flickr.com/services/rest/?method=flickr.photosets.getPhotos"
217 url
+= "&photoset_id=" + pid
219 # Append to our list of urls
220 urls
.append( (url
, dir) )
222 # Free the DOM memory
225 # Add the photos which are not in any set
226 url
= "http://api.flickr.com/services/rest/?method=flickr.photos.getNotInSet"
227 urls
.append( (url
, "No Set") )
229 # Add the user's Favourites
230 url
= "http://api.flickr.com/services/rest/?method=flickr.favorites.getList"
231 urls
.append( (url
, "Favourites") )
233 # Time to get the photos
235 for (url
, dir) in urls
:
236 # Create the directory
242 # Get 500 results per page
243 url
+= "&per_page=500"
246 # Get Date-Taken and Original-size URL for each result photo
247 url
+= "&extras=date_taken,url_o"
250 request
= url
+ "&page=" + str(page
)
253 request
= flickrsign(request
, config
["token"])
256 response
= urllib2
.urlopen(request
)
259 dom
= xml
.dom
.minidom
.parse(response
)
262 pages
= int(dom
.getElementsByTagName("photo")[0].parentNode
.getAttribute("pages"))
265 for photo
in dom
.getElementsByTagName("photo"):
266 # Tell the user we're grabbing the file
267 print photo
.getAttribute("title").encode("utf8") + " ... in set ... " + dir
270 photoid
= photo
.getAttribute("id")
272 # Grab the taken date
273 taken
= photo
.getAttribute("datetaken")
274 taken
= taken
.replace(":","").replace("-","").replace(" ","")
276 # Build the target filename
277 target
= dir + "/" + taken
+ "-" + photoid
+ ".jpg"
279 # Skip files that exist
280 if os
.access(target
, os
.R_OK
):
281 inodes
[photoid
] = target
284 # Look it up in our dictionary of inodes first
285 if photoid
in inodes
and inodes
[photoid
] and os
.access(inodes
[photoid
], os
.R_OK
):
286 # woo, we have it already, use a hard-link
287 os
.link(inodes
[photoid
], target
)
289 # Get URL to the "Original" size of the photo
290 imgurl
= photo
.getAttribute("url_o")
292 # Grab image and save to local file
294 inodes
[photoid
] = getphoto(imgurl
, target
)
296 print "Failed to retrieve URL for photo id " + photoid
298 # Move on the next page