Пример #1
0
 def cb():
     fapi = FlickrAPI(config["api_key"], config["api_secret"])
     try:
         rsp = fapi.photos_comments_getList(apikey=config["api_key"], photo_id=self.image)
     except Exception, msg:
         log.debug("Exception getting comments: %s" % msg)
         return {}
Пример #2
0
class Offlickr:
    def __init__(
        self,
        key,
        secret,
        httplib=None,
        dryrun=False,
        verbose=False,
    ):
        """Instantiates an Offlickr object
        An API key is needed, as well as an API secret"""

        self.__flickrAPIKey = key
        self.__flickrSecret = secret
        self.__httplib = httplib

        # Get authentication token
        # note we must explicitly select the xmlnode parser to be compatible with FlickrAPI 1.2

        self.fapi = FlickrAPI(self.__flickrAPIKey,
                              self.__flickrSecret,
                              format='xmlnode')
        (token, frob) = self.fapi.get_token_part_one()
        if not token:
            raw_input('Press ENTER after you authorized this program')
        self.fapi.get_token_part_two((token, frob))
        self.token = token
        test_login = self.fapi.test_login()
        uid = test_login.user[0]['id']
        self.flickrUserId = uid
        self.dryrun = dryrun
        self.verbose = verbose

    def __testFailure(self, rsp):
        """Returns whether the previous call was successful"""

        if rsp['stat'] == 'fail':
            print 'Error!'
            return True
        else:
            return False

    def getPhotoList(self, dateLo, dateHi):
        """Returns a list of photo given a time frame"""

        n = 0
        flickr_max = 500
        photos = []

        print 'Retrieving list of photos'
        while True:
            if self.verbose:
                print 'Requesting a page...'
            n = n + 1
            rsp = self.fapi.photos_search(
                api_key=self.__flickrAPIKey,
                auth_token=self.token,
                user_id=self.flickrUserId,
                per_page=str(flickr_max),
                page=str(n),
                min_upload_date=dateLo,
                max_upload_date=dateHi,
            )
            if self.__testFailure(rsp):
                return None
            if rsp.photos[0]['total'] == '0':
                return None
            photos += rsp.photos[0].photo
            if self.verbose:
                print ' %d photos so far' % len(photos)
            if len(photos) >= int(rsp.photos[0]['total']):
                break

        return photos

    def getGeotaggedPhotoList(self, dateLo, dateHi):
        """Returns a list of photo given a time frame"""

        n = 0
        flickr_max = 500
        photos = []

        print 'Retrieving list of photos'
        while True:
            if self.verbose:
                print 'Requesting a page...'
            n = n + 1
            rsp = \
                self.fapi.photos_getWithGeoData(api_key=self.__flickrAPIKey,
                    auth_token=self.token, user_id=self.flickrUserId,
                    per_page=str(flickr_max), page=str(n))
            if self.__testFailure(rsp):
                return None
            if rsp.photos[0]['total'] == '0':
                return None
            photos += rsp.photos[0].photo
            if self.verbose:
                print ' %d photos so far' % len(photos)
            if len(photos) >= int(rsp.photos[0]['total']):
                break

        return photos

    def getPhotoLocation(self, pid):
        """Returns a string containing location of a photo (in XML)"""

        rsp = \
            self.fapi.photos_geo_getLocation(api_key=self.__flickrAPIKey,
                auth_token=self.token, photo_id=pid)
        if self.__testFailure(rsp):
            return None
        doc = libxml2.parseDoc(rsp.xml)
        info = doc.xpathEval('/rsp/photo')[0].serialize()
        doc.freeDoc()
        return info

    def getPhotoLocationPermission(self, pid):
        """Returns a string containing location permision for a photo (in XML)"""

        rsp = \
            self.fapi.photos_geo_getPerms(api_key=self.__flickrAPIKey,
                auth_token=self.token, photo_id=pid)
        if self.__testFailure(rsp):
            return None
        doc = libxml2.parseDoc(rsp.xml)
        info = doc.xpathEval('/rsp/perms')[0].serialize()
        doc.freeDoc()
        return info

    def getPhotosetList(self):
        """Returns a list of photosets for a user"""

        rsp = self.fapi.photosets_getList(api_key=self.__flickrAPIKey,
                                          auth_token=self.token,
                                          user_id=self.flickrUserId)
        if self.__testFailure(rsp):
            return None
        return rsp.photosets[0].photoset

    def getPhotosetInfo(self, pid, method):
        """Returns a string containing information about a photoset (in XML)"""

        rsp = method(api_key=self.__flickrAPIKey,
                     auth_token=self.token,
                     photoset_id=pid)
        if self.__testFailure(rsp):
            return None
        doc = libxml2.parseDoc(rsp.xml)
        info = doc.xpathEval('/rsp/photoset')[0].serialize()
        doc.freeDoc()
        return info

    def getPhotoMetadata(self, pid):
        """Returns an array containing containing the photo metadata (as a string), and the format of the photo"""

        if self.verbose:
            print 'Requesting metadata for photo %s' % pid
        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 = doc.xpathEval('/rsp/photo')[0].serialize()
        doc.freeDoc()
        return [metadata, rsp.photo[0]['originalformat']]

    def getPhotoComments(self, pid):
        """Returns an XML string containing the photo comments"""

        if self.verbose:
            print 'Requesting comments for photo %s' % pid
        rsp = \
            self.fapi.photos_comments_getList(api_key=self.__flickrAPIKey,
                auth_token=self.token, photo_id=pid)
        if self.__testFailure(rsp):
            return None
        doc = libxml2.parseDoc(rsp.xml)
        comments = doc.xpathEval('/rsp/comments')[0].serialize()
        doc.freeDoc()
        return comments

    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']
        for s in rsp.sizes[0].size:
            if s['label'] == 'Video Original':
                source = s['source']
        return [source, s['label'] == 'Video Original']

    def __downloadReportHook(
        self,
        count,
        blockSize,
        totalSize,
    ):

        if not self.__verbose:
            return
        p = ((100 * count) * blockSize) / totalSize
        if p > 100:
            p = 100
        print '\r %3d %%' % p,
        sys.stdout.flush()

    def downloadURL(
        self,
        url,
        target,
        filename,
        verbose=False,
    ):
        """Saves a photo in a file"""

        if self.dryrun:
            return
        self.__verbose = verbose
        tmpfile = '%s/%s.TMP' % (target, filename)
        if self.__httplib == 'wget':
            cmd = 'wget -q -t 0 -T 120 -w 10 -c -O %s %s' % (tmpfile, url)
            os.system(cmd)
        else:
            urllib.urlretrieve(url,
                               tmpfile,
                               reporthook=self.__downloadReportHook)
        os.rename(tmpfile, '%s/%s' % (target, filename))
Пример #3
0
class Offlickr:

    def __init__(
        self,
        key,
        secret,
        httplib=None,
        dryrun=False,
        verbose=False,
        ):
        """Instantiates an Offlickr object
        An API key is needed, as well as an API secret"""

        self.__flickrAPIKey = key
        self.__flickrSecret = secret
        self.__httplib = httplib

        # Get authentication token
        # note we must explicitly select the xmlnode parser to be compatible with FlickrAPI 1.2

        self.fapi = FlickrAPI(self.__flickrAPIKey, self.__flickrSecret,
                              format='xmlnode')
        (token, frob) = self.fapi.get_token_part_one()
        if not token:
            raw_input('Press ENTER after you authorized this program')
        self.fapi.get_token_part_two((token, frob))
        self.token = token
        test_login = self.fapi.test_login()
        uid = test_login.user[0]['id']
        self.flickrUserId = uid
        self.dryrun = dryrun
        self.verbose = verbose

    def __testFailure(self, rsp):
        """Returns whether the previous call was successful"""

        if rsp['stat'] == 'fail':
            print 'Error!'
            return True
        else:
            return False

    def getPhotoList(self, dateLo, dateHi):
        """Returns a list of photo given a time frame"""

        n = 0
        flickr_max = 500
        photos = []

        print 'Retrieving list of photos'
        while True:
            if self.verbose:
                print 'Requesting a page...'
            n = n + 1
            rsp = self.fapi.photos_search(
                api_key=self.__flickrAPIKey,
                auth_token=self.token,
                user_id=self.flickrUserId,
                per_page=str(flickr_max),
                page=str(n),
                min_upload_date=dateLo,
                max_upload_date=dateHi,
                )
            if self.__testFailure(rsp):
                return None
            if rsp.photos[0]['total'] == '0':
                return None
            photos += rsp.photos[0].photo
            if self.verbose:
                print ' %d photos so far' % len(photos)
            if len(photos) >= int(rsp.photos[0]['total']):
                break

        return photos

    def getGeotaggedPhotoList(self, dateLo, dateHi):
        """Returns a list of photo given a time frame"""

        n = 0
        flickr_max = 500
        photos = []

        print 'Retrieving list of photos'
        while True:
            if self.verbose:
                print 'Requesting a page...'
            n = n + 1
            rsp = \
                self.fapi.photos_getWithGeoData(api_key=self.__flickrAPIKey,
                    auth_token=self.token, user_id=self.flickrUserId,
                    per_page=str(flickr_max), page=str(n))
            if self.__testFailure(rsp):
                return None
            if rsp.photos[0]['total'] == '0':
                return None
            photos += rsp.photos[0].photo
            if self.verbose:
                print ' %d photos so far' % len(photos)
            if len(photos) >= int(rsp.photos[0]['total']):
                break

        return photos

    def getPhotoLocation(self, pid):
        """Returns a string containing location of a photo (in XML)"""

        rsp = \
            self.fapi.photos_geo_getLocation(api_key=self.__flickrAPIKey,
                auth_token=self.token, photo_id=pid)
        if self.__testFailure(rsp):
            return None
        doc = libxml2.parseDoc(rsp.xml)
        info = doc.xpathEval('/rsp/photo')[0].serialize()
        doc.freeDoc()
        return info

    def getPhotoLocationPermission(self, pid):
        """Returns a string containing location permision for a photo (in XML)"""

        rsp = \
            self.fapi.photos_geo_getPerms(api_key=self.__flickrAPIKey,
                auth_token=self.token, photo_id=pid)
        if self.__testFailure(rsp):
            return None
        doc = libxml2.parseDoc(rsp.xml)
        info = doc.xpathEval('/rsp/perms')[0].serialize()
        doc.freeDoc()
        return info

    def getPhotosetList(self):
        """Returns a list of photosets for a user"""

        rsp = self.fapi.photosets_getList(api_key=self.__flickrAPIKey,
                auth_token=self.token, user_id=self.flickrUserId)
        if self.__testFailure(rsp):
            return None
        return rsp.photosets[0].photoset

    def getPhotosetInfo(self, pid, method):
        """Returns a string containing information about a photoset (in XML)"""

        rsp = method(api_key=self.__flickrAPIKey,
                     auth_token=self.token, photoset_id=pid)
        if self.__testFailure(rsp):
            return None
        doc = libxml2.parseDoc(rsp.xml)
        info = doc.xpathEval('/rsp/photoset')[0].serialize()
        doc.freeDoc()
        return info

    def getPhotoMetadata(self, pid):
        """Returns an array containing containing the photo metadata (as a string), and the format of the photo"""

        if self.verbose:
            print 'Requesting metadata for photo %s' % pid
        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 = doc.xpathEval('/rsp/photo')[0].serialize()
        doc.freeDoc()
        return [metadata, rsp.photo[0]['originalformat']]

    def getPhotoComments(self, pid):
        """Returns an XML string containing the photo comments"""

        if self.verbose:
            print 'Requesting comments for photo %s' % pid
        rsp = \
            self.fapi.photos_comments_getList(api_key=self.__flickrAPIKey,
                auth_token=self.token, photo_id=pid)
        if self.__testFailure(rsp):
            return None
        doc = libxml2.parseDoc(rsp.xml)
        comments = doc.xpathEval('/rsp/comments')[0].serialize()
        doc.freeDoc()
        return comments

    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']
        for s in rsp.sizes[0].size:
            if s['label'] == 'Video Original':
                source = s['source']
        return [source, s['label'] == 'Video Original']

    def __downloadReportHook(
        self,
        count,
        blockSize,
        totalSize,
        ):

        if not self.__verbose:
            return
        p = ((100 * count) * blockSize) / totalSize
        if p > 100:
            p = 100
        print '\r %3d %%' % p,
        sys.stdout.flush()

    def downloadURL(
        self,
        url,
        target,
        filename,
        verbose=False,
        ):
        """Saves a photo in a file"""

        if self.dryrun:
            return
        self.__verbose = verbose
        tmpfile = '%s/%s.TMP' % (target, filename)
        if self.__httplib == 'wget':
            cmd = 'wget -q -t 0 -T 120 -w 10 -c -O %s %s' % (tmpfile,
                    url)
            os.system(cmd)
        else:
            urllib.urlretrieve(url, tmpfile,
                               reporthook=self.__downloadReportHook)
        os.rename(tmpfile, '%s/%s' % (target, filename))
Пример #4
0
def importFromFlickr():

    if g.user is None:
        return jsonify(result = False, error = "You need to be logged in to import from Flickr")
        
    if not g.user.flickr_auth:
        return jsonify(result = False, error = "Your account has not been authenticated with Flickr")

    try:  # Yes yes, a massive try block, the horror! But almost every single line in here throws an error from FlickrAPI
        photoID = request.form.get('photoID')
        api_key = os.environ['PARAM1']
        api_secret = os.environ['PARAM2']
        flickr = FlickrAPI(api_key, api_secret, store_token = False)

        # Get original photo's URL
        sizes = flickr.photos_getSizes(photo_id = photoID).find('sizes')[-1]
        photo_url = sizes.attrib['source']
        img_width = int(sizes.attrib['width'])   # necessary to correctly scale notes
        img_height = int(sizes.attrib['height'])

        # Pull a blob of most of the photo's metadata
        photo_info = flickr.photos_getInfo(photo_id = photoID).find('photo')

        # Check if the person importing this photo actually owns it
        flickr_screen_name = photo_info.find('owner').attrib['username']
        if flickr_screen_name.lower() != g.user.name.lower():
            return jsonify(result = False, error = 'You dog!  You don\'t own this photo!  %s does.  For shame.' % flickr_screen_name)

        # Pull photo's title, desc, timestamps from metadata blob
        flickr_owner_id = photo_info.find('owner').attrib['nsid']  # used to retrieve views
        title = photo_info.find('title').text
        desc = photo_info.find('description').text
        time_taken = photo_info.find('dates').attrib['taken']  # '2013-06-22 11:16:32' ... wtf?
        time_posted = photo_info.find('dates').attrib['posted']  # '1372279163'

        # flickr notes are in a 0..500px coordinate space, where 500 maps to max(img_width, img_height)
        # brickr notes are normalized to a 0..100 % coordinate space, regardless of image aspect ratio (because I'm smarter)
        # flickr notes don't have timestamp info
        scale_w = 500 if img_width >= img_height else (500 / img_height * img_width)
        scale_h = 500 if img_width < img_height else (500 / img_width * img_height)
        notes = []
        for note in photo_info.find('notes'):
            notes.append({
                'user_id': note.attrib['author'],
                'screen_name': note.attrib['authorname'],
                'text': note.text,
                'x': int(note.attrib['x']) / scale_w * 100,
                'y': int(note.attrib['y']) / scale_h * 100,
                'w': int(note.attrib['w']) / scale_w * 100,
                'h': int(note.attrib['h']) / scale_h * 100
            })

        # Photo tags are easy
        tags = []
        for tag in photo_info.find('tags'):
            if tag.attrib['machine_tag'] != '1':  # Ignore ugly automatically created inivisible-to-users tags
                tags.append(tag.attrib['raw'])

        # Import comments - needs its own Flickr API call
        comments = []
        if int(photo_info.find('comments').text) > 0:
            comment_rsp = flickr.photos_comments_getList(photo_id = photoID).find('comments')
            for comment in comment_rsp:
                comments.append({
                    'user_id': comment.attrib.get('author'),
                    'screen_name': comment.attrib.get('authorname'),
                    'timestamp': comment.attrib.get('datecreate'),
                    'iconfarm': comment.attrib.get('iconfarm'),
                    'iconserver': comment.attrib.get('iconserver'),
                    'text': comment.text
                })

        # Import Favorites.  These come in at most 50 per request. Another dedicated Flickr API call
        favorites = []
        favorite_rsp = flickr.photos_getFavorites(photo_id = photoID, per_page = '50').find('photo')
        for fav in favorite_rsp:
            favorites.append({
                'user_id': fav.attrib.get('nsid'),
                'screen_name': fav.attrib.get('username'),
                'timestamp': fav.attrib.get('favedate'),
                'iconfarm': comment.attrib.get('iconfarm'),
                'iconserver': comment.attrib.get('iconserver')
            })

        fav_page_count = int(favorite_rsp.attrib['pages'])
    
        if fav_page_count > 1:
            for i in range(2, fav_page_count + 1):
                favorite_rsp = flickr.photos_getFavorites(photo_id = photoID, page = str(i), per_page = '50').find('photo')
                for fav in favorite_rsp:
                    favorites.append({
                        'user_id': fav.attrib['nsid'],
                        'screen_name': fav.attrib.get('username'),
                        'timestamp': fav.attrib.get('favedate'),
                        'iconfarm': comment.attrib.get('iconfarm'),
                        'iconserver': comment.attrib.get('iconserver')
                    })

        # View count
        # There's no direct flickr API to get a photo's view count (weird)
        # But we can add 'views' to the list of extra info returned by photo.search... (weird)
        # Can't search by photo ID (not weird), but can search by min & max upload time... set those to the photo's upload time, and we find the exact photo... (lucky)
        views = flickr.photos_search(user_id = flickr_owner_id, min_upload_date = time_posted, max_upload_date = time_posted, extras = 'views')
        views = views.find('photos')[0].attrib['views']

    except Exception as e:
        return jsonify(result = False, error = "F**k me.  Flickr Import went horribly awry.  Send this message to Remi:\n\nPhoto: %s - %s" % (photoID, e.__repr__()))

    try:
        # So, we've pulled absolutely everything about this one photo out of Flickr.
        # Now dump it all into Brickr. You're welcome.
        photo = Photo(photo_url, g.user, title, desc)
        file_object = urllib2.urlopen(photo_url)  # Download photo from Flickr
        fp = StringIO(file_object.read())
        if not photo.save_file(fp):
            return jsonify(result = False, error = "Well shit. So, everything exported FROM Flickr just fine.  But we failed to save the exported photo file.  Send this message to Remi:\n\nPhoto: %s - Flickr Export - %s" % (photoID, photo_url))

            
        # Flickr buddy icon URL:
        # http://farm{icon-farm}.staticflickr.com/{icon-server}/buddyicons/{nsid}.jpg
        # http://farm4.staticflickr.com/3692/buddyicons/[email protected]
        photo.views = views
        db.session.add(photo)
        db.session.commit()  # Shit, should do everything in one commit, but we need a photo ID before adding things to the photo...

        for c in comments:
            user = User.get_user_or_placeholder(c['screen_name'], c['user_id'])
            comment = Comment(user, photo, c['text'], datetime.date.fromtimestamp(float(c['timestamp'])))
            db.session.add(comment)

        for n in notes:
            user = User.get_user_or_placeholder(n['screen_name'], n['user_id'])
            note = Note(user, photo, n['text'], n['x'], n['y'], n['w'], n['h'])
            db.session.add(note)

        for t in tags:
            tag = Tag.get_or_create(t)
            photo.tags.extend([tag])
            db.session.add(tag)

        for f in favorites:
            user = User.get_user_or_placeholder(f['screen_name'], f['user_id'])
            fav = Favorite(user, photo)
            db.session.add(fav)

        db.session.commit()

        return jsonify(result = True, url = url_for('photos.photo', user_url = g.user.url, photoID = photo.id))

    except Exception as e:
        return jsonify(result = False, error = "Well shit. So, everything exported FROM flickr just fine.  But dumping it INTO Brickr is apparently too much to ask.  Send this message to Remi:\n\nPhoto: %s - Brickr Import - %s" % (photoID, e.__repr__()))
Пример #5
0
class Offlickr:
    def __init__(self,
                 key,
                 secret,
                 uid,
                 httplib=None,
                 browser="lynx",
                 verbose=False):
        """Instantiates an Offlickr object
        An API key is needed, as well as an API secret and a user id.
        A browser can be specified to be used for authorizing the program
        to access the user account."""
        self.__flickrAPIKey = key
        self.__flickrSecret = secret
        self.__httplib = httplib
        # Get authentication token
        self.fapi = FlickrAPI(self.__flickrAPIKey, self.__flickrSecret)
        self.token = self.fapi.getToken(browser=browser)
        self.flickrUserId = uid
        self.verbose = verbose

    def __testFailure(self, rsp):
        """Returns whether the previous call was successful"""
        if rsp['stat'] == "fail":
            print "Error!"
            return True
        else:
            return False

    def getPhotoList(self, dateLo, dateHi):
        """Returns a list of photo given a time frame"""
        n = 0
        flickr_max = 500
        photos = []

        print "Retrieving list of photos"
        while True:
            if self.verbose:
                print "Requesting a page..."
            n = n + 1
            rsp = self.fapi.photos_search(
                api_key=self.__flickrAPIKey,
                auth_token=self.token,
                user_id=self.flickrUserId,
                per_page=str(flickr_max),  # 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':
                return None
            photos += rsp.photos[0].photo
            if self.verbose:
                print " %d photos so far" % len(photos)
            if len(photos) >= int(rsp.photos[0]['total']):
                break

        return photos

    def getPhotosetList(self):
        """Returns a list of photosets for a user"""

        rsp = self.fapi.photosets_getList(api_key=self.__flickrAPIKey,
                                          auth_token=self.token,
                                          user_id=self.flickrUserId)
        if self.__testFailure(rsp):
            return None
        return rsp.photosets[0].photoset

    def getPhotosetInfo(self, pid, method):
        """Returns a string containing information about a photoset (in XML)"""
        rsp = method(api_key=self.__flickrAPIKey,
                     auth_token=self.token,
                     photoset_id=pid)
        if self.__testFailure(rsp):
            return None
        doc = libxml2.parseDoc(rsp.xml)
        info = str(doc.xpathEval("/rsp/photoset")[0])
        doc.freeDoc()
        return info

    def getPhotoMetadata(self, pid):
        """Returns an array containing containing the photo metadata (as a string), and the format of the photo"""
        if self.verbose:
            print "Requesting metadata for photo %s" % pid
        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 = doc.xpathEval("/rsp/photo")[0].serialize()
        doc.freeDoc()
        return [metadata, rsp.photo[0]['originalformat']]

    def getPhotoComments(self, pid):
        """Returns an XML string containing the photo comments"""
        if self.verbose:
            print "Requesting comments for photo %s" % pid
        rsp = self.fapi.photos_comments_getList(api_key=self.__flickrAPIKey,
                                                auth_token=self.token,
                                                photo_id=pid)
        if self.__testFailure(rsp):
            return None
        doc = libxml2.parseDoc(rsp.xml)
        comments = doc.xpathEval("/rsp/comments")[0].serialize()
        doc.freeDoc()
        return comments

    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, target, filename, verbose=False):
        """Saves a photo in a file"""
        self.__verbose = verbose
        tmpfile = "%s/%s.TMP" % (target, filename)
        if self.__httplib == 'wget':
            cmd = 'wget -q -t 0 -T 120 -w 10 -c -O %s %s' % (tmpfile, url)
            os.system(cmd)
        else:
            urllib.urlretrieve(url,
                               tmpfile,
                               reporthook=self.__downloadReportHook)
        os.rename(tmpfile, "%s/%s" % (target, filename))