diff --git a/flickrscripts/.gitignore b/flickrscripts/.gitignore new file mode 100644 index 0000000..11041c7 --- /dev/null +++ b/flickrscripts/.gitignore @@ -0,0 +1 @@ +*.egg-info diff --git a/flickrscripts/README.rst b/flickrscripts/README.rst new file mode 100644 index 0000000..00ef6fb --- /dev/null +++ b/flickrscripts/README.rst @@ -0,0 +1,5 @@ +============== +Flickr Scripts +============== + +Miscellaneous crap for doing stuff with my Flickr photos. diff --git a/flickrscripts/flickrscripts/__init__.py b/flickrscripts/flickrscripts/__init__.py new file mode 100644 index 0000000..eb8947f --- /dev/null +++ b/flickrscripts/flickrscripts/__init__.py @@ -0,0 +1,30 @@ +from __future__ import print_function + +import sys +from os.path import basename + + +__meta__ = dict( + name='flickrscripts', + version='0.1.0', + entry_points={ + 'console_scripts': [ + 'flickrscripts = flickrscripts:route', + ], + }, + test_suite='nose.collector', +) +USAGE = 'Usage: {prog} [args]' + + +def route(sysargs=sys.argv[:]): + try: + subcommand = sysargs.pop(1) + import_name = 'flickrscripts.{}'.format(subcommand) + + module = __import__(import_name, fromlist=[import_name]) + return module.main(sysargs) + except IndexError: + print(USAGE.format(prog=basename(sysargs[0])), file=sys.stderr) + print('You must provide a subcommand', file=sys.stderr) + return 1 diff --git a/flickrscripts/flickrscripts/common.py b/flickrscripts/flickrscripts/common.py new file mode 100644 index 0000000..cb2d39c --- /dev/null +++ b/flickrscripts/flickrscripts/common.py @@ -0,0 +1,49 @@ +import json +from hashlib import sha1 +from os.path import expanduser + +import flickrapi + + +def setup_flickr(api_key, api_secret): + flickr = flickrapi.FlickrAPI(api_key, api_secret) + + token, frob = flickr.get_token_part_one(perms='write') + if not token: + raw_input("Press ENTER after you authorized this program") + + flickr.get_token_part_two((token, frob)) + return flickr + + +def get_photo_signature(flickr, photo_id): + info = flickr.photos_getInfo(photo_id=photo_id).find('photo') + exif = flickr.photos_getExif(photo_id=photo_id).find('photo') + + if not info or not exif: + raise InvalidPhotoIdError(photo_id) + + return [ + info.get('originalformat'), info.find('owner').get('location'), + info.find('dates').get('taken') + ] + sorted( + (e.get('label'), e.get('tag'), e.get('tagspace'), e.get('tagspaceid'), + e.find('raw').text) for e in exif.findall('exif') + ) + + +def get_photo_sha1sum(flickr, photo_id): + return sha1(str(get_photo_signature(flickr, photo_id))) + + +def get_flickr_from_rc_file(rc_file=expanduser('~/.flickrscripts.json')): + rc_conf = json.load(open(rc_file)) + return setup_flickr(rc_conf['APIKEY'], rc_conf['APISECRET']) + + +def load_rc_file(rc_file=expanduser('~/.flickrscripts.json')): + return json.load(open(rc_file)) + + +class InvalidPhotoIdError(ValueError): + pass diff --git a/flickrscripts/flickrscripts/timewarp.py b/flickrscripts/flickrscripts/timewarp.py new file mode 100644 index 0000000..b76e094 --- /dev/null +++ b/flickrscripts/flickrscripts/timewarp.py @@ -0,0 +1,95 @@ +import argparse +import json +import logging +import sys +from datetime import datetime, timedelta +from os.path import expanduser + +from flickrscripts.common import setup_flickr + + +class TimeWarper(object): + flickr = None + _base_query_args = dict(user_id='me', page=1) + + def __init__(self, api_key, api_secret): + self.flickr = setup_flickr(api_key, api_secret) + self.log = logging.getLogger(self.__class__.__name__) + self.log.level = logging.INFO + + def run(self, set_to_date, **query_args): + self.log.info( + 'Setting upload dates for photos matching query %r', query_args + ) + query = self._base_query_args.copy() + query.update(query_args) + + for i, page in self._get_pages(query): + self.log.info('Processing page %s, attributes=%r', + i, page.attrib) + self._set_upload_date_for_photos(i, page, set_to_date) + + return 0 + + def _set_upload_date_for_photos(self, pagenum, page, set_to_date): + for i, photo in enumerate(page.findall('photos/photo')): + setdate_response = self.flickr.photos_setDates( + photo_id=photo.attrib['id'], + date_posted=set_to_date + ) + self.log.info('page %s photo %s: response attribs=%s', + pagenum, i, setdate_response.attrib) + + def _get_pages(self, query): + self.log.info('Getting pages for query %r', query) + i = 1 + while True: + page = self.flickr.photos_search(**query) + if len(page.findall('photos/photo')): + yield i, page + i += 1 + else: + raise StopIteration + + +def main(sysargs=sys.argv[:]): + parser = argparse.ArgumentParser( + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + prog='flickrscripts timewarp') + parser.add_argument('set_to_date') + parser.add_argument('-q', '--search-query-param', default=[], + action='append', help='Add a key=value param to the search query list') + + parser.add_argument( + '-c', '--rcfile', default=expanduser('~/.flickrscripts.json'), + help='JSON file containing `APIKEY` and `APISECRET` keys', + type=argparse.FileType('r') + ) + + args = parser.parse_args(sysargs[1:]) + rc_conf = json.load(args.rcfile) + + logging.basicConfig(level=logging.INFO, stream=sys.stdout) + log = logging.getLogger('main') + log.level = logging.INFO + log.info('Preparing query args') + + query_args = {} + for key_val in args.search_query_param: + key, value = key_val.split('=', 1) + query_args[key] = value + + if len(query_args.keys()) == 0: + query_args = dict( + min_upload_date=(datetime.now() + timedelta(days=-1)), + max_upload_date=(datetime.now() + timedelta(days=1)) + ) + log.warn('No query arguments provided! ' + + 'Defaulting to %r', query_args) + + warper = TimeWarper(rc_conf['APIKEY'], rc_conf['APISECRET']) + return warper.run(args.set_to_date, **query_args) + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/flickrscripts/setup.py b/flickrscripts/setup.py new file mode 100644 index 0000000..84b1a72 --- /dev/null +++ b/flickrscripts/setup.py @@ -0,0 +1,10 @@ +import sys + +from setuptools import setup + +from flickrscripts import __meta__ + + +if __name__ == '__main__': + setup(**__meta__) + sys.exit(0) diff --git a/flickrscripts/tests/itest_timewarp.py b/flickrscripts/tests/itest_timewarp.py new file mode 100644 index 0000000..248ab2c --- /dev/null +++ b/flickrscripts/tests/itest_timewarp.py @@ -0,0 +1,19 @@ +import unittest + +from flickrscripts.timewarp import TimeWarper +from flickrscripts.common import load_rc_file + + +class TimeWarperTestCase(unittest.TestCase): + + def setUp(self): + rc_conf = load_rc_file() + self.warper = TimeWarper(rc_conf['APIKEY'], rc_conf['APISECRET']) + + def test_is_not_horribly_busted(self): + self.assertTrue(True, 'please forgive the horrible test') + + + +if __name__ == '__main__': + unittest.main()