MadMode

Dan Connolly's tinkering lab notebook

Syncing a 5 Year iPhoto Library with flickr

Yay! jawj's iphoto-flickr sync'd a 30GB iPhoto library flickr. Not only did it upload the images, but it made a map from iPhoto metadata to flickr metadata that lets me continue on with the flickr API, syncing dates and such, during and after the upload.

OS X Yosemite runs iPhoto, reluctantly

Having replaced it with a newer model, I gave airbook, our late 2008 MacBook Air MB543LL/A, a complete labotomy and installed OS X Yosemite. When I tried to re-introduce it to our photo archive, Apple told me iPhoto is no longer; Photos is the new thing, complete with iCloud hip-ness.

So I'm faced with another "to Mac or not to Mac?" moment.

This time I figure no, I lead a multi-platform life and I want something more web-native.

I spent a bunch of time trying to downgrade to Mavericks. Just when I had given up trying to do it myself and ordered Mavericks on a USB flash drive via eBay, I learned that if you start iPhoto from a command prompt, it runs on Yosemite after all.

The only complication was a dangling reference to iLifeSlideshow.framework in /System/Library/PrivateFrameworks. (Thank you Carbon Copy Cloner for a complete backup!)

Home directory in encrypted sparsebundle

This photo library is on an external USB drive, in an encrypted sparsebundle. The sparsebundle support discussion said all I have to do is double-click it, but it's hidden (filename starts with a dot). Command-line to the rescue, again: open /Users/.maryc.

Flickr is the web photo service for the closet librarian

As an Android mobile user, Google's photo offerings were tempting. Then I discovered browsing photos of person X only works within one album. And to put a photo in multiple albums, you have to copy it, i.e. maintain the tags and such twice.

My friends-and-family photo sharing community mostly uses facebook these days, but for curating an archive, flickr is a much better match. Imagine my horror when I downloaded some of my photos from facebook and discovered they were only available at reduced resolution. Perhaps they've addressed that since, but I still haven't seen any support for date-taken as separate from date-uploaded on facebook. There's little, if any, support for quietly curating without notifications firing every which way.

My photostream on flickr goes back to Dec 2004 when it was big in the open web community. I could never bring myself to go premium, but in May 2013 when they announced the terabyte storage offer, I dusted it off. Re-establishing my long lost yahoo credentials was no small feat, but I managed.

Flickr Backup from Mac App Store was a Bust

A quick search of the Mac App store turned up promising results:

It was just a few bucks, so I went ahead. But oops: Your iPhoto library is either too old (iPhoto version < 9.0) or no photo found. Indeed, my library is from 8.1.2. I might have been able to upgrade the library, but with Apple pushing Photos over iPhoto, I didn't want to bet on it.

iPhoto -> Flickr in 350 lines of code

I was thinking about rolling my own with the flickr API when I discovered a kindred spirit had already been down this path and come up with iPhoto -> Flickr.

It worked in one go, so the incremental upload support wasn't necessary for the initial bulk upload, but to further sync the metadata, the resulting uploaded-photo-ids-map.txt is critical. In fact, I had to wrestle with iPhoto a bit to get ids that are useful without iPhoto running.

Just Add API Key and Authorize with OAuth

It works pretty much like it says on the tin. (The colorize dependency issue was easy enough to figure out.)

airbook:src connolly$ git clone https://github.com/jawj/flickrbackup.git  # e339169212
airbook:flickrbackup connolly$ sudo gem install flickraw-cached colorize
Successfully installed flickraw-0.9.8
Successfully installed flickraw-cached-20120701
Successfully installed colorize-0.7.7

airbook:flickrbackup connolly$ ruby flickrbackup.rb 

Flickr API key: 0481...
Flickr API shared secret: 897...
Authorise access to your Flickr account: press [Return] when ready
Authorisation code: 162-...

2015-07-04 13:47:28 -0500  Authenticated as: DanC

2015-07-04 13:47:44 -0500  8057 photos and 78 standard albums in iPhoto library
2015-07-04 13:47:44 -0500  8057 photos not yet uploaded to Flickr

Platform Independent Data

The kernel for this notebook is on my linux desktop, but iPhoto is running on airbook.

Since spaces in filenames are a royal pain over ssh, I made a convenient symlink.

In [190]:
!ssh airbook.local ls -l Pictures/flickrbackup
lrwxr-xr-x  1 connolly  staff  57 Jul  5 09:34 Pictures/flickrbackup -> /Users/connolly/Library/Application Support/flickrbackup/

Upload Map DataFrame

It carefully logs the correspondence to support incremental update:

2015-07-04 13:47:44 -0500  (1/8057)  Uploading '...2002/Sep 25, 2002/....jpg' ... 4294967334 -> 19226418710

Let's make sure we have redundant copies of the map. And let's use ordinary CSV rather than the funky -> format.

In [191]:
from IPython.display import display, Image
import pandas as pd
import numpy as np
dict(pandas=pd.__version__, numpy=np.__version__)
Out[191]:
{'numpy': '1.9.2', 'pandas': '0.14.1'}
In [112]:
upload_map = !ssh airbook.local cat Pictures/flickrbackup/uploaded-photo-ids-map.txt
upload_map = pd.DataFrame(dict(apple=int(a), flickr=int(f))
                          for (a, f) in [line.split(' -> ') for line in upload_map])
upload_map.head()
Out[112]:
apple flickr
0 4294967334 19226418710
1 4294976544 18793385783
2 4294976530 18793388523
3 4294976542 18791506174
4 4294971867 19414016905
In [187]:
upload_map.to_csv('uploaded-photo-ids-map.csv')

Flickr metadata access

Let's take a at the results on flickr. I experimented with python flickr apis; the main one seems to be Python Flickr. flickdata.py (in palmagent) is a least-authority packaging of that API.

TODO: use a separate Photo object for setDates.

In [168]:
import flickdata
reload(flickdata)
flickdata.__version__
Out[168]:
'0.4'

To make a flickdata.Account, we use the privileged iPython notebook environment to get network access and the API key (and OAuth credentials... where do they get squirrelled away?) and pass it to flickdata:

In [45]:
import json
import logging
logger = logging.getLogger()
logger.setLevel(logging.INFO)
logging.info('We try to log I/O.')
INFO:root:We try to log I/O.
In [75]:
def myFlickrAcct(user_id='14874637@N00'):
    import pathlib
    import flickrapi
    api_secret = pathlib.Path('flickr_api_secret').open().read().strip()
    return flickdata.Read.make(flickrapi, api_secret, user_id)

myAcct = myFlickrAcct()
INFO:flickdata:authenticating...
INFO:flickrapi.core:Calling {'nojsoncallback': 1, 'method': 'flickr.auth.oauth.checkToken', 'format': 'parsed-json'}
INFO:requests.packages.urllib3.connectionpool:Starting new HTTPS connection (1): api.flickr.com
INFO:flickrapi.core:REST Parser: using xml.etree.cElementTree

Photostream confused about recent photos

A bunch of old photos and videos are showing up as recent in my photostream as the upload progresses.

Flickr seems to set datetaken = date uploaded when there's no EXIF date, so let's look at these supposedly recent photos.

In [172]:
records = [
    r
    for page in
    myAcct.getPhotos(
        min_taken_date='2015-07',
        max_taken_date='2015-08',
        sort='date-taken-asc')
    for r in page]
photo = pd.DataFrame(records)
photo['id'] = photo.id.astype(int)  # odd... even in JSON format, ids come back as strings
photo = photo.set_index('id')
len(photo)
INFO:flickdata:getPhotos page 1 cirt: {'sort': 'date-taken-asc', 'min_taken_date': '2015-07', 'max_taken_date': '2015-08'}...
INFO:flickrapi.core:Calling {'nojsoncallback': 1, 'method': 'flickr.people.getPhotos', 'format': 'parsed-json'}
INFO:requests.packages.urllib3.connectionpool:Starting new HTTPS connection (1): api.flickr.com
INFO:flickdata:page 1 of 1
Out[172]:
78

Photo URLs: Thumbnail

Flickr's URLs API uses "secrets" so they serve as nice tasty capability URLs for the photos. So we can see this thumbnail from this iPython notebook even though we're not logged in to flickr here.

In [173]:
Image(url=photo.iloc[0].url_t)
Out[173]:

Photo info fields

Note: this isn't all fields available from getPhotos.

In [174]:
photo.iloc[0]
Out[174]:
accuracy                                                               16
context                                                                 0
datetaken                                             2015-07-02 11:28:40
datetakengranularity                                                    0
datetakenunknown                                                        0
dateupload                                                     1435854531
description                                            {u'_content': u''}
farm                                                                    1
geo_is_contact                                                          0
geo_is_family                                                           0
geo_is_friend                                                           0
geo_is_public                                                           1
height_k                                                             1516
height_o                                                             2368
height_t                                                               74
height_z                                                              474
isfamily                                                                0
isfriend                                                                0
ispublic                                                                0
latitude                                                        39.054611
longitude                                                      -94.611264
machine_tags                                                             
owner                                                        14874637@N00
place_id                                               _zncmCVTVLmQsuBlEg
secret                                                         2ff03b551d
server                                                                540
tags                                                                     
title                                                 IMG_20150702_112835
url_k                   https://farm1.staticflickr.com/540/19350464562...
url_o                   https://farm1.staticflickr.com/540/19350464562...
url_t                   https://farm1.staticflickr.com/540/19350464562...
url_z                   https://farm1.staticflickr.com/540/19350464562...
width_k                                                              2048
width_o                                                              3200
width_t                                                               100
width_z                                                               640
woeid                                                            26342889
Name: 19350464562, dtype: object

Ah. Good. When the display defaults date taken to upload date, the underlying data tells us so:

In [176]:
import datetime

photo['upload_date'] = [np.datetime64(datetime.datetime.fromtimestamp(int(ts))) for ts in photo.dateupload]
photo[photo.datetakenunknown == '1'][['datetaken', 'upload_date', 'title', 'width_o', 'height_o']].head()
Out[176]:
datetaken upload_date title width_o height_o
id
19426161436 2015-07-05 20:23:04 2015-07-05 20:23:04 segway tour - 4 466 630
19456508621 2015-07-05 20:23:10 2015-07-05 20:23:10 segway tour - 28 851 630
19266094319 2015-07-05 20:23:12 2015-07-05 20:23:12 segway tour - 2 466 630
19264673498 2015-07-05 20:23:16 2015-07-05 20:23:16 segway tour - 33 466 630
19445946592 2015-07-05 20:23:47 2015-07-05 20:23:47 Brennan baby tub 2351 2945

These were uploaded very soon after being taken; I suppose I turned on auto-upload on my phone:

In [192]:
photo[photo.datetakenunknown == '0'][['datetaken', 'upload_date', 'title', 'width_o', 'height_o']].head()
Out[192]:
datetaken upload_date title width_o height_o
id
19350464562 2015-07-02 11:28:40 2015-07-02 11:28:51 IMG_20150702_112835 3200 2368
19425609241 2015-07-04 13:17:22 2015-07-04 19:49:20 1122 712
19235236159 2015-07-04 13:17:33 2015-07-04 19:49:22 458 46
18800793223 2015-07-04 13:17:43 2015-07-04 19:49:23 546 100

I verified the most recent upload dates against iphoto-flickr logs to be sure there were no timezone issues:

In [177]:
photo[['datetaken', 'upload_date', 'title', 'width_o', 'height_o']].sort('upload_date', ascending=False).head()
Out[177]:
datetaken upload_date title width_o height_o
id
19453379905 2015-07-05 21:18:18 2015-07-05 21:18:18 11223919_10103150053978467_4385673914430987089_n 960 639
19457659531 2015-07-05 21:18:17 2015-07-05 21:18:17 Dining_Room_Turned_Office 550 400
19447074092 2015-07-05 21:18:04 2015-07-05 21:18:04 11261973_10103150053743937_5354724846552149885_n 960 639
19447070902 2015-07-05 21:17:59 2015-07-05 21:17:59 11143223_10103150052730967_2134381211714517553_n 960 639
19457651521 2015-07-05 21:17:55 2015-07-05 21:17:55 11265214_10103150052591247_2966764364887797640_n 960 639

iPhoto, give me my data back!

flickrbackup found the library I'm interested in even though it's not in the default path. Ah... it's using Applescript.

iPhoto uses fairly nice .xml and .db files with a nice, sturdy uuid for each photo. But the id flickerbackup.rb got via applescript is nowhere to be found in there!

In [16]:
!ssh airbook.local grep {photo.index[0]} Pictures/flickrbackup/uploaded-photo-ids-map.txt 
4294967334 -> 19226418710
In [17]:
!ssh airbook.local grep 4294967334 Pictures/iphoto-maryc/AlbumData.xml
In [18]:
!ssh airbook.local sqlite3 Pictures/iphoto-maryc/iPhotoMain.db .dump | grep 4294967334

The iPhoto script dictionary doesn't show a uid property. Darn. We'll have to use file paths or something.

iPhoto takes orders in JavaScript

Ooh! We can use JXA, JavaScript for Automation. Somebody made a nice cookbook on github.

The first API I found for writing a string to a file was this $.NSString objective-C bridge thing. Oh well. It works.

In [39]:
export_keys = '''
#!/usr/bin/env osascript -l JavaScript

function save(info, where) {
    console.log('saving to...', where)
    var str = $.NSString.alloc.initWithUTF8String(JSON.stringify(info));
    str.writeToFileAtomicallyEncodingError(where, true, $.NSUTF8StringEncoding, null);    
}

function getKeys(iPhoto) {
    var photos = iPhoto.photoLibraryAlbum().photos;

    return {
        id: photos.id(),
        date: photos.date(),
        width: photos.width(),
        height: photos.height(),
        originalPath: photos.originalPath(),
        imagePath: photos.imagePath()
    };
}

function run(argv) {
    out = argv[0];
    iPhoto = Application('iPhoto');
    save(getKeys(iPhoto), out)
}
'''.strip()

Let's save it, scp it over, run it, and scp the results back:

In [40]:
def save_script(name, text):
    from pathlib import Path
    with Path('photo_keys.js').open('wb') as out:
        out.write(export_keys)

save_script('photo_keys.js', export_keys)
In [41]:
!scp photo_keys.js airbook.local:Pictures/
photo_keys.js                                 100%  687     0.7KB/s   00:00    
In [42]:
!ssh airbook.local osascript -l JavaScript Pictures/photo_keys.js Pictures/keys.json
saving to... Pictures/keys.json

In [43]:
!scp airbook.local:Pictures/keys.json .
keys.json                                     100% 1697KB 848.6KB/s   00:02    

Now we can ground these ids in key information such as file paths, dates, and image sizes that we can join with other sources:

In [47]:
pk = pd.DataFrame(json.load(open('keys.json'))).set_index('id')
pk.head()
Out[47]:
date height imagePath originalPath width
id
4294967334 2002-09-25T15:14:23.000Z 600 /Volumes/maryc/Pictures/iPhoto Library/Origina... /Volumes/maryc/Pictures/iPhoto Library/Origina... 800
4294976544 2003-07-21T03:52:05.000Z 1385 /Volumes/maryc/Pictures/iPhoto Library/Origina... /Volumes/maryc/Pictures/iPhoto Library/Origina... 1332
4294976530 2003-08-08T20:09:51.000Z 1459 /Volumes/maryc/Pictures/iPhoto Library/Modifie... /Volumes/maryc/Pictures/iPhoto Library/Origina... 1647
4294976542 2003-08-08T20:09:51.000Z 1536 /Volumes/maryc/Pictures/iPhoto Library/Origina... /Volumes/maryc/Pictures/iPhoto Library/Origina... 2048
4294971867 2006-02-01T17:05:22.000Z 377 /Volumes/maryc/Pictures/iPhoto Library/Origina... /Volumes/maryc/Pictures/iPhoto Library/Origina... 495

Applescript reports the full image paths, but we'll need library-relative paths for our work below.

In [62]:
libloc = '/Volumes/maryc/Pictures/iPhoto Library/'  # TODO: get from applescript?

pk['relativePath'] = [p[len(libloc):] for p in pk.imagePath]

pk[(pk.date >= '2002-09') & (pk.date < '2002-10')][['date', 'height', 'width', 'relativePath']]
Out[62]:
date height width relativePath
id
4294967334 2002-09-25T15:14:23.000Z 600 800 Originals/2002/Sep 25, 2002/Santa Cecelia gran...

iPhoto data without iPhoto

iPhoto keeps nice sqlite3 databases.

In [207]:
def my_photo_db(path='maryc-airbook-iphoto-meta/iPhotoMain.db'):
    import sqlite3
    return sqlite3.connect(path)

db1 = my_photo_db()

q = '''
select count(distinct uid) from SqPhotoInfo
'''
pd.read_sql(q, db1)
Out[207]:
count(distinct uid)
0 8607
In [212]:
q = '''
select count(*) qty, year from (
  select substr(datetime(photoDate +  julianday('2000-01-01 00:00:00')), 1, 4) year
  from SqPhotoInfo
) t
group by year
having count(*) > 10
'''
pd.read_sql(q, db1)
Out[212]:
qty year
0 306 2011
1 4368 2012
2 1564 2013
3 2190 2014
4 161 2015

Cameras

In [195]:
q = '''
select qty,
 datetime(min_date +  julianday('2000-01-01 00:00:00')) min_date,
 datetime(max_date +  julianday('2000-01-01 00:00:00')) max_date,
 cameraModel from (
select count(*) qty, min(photoDate) min_date, max(photoDate) max_date, cameraModel
 from
sqphotoinfo
where photoDate >  julianday('1993-01-01') - julianday('2000-01-01 00:00:00')
group by cameraModel
)
where qty >= 10
order by 1 desc
'''
pd.read_sql(q, db1)
Out[195]:
qty min_date max_date cameraModel
0 5568 2009-11-25 17:28:32 2013-11-04 11:05:02 Canon PowerShot SD1100 IS
1 872 2013-12-04 07:02:10 2015-04-18 15:50:41 FinePix S4850
2 641 2006-02-01 11:05:22 2015-05-23 09:47:31 None
3 401 2012-09-21 11:06:43 2015-01-02 09:22:16 Galaxy Nexus
4 399 2014-08-13 15:55:36 2014-08-19 11:15:30 NIKON D800
5 273 2014-05-16 16:59:18 2014-06-28 14:09:51 NIKON D3200
6 137 2012-11-14 17:35:19 2013-11-29 13:29:43 FinePix S5Pro
7 90 2012-10-19 17:55:45 2015-03-12 11:37:07 iPhone 4S
8 78 2012-09-15 18:21:05 2012-10-21 18:17:14 Canon PowerShot ELPH 100 HS
9 65 2011-04-18 12:19:47 2012-05-05 15:40:20 NIKON D90
10 28 2014-06-27 22:17:35 2014-06-28 01:07:04 Canon PowerShot SD1200 IS
11 28 2014-10-02 13:22:41 2014-10-02 15:31:08 SGH-T999

Photos and Images

The model is nice and clean, separating photos, relating any number of possibly-edited images to each photo-taking event, and issuing a uuid to the photo-taking event.

In [139]:
q = '''

select photo.primaryKey, photo.uid, datetime(photo.photoDate + julianday('2000-01-01 00:00:00')) as photoDate,
       photo.cameraModel, photo.archiveFilename,
       fi.imageWidth, fi.imageHeight, fi.fileSize, fi.imageType, fi.version,
       fl.relativePath, fl.aliasPath
       -- TODO: decode fl.format
from SqPhotoInfo photo
join SqFileImage fi on fi.photoKey = photo.primaryKey
join SqFileInfo fl on fi.sqFileInfo = fl.primaryKey

where fileSize > 0
order by photo.photoDate desc
'''
pdb = pd.read_sql(q, db1)
pdb.head()
Out[139]:
primaryKey uid photoDate cameraModel archiveFilename imageWidth imageHeight fileSize imageType version relativePath aliasPath
0 9272 4BC69B9E-3AEF-4FA6-BFCC-5476CC6CAC0D 2015-05-23 09:47:31 None Dining_Room_Turned_Office.jpg 550 400 34651 6 100 Originals/2015/May 23, 2015/Dining_Room_Turned... None
1 9272 4BC69B9E-3AEF-4FA6-BFCC-5476CC6CAC0D 2015-05-23 09:47:31 None Dining_Room_Turned_Office.jpg 360 262 52460 5 100 Data/2015/May 23, 2015/Dining_Room_Turned_Offi... None
2 9282 11F6E0C9-90F8-43E3-8427-EFD1F98D5ECD 2015-05-20 09:24:50 None 11223919_10103150053978467_4385673914430987089... 960 639 101115 6 100 Originals/2015/May 20, 2015/11223919_101031500... None
3 9282 11F6E0C9-90F8-43E3-8427-EFD1F98D5ECD 2015-05-20 09:24:50 None 11223919_10103150053978467_4385673914430987089... 360 240 67356 5 100 Data/2015/May 20, 2015/11223919_10103150053978... None
4 9289 88DC7357-96A2-4C29-88CD-6DF9E39E4CBB 2015-05-20 09:24:41 None 11261973_10103150053743937_5354724846552149885... 960 639 97108 6 100 Originals/2015/May 20, 2015/11261973_101031500... None

Joining sqlite data with flickr via applescript key info

Ah... excellent... even though there are more image files than photos, we get an exact 1-1 match when we join with our photo keys (implicitly on relativePath).

In [109]:
len(pdb), len(pk), len(pdb.merge(pk))
Out[109]:
(19127, 8057, 8057)
In [150]:
pkdb = pk.reset_index().merge(pdb).set_index('id')
pkdb.head()
Out[150]:
date height imagePath originalPath width relativePath primaryKey uid photoDate cameraModel archiveFilename imageWidth imageHeight fileSize imageType version aliasPath
id
4294967334 2002-09-25T15:14:23.000Z 600 /Volumes/maryc/Pictures/iPhoto Library/Origina... /Volumes/maryc/Pictures/iPhoto Library/Origina... 800 Originals/2002/Sep 25, 2002/Santa Cecelia gran... 38 7281F4D1-E140-4832-B759-60D5B9DF78B1 2002-09-25 10:14:23 PDR-3320 Santa Cecelia granite.jpg 800 600 63092 6 100 None
4294976544 2003-07-21T03:52:05.000Z 1385 /Volumes/maryc/Pictures/iPhoto Library/Origina... /Volumes/maryc/Pictures/iPhoto Library/Origina... 1332 Originals/2003/Jul 20, 2003/brothers.jpg 9248 7EFC6CF1-F3A5-42DD-ADBC-52561476CC50 2003-07-20 22:52:05 hp photosmart 720 brothers.jpg 1332 1385 615319 6 100 None
4294976530 2003-08-08T20:09:51.000Z 1459 /Volumes/maryc/Pictures/iPhoto Library/Modifie... /Volumes/maryc/Pictures/iPhoto Library/Origina... 1647 Modified/2003/Aug 8, 2003/justin car.jpg 9234 E3D5AD74-7FC1-4916-A9DA-6C2CB47B5D16 2003-08-08 15:09:51 hp photosmart 720 justin car.jpg 1647 1459 828662 6 100 None
4294976542 2003-08-08T20:09:51.000Z 1536 /Volumes/maryc/Pictures/iPhoto Library/Origina... /Volumes/maryc/Pictures/iPhoto Library/Origina... 2048 Originals/2003/Aug 8, 2003_2/justin car.jpg 9246 CCD2008C-4CB5-46BF-B99D-4BAE8999D8AD 2003-08-08 15:09:51 hp photosmart 720 justin car.jpg 2048 1536 738791 6 100 None
4294971867 2006-02-01T17:05:22.000Z 377 /Volumes/maryc/Pictures/iPhoto Library/Origina... /Volumes/maryc/Pictures/iPhoto Library/Origina... 495 Originals/2006/Feb 1, 2006/NY-Skyline-new-york... 4571 5D1426FC-5F86-457D-9EB7-6F761607C8FA 2006-02-01 11:05:22 None NY-Skyline-new-york-1138029_495_377.jpeg 495 377 159106 6 100 None

Merging with the upload_map gives us a clear correspondence between iPhoto applescript ids and flickr ids.

In [151]:
upkdb = upload_map.merge(pkdb, left_on='apple', right_index=True)

len(upkdb)
Out[151]:
8057
In [152]:
upkdb.head()
Out[152]:
apple flickr date height imagePath originalPath width relativePath primaryKey uid photoDate cameraModel archiveFilename imageWidth imageHeight fileSize imageType version aliasPath
0 4294967334 19226418710 2002-09-25T15:14:23.000Z 600 /Volumes/maryc/Pictures/iPhoto Library/Origina... /Volumes/maryc/Pictures/iPhoto Library/Origina... 800 Originals/2002/Sep 25, 2002/Santa Cecelia gran... 38 7281F4D1-E140-4832-B759-60D5B9DF78B1 2002-09-25 10:14:23 PDR-3320 Santa Cecelia granite.jpg 800 600 63092 6 100 None
1 4294976544 18793385783 2003-07-21T03:52:05.000Z 1385 /Volumes/maryc/Pictures/iPhoto Library/Origina... /Volumes/maryc/Pictures/iPhoto Library/Origina... 1332 Originals/2003/Jul 20, 2003/brothers.jpg 9248 7EFC6CF1-F3A5-42DD-ADBC-52561476CC50 2003-07-20 22:52:05 hp photosmart 720 brothers.jpg 1332 1385 615319 6 100 None
2 4294976530 18793388523 2003-08-08T20:09:51.000Z 1459 /Volumes/maryc/Pictures/iPhoto Library/Modifie... /Volumes/maryc/Pictures/iPhoto Library/Origina... 1647 Modified/2003/Aug 8, 2003/justin car.jpg 9234 E3D5AD74-7FC1-4916-A9DA-6C2CB47B5D16 2003-08-08 15:09:51 hp photosmart 720 justin car.jpg 1647 1459 828662 6 100 None
3 4294976542 18791506174 2003-08-08T20:09:51.000Z 1536 /Volumes/maryc/Pictures/iPhoto Library/Origina... /Volumes/maryc/Pictures/iPhoto Library/Origina... 2048 Originals/2003/Aug 8, 2003_2/justin car.jpg 9246 CCD2008C-4CB5-46BF-B99D-4BAE8999D8AD 2003-08-08 15:09:51 hp photosmart 720 justin car.jpg 2048 1536 738791 6 100 None
4 4294971867 19414016905 2006-02-01T17:05:22.000Z 377 /Volumes/maryc/Pictures/iPhoto Library/Origina... /Volumes/maryc/Pictures/iPhoto Library/Origina... 495 Originals/2006/Feb 1, 2006/NY-Skyline-new-york... 4571 5D1426FC-5F86-457D-9EB7-6F761607C8FA 2006-02-01 11:05:22 None NY-Skyline-new-york-1138029_495_377.jpeg 495 377 159106 6 100 None

Fixing Dates

Let's grab flickr photos with unkonwn date taken (with upload date, title, and original size).

Then merge with the date information from the sqlite3 db.

In [178]:
tofix = photo[photo.datetakenunknown == '1'][['datetaken', 'upload_date', 'title', 'width_o', 'height_o']]
fixed = tofix.merge(upkdb, left_index=True, right_on='flickr')[
    ['date', 'photoDate', 'upload_date', 'title', 'archiveFilename',
     'width_o', 'imageWidth', 'height_o', 'imageHeight',
    'flickr', 'uid']].set_index('flickr')
print len(tofix), len(fixed)
fixed.head()
74 74
Out[178]:
date photoDate upload_date title archiveFilename width_o imageWidth height_o imageHeight uid
flickr
19426161436 2014-10-20T18:47:27.000Z 2014-10-20 13:47:27 2015-07-05 20:23:04 segway tour - 4 segway tour - 4.jpg 466 466 630 630 93061866-7680-43EA-9D58-8DE25E554B43
19456508621 2014-10-20T18:48:03.000Z 2014-10-20 13:48:03 2015-07-05 20:23:10 segway tour - 28 segway tour - 28.jpg 851 851 630 630 BFCD692A-52C7-4059-8A9F-F6264133C155
19266094319 2014-10-20T18:52:50.000Z 2014-10-20 13:52:50 2015-07-05 20:23:12 segway tour - 2 segway tour - 2.jpg 466 291 630 438 B854FE26-841E-4968-BDF2-975C18AB3B05
19264673498 2014-10-20T18:48:22.000Z 2014-10-20 13:48:22 2015-07-05 20:23:16 segway tour - 33 segway tour - 33.jpg 466 338 630 519 8976E385-2BE0-46AB-8E9E-2FA573574ED4
19445946592 2014-10-31T13:45:47.000Z 2014-10-31 08:45:47 2015-07-05 20:23:47 Brennan baby tub Brennan baby tub.jpg 2351 2351 2945 2945 BA448463-3932-40C2-811C-B30017C68087

For this, we need write access.

In [169]:
def myFlickrEdit(user_id='14874637@N00'):
    import pathlib
    import flickrapi
    api_secret = pathlib.Path('flickr_api_secret').open().read().strip()
    return flickdata.Write.make(flickrapi, api_secret, user_id)

edit = myFlickrEdit()
INFO:flickdata:authenticating...
INFO:flickrapi.core:Calling {'nojsoncallback': 1, 'method': 'flickr.auth.oauth.checkToken', 'format': 'parsed-json'}
INFO:requests.packages.urllib3.connectionpool:Starting new HTTPS connection (1): api.flickr.com
INFO:flickrapi.core:REST Parser: using xml.etree.cElementTree

Let's work with one photo at first, verifying with the flickr web UI as we go.

In [179]:
Image(url=photo.loc[19426161436].url_t)
Out[179]:
In [180]:
fixed.loc[19426161436].photoDate
Out[180]:
u'2014-10-20 13:47:27'
In [181]:
edit.setDates(19426161436, date_taken=fixed.loc[19426161436].photoDate)
INFO:flickdata:setDates: {'date_taken': u'2014-10-20 13:47:27'}...
INFO:flickrapi.core:Calling {'nojsoncallback': 1, 'method': 'flickr.photos.setDates', 'format': 'parsed-json'}
INFO:requests.packages.urllib3.connectionpool:Starting new HTTPS connection (1): api.flickr.com
Out[181]:
{u'stat': u'ok'}

Now we can iterate over all the fixes.

Incremental updates came in handy here. At first, I forgot to rate-limit my requests and flickr noticed after a few hundred. I went back and fetched metadata for recent photos in my photostream again and finished off the rest.

In [182]:
import time
In [185]:
def do_fixes():
    for pid, photo in fixed.iterrows():
        edit.setDates(pid, date_taken=photo.photoDate)
        time.sleep(0.5)
do_fixes()
INFO:flickdata:setDates: {'date_taken': u'2014-10-20 13:47:27'}...
INFO:flickrapi.core:Calling {'nojsoncallback': 1, 'method': 'flickr.photos.setDates', 'format': 'parsed-json'}
INFO:requests.packages.urllib3.connectionpool:Starting new HTTPS connection (1): api.flickr.com
INFO:flickdata:setDates: {'date_taken': u'2014-10-20 13:48:03'}...
INFO:flickrapi.core:Calling {'nojsoncallback': 1, 'method': 'flickr.photos.setDates', 'format': 'parsed-json'}
INFO:requests.packages.urllib3.connectionpool:Starting new HTTPS connection (1): api.flickr.com
INFO:flickdata:setDates: {'date_taken': u'2014-10-20 13:52:50'}...
INFO:flickrapi.core:Calling {'nojsoncallback': 1, 'method': 'flickr.photos.setDates', 'format': 'parsed-json'}
INFO:requests.packages.urllib3.connectionpool:Starting new HTTPS connection (1): api.flickr.com
INFO:flickdata:setDates: {'date_taken': u'2014-10-20 13:48:22'}...
INFO:flickrapi.core:Calling {'nojsoncallback': 1, 'method': 'flickr.photos.setDates', 'format': 'parsed-json'}
INFO:requests.packages.urllib3.connectionpool:Starting new HTTPS connection (1): api.flickr.com
INFO:flickdata:setDates: {'date_taken': u'2014-10-31 08:45:47'}...
INFO:flickrapi.core:Calling {'nojsoncallback': 1, 'method': 'flickr.photos.setDates', 'format': 'parsed-json'}
INFO:requests.packages.urllib3.connectionpool:Starting new HTTPS connection (1): api.flickr.com
INFO:flickdata:setDates: {'date_taken': u'2014-10-31 09:07:10'}...
INFO:flickrapi.core:Calling {'nojsoncallback': 1, 'method': 'flickr.photos.setDates', 'format': 'parsed-json'}
INFO:requests.packages.urllib3.connectionpool:Starting new HTTPS connection (1): api.flickr.com
INFO:flickdata:setDates: {'date_taken': u'2014-10-31 08:59:05'}...
INFO:flickrapi.core:Calling {'nojsoncallback': 1, 'method': 'flickr.photos.setDates', 'format': 'parsed-json'}
INFO:requests.packages.urllib3.connectionpool:Starting new HTTPS connection (1): api.flickr.com
INFO:flickdata:setDates: {'date_taken': u'2014-10-31 09:07:30'}...
INFO:flickrapi.core:Calling {'nojsoncallback': 1, 'method': 'flickr.photos.setDates', 'format': 'parsed-json'}
INFO:requests.packages.urllib3.connectionpool:Starting new HTTPS connection (1): api.flickr.com
INFO:flickdata:setDates: {'date_taken': u'2014-11-01 07:06:54'}...
INFO:flickrapi.core:Calling {'nojsoncallback': 1, 'method': 'flickr.photos.setDates', 'format': 'parsed-json'}
INFO:requests.packages.urllib3.connectionpool:Starting new HTTPS connection (1): api.flickr.com
INFO:flickdata:setDates: {'date_taken': u'2014-11-08 18:27:35'}...
INFO:flickrapi.core:Calling {'nojsoncallback': 1, 'method': 'flickr.photos.setDates', 'format': 'parsed-json'}
INFO:requests.packages.urllib3.connectionpool:Starting new HTTPS connection (1): api.flickr.com
INFO:flickdata:setDates: {'date_taken': u'2014-11-08 18:28:06'}...
INFO:flickrapi.core:Calling {'nojsoncallback': 1, 'method': 'flickr.photos.setDates', 'format': 'parsed-json'}
INFO:requests.packages.urllib3.connectionpool:Starting new HTTPS connection (1): api.flickr.com
INFO:flickdata:setDates: {'date_taken': u'2014-11-08 18:27:35'}...
INFO:flickrapi.core:Calling {'nojsoncallback': 1, 'method': 'flickr.photos.setDates', 'format': 'parsed-json'}
INFO:requests.packages.urllib3.connectionpool:Starting new HTTPS connection (1): api.flickr.com
INFO:flickdata:setDates: {'date_taken': u'2014-11-08 18:28:40'}...
INFO:flickrapi.core:Calling {'nojsoncallback': 1, 'method': 'flickr.photos.setDates', 'format': 'parsed-json'}
INFO:requests.packages.urllib3.connectionpool:Starting new HTTPS connection (1): api.flickr.com
INFO:flickdata:setDates: {'date_taken': u'2014-11-08 18:28:16'}...
INFO:flickrapi.core:Calling {'nojsoncallback': 1, 'method': 'flickr.photos.setDates', 'format': 'parsed-json'}
INFO:requests.packages.urllib3.connectionpool:Starting new HTTPS connection (1): api.flickr.com
INFO:flickdata:setDates: {'date_taken': u'2014-11-08 18:29:05'}...
INFO:flickrapi.core:Calling {'nojsoncallback': 1, 'method': 'flickr.photos.setDates', 'format': 'parsed-json'}
INFO:requests.packages.urllib3.connectionpool:Starting new HTTPS connection (1): api.flickr.com
INFO:flickdata:setDates: {'date_taken': u'2014-11-08 18:30:01'}...
INFO:flickrapi.core:Calling {'nojsoncallback': 1, 'method': 'flickr.photos.setDates', 'format': 'parsed-json'}
INFO:requests.packages.urllib3.connectionpool:Starting new HTTPS connection (1): api.flickr.com
INFO:flickdata:setDates: {'date_taken': u'2014-11-08 18:36:10'}...
INFO:flickrapi.core:Calling {'nojsoncallback': 1, 'method': 'flickr.photos.setDates', 'format': 'parsed-json'}
INFO:requests.packages.urllib3.connectionpool:Starting new HTTPS connection (1): api.flickr.com
INFO:flickdata:setDates: {'date_taken': u'2014-11-08 18:31:59'}...
INFO:flickrapi.core:Calling {'nojsoncallback': 1, 'method': 'flickr.photos.setDates', 'format': 'parsed-json'}
INFO:requests.packages.urllib3.connectionpool:Starting new HTTPS connection (1): api.flickr.com
INFO:flickdata:setDates: {'date_taken': u'2014-11-08 18:35:29'}...
INFO:flickrapi.core:Calling {'nojsoncallback': 1, 'method': 'flickr.photos.setDates', 'format': 'parsed-json'}
INFO:requests.packages.urllib3.connectionpool:Starting new HTTPS connection (1): api.flickr.com
INFO:flickdata:setDates: {'date_taken': u'2014-11-08 18:35:15'}...
INFO:flickrapi.core:Calling {'nojsoncallback': 1, 'method': 'flickr.photos.setDates', 'format': 'parsed-json'}
INFO:requests.packages.urllib3.connectionpool:Starting new HTTPS connection (1): api.flickr.com
INFO:flickdata:setDates: {'date_taken': u'2014-11-08 18:39:51'}...
INFO:flickrapi.core:Calling {'nojsoncallback': 1, 'method': 'flickr.photos.setDates', 'format': 'parsed-json'}
INFO:requests.packages.urllib3.connectionpool:Starting new HTTPS connection (1): api.flickr.com
INFO:flickdata:setDates: {'date_taken': u'2014-11-08 19:26:15'}...
INFO:flickrapi.core:Calling {'nojsoncallback': 1, 'method': 'flickr.photos.setDates', 'format': 'parsed-json'}
INFO:requests.packages.urllib3.connectionpool:Starting new HTTPS connection (1): api.flickr.com
INFO:flickdata:setDates: {'date_taken': u'2014-11-08 19:26:30'}...
INFO:flickrapi.core:Calling {'nojsoncallback': 1, 'method': 'flickr.photos.setDates', 'format': 'parsed-json'}
INFO:requests.packages.urllib3.connectionpool:Starting new HTTPS connection (1): api.flickr.com
INFO:flickdata:setDates: {'date_taken': u'2014-11-08 19:26:42'}...
INFO:flickrapi.core:Calling {'nojsoncallback': 1, 'method': 'flickr.photos.setDates', 'format': 'parsed-json'}
INFO:requests.packages.urllib3.connectionpool:Starting new HTTPS connection (1): api.flickr.com
INFO:flickdata:setDates: {'date_taken': u'2014-11-08 19:27:04'}...
INFO:flickrapi.core:Calling {'nojsoncallback': 1, 'method': 'flickr.photos.setDates', 'format': 'parsed-json'}
INFO:requests.packages.urllib3.connectionpool:Starting new HTTPS connection (1): api.flickr.com
INFO:flickdata:setDates: {'date_taken': u'2014-11-08 19:28:15'}...
INFO:flickrapi.core:Calling {'nojsoncallback': 1, 'method': 'flickr.photos.setDates', 'format': 'parsed-json'}
INFO:requests.packages.urllib3.connectionpool:Starting new HTTPS connection (1): api.flickr.com
INFO:flickdata:setDates: {'date_taken': u'2014-11-08 19:28:56'}...
INFO:flickrapi.core:Calling {'nojsoncallback': 1, 'method': 'flickr.photos.setDates', 'format': 'parsed-json'}
INFO:requests.packages.urllib3.connectionpool:Starting new HTTPS connection (1): api.flickr.com
INFO:flickdata:setDates: {'date_taken': u'2014-11-08 19:28:36'}...
INFO:flickrapi.core:Calling {'nojsoncallback': 1, 'method': 'flickr.photos.setDates', 'format': 'parsed-json'}
INFO:requests.packages.urllib3.connectionpool:Starting new HTTPS connection (1): api.flickr.com
INFO:flickdata:setDates: {'date_taken': u'2014-11-08 19:29:09'}...
INFO:flickrapi.core:Calling {'nojsoncallback': 1, 'method': 'flickr.photos.setDates', 'format': 'parsed-json'}
INFO:requests.packages.urllib3.connectionpool:Starting new HTTPS connection (1): api.flickr.com
INFO:flickdata:setDates: {'date_taken': u'2014-11-08 19:30:34'}...
INFO:flickrapi.core:Calling {'nojsoncallback': 1, 'method': 'flickr.photos.setDates', 'format': 'parsed-json'}
INFO:requests.packages.urllib3.connectionpool:Starting new HTTPS connection (1): api.flickr.com
INFO:flickdata:setDates: {'date_taken': u'2014-11-08 19:30:45'}...
INFO:flickrapi.core:Calling {'nojsoncallback': 1, 'method': 'flickr.photos.setDates', 'format': 'parsed-json'}
INFO:requests.packages.urllib3.connectionpool:Starting new HTTPS connection (1): api.flickr.com
INFO:flickdata:setDates: {'date_taken': u'2014-11-08 19:30:56'}...
INFO:flickrapi.core:Calling {'nojsoncallback': 1, 'method': 'flickr.photos.setDates', 'format': 'parsed-json'}
INFO:requests.packages.urllib3.connectionpool:Starting new HTTPS connection (1): api.flickr.com
INFO:flickdata:setDates: {'date_taken': u'2014-11-08 19:31:13'}...
INFO:flickrapi.core:Calling {'nojsoncallback': 1, 'method': 'flickr.photos.setDates', 'format': 'parsed-json'}
INFO:requests.packages.urllib3.connectionpool:Starting new HTTPS connection (1): api.flickr.com
INFO:flickdata:setDates: {'date_taken': u'2014-11-08 19:32:18'}...
INFO:flickrapi.core:Calling {'nojsoncallback': 1, 'method': 'flickr.photos.setDates', 'format': 'parsed-json'}
INFO:requests.packages.urllib3.connectionpool:Starting new HTTPS connection (1): api.flickr.com
INFO:flickdata:setDates: {'date_taken': u'2014-11-08 19:32:18'}...
INFO:flickrapi.core:Calling {'nojsoncallback': 1, 'method': 'flickr.photos.setDates', 'format': 'parsed-json'}
INFO:requests.packages.urllib3.connectionpool:Starting new HTTPS connection (1): api.flickr.com
INFO:flickdata:setDates: {'date_taken': u'2014-11-08 19:32:41'}...
INFO:flickrapi.core:Calling {'nojsoncallback': 1, 'method': 'flickr.photos.setDates', 'format': 'parsed-json'}
INFO:requests.packages.urllib3.connectionpool:Starting new HTTPS connection (1): api.flickr.com
INFO:flickdata:setDates: {'date_taken': u'2014-11-08 19:32:30'}...
INFO:flickrapi.core:Calling {'nojsoncallback': 1, 'method': 'flickr.photos.setDates', 'format': 'parsed-json'}
INFO:requests.packages.urllib3.connectionpool:Starting new HTTPS connection (1): api.flickr.com
INFO:flickdata:setDates: {'date_taken': u'2014-11-08 19:32:52'}...
INFO:flickrapi.core:Calling {'nojsoncallback': 1, 'method': 'flickr.photos.setDates', 'format': 'parsed-json'}
INFO:requests.packages.urllib3.connectionpool:Starting new HTTPS connection (1): api.flickr.com
INFO:flickdata:setDates: {'date_taken': u'2014-11-08 19:33:23'}...
INFO:flickrapi.core:Calling {'nojsoncallback': 1, 'method': 'flickr.photos.setDates', 'format': 'parsed-json'}
INFO:requests.packages.urllib3.connectionpool:Starting new HTTPS connection (1): api.flickr.com
INFO:flickdata:setDates: {'date_taken': u'2014-11-08 19:35:17'}...
INFO:flickrapi.core:Calling {'nojsoncallback': 1, 'method': 'flickr.photos.setDates', 'format': 'parsed-json'}
INFO:requests.packages.urllib3.connectionpool:Starting new HTTPS connection (1): api.flickr.com
INFO:flickdata:setDates: {'date_taken': u'2014-11-08 19:34:47'}...
INFO:flickrapi.core:Calling {'nojsoncallback': 1, 'method': 'flickr.photos.setDates', 'format': 'parsed-json'}
INFO:requests.packages.urllib3.connectionpool:Starting new HTTPS connection (1): api.flickr.com
INFO:flickdata:setDates: {'date_taken': u'2014-11-08 19:34:56'}...
INFO:flickrapi.core:Calling {'nojsoncallback': 1, 'method': 'flickr.photos.setDates', 'format': 'parsed-json'}
INFO:requests.packages.urllib3.connectionpool:Starting new HTTPS connection (1): api.flickr.com
INFO:flickdata:setDates: {'date_taken': u'2014-11-08 19:35:08'}...
INFO:flickrapi.core:Calling {'nojsoncallback': 1, 'method': 'flickr.photos.setDates', 'format': 'parsed-json'}
INFO:requests.packages.urllib3.connectionpool:Starting new HTTPS connection (1): api.flickr.com
INFO:flickdata:setDates: {'date_taken': u'2014-11-08 19:36:40'}...
INFO:flickrapi.core:Calling {'nojsoncallback': 1, 'method': 'flickr.photos.setDates', 'format': 'parsed-json'}
INFO:requests.packages.urllib3.connectionpool:Starting new HTTPS connection (1): api.flickr.com
INFO:flickdata:setDates: {'date_taken': u'2014-11-08 19:35:38'}...
INFO:flickrapi.core:Calling {'nojsoncallback': 1, 'method': 'flickr.photos.setDates', 'format': 'parsed-json'}
INFO:requests.packages.urllib3.connectionpool:Starting new HTTPS connection (1): api.flickr.com
INFO:flickdata:setDates: {'date_taken': u'2014-11-08 19:36:51'}...
INFO:flickrapi.core:Calling {'nojsoncallback': 1, 'method': 'flickr.photos.setDates', 'format': 'parsed-json'}
INFO:requests.packages.urllib3.connectionpool:Starting new HTTPS connection (1): api.flickr.com
INFO:flickdata:setDates: {'date_taken': u'2014-11-08 20:04:18'}...
INFO:flickrapi.core:Calling {'nojsoncallback': 1, 'method': 'flickr.photos.setDates', 'format': 'parsed-json'}
INFO:requests.packages.urllib3.connectionpool:Starting new HTTPS connection (1): api.flickr.com
INFO:flickdata:setDates: {'date_taken': u'2014-11-08 20:07:33'}...
INFO:flickrapi.core:Calling {'nojsoncallback': 1, 'method': 'flickr.photos.setDates', 'format': 'parsed-json'}
INFO:requests.packages.urllib3.connectionpool:Starting new HTTPS connection (1): api.flickr.com
INFO:flickdata:setDates: {'date_taken': u'2014-11-08 20:07:44'}...
INFO:flickrapi.core:Calling {'nojsoncallback': 1, 'method': 'flickr.photos.setDates', 'format': 'parsed-json'}
INFO:requests.packages.urllib3.connectionpool:Starting new HTTPS connection (1): api.flickr.com
INFO:flickdata:setDates: {'date_taken': u'2014-11-08 20:08:11'}...
INFO:flickrapi.core:Calling {'nojsoncallback': 1, 'method': 'flickr.photos.setDates', 'format': 'parsed-json'}
INFO:requests.packages.urllib3.connectionpool:Starting new HTTPS connection (1): api.flickr.com
INFO:flickdata:setDates: {'date_taken': u'2014-12-27 14:51:54'}...
INFO:flickrapi.core:Calling {'nojsoncallback': 1, 'method': 'flickr.photos.setDates', 'format': 'parsed-json'}
INFO:requests.packages.urllib3.connectionpool:Starting new HTTPS connection (1): api.flickr.com
INFO:flickdata:setDates: {'date_taken': u'2014-12-27 14:52:04'}...
INFO:flickrapi.core:Calling {'nojsoncallback': 1, 'method': 'flickr.photos.setDates', 'format': 'parsed-json'}
INFO:requests.packages.urllib3.connectionpool:Starting new HTTPS connection (1): api.flickr.com
INFO:flickdata:setDates: {'date_taken': u'2014-12-27 14:52:16'}...
INFO:flickrapi.core:Calling {'nojsoncallback': 1, 'method': 'flickr.photos.setDates', 'format': 'parsed-json'}
INFO:requests.packages.urllib3.connectionpool:Starting new HTTPS connection (1): api.flickr.com
INFO:flickdata:setDates: {'date_taken': u'2014-12-27 14:52:34'}...
INFO:flickrapi.core:Calling {'nojsoncallback': 1, 'method': 'flickr.photos.setDates', 'format': 'parsed-json'}
INFO:requests.packages.urllib3.connectionpool:Starting new HTTPS connection (1): api.flickr.com
INFO:flickdata:setDates: {'date_taken': u'2014-12-27 14:52:48'}...
INFO:flickrapi.core:Calling {'nojsoncallback': 1, 'method': 'flickr.photos.setDates', 'format': 'parsed-json'}
INFO:requests.packages.urllib3.connectionpool:Starting new HTTPS connection (1): api.flickr.com
INFO:flickdata:setDates: {'date_taken': u'2015-01-09 14:22:41'}...
INFO:flickrapi.core:Calling {'nojsoncallback': 1, 'method': 'flickr.photos.setDates', 'format': 'parsed-json'}
INFO:requests.packages.urllib3.connectionpool:Starting new HTTPS connection (1): api.flickr.com
INFO:flickdata:setDates: {'date_taken': u'2015-03-03 17:19:17'}...
INFO:flickrapi.core:Calling {'nojsoncallback': 1, 'method': 'flickr.photos.setDates', 'format': 'parsed-json'}
INFO:requests.packages.urllib3.connectionpool:Starting new HTTPS connection (1): api.flickr.com
INFO:flickdata:setDates: {'date_taken': u'2015-04-09 19:53:29'}...
INFO:flickrapi.core:Calling {'nojsoncallback': 1, 'method': 'flickr.photos.setDates', 'format': 'parsed-json'}
INFO:requests.packages.urllib3.connectionpool:Starting new HTTPS connection (1): api.flickr.com
INFO:flickdata:setDates: {'date_taken': u'2015-04-09 19:47:33'}...
INFO:flickrapi.core:Calling {'nojsoncallback': 1, 'method': 'flickr.photos.setDates', 'format': 'parsed-json'}
INFO:requests.packages.urllib3.connectionpool:Starting new HTTPS connection (1): api.flickr.com
INFO:flickdata:setDates: {'date_taken': u'2015-04-09 19:49:17'}...
INFO:flickrapi.core:Calling {'nojsoncallback': 1, 'method': 'flickr.photos.setDates', 'format': 'parsed-json'}
INFO:requests.packages.urllib3.connectionpool:Starting new HTTPS connection (1): api.flickr.com
INFO:flickdata:setDates: {'date_taken': u'2015-04-09 19:55:26'}...
INFO:flickrapi.core:Calling {'nojsoncallback': 1, 'method': 'flickr.photos.setDates', 'format': 'parsed-json'}
INFO:requests.packages.urllib3.connectionpool:Starting new HTTPS connection (1): api.flickr.com
INFO:flickdata:setDates: {'date_taken': u'2015-04-11 09:03:06'}...
INFO:flickrapi.core:Calling {'nojsoncallback': 1, 'method': 'flickr.photos.setDates', 'format': 'parsed-json'}
INFO:requests.packages.urllib3.connectionpool:Starting new HTTPS connection (1): api.flickr.com
INFO:flickdata:setDates: {'date_taken': u'2015-04-09 20:09:42'}...
INFO:flickrapi.core:Calling {'nojsoncallback': 1, 'method': 'flickr.photos.setDates', 'format': 'parsed-json'}
INFO:requests.packages.urllib3.connectionpool:Starting new HTTPS connection (1): api.flickr.com
INFO:flickdata:setDates: {'date_taken': u'2015-04-11 09:11:11'}...
INFO:flickrapi.core:Calling {'nojsoncallback': 1, 'method': 'flickr.photos.setDates', 'format': 'parsed-json'}
INFO:requests.packages.urllib3.connectionpool:Starting new HTTPS connection (1): api.flickr.com
INFO:flickdata:setDates: {'date_taken': u'2015-04-18 21:51:53'}...
INFO:flickrapi.core:Calling {'nojsoncallback': 1, 'method': 'flickr.photos.setDates', 'format': 'parsed-json'}
INFO:requests.packages.urllib3.connectionpool:Starting new HTTPS connection (1): api.flickr.com
INFO:flickdata:setDates: {'date_taken': u'2015-05-20 09:23:33'}...
INFO:flickrapi.core:Calling {'nojsoncallback': 1, 'method': 'flickr.photos.setDates', 'format': 'parsed-json'}
INFO:requests.packages.urllib3.connectionpool:Starting new HTTPS connection (1): api.flickr.com
INFO:flickdata:setDates: {'date_taken': u'2015-05-20 09:23:42'}...
INFO:flickrapi.core:Calling {'nojsoncallback': 1, 'method': 'flickr.photos.setDates', 'format': 'parsed-json'}
INFO:requests.packages.urllib3.connectionpool:Starting new HTTPS connection (1): api.flickr.com
INFO:flickdata:setDates: {'date_taken': u'2015-05-20 09:24:28'}...
INFO:flickrapi.core:Calling {'nojsoncallback': 1, 'method': 'flickr.photos.setDates', 'format': 'parsed-json'}
INFO:requests.packages.urllib3.connectionpool:Starting new HTTPS connection (1): api.flickr.com
INFO:flickdata:setDates: {'date_taken': u'2015-05-20 09:23:52'}...
INFO:flickrapi.core:Calling {'nojsoncallback': 1, 'method': 'flickr.photos.setDates', 'format': 'parsed-json'}
INFO:requests.packages.urllib3.connectionpool:Starting new HTTPS connection (1): api.flickr.com
INFO:flickdata:setDates: {'date_taken': u'2015-05-20 09:24:08'}...
INFO:flickrapi.core:Calling {'nojsoncallback': 1, 'method': 'flickr.photos.setDates', 'format': 'parsed-json'}
INFO:requests.packages.urllib3.connectionpool:Starting new HTTPS connection (1): api.flickr.com
INFO:flickdata:setDates: {'date_taken': u'2015-05-20 09:24:17'}...
INFO:flickrapi.core:Calling {'nojsoncallback': 1, 'method': 'flickr.photos.setDates', 'format': 'parsed-json'}
INFO:requests.packages.urllib3.connectionpool:Starting new HTTPS connection (1): api.flickr.com
INFO:flickdata:setDates: {'date_taken': u'2015-05-20 09:24:41'}...
INFO:flickrapi.core:Calling {'nojsoncallback': 1, 'method': 'flickr.photos.setDates', 'format': 'parsed-json'}
INFO:requests.packages.urllib3.connectionpool:Starting new HTTPS connection (1): api.flickr.com
INFO:flickdata:setDates: {'date_taken': u'2015-05-23 09:47:31'}...
INFO:flickrapi.core:Calling {'nojsoncallback': 1, 'method': 'flickr.photos.setDates', 'format': 'parsed-json'}
INFO:requests.packages.urllib3.connectionpool:Starting new HTTPS connection (1): api.flickr.com
INFO:flickdata:setDates: {'date_taken': u'2015-05-20 09:24:50'}...
INFO:flickrapi.core:Calling {'nojsoncallback': 1, 'method': 'flickr.photos.setDates', 'format': 'parsed-json'}
INFO:requests.packages.urllib3.connectionpool:Starting new HTTPS connection (1): api.flickr.com

Future Work

  • make an album of all the photos uploaded in this process?
  • tag flickr photos with uids
    • don't lose "untagged" state, though! capture untagged-ness in an album or something.
  • sync events... using photosets?
  • sync faces

Additional notes bookmarked under: mac photos, mac sysadmin