# Offlickr # Hugo Haas -- mailto:hugo@larve.net -- http://larve.net/people/hugo/ # Homepage: http://larve.net/people/hugo/2005/12/offlickr/ # License: GPLv2 import sys import libxml2 import urllib import getopt import time import os.path from flickrapi import FlickrAPI version = '0.2 - 2006-01-03' class Offlickr: # Gotten from Flickr __flickrAPIKey = '1391fcd0a9780b247cd6a101272acf71' __flickrSecret = 'fd221d0336de3b6d' def __init__(self, uid, browser = "lynx"): """Instantiates an Offlickr object""" # Get authentication token self.fapi = FlickrAPI(self.__flickrAPIKey, self.__flickrSecret) self.token = self.fapi.getToken(browser=browser) self.flickrUserId = uid def __testFailure(self, rsp): """Returns whether the previous call was successful""" return rsp['stat'] == "fail" def getPhotoList(self, dateLo, dateHi): """Returns a list of photo given a time frame""" n = 0 photos = [ ] while True: n = n + 1 rsp = self.fapi.photos_search(api_key=self.__flickrAPIKey, auth_token=self.token, user_id = self.flickrUserId, per_page = "500", # Max allowed by Flickr page = str(n), min_upload_date = dateLo, max_upload_date = dateHi) if self.__testFailure(rsp): return None if rsp.photos[0]['total'] == '0': print "No photos to back up" sys.exit(0) photos += rsp.photos[0].photo if len(photos) >= int(rsp.photos[0]['total']): break return photos def getPhotoMetadata(self, pid): """Returns a string containing the photo metadata (in XML)""" rsp = self.fapi.photos_getInfo(api_key=self.__flickrAPIKey, auth_token=self.token, photo_id=pid) if self.__testFailure(rsp): return None doc = libxml2.parseDoc(rsp.xml) metadata = str(doc.xpathEval( "/rsp/photo")[0]) doc.freeDoc() return [ metadata, rsp.photo[0]['originalformat'] ] def getPhotoSizes(self, pid): """Returns a string with is a list of available sizes for a photo""" rsp = self.fapi.photos_getSizes(api_key=self.__flickrAPIKey, auth_token=self.token, photo_id=pid) if self.__testFailure(rsp): return None return rsp def getOriginalPhoto(self, pid): """Returns a URL which is the original photo, if it exists""" source = None rsp = self.getPhotoSizes(pid) if rsp == None: return None for s in rsp.sizes[0].size: if s['label'] == 'Original': source = s['source'] return source def __downloadReportHook(self, count, blockSize, totalSize): if self.__verbose == False: return p = 100 * count * blockSize / totalSize if (p > 100): p = 100 print "\r %3d %%" % p, sys.stdout.flush() def downloadURL(self, url, filename, verbose = False): """Saves a photo in a file""" self.__verbose = verbose urllib.urlretrieve(url, filename, reporthook=self.__downloadReportHook) def usage(): print "Usage: Offlickr.py -d " print "Backs up Flickr photos and metadata" print "Options:" print "\t-f \tbeginning of the date range" print "\t\t\t(default: since you started using Flickr)" print "\t-t \tend of the date range" print "\t\t\t(default: until now)" print "\t-d \tdirectory for saving files (default: ./dst)" print "\t-p\t\tback up photos in addition to metadata" print "\t-b \tbrowser to use for authentication (default: opera)" print "\t-h\t\tthis help message" print "\nDates are specified in seconds since the Epoch (00:00:00 UTC, January 1, 1970)." print "\nVersion " + version def main(): # Default options flickrUserId = None dateLo = '1' maxTime = '9999999999' dateHi = maxTime getPhotos = False target = 'dst' browser = 'opera' # Parse command line try: opts, args = getopt.getopt(sys.argv[1:], "hpb:f:t:d:i:", ["help"]) except getopt.GetoptError: usage() sys.exit(2) for o, a in opts: if o in ('-h', '--help'): usage() sys.exit(0) if o == '-i': flickrUserId = a if o == '-p': getPhotos = True if o == '-f': dateLo = a if o == '-t': dateHi = a if o == '-d': target = a if o == '-b': browser = a # Check that we have a user id specified if flickrUserId == None: print "You need to specify a Flickr Id" sys.exit(1) # Check that the target directory exists if not os.path.isdir(target): print target + " is not a directory; please fix that." sys.exit(1) if dateHi == maxTime: t = time.time() print "For incremental backups, the current time is %.0f" % t print "You can rerun the program with '-f %.0f'" % t offlickr = Offlickr(flickrUserId, browser) photos = offlickr.getPhotoList(dateLo, dateHi) if photos == None: print "There seems to be a problem somewhere!" sys.exit(1) total = len(photos) print "Backing up" , total , "photos" i = 0 for p in photos: i = i + 1 pid = str(int(p['id'])) # Making sure we don't have weird things here print str(i) + "/" + str(total) + ": " + pid + ": " + p['title'] # Get Metadata metadata = offlickr.getPhotoMetadata(pid) if metadata == None: print "Failed!" continue fn = target + '/' + pid + '.xml' f = open(fn, 'w') f.write(metadata[0]) f.close() print 'Saved as ' + fn # Do we want the picture too? if getPhotos == False: continue f = pid + '.' + metadata[1] source = offlickr.getOriginalPhoto(pid) if source == None: print "Oopsie, no photo found" print 'Retrieving ' + source + ' as ' + f offlickr.downloadURL(source, target + '/' + f, verbose = True); print "\r... done!" if __name__ == "__main__": main()