faset over fra Z3950 til google books
This commit is contained in:
1112
python/gdata/photos/__init__.py
Normal file
1112
python/gdata/photos/__init__.py
Normal file
File diff suppressed because it is too large
Load Diff
680
python/gdata/photos/service.py
Normal file
680
python/gdata/photos/service.py
Normal file
@@ -0,0 +1,680 @@
|
||||
#!/usr/bin/env python
|
||||
# -*-*- encoding: utf-8 -*-*-
|
||||
#
|
||||
# This is the service file for the Google Photo python client.
|
||||
# It is used for higher level operations.
|
||||
#
|
||||
# $Id: service.py 144 2007-10-25 21:03:34Z havard.gulldahl $
|
||||
#
|
||||
# Copyright 2007 Håvard Gulldahl
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""Google PhotoService provides a human-friendly interface to
|
||||
Google Photo (a.k.a Picasa Web) services[1].
|
||||
|
||||
It extends gdata.service.GDataService and as such hides all the
|
||||
nasty details about authenticating, parsing and communicating with
|
||||
Google Photos.
|
||||
|
||||
[1]: http://code.google.com/apis/picasaweb/gdata.html
|
||||
|
||||
Example:
|
||||
import gdata.photos, gdata.photos.service
|
||||
pws = gdata.photos.service.PhotosService()
|
||||
pws.ClientLogin(username, password)
|
||||
#Get all albums
|
||||
albums = pws.GetUserFeed().entry
|
||||
# Get all photos in second album
|
||||
photos = pws.GetFeed(albums[1].GetPhotosUri()).entry
|
||||
# Get all tags for photos in second album and print them
|
||||
tags = pws.GetFeed(albums[1].GetTagsUri()).entry
|
||||
print [ tag.summary.text for tag in tags ]
|
||||
# Get all comments for the first photos in list and print them
|
||||
comments = pws.GetCommentFeed(photos[0].GetCommentsUri()).entry
|
||||
print [ c.summary.text for c in comments ]
|
||||
|
||||
# Get a photo to work with
|
||||
photo = photos[0]
|
||||
# Update metadata
|
||||
|
||||
# Attributes from the <gphoto:*> namespace
|
||||
photo.summary.text = u'A nice view from my veranda'
|
||||
photo.title.text = u'Verandaview.jpg'
|
||||
|
||||
# Attributes from the <media:*> namespace
|
||||
photo.media.keywords.text = u'Home, Long-exposure, Sunset' # Comma-separated
|
||||
|
||||
# Adding attributes to media object
|
||||
|
||||
# Rotate 90 degrees clockwise
|
||||
photo.rotation = gdata.photos.Rotation(text='90')
|
||||
|
||||
# Submit modified photo object
|
||||
photo = pws.UpdatePhotoMetadata(photo)
|
||||
|
||||
# Make sure you only modify the newly returned object, else you'll get
|
||||
# versioning errors. See Optimistic-concurrency
|
||||
|
||||
# Add comment to a picture
|
||||
comment = pws.InsertComment(photo, u'I wish the water always was this warm')
|
||||
|
||||
# Remove comment because it was silly
|
||||
print "*blush*"
|
||||
pws.Delete(comment.GetEditLink().href)
|
||||
|
||||
"""
|
||||
|
||||
__author__ = u'havard@gulldahl.no'# (Håvard Gulldahl)' #BUG: pydoc chokes on non-ascii chars in __author__
|
||||
__license__ = 'Apache License v2'
|
||||
__version__ = '$Revision: 176 $'[11:-2]
|
||||
|
||||
|
||||
import sys, os.path, StringIO
|
||||
import time
|
||||
import gdata.service
|
||||
import gdata
|
||||
import atom.service
|
||||
import atom
|
||||
import gdata.photos
|
||||
|
||||
SUPPORTED_UPLOAD_TYPES = ('bmp', 'jpeg', 'jpg', 'gif', 'png')
|
||||
|
||||
UNKOWN_ERROR=1000
|
||||
GPHOTOS_BAD_REQUEST=400
|
||||
GPHOTOS_CONFLICT=409
|
||||
GPHOTOS_INTERNAL_SERVER_ERROR=500
|
||||
GPHOTOS_INVALID_ARGUMENT=601
|
||||
GPHOTOS_INVALID_CONTENT_TYPE=602
|
||||
GPHOTOS_NOT_AN_IMAGE=603
|
||||
GPHOTOS_INVALID_KIND=604
|
||||
|
||||
class GooglePhotosException(Exception):
|
||||
def __init__(self, response):
|
||||
|
||||
self.error_code = response['status']
|
||||
self.reason = response['reason'].strip()
|
||||
if '<html>' in str(response['body']): #general html message, discard it
|
||||
response['body'] = ""
|
||||
self.body = response['body'].strip()
|
||||
self.message = "(%(status)s) %(body)s -- %(reason)s" % response
|
||||
|
||||
#return explicit error codes
|
||||
error_map = { '(12) Not an image':GPHOTOS_NOT_AN_IMAGE,
|
||||
'kind: That is not one of the acceptable values':
|
||||
GPHOTOS_INVALID_KIND,
|
||||
|
||||
}
|
||||
for msg, code in error_map.iteritems():
|
||||
if self.body == msg:
|
||||
self.error_code = code
|
||||
break
|
||||
self.args = [self.error_code, self.reason, self.body]
|
||||
|
||||
class PhotosService(gdata.service.GDataService):
|
||||
userUri = '/data/feed/api/user/%s'
|
||||
|
||||
def __init__(self, email=None, password=None, source=None,
|
||||
server='picasaweb.google.com', additional_headers=None,
|
||||
**kwargs):
|
||||
"""Creates a client for the Google Photos service.
|
||||
|
||||
Args:
|
||||
email: string (optional) The user's email address, used for
|
||||
authentication.
|
||||
password: string (optional) The user's password.
|
||||
source: string (optional) The name of the user's application.
|
||||
server: string (optional) The name of the server to which a connection
|
||||
will be opened. Default value: 'picasaweb.google.com'.
|
||||
**kwargs: The other parameters to pass to gdata.service.GDataService
|
||||
constructor.
|
||||
"""
|
||||
self.email = email
|
||||
self.client = source
|
||||
gdata.service.GDataService.__init__(
|
||||
self, email=email, password=password, service='lh2', source=source,
|
||||
server=server, additional_headers=additional_headers, **kwargs)
|
||||
|
||||
def GetFeed(self, uri, limit=None, start_index=None):
|
||||
"""Get a feed.
|
||||
|
||||
The results are ordered by the values of their `updated' elements,
|
||||
with the most recently updated entry appearing first in the feed.
|
||||
|
||||
Arguments:
|
||||
uri: the uri to fetch
|
||||
limit (optional): the maximum number of entries to return. Defaults to what
|
||||
the server returns.
|
||||
|
||||
Returns:
|
||||
one of gdata.photos.AlbumFeed,
|
||||
gdata.photos.UserFeed,
|
||||
gdata.photos.PhotoFeed,
|
||||
gdata.photos.CommentFeed,
|
||||
gdata.photos.TagFeed,
|
||||
depending on the results of the query.
|
||||
Raises:
|
||||
GooglePhotosException
|
||||
|
||||
See:
|
||||
http://code.google.com/apis/picasaweb/gdata.html#Get_Album_Feed_Manual
|
||||
"""
|
||||
if limit is not None:
|
||||
uri += '&max-results=%s' % limit
|
||||
if start_index is not None:
|
||||
uri += '&start-index=%s' % start_index
|
||||
try:
|
||||
return self.Get(uri, converter=gdata.photos.AnyFeedFromString)
|
||||
except gdata.service.RequestError, e:
|
||||
raise GooglePhotosException(e.args[0])
|
||||
|
||||
def GetEntry(self, uri, limit=None, start_index=None):
|
||||
"""Get an Entry.
|
||||
|
||||
Arguments:
|
||||
uri: the uri to the entry
|
||||
limit (optional): the maximum number of entries to return. Defaults to what
|
||||
the server returns.
|
||||
|
||||
Returns:
|
||||
one of gdata.photos.AlbumEntry,
|
||||
gdata.photos.UserEntry,
|
||||
gdata.photos.PhotoEntry,
|
||||
gdata.photos.CommentEntry,
|
||||
gdata.photos.TagEntry,
|
||||
depending on the results of the query.
|
||||
Raises:
|
||||
GooglePhotosException
|
||||
"""
|
||||
if limit is not None:
|
||||
uri += '&max-results=%s' % limit
|
||||
if start_index is not None:
|
||||
uri += '&start-index=%s' % start_index
|
||||
try:
|
||||
return self.Get(uri, converter=gdata.photos.AnyEntryFromString)
|
||||
except gdata.service.RequestError, e:
|
||||
raise GooglePhotosException(e.args[0])
|
||||
|
||||
def GetUserFeed(self, kind='album', user='default', limit=None):
|
||||
"""Get user-based feed, containing albums, photos, comments or tags;
|
||||
defaults to albums.
|
||||
|
||||
The entries are ordered by the values of their `updated' elements,
|
||||
with the most recently updated entry appearing first in the feed.
|
||||
|
||||
Arguments:
|
||||
kind: the kind of entries to get, either `album', `photo',
|
||||
`comment' or `tag', or a python list of these. Defaults to `album'.
|
||||
user (optional): whose albums we're querying. Defaults to current user.
|
||||
limit (optional): the maximum number of entries to return.
|
||||
Defaults to everything the server returns.
|
||||
|
||||
|
||||
Returns:
|
||||
gdata.photos.UserFeed, containing appropriate Entry elements
|
||||
|
||||
See:
|
||||
http://code.google.com/apis/picasaweb/gdata.html#Get_Album_Feed_Manual
|
||||
http://googledataapis.blogspot.com/2007/07/picasa-web-albums-adds-new-api-features.html
|
||||
"""
|
||||
if isinstance(kind, (list, tuple) ):
|
||||
kind = ",".join(kind)
|
||||
|
||||
uri = '/data/feed/api/user/%s?kind=%s' % (user, kind)
|
||||
return self.GetFeed(uri, limit=limit)
|
||||
|
||||
def GetTaggedPhotos(self, tag, user='default', limit=None):
|
||||
"""Get all photos belonging to a specific user, tagged by the given keyword
|
||||
|
||||
Arguments:
|
||||
tag: The tag you're looking for, e.g. `dog'
|
||||
user (optional): Whose images/videos you want to search, defaults
|
||||
to current user
|
||||
limit (optional): the maximum number of entries to return.
|
||||
Defaults to everything the server returns.
|
||||
|
||||
Returns:
|
||||
gdata.photos.UserFeed containing PhotoEntry elements
|
||||
"""
|
||||
# Lower-casing because of
|
||||
# http://code.google.com/p/gdata-issues/issues/detail?id=194
|
||||
uri = '/data/feed/api/user/%s?kind=photo&tag=%s' % (user, tag.lower())
|
||||
return self.GetFeed(uri, limit)
|
||||
|
||||
def SearchUserPhotos(self, query, user='default', limit=100):
|
||||
"""Search through all photos for a specific user and return a feed.
|
||||
This will look for matches in file names and image tags (a.k.a. keywords)
|
||||
|
||||
Arguments:
|
||||
query: The string you're looking for, e.g. `vacation'
|
||||
user (optional): The username of whose photos you want to search, defaults
|
||||
to current user.
|
||||
limit (optional): Don't return more than `limit' hits, defaults to 100
|
||||
|
||||
Only public photos are searched, unless you are authenticated and
|
||||
searching through your own photos.
|
||||
|
||||
Returns:
|
||||
gdata.photos.UserFeed with PhotoEntry elements
|
||||
"""
|
||||
uri = '/data/feed/api/user/%s?kind=photo&q=%s' % (user, query)
|
||||
return self.GetFeed(uri, limit=limit)
|
||||
|
||||
def SearchCommunityPhotos(self, query, limit=100):
|
||||
"""Search through all public photos and return a feed.
|
||||
This will look for matches in file names and image tags (a.k.a. keywords)
|
||||
|
||||
Arguments:
|
||||
query: The string you're looking for, e.g. `vacation'
|
||||
limit (optional): Don't return more than `limit' hits, defaults to 100
|
||||
|
||||
Returns:
|
||||
gdata.GDataFeed with PhotoEntry elements
|
||||
"""
|
||||
uri='/data/feed/api/all?q=%s' % query
|
||||
return self.GetFeed(uri, limit=limit)
|
||||
|
||||
def GetContacts(self, user='default', limit=None):
|
||||
"""Retrieve a feed that contains a list of your contacts
|
||||
|
||||
Arguments:
|
||||
user: Username of the user whose contacts you want
|
||||
|
||||
Returns
|
||||
gdata.photos.UserFeed, with UserEntry entries
|
||||
|
||||
See:
|
||||
http://groups.google.com/group/Google-Picasa-Data-API/msg/819b0025b5ff5e38
|
||||
"""
|
||||
uri = '/data/feed/api/user/%s/contacts?kind=user' % user
|
||||
return self.GetFeed(uri, limit=limit)
|
||||
|
||||
def SearchContactsPhotos(self, user='default', search=None, limit=None):
|
||||
"""Search over your contacts' photos and return a feed
|
||||
|
||||
Arguments:
|
||||
user: Username of the user whose contacts you want
|
||||
search (optional): What to search for (photo title, description and keywords)
|
||||
|
||||
Returns
|
||||
gdata.photos.UserFeed, with PhotoEntry elements
|
||||
|
||||
See:
|
||||
http://groups.google.com/group/Google-Picasa-Data-API/msg/819b0025b5ff5e38
|
||||
"""
|
||||
|
||||
uri = '/data/feed/api/user/%s/contacts?kind=photo&q=%s' % (user, search)
|
||||
return self.GetFeed(uri, limit=limit)
|
||||
|
||||
def InsertAlbum(self, title, summary, location=None, access='public',
|
||||
commenting_enabled='true', timestamp=None):
|
||||
"""Add an album.
|
||||
|
||||
Needs authentication, see self.ClientLogin()
|
||||
|
||||
Arguments:
|
||||
title: Album title
|
||||
summary: Album summary / description
|
||||
access (optional): `private' or `public'. Public albums are searchable
|
||||
by everyone on the internet. Defaults to `public'
|
||||
commenting_enabled (optional): `true' or `false'. Defaults to `true'.
|
||||
timestamp (optional): A date and time for the album, in milliseconds since
|
||||
Unix epoch[1] UTC. Defaults to now.
|
||||
|
||||
Returns:
|
||||
The newly created gdata.photos.AlbumEntry
|
||||
|
||||
See:
|
||||
http://code.google.com/apis/picasaweb/gdata.html#Add_Album_Manual_Installed
|
||||
|
||||
[1]: http://en.wikipedia.org/wiki/Unix_epoch
|
||||
"""
|
||||
album = gdata.photos.AlbumEntry()
|
||||
album.title = atom.Title(text=title, title_type='text')
|
||||
album.summary = atom.Summary(text=summary, summary_type='text')
|
||||
if location is not None:
|
||||
album.location = gdata.photos.Location(text=location)
|
||||
album.access = gdata.photos.Access(text=access)
|
||||
if commenting_enabled in ('true', 'false'):
|
||||
album.commentingEnabled = gdata.photos.CommentingEnabled(text=commenting_enabled)
|
||||
if timestamp is None:
|
||||
timestamp = '%i' % int(time.time() * 1000)
|
||||
album.timestamp = gdata.photos.Timestamp(text=timestamp)
|
||||
try:
|
||||
return self.Post(album, uri=self.userUri % self.email,
|
||||
converter=gdata.photos.AlbumEntryFromString)
|
||||
except gdata.service.RequestError, e:
|
||||
raise GooglePhotosException(e.args[0])
|
||||
|
||||
def InsertPhoto(self, album_or_uri, photo, filename_or_handle,
|
||||
content_type='image/jpeg'):
|
||||
"""Add a PhotoEntry
|
||||
|
||||
Needs authentication, see self.ClientLogin()
|
||||
|
||||
Arguments:
|
||||
album_or_uri: AlbumFeed or uri of the album where the photo should go
|
||||
photo: PhotoEntry to add
|
||||
filename_or_handle: A file-like object or file name where the image/video
|
||||
will be read from
|
||||
content_type (optional): Internet media type (a.k.a. mime type) of
|
||||
media object. Currently Google Photos supports these types:
|
||||
o image/bmp
|
||||
o image/gif
|
||||
o image/jpeg
|
||||
o image/png
|
||||
|
||||
Images will be converted to jpeg on upload. Defaults to `image/jpeg'
|
||||
|
||||
"""
|
||||
|
||||
try:
|
||||
assert(isinstance(photo, gdata.photos.PhotoEntry))
|
||||
except AssertionError:
|
||||
raise GooglePhotosException({'status':GPHOTOS_INVALID_ARGUMENT,
|
||||
'body':'`photo` must be a gdata.photos.PhotoEntry instance',
|
||||
'reason':'Found %s, not PhotoEntry' % type(photo)
|
||||
})
|
||||
try:
|
||||
majtype, mintype = content_type.split('/')
|
||||
assert(mintype in SUPPORTED_UPLOAD_TYPES)
|
||||
except (ValueError, AssertionError):
|
||||
raise GooglePhotosException({'status':GPHOTOS_INVALID_CONTENT_TYPE,
|
||||
'body':'This is not a valid content type: %s' % content_type,
|
||||
'reason':'Accepted content types: %s' % \
|
||||
['image/'+t for t in SUPPORTED_UPLOAD_TYPES]
|
||||
})
|
||||
if isinstance(filename_or_handle, (str, unicode)) and \
|
||||
os.path.exists(filename_or_handle): # it's a file name
|
||||
mediasource = gdata.MediaSource()
|
||||
mediasource.setFile(filename_or_handle, content_type)
|
||||
elif hasattr(filename_or_handle, 'read'):# it's a file-like resource
|
||||
if hasattr(filename_or_handle, 'seek'):
|
||||
filename_or_handle.seek(0) # rewind pointer to the start of the file
|
||||
# gdata.MediaSource needs the content length, so read the whole image
|
||||
file_handle = StringIO.StringIO(filename_or_handle.read())
|
||||
name = 'image'
|
||||
if hasattr(filename_or_handle, 'name'):
|
||||
name = filename_or_handle.name
|
||||
mediasource = gdata.MediaSource(file_handle, content_type,
|
||||
content_length=file_handle.len, file_name=name)
|
||||
else: #filename_or_handle is not valid
|
||||
raise GooglePhotosException({'status':GPHOTOS_INVALID_ARGUMENT,
|
||||
'body':'`filename_or_handle` must be a path name or a file-like object',
|
||||
'reason':'Found %s, not path name or object with a .read() method' % \
|
||||
type(filename_or_handle)
|
||||
})
|
||||
|
||||
if isinstance(album_or_uri, (str, unicode)): # it's a uri
|
||||
feed_uri = album_or_uri
|
||||
elif hasattr(album_or_uri, 'GetFeedLink'): # it's a AlbumFeed object
|
||||
feed_uri = album_or_uri.GetFeedLink().href
|
||||
|
||||
try:
|
||||
return self.Post(photo, uri=feed_uri, media_source=mediasource,
|
||||
converter=gdata.photos.PhotoEntryFromString)
|
||||
except gdata.service.RequestError, e:
|
||||
raise GooglePhotosException(e.args[0])
|
||||
|
||||
def InsertPhotoSimple(self, album_or_uri, title, summary, filename_or_handle,
|
||||
content_type='image/jpeg', keywords=None):
|
||||
"""Add a photo without constructing a PhotoEntry.
|
||||
|
||||
Needs authentication, see self.ClientLogin()
|
||||
|
||||
Arguments:
|
||||
album_or_uri: AlbumFeed or uri of the album where the photo should go
|
||||
title: Photo title
|
||||
summary: Photo summary / description
|
||||
filename_or_handle: A file-like object or file name where the image/video
|
||||
will be read from
|
||||
content_type (optional): Internet media type (a.k.a. mime type) of
|
||||
media object. Currently Google Photos supports these types:
|
||||
o image/bmp
|
||||
o image/gif
|
||||
o image/jpeg
|
||||
o image/png
|
||||
|
||||
Images will be converted to jpeg on upload. Defaults to `image/jpeg'
|
||||
keywords (optional): a 1) comma separated string or 2) a python list() of
|
||||
keywords (a.k.a. tags) to add to the image.
|
||||
E.g. 1) `dog, vacation, happy' 2) ['dog', 'happy', 'vacation']
|
||||
|
||||
Returns:
|
||||
The newly created gdata.photos.PhotoEntry or GooglePhotosException on errors
|
||||
|
||||
See:
|
||||
http://code.google.com/apis/picasaweb/gdata.html#Add_Album_Manual_Installed
|
||||
[1]: http://en.wikipedia.org/wiki/Unix_epoch
|
||||
"""
|
||||
|
||||
metadata = gdata.photos.PhotoEntry()
|
||||
metadata.title=atom.Title(text=title)
|
||||
metadata.summary = atom.Summary(text=summary, summary_type='text')
|
||||
if keywords is not None:
|
||||
if isinstance(keywords, list):
|
||||
keywords = ','.join(keywords)
|
||||
metadata.media.keywords = gdata.media.Keywords(text=keywords)
|
||||
return self.InsertPhoto(album_or_uri, metadata, filename_or_handle,
|
||||
content_type)
|
||||
|
||||
def UpdatePhotoMetadata(self, photo):
|
||||
"""Update a photo's metadata.
|
||||
|
||||
Needs authentication, see self.ClientLogin()
|
||||
|
||||
You can update any or all of the following metadata properties:
|
||||
* <title>
|
||||
* <media:description>
|
||||
* <gphoto:checksum>
|
||||
* <gphoto:client>
|
||||
* <gphoto:rotation>
|
||||
* <gphoto:timestamp>
|
||||
* <gphoto:commentingEnabled>
|
||||
|
||||
Arguments:
|
||||
photo: a gdata.photos.PhotoEntry object with updated elements
|
||||
|
||||
Returns:
|
||||
The modified gdata.photos.PhotoEntry
|
||||
|
||||
Example:
|
||||
p = GetFeed(uri).entry[0]
|
||||
p.title.text = u'My new text'
|
||||
p.commentingEnabled.text = 'false'
|
||||
p = UpdatePhotoMetadata(p)
|
||||
|
||||
It is important that you don't keep the old object around, once
|
||||
it has been updated. See
|
||||
http://code.google.com/apis/gdata/reference.html#Optimistic-concurrency
|
||||
"""
|
||||
try:
|
||||
return self.Put(data=photo, uri=photo.GetEditLink().href,
|
||||
converter=gdata.photos.PhotoEntryFromString)
|
||||
except gdata.service.RequestError, e:
|
||||
raise GooglePhotosException(e.args[0])
|
||||
|
||||
|
||||
def UpdatePhotoBlob(self, photo_or_uri, filename_or_handle,
|
||||
content_type = 'image/jpeg'):
|
||||
"""Update a photo's binary data.
|
||||
|
||||
Needs authentication, see self.ClientLogin()
|
||||
|
||||
Arguments:
|
||||
photo_or_uri: a gdata.photos.PhotoEntry that will be updated, or a
|
||||
`edit-media' uri pointing to it
|
||||
filename_or_handle: A file-like object or file name where the image/video
|
||||
will be read from
|
||||
content_type (optional): Internet media type (a.k.a. mime type) of
|
||||
media object. Currently Google Photos supports these types:
|
||||
o image/bmp
|
||||
o image/gif
|
||||
o image/jpeg
|
||||
o image/png
|
||||
Images will be converted to jpeg on upload. Defaults to `image/jpeg'
|
||||
|
||||
Returns:
|
||||
The modified gdata.photos.PhotoEntry
|
||||
|
||||
Example:
|
||||
p = GetFeed(PhotoUri)
|
||||
p = UpdatePhotoBlob(p, '/tmp/newPic.jpg')
|
||||
|
||||
It is important that you don't keep the old object around, once
|
||||
it has been updated. See
|
||||
http://code.google.com/apis/gdata/reference.html#Optimistic-concurrency
|
||||
"""
|
||||
|
||||
try:
|
||||
majtype, mintype = content_type.split('/')
|
||||
assert(mintype in SUPPORTED_UPLOAD_TYPES)
|
||||
except (ValueError, AssertionError):
|
||||
raise GooglePhotosException({'status':GPHOTOS_INVALID_CONTENT_TYPE,
|
||||
'body':'This is not a valid content type: %s' % content_type,
|
||||
'reason':'Accepted content types: %s' % \
|
||||
['image/'+t for t in SUPPORTED_UPLOAD_TYPES]
|
||||
})
|
||||
|
||||
if isinstance(filename_or_handle, (str, unicode)) and \
|
||||
os.path.exists(filename_or_handle): # it's a file name
|
||||
photoblob = gdata.MediaSource()
|
||||
photoblob.setFile(filename_or_handle, content_type)
|
||||
elif hasattr(filename_or_handle, 'read'):# it's a file-like resource
|
||||
if hasattr(filename_or_handle, 'seek'):
|
||||
filename_or_handle.seek(0) # rewind pointer to the start of the file
|
||||
# gdata.MediaSource needs the content length, so read the whole image
|
||||
file_handle = StringIO.StringIO(filename_or_handle.read())
|
||||
name = 'image'
|
||||
if hasattr(filename_or_handle, 'name'):
|
||||
name = filename_or_handle.name
|
||||
mediasource = gdata.MediaSource(file_handle, content_type,
|
||||
content_length=file_handle.len, file_name=name)
|
||||
else: #filename_or_handle is not valid
|
||||
raise GooglePhotosException({'status':GPHOTOS_INVALID_ARGUMENT,
|
||||
'body':'`filename_or_handle` must be a path name or a file-like object',
|
||||
'reason':'Found %s, not path name or an object with .read() method' % \
|
||||
type(filename_or_handle)
|
||||
})
|
||||
|
||||
if isinstance(photo_or_uri, (str, unicode)):
|
||||
entry_uri = photo_or_uri # it's a uri
|
||||
elif hasattr(photo_or_uri, 'GetEditMediaLink'):
|
||||
entry_uri = photo_or_uri.GetEditMediaLink().href
|
||||
try:
|
||||
return self.Put(photoblob, entry_uri,
|
||||
converter=gdata.photos.PhotoEntryFromString)
|
||||
except gdata.service.RequestError, e:
|
||||
raise GooglePhotosException(e.args[0])
|
||||
|
||||
def InsertTag(self, photo_or_uri, tag):
|
||||
"""Add a tag (a.k.a. keyword) to a photo.
|
||||
|
||||
Needs authentication, see self.ClientLogin()
|
||||
|
||||
Arguments:
|
||||
photo_or_uri: a gdata.photos.PhotoEntry that will be tagged, or a
|
||||
`post' uri pointing to it
|
||||
(string) tag: The tag/keyword
|
||||
|
||||
Returns:
|
||||
The new gdata.photos.TagEntry
|
||||
|
||||
Example:
|
||||
p = GetFeed(PhotoUri)
|
||||
tag = InsertTag(p, 'Beautiful sunsets')
|
||||
|
||||
"""
|
||||
tag = gdata.photos.TagEntry(title=atom.Title(text=tag))
|
||||
if isinstance(photo_or_uri, (str, unicode)):
|
||||
post_uri = photo_or_uri # it's a uri
|
||||
elif hasattr(photo_or_uri, 'GetEditMediaLink'):
|
||||
post_uri = photo_or_uri.GetPostLink().href
|
||||
try:
|
||||
return self.Post(data=tag, uri=post_uri,
|
||||
converter=gdata.photos.TagEntryFromString)
|
||||
except gdata.service.RequestError, e:
|
||||
raise GooglePhotosException(e.args[0])
|
||||
|
||||
|
||||
def InsertComment(self, photo_or_uri, comment):
|
||||
"""Add a comment to a photo.
|
||||
|
||||
Needs authentication, see self.ClientLogin()
|
||||
|
||||
Arguments:
|
||||
photo_or_uri: a gdata.photos.PhotoEntry that is about to be commented
|
||||
, or a `post' uri pointing to it
|
||||
(string) comment: The actual comment
|
||||
|
||||
Returns:
|
||||
The new gdata.photos.CommentEntry
|
||||
|
||||
Example:
|
||||
p = GetFeed(PhotoUri)
|
||||
tag = InsertComment(p, 'OOOH! I would have loved to be there.
|
||||
Who's that in the back?')
|
||||
|
||||
"""
|
||||
comment = gdata.photos.CommentEntry(content=atom.Content(text=comment))
|
||||
if isinstance(photo_or_uri, (str, unicode)):
|
||||
post_uri = photo_or_uri # it's a uri
|
||||
elif hasattr(photo_or_uri, 'GetEditMediaLink'):
|
||||
post_uri = photo_or_uri.GetPostLink().href
|
||||
try:
|
||||
return self.Post(data=comment, uri=post_uri,
|
||||
converter=gdata.photos.CommentEntryFromString)
|
||||
except gdata.service.RequestError, e:
|
||||
raise GooglePhotosException(e.args[0])
|
||||
|
||||
def Delete(self, object_or_uri, *args, **kwargs):
|
||||
"""Delete an object.
|
||||
|
||||
Re-implementing the GDataService.Delete method, to add some
|
||||
convenience.
|
||||
|
||||
Arguments:
|
||||
object_or_uri: Any object that has a GetEditLink() method that
|
||||
returns a link, or a uri to that object.
|
||||
|
||||
Returns:
|
||||
? or GooglePhotosException on errors
|
||||
"""
|
||||
try:
|
||||
uri = object_or_uri.GetEditLink().href
|
||||
except AttributeError:
|
||||
uri = object_or_uri
|
||||
try:
|
||||
return gdata.service.GDataService.Delete(self, uri, *args, **kwargs)
|
||||
except gdata.service.RequestError, e:
|
||||
raise GooglePhotosException(e.args[0])
|
||||
|
||||
def GetSmallestThumbnail(media_thumbnail_list):
|
||||
"""Helper function to get the smallest thumbnail of a list of
|
||||
gdata.media.Thumbnail.
|
||||
Returns gdata.media.Thumbnail """
|
||||
r = {}
|
||||
for thumb in media_thumbnail_list:
|
||||
r[int(thumb.width)*int(thumb.height)] = thumb
|
||||
keys = r.keys()
|
||||
keys.sort()
|
||||
return r[keys[0]]
|
||||
|
||||
def ConvertAtomTimestampToEpoch(timestamp):
|
||||
"""Helper function to convert a timestamp string, for instance
|
||||
from atom:updated or atom:published, to milliseconds since Unix epoch
|
||||
(a.k.a. POSIX time).
|
||||
|
||||
`2007-07-22T00:45:10.000Z' -> """
|
||||
return time.mktime(time.strptime(timestamp, '%Y-%m-%dT%H:%M:%S.000Z'))
|
||||
## TODO: Timezone aware
|
||||
Reference in New Issue
Block a user