Copyright (C) 2010 Google Inc.
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 datetime
import googlecl
import googlecl.base
import logging
service_name = __name__.split('.')[-1]
LOGGER_NAME = __name__
SECTION_HEADER = service_name.upper()
LOG = logging.getLogger(LOGGER_NAME)
Makes the given URL for a picasa image point to the download.
def make_download_url(url):
return url[:url.rfind('/')+1]+'d'+url[url.rfind('/'):]
def _map_access_string(access_string, default_value='private'):
if not access_string:
return default_value
It seems to me that 'private' is less private than 'protected' but I'm going with what Picasa seems to be using.
access_string_mappings = {'public': 'public',
'private': 'protected',
'protected': 'private',
'draft': 'private',
'hidden': 'private',
'link': 'private'}
try:
return access_string_mappings[access_string]
except KeyError:
import re
if access_string.find('link') != -1:
return 'private'
return default_value
class PhotoEntryToStringWrapper(googlecl.base.BaseEntryToStringWrapper):
caption = googlecl.base.BaseEntryToStringWrapper.summary
The distance to the subject.
@property
def distance(self):
return self.entry.exif.distance.text
Exposure value, if possible to calculate
@property
def ev(self):
try:
Using the equation for EV I found on Wikipedia...
N = float(self.fstop)
t = float(self.exposure)
import math # import math if fstop and exposure work
str() actually "rounds" floats. Try repr(3.3) and print 3.3
ev_long_str = str(math.log(math.pow(N,2)/t, 2))
dec_point = ev_long_str.find('.')
In the very rare case that there is no decimal point:
if dec_point == -1:
Technically this can return something like 10000, violating our desired precision. But not likely.
return ev_long_str
else:
return value to 1 decimal place
return ev_long_str[0:dec_point+2]
except Exception:
Don't really care what goes wrong -- result is the same.
return None
The exposure time used.
@property
def exposure(self):
return self.entry.exif.exposure.text
shutter = exposure
speed = exposure
Boolean value indicating whether the flash was used.
@property
def flash(self):
return self.entry.exif.flash.text
The focal length used.
@property
def focallength(self):
return self.entry.exif.focallength.text
The fstop value used.
@property
def fstop(self):
return self.entry.exif.fstop.text
The unique image ID for the photo.
@property
def imageUniqueID(self):
return self.entry.exif.imageUniqueID.text
id = imageUniqueID
The iso equivalent value used.
@property
def iso(self):
return self.entry.exif.iso.text
The make of the camera used.
@property
def make(self):
return self.entry.exif.make.text
The model of the camera used.
@property
def model(self):
return self.entry.exif.model.text
Tags / keywords or labels.
@property
def tags(self):
tags_text = self.entry.media.keywords.text
tags_text = tags_text.replace(', ', ',')
tags_list = tags_text.split(',')
return self.intra_property_delimiter.join(tags_list)
labels = tags
keywords = tags
The date/time the photo was taken.
@property
def time(self):
Represented as the number of milliseconds since January 1st, 1970.
Note: The value of this element should always be identical to the value of
the
return self.entry.exif.time.text
when = time
Overload from base.EntryToStringWrapper to use make_download_url URL to the original uploaded image, suitable for downloading from.
@property
def url_download(self):
return make_download_url(self.url_direct)
class AlbumEntryToStringWrapper(googlecl.base.BaseEntryToStringWrapper):
Access level of the album, one of "public", "private", or "unlisted".
@property
def access(self):
Convert values to ones the user selects on the web
txt = self.entry.access.text
if txt == 'protected':
return 'private'
if txt == 'private':
return 'anyone with link'
return txt
visibility = access
Location of the album (where pictures were taken).
@property
def location(self):
return self.entry.location.text
where = location
When the album was published/uploaded in local time.
@property
def published(self):
date = datetime.datetime.strptime(self.entry.published.text,
googlecl.calendar.date.QUERY_DATE_FORMAT)
date = date - googlecl.calendar.date.get_utc_timedelta()
return date.strftime('%Y-%m-%dT%H:%M:%S')
when = published
keyword-arguments: -client:-client-to-the-service-being-used. -options:-contains-all-attributes-required-to-perform-the-task -args:-additional-arguments-passed-in-on-the-command-line,-may-or-may-not-be -------required" href="each-of-the-following-run*-functions-execute-a-particular-task.
keyword-arguments: -client:-client-to-the-service-being-used. -options:-contains-all-attributes-required-to-perform-the-task -args:-additional-arguments-passed-in-on-the-command-line,-may-or-may-not-be -------required"> Each of the following run* functions execute a particular task.
Keyword arguments: client: Client to the service being used. options: Contains all attributes required to perform the task args: Additional arguments passed in on the command line, may or may not be required
def _run_create(client, options, args):
Paths to media might be in options.src, args, both, or neither. But both are guaranteed to be lists.
media_list = options.src + args
album = client.create_album(title=options.title, summary=options.summary,
access=options.access, date=options.date)
if media_list:
client.InsertMediaList(album, media_list=media_list,
tags=options.tags)
LOG.info('Created album: %s' % album.GetHtmlLink().href)
def _run_delete(client, options, args):
if options.query or options.photo:
entry_type = 'media'
search_string = options.query
else:
entry_type = 'album'
search_string = options.title
titles_list = googlecl.build_titles_list(options.title, args)
entries = client.build_entry_list(titles=titles_list,
query=options.query,
photo_title=options.photo)
if not entries:
LOG.info('No %ss matching %s' % (entry_type, search_string))
else:
client.DeleteEntryList(entries, entry_type, options.prompt)
def _run_list(client, options, args):
titles_list = googlecl.build_titles_list(options.title, args)
entries = client.build_entry_list(user=options.owner or options.user,
titles=titles_list,
query=options.query,
force_photos=True,
photo_title=options.photo)
for entry in entries:
print googlecl.base.compile_entry_string(PhotoEntryToStringWrapper(entry),
options.fields.split(','),
delimiter=options.delimiter)
def _run_list_albums(client, options, args):
titles_list = googlecl.build_titles_list(options.title, args)
entries = client.build_entry_list(user=options.owner or options.user,
titles=titles_list,
force_photos=False)
for entry in entries:
print googlecl.base.compile_entry_string(AlbumEntryToStringWrapper(entry),
options.fields.split(','),
delimiter=options.delimiter)
def _run_post(client, options, args):
media_list = options.src + args
if not media_list:
LOG.error('Must provide paths to media to post!')
album = client.GetSingleAlbum(user=options.owner or options.user,
title=options.title)
if album:
client.InsertMediaList(album, media_list, tags=options.tags,
user=options.owner or options.user,
photo_name=options.photo, caption=options.summary)
else:
LOG.error('No albums found that match ' + options.title)
def _run_get(client, options, args):
if not options.dest:
LOG.error('Must provide destination of album(s)!')
return
titles_list = googlecl.build_titles_list(options.title, args)
client.DownloadAlbum(options.dest,
user=options.owner or options.user,
video_format=options.format or 'mp4',
titles=titles_list,
photo_title=options.photo)
def _run_tag(client, options, args):
titles_list = googlecl.build_titles_list(options.title, args)
entries = client.build_entry_list(user=options.owner or options.user,
query=options.query,
titles=titles_list,
force_photos=True,
photo_title=options.photo)
if entries:
client.TagPhotos(entries, options.tags, options.summary)
else:
LOG.error('No matches for the title and/or query you gave.')
TASKS = {'create': googlecl.base.Task('Create an album',
callback=_run_create,
required='title',
optional=['src', 'date',
'summary', 'tags', 'access']),
'post': googlecl.base.Task('Post photos to an album',
callback=_run_post,
required=['title', 'src'],
optional=['tags', 'owner', 'photo',
'summary']),
'delete': googlecl.base.Task('Delete photos or albums',
callback=_run_delete,
required=[['title', 'query']],
optional='photo'),
'list': googlecl.base.Task('List photos', callback=_run_list,
required=['fields', 'delimiter'],
optional=['title', 'query',
'owner', 'photo']),
'list-albums': googlecl.base.Task('List albums',
callback=_run_list_albums,
required=['fields', 'delimiter'],
optional=['title', 'owner']),
'get': googlecl.base.Task('Download albums', callback=_run_get,
required=['title', 'dest'],
optional=['owner', 'format', 'photo']),
'tag': googlecl.base.Task('Tag/caption photos', callback=_run_tag,
required=[['title', 'query'],
['tags', 'summary']],
optional=['owner', 'photo'])}