From d7789fa2985c9f14459ab0b7a1223104355a1dac Mon Sep 17 00:00:00 2001 From: Scott Gasch Date: Wed, 1 Jun 2016 19:39:03 -0700 Subject: [PATCH] Initial checking of picasa project. --- gdata_oauth.py | 204 +++++++++++++++++++++++++++++++++++ mirror_picasa.py | 269 +++++++++++++++++++++++++++++++++++++++++++++++ secrets.py | 5 + 3 files changed, 478 insertions(+) create mode 100644 gdata_oauth.py create mode 100755 mirror_picasa.py create mode 100644 secrets.py diff --git a/gdata_oauth.py b/gdata_oauth.py new file mode 100644 index 0000000..e7296e5 --- /dev/null +++ b/gdata_oauth.py @@ -0,0 +1,204 @@ +# https://developers.google.com/accounts/docs/OAuth2ForDevices +# https://developers.google.com/drive/web/auth/web-server +# https://developers.google.com/google-apps/calendar/v3/reference/calendars +# https://developers.google.com/picasa-web/ + +import sys +import urllib +try: + import httplib # python2 +except ImportError: + import http.client # python3 +import os.path +import json +import time +from oauth2client.client import OAuth2Credentials +import gdata.calendar.service +import gdata.docs.service +import gdata.photos.service, gdata.photos +from apiclient.discovery import build +import httplib2 +from apiclient.discovery import build +import datetime +import ssl + +class OAuth: + def __init__(self, client_id, client_secret): + print "Initializing oauth token..." + self.client_id = client_id + self.client_secret = client_secret + #print 'Client id: %s' % (client_id) + #print 'Client secret: %s' % (client_secret) + self.token = None + self.device_code = None + self.verfication_url = None + self.token_file = 'client_secrets.json' + self.scope = [ + #'https://www.googleapis.com/auth/calendar', + #'https://www.googleapis.com/auth/drive', + 'https://docs.google.com/feeds', + 'https://www.googleapis.com/auth/calendar.readonly', + 'https://picasaweb.google.com/data/', + 'https://photos.googleapis.com/data/', + #'http://picasaweb.google.com/data/', + #'https://www.google.com/calendar/feeds/', + ] + self.host = 'accounts.google.com' + self.reset_connection() + self.load_token() + self.last_action = 0 + self.ssl_ctx = None + + # this setup is isolated because it eventually generates a BadStatusLine + # exception, after which we always get httplib.CannotSendRequest errors. + # When this happens, we try re-creating the exception. + def reset_connection(self): + self.ssl_ctx = ssl.create_default_context(cafile='/usr/local/etc/ssl/cert.pem') + httplib.HTTPConnection.debuglevel = 2 + self.conn = httplib.HTTPSConnection(self.host, context=self.ssl_ctx) + + def load_token(self): + token = None + if os.path.isfile(self.token_file): + f = open(self.token_file) + json_token = f.read() + self.token = json.loads(json_token) + f.close() + + def save_token(self): + f = open(self.token_file, 'w') + f.write(json.dumps(self.token)) + f.close() + + def has_token(self): + if self.token != None: + print "We have a token!" + else: + print "We have no token." + return self.token != None + + def get_user_code(self): + self.conn.request( + "POST", + "/o/oauth2/device/code", + urllib.urlencode({ + 'client_id': self.client_id, + 'scope' : ' '.join(self.scope) + }), + {"Content-type": "application/x-www-form-urlencoded"}) + response = self.conn.getresponse() + if response.status == 200: + data = json.loads(response.read()) + self.device_code = data['device_code'] + self.user_code = data['user_code'] + self.verification_url = data['verification_url'] + self.retry_interval = data['interval'] + else: + print response.status + print response.read() + sys.exit() + return self.user_code + + def get_new_token(self): + # call get_device_code if not already set + if not self.user_code: + print "getting user code" + self.get_user_code() + + while self.token == None: + self.conn.request( + "POST", + "/o/oauth2/token", + urllib.urlencode({ + 'client_id' : self.client_id, + 'client_secret' : self.client_secret, + 'code' : self.device_code, + 'grant_type' : 'http://oauth.net/grant_type/device/1.0' + }), + {"Content-type": "application/x-www-form-urlencoded"}) + response = self.conn.getresponse() + if response.status == 200: + data = json.loads(response.read()) + if 'access_token' in data: + self.token = data + self.save_token() + else: + time.sleep(self.retry_interval + 2) + else: + print "failed to get token" + print response.status + print response.read() + + def refresh_token(self): + if self.checking_too_often(): + print "Not refreshing yet, too soon..." + return False + else: + print 'Trying to refresh oauth token...' + self.reset_connection() + refresh_token = self.token['refresh_token'] + self.conn.request( + "POST", + "/o/oauth2/token", + urllib.urlencode({ + 'client_id' : self.client_id, + 'client_secret' : self.client_secret, + 'refresh_token' : refresh_token, + 'grant_type' : 'refresh_token' + }), + {"Content-type": "application/x-www-form-urlencoded"}) + + response = self.conn.getresponse() + self.last_action = time.time() + if response.status == 200: + data = json.loads(response.read()) + if 'access_token' in data: + self.token = data + # in fact we NEVER get a new refresh token at this point + if not 'refresh_token' in self.token: + self.token['refresh_token'] = refresh_token + self.save_token() + return True + print "Unexpected response %d to renewal request" % response.status + print response.read() + return False + + def checking_too_often(self): + now = time.time() + return (now - self.last_action) <= 30 + + # https://developers.google.com/picasa-web/ + def photos_service(self): + headers = { + "Authorization": "%s %s" % (self.token['token_type'], self.token['access_token']) + } + client = gdata.photos.service.PhotosService(additional_headers=headers) + return client + + # https://developers.google.com/drive/ + def docs_service(self): + cred = OAuth2Credentials(self.token['access_token'], + self.client_id, + self.client_secret, + self.token['refresh_token'], + datetime.datetime.now(), + 'http://accounts.google.com/o/oauth2/token', + 'KitchenKiosk/0.9') + http = httplib2.Http(disable_ssl_certificate_validation=True) + http = cred.authorize(http) + service = build('drive', 'v2', http) + return service + + # https://developers.google.com/google-apps/calendar/ + def calendar_service(self): + cred = OAuth2Credentials(self.token['access_token'], + self.client_id, + self.client_secret, + self.token['refresh_token'], + datetime.datetime.now(), + 'http://accounts.google.com/o/oauth2/token', + 'KitchenKiosk/0.9') + http = httplib2.Http(disable_ssl_certificate_validation=True) + http = cred.authorize(http) + service = build('calendar', 'v3', http) + return service diff --git a/mirror_picasa.py b/mirror_picasa.py new file mode 100755 index 0000000..88ae3fc --- /dev/null +++ b/mirror_picasa.py @@ -0,0 +1,269 @@ +#!/usr/local/bin/python + +# Copyright (c) 2015 Scott Gasch +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import sys +import traceback +import os +from threading import Thread +import time +import gdata_oauth +from datetime import datetime +import sets +import gdata_oauth +import gdata.photos, gdata.photos.service +import gdata +import secrets + +class mirror_picasaweb: + """A program to mirror my picasaweb photos and make a local webpage out of it.""" + + album_whitelist = sets.ImmutableSet([ + '1-Wire Project', + 'Alex 6.0..8.0 years old', + 'Barn', + 'Bangkok and Phuket, 2003', + 'Blue Angels... Seafair', + 'Dunn Gardens', + 'East Coast, 2011', + 'East Coast, 2013', + 'Friends', + 'Gasches', + 'Gasch Wedding', + 'Hiking and Ohme Gardens', + 'Hiking', + 'Kiosk Project', + 'Krakow 2009', + 'NJ 2015', + 'Oahu 2010' + 'Ocean Shores 2009', + 'Ohme Gardens', + 'Olympic Sculpture Park', + 'Paintings', + 'Puerto Vallarta', + 'Photos from posts', + 'Random', + 'SFO 2014', + 'Soccer', + 'Skiing with Alex', + 'Tuscany 2008', + 'Yosemite 2010', + 'Zoo', + ]) + + def __init__(self): + self.client = oauth.photos_service() + self.album = None + self.photo = None + + def write_album_header(self, f, album): + f.write(""" + +%s + +

Album List | %s

+""" % (album.title.text, + album.link[0].href, + album.title.text)) + if album.location.text is not None: + f.write("Location: %s
\n" % album.location.text) + if album.summary.text is not None: + f.write("Summary: %s
\n" % album.summary.text) + if album.updated.text is not None: + f.write("Updated: %s
\n" % album.updated.text) + f.write(""" +
+""") + + def add_photo_to_album(self, f, photo, url, number): + descr = photo.summary.text + camera = photo.exif.model.text if photo.exif.model else 'unknown' + if photo.timestamp: + ts = photo.timestamp.datetime().strftime('%d %b %Y') + else: + ts = 'unknown' + if photo.commentCount and photo.commentCount > 0: + comments = self.client.GetFeed( + '/data/feed/api/user/default/albumid/%s/photoid/%s?kind=comment' % ( + self.album.gphoto_id.text, photo.gphoto_id.text)) + else: + comments = None + thumbnail = photo.media.thumbnail[1].url + + if number % 2 == 0: + f.write('') + f.write(""" +""") + if number % 2 == 1: + f.write('') + + def write_album_footer(self, f): + f.write(""" +
+ + + + + + +
+
+ +
%s
+
+
+
+
+ %s
+ Date: %s
+ Camera: %s
""" % ( + url, + thumbnail, + descr, + descr, + ts, + camera)) + if comments is not None: + for comment in comments.entry: + print '%s
' % comment.content.text + f.write(""" +
+
+
+
+ + Are you interested in how this page was made? + +
+ +""") + + def write_index_header(self, f): + f.write(""" + +Scott's Photo Albums + + + + + + + """) + + def add_album_to_index(self, f, album, count): + thumbnail = album.media.thumbnail[0].url + if count % 2 == 0: + f.write('\n') + f.write(""" +""") + if count % 2 == 1: + f.write('\n') + + def write_index_footer(self, f): + f.write(""" +
+

Album List

+
+ + + + + +
+
+ + + +
+
+ Title: %s
+ Subtitle: %s
+ Date: %s
+ %s photo(s)
+ %d Mb""" % ( + album.name.text, + thumbnail, + album.title.text, + album.summary.text, + album.published.text, + album.numphotos.text, + int(album.bytesUsed) / (1024 * 1024))) + f.write(""" +
+
+""") + + def run(self): + attempts = 0 + while attempts < 3: + attempts += 1 + try: + self.run_internal() + return + except (gdata.service.RequestError, + gdata.photos.service.GooglePhotosException): + print "******** TRYING TO REFRESH PHOTOS CLIENT *********" + oauth.refresh_token() + self.client = oauth.photos_service() + print "Tried 3x, giving up." + + def run_internal(self): + albums = self.client.GetUserFeed().entry + album_count = 0 + index = open('./index.html', 'w') + self.write_index_header(index) + for album in albums: + if album.title.text not in mirror_picasaweb.album_whitelist: + print '--> Skipping "%s" (%s)' % (album.title.text, album.access.text) + continue + print '--> Doing "%s" (%s)' % (album.title.text, album.access.text) + self.album = album + self.add_album_to_index(index, album, album_count) + filename = './%s.html' % album.name.text + detail = open(filename, 'w') + self.write_album_header(detail, album) + photos = self.client.GetFeed( + '/data/feed/api/user/%s/albumid/%s?kind=photo&imgmax=1024u' % + (secrets.account, album.gphoto_id.text)) + photo_count = 0 + for photo in photos.entry: + resolution = 0 + for x in photo.media.content: + if "video" in x.type and int(x.height) > resolution: + url = x.url + resolution = int(x.height) + elif resolution == 0: + url = x.url + resolution = int(x.height) + self.photo = photo + self.add_photo_to_album(detail, photo, url, photo_count) + photo_count += 1 + self.write_album_footer(detail) + detail.close() + album_count += 1 + self.write_index_footer(index) + index.close() + +if __name__ == "__main__": + oauth = gdata_oauth.OAuth(secrets.client_id, + secrets.client_secret) + if not oauth.has_token(): + user_code = oauth.get_user_code() + print '------------------------------------------------------------' + print 'Go to %s and enter the code "%s" (no quotes, case-sensitive)' % ( + oauth.verification_url, user_code) + oauth.get_new_token() + x = mirror_picasaweb() + x.run() diff --git a/secrets.py b/secrets.py new file mode 100644 index 0000000..9e71d6c --- /dev/null +++ b/secrets.py @@ -0,0 +1,5 @@ +#!/usr/local/bin/python + +client_id = '.apps.googleusercontent.com' +client_secret = '' +account = '' -- 2.47.1