PageRenderTime 151ms CodeModel.GetById 29ms RepoModel.GetById 1ms app.codeStats 0ms

/src/googlecl/picasa/__init__.py

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