PageRenderTime 538ms CodeModel.GetById 131ms app.highlight 148ms RepoModel.GetById 144ms app.codeStats 111ms

/src/googlecl/picasa/__init__.py

http://googlecl.googlecode.com/
Python | 315 lines | 249 code | 24 blank | 42 comment | 4 complexity | 0bfdbf07fed889977deabf3653d47083 MD5 | raw file
  1# Copyright (C) 2010 Google Inc.
  2#
  3# Licensed under the Apache License, Version 2.0 (the "License");
  4# you may not use this file except in compliance with the License.
  5# You may obtain a copy of the License at
  6#
  7#      http://www.apache.org/licenses/LICENSE-2.0
  8#
  9# Unless required by applicable law or agreed to in writing, software
 10# distributed under the License is distributed on an "AS IS" BASIS,
 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 12# See the License for the specific language governing permissions and
 13# limitations under the License.
 14import datetime
 15import googlecl
 16import googlecl.base
 17import logging
 18
 19
 20service_name = __name__.split('.')[-1]
 21LOGGER_NAME = __name__
 22SECTION_HEADER = service_name.upper()
 23
 24LOG = logging.getLogger(LOGGER_NAME)
 25
 26
 27def make_download_url(url):
 28  """Makes the given URL for a picasa image point to the download."""
 29  return url[:url.rfind('/')+1]+'d'+url[url.rfind('/'):]
 30
 31
 32def _map_access_string(access_string, default_value='private'):
 33  if not access_string:
 34    return default_value
 35  # It seems to me that 'private' is less private than 'protected'
 36  # but I'm going with what Picasa seems to be using.
 37  access_string_mappings = {'public': 'public',
 38                            'private': 'protected',
 39                            'protected': 'private',
 40                            'draft': 'private',
 41                            'hidden': 'private',
 42                            'link': 'private'}
 43  try:
 44    return access_string_mappings[access_string]
 45  except KeyError:
 46    import re
 47    if access_string.find('link') != -1:
 48      return 'private'
 49  return default_value
 50
 51
 52class PhotoEntryToStringWrapper(googlecl.base.BaseEntryToStringWrapper):
 53  caption = googlecl.base.BaseEntryToStringWrapper.summary
 54
 55  @property
 56  def distance(self):
 57    """The distance to the subject."""
 58    return self.entry.exif.distance.text
 59
 60  @property
 61  def ev(self):
 62    """Exposure value, if possible to calculate"""
 63    try:
 64      # Using the equation for EV I found on Wikipedia...
 65      N = float(self.fstop)
 66      t = float(self.exposure)
 67      import math       # import math if fstop and exposure work
 68      # str() actually "rounds" floats. Try repr(3.3) and print 3.3
 69      ev_long_str = str(math.log(math.pow(N,2)/t, 2))
 70      dec_point = ev_long_str.find('.')
 71      # In the very rare case that there is no decimal point:
 72      if dec_point == -1:
 73        # Technically this can return something like 10000, violating
 74        # our desired precision. But not likely.
 75        return ev_long_str
 76      else:
 77        # return value to 1 decimal place
 78        return ev_long_str[0:dec_point+2]
 79    except Exception:
 80      # Don't really care what goes wrong -- result is the same.
 81      return None
 82
 83  @property
 84  def exposure(self):
 85    """The exposure time used."""
 86    return self.entry.exif.exposure.text
 87  shutter = exposure
 88  speed = exposure
 89
 90  @property
 91  def flash(self):
 92    """Boolean value indicating whether the flash was used."""
 93    return self.entry.exif.flash.text
 94
 95  @property
 96  def focallength(self):
 97    """The focal length used."""
 98    return self.entry.exif.focallength.text
 99
100  @property
101  def fstop(self):
102    """The fstop value used."""
103    return self.entry.exif.fstop.text
104
105  @property
106  def imageUniqueID(self):
107    """The unique image ID for the photo."""
108    return self.entry.exif.imageUniqueID.text
109  id = imageUniqueID
110
111  @property
112  def iso(self):
113    """The iso equivalent value used."""
114    return self.entry.exif.iso.text
115
116  @property
117  def make(self):
118    """The make of the camera used."""
119    return self.entry.exif.make.text
120
121  @property
122  def model(self):
123    """The model of the camera used."""
124    return self.entry.exif.model.text
125
126  @property
127  def tags(self):
128    """Tags / keywords or labels."""
129    tags_text = self.entry.media.keywords.text
130    tags_text = tags_text.replace(', ', ',')
131    tags_list = tags_text.split(',')
132    return self.intra_property_delimiter.join(tags_list)
133  labels = tags
134  keywords = tags
135
136  @property
137  def time(self):
138    """The date/time the photo was taken.
139
140    Represented as the number of milliseconds since January 1st, 1970.
141    Note: The value of this element should always be identical to the value of
142    the <gphoto:timestamp>.
143    """
144    return self.entry.exif.time.text
145  when = time
146
147  # Overload from base.EntryToStringWrapper to use make_download_url
148  @property
149  def url_download(self):
150    """URL to the original uploaded image, suitable for downloading from."""
151    return make_download_url(self.url_direct)
152
153
154class AlbumEntryToStringWrapper(googlecl.base.BaseEntryToStringWrapper):
155  @property
156  def access(self):
157    """Access level of the album, one of "public", "private", or "unlisted"."""
158    # Convert values to ones the user selects on the web
159    txt = self.entry.access.text
160    if txt == 'protected':
161      return 'private'
162    if txt == 'private':
163      return 'anyone with link'
164    return txt
165  visibility = access
166
167  @property
168  def location(self):
169    """Location of the album (where pictures were taken)."""
170    return self.entry.location.text
171  where = location
172
173  @property
174  def published(self):
175    """When the album was published/uploaded in local time."""
176    date = datetime.datetime.strptime(self.entry.published.text,
177                                      googlecl.calendar.date.QUERY_DATE_FORMAT)
178    date = date - googlecl.calendar.date.get_utc_timedelta()
179    return date.strftime('%Y-%m-%dT%H:%M:%S')
180  when = published
181
182
183#===============================================================================
184# Each of the following _run_* functions execute a particular task.
185#
186# Keyword arguments:
187#  client: Client to the service being used.
188#  options: Contains all attributes required to perform the task
189#  args: Additional arguments passed in on the command line, may or may not be
190#        required
191#===============================================================================
192def _run_create(client, options, args):
193  # Paths to media might be in options.src, args, both, or neither.
194  # But both are guaranteed to be lists.
195  media_list = options.src + args
196
197  album = client.create_album(title=options.title, summary=options.summary,
198                              access=options.access, date=options.date)
199  if media_list:
200    client.InsertMediaList(album, media_list=media_list,
201                           tags=options.tags)
202  LOG.info('Created album: %s' % album.GetHtmlLink().href)
203
204
205def _run_delete(client, options, args):
206  if options.query or options.photo:
207    entry_type = 'media'
208    search_string = options.query
209  else:
210    entry_type = 'album'
211    search_string = options.title
212
213  titles_list = googlecl.build_titles_list(options.title, args)
214  entries = client.build_entry_list(titles=titles_list,
215                                    query=options.query,
216                                    photo_title=options.photo)
217  if not entries:
218    LOG.info('No %ss matching %s' % (entry_type, search_string))
219  else:
220    client.DeleteEntryList(entries, entry_type, options.prompt)
221
222
223def _run_list(client, options, args):
224  titles_list = googlecl.build_titles_list(options.title, args)
225  entries = client.build_entry_list(user=options.owner or options.user,
226                                    titles=titles_list,
227                                    query=options.query,
228                                    force_photos=True,
229                                    photo_title=options.photo)
230  for entry in entries:
231    print googlecl.base.compile_entry_string(PhotoEntryToStringWrapper(entry),
232                                             options.fields.split(','),
233                                             delimiter=options.delimiter)
234
235
236def _run_list_albums(client, options, args):
237  titles_list = googlecl.build_titles_list(options.title, args)
238  entries = client.build_entry_list(user=options.owner or options.user,
239                                    titles=titles_list,
240                                    force_photos=False)
241  for entry in entries:
242    print googlecl.base.compile_entry_string(AlbumEntryToStringWrapper(entry),
243                                             options.fields.split(','),
244                                             delimiter=options.delimiter)
245
246
247def _run_post(client, options, args):
248  media_list = options.src + args
249  if not media_list:
250    LOG.error('Must provide paths to media to post!')
251  album = client.GetSingleAlbum(user=options.owner or options.user,
252                                title=options.title)
253  if album:
254    client.InsertMediaList(album, media_list, tags=options.tags,
255                           user=options.owner or options.user,
256                           photo_name=options.photo, caption=options.summary)
257  else:
258    LOG.error('No albums found that match ' + options.title)
259
260
261def _run_get(client, options, args):
262  if not options.dest:
263    LOG.error('Must provide destination of album(s)!')
264    return
265
266  titles_list = googlecl.build_titles_list(options.title, args)
267  client.DownloadAlbum(options.dest,
268                       user=options.owner or options.user,
269                       video_format=options.format or 'mp4',
270                       titles=titles_list,
271                       photo_title=options.photo)
272
273
274def _run_tag(client, options, args):
275  titles_list = googlecl.build_titles_list(options.title, args)
276  entries = client.build_entry_list(user=options.owner or options.user,
277                                    query=options.query,
278                                    titles=titles_list,
279                                    force_photos=True,
280                                    photo_title=options.photo)
281  if entries:
282    client.TagPhotos(entries, options.tags, options.summary)
283  else:
284    LOG.error('No matches for the title and/or query you gave.')
285
286
287TASKS = {'create': googlecl.base.Task('Create an album',
288                                      callback=_run_create,
289                                      required='title',
290                                      optional=['src', 'date',
291                                                'summary', 'tags', 'access']),
292         'post': googlecl.base.Task('Post photos to an album',
293                                    callback=_run_post,
294                                    required=['title', 'src'],
295                                    optional=['tags', 'owner', 'photo',
296                                              'summary']),
297         'delete': googlecl.base.Task('Delete photos or albums',
298                                      callback=_run_delete,
299                                      required=[['title', 'query']],
300                                      optional='photo'),
301         'list': googlecl.base.Task('List photos', callback=_run_list,
302                                    required=['fields', 'delimiter'],
303                                    optional=['title', 'query',
304                                              'owner', 'photo']),
305         'list-albums': googlecl.base.Task('List albums',
306                                           callback=_run_list_albums,
307                                           required=['fields', 'delimiter'],
308                                           optional=['title', 'owner']),
309         'get': googlecl.base.Task('Download albums', callback=_run_get,
310                                   required=['title', 'dest'],
311                                   optional=['owner', 'format', 'photo']),
312         'tag': googlecl.base.Task('Tag/caption photos', callback=_run_tag,
313                                   required=[['title', 'query'],
314                                             ['tags', 'summary']],
315                                   optional=['owner', 'photo'])}