# -*- coding: utf-8 -*-
## PycasaWeb V0.4 (inspiration from "google-sharp")
##
## Copyright (C) 2006 manatlan manatlan[at]gmail(dot)com
##
##
## This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published
## by the Free Software Foundation; version 2 only.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
##
"""
Changelog
V0.4.2 (07-04-17):
- getAlbums() : change the get by NS to nodename (photo:id) (thankx to fabien)
V0.4.1 (07-02-11):
- change in connection (the "=" stuff) in "location"
V0.4 (06-08-26):
- connection works for recent google account now !
- some possible errors are intercepted
"""
import sys,os
import urllib,urllib2,urlparse,cookielib,mimetypes,mimetools
from datetime import datetime
from xml.dom.minidom import parseString
# init ...
cj = cookielib.CookieJar()
opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj))
urllib2.install_opener(opener)
#===============================================================================
# functions helpers ...
#===============================================================================
def getText(x):
""" get textual content of the node 'x' """
r=""
for i in x.childNodes:
if i.nodeType == x.TEXT_NODE:
r+=i.nodeValue
return r
def getResultFromXml(xml):
""" Test node result of the xml returned by google api
return content "ID node" if present, or True
or raise PycasaWebException if failure !
"""
try:
root=parseString(xml).documentElement
result=getText(root.getElementsByTagName("result")[0])
except Exception,m:
raise PycasaWebException( "xml resulted is bad :"+m )
if result=="success":
l=root.getElementsByTagName("id")
if len(l)>0:
return getText(l[0])
else:
return True
else:
reason=getText(root.getElementsByTagName("reason")[0])
raise PycasaWebException( utf8(reason) )
def utf8(v):
""" ensure to get 'v' in an UTF8 encoding (respect None) """
if v!=None:
if type(v)!=unicode:
v=unicode(v,"utf_8","replace")
v=v.encode("utf_8")
return v
def getUrlScheme(u):
""" explode url in (scheme,host,path)"""
scheme,host,path,nop,params,nop= urlparse.urlparse(u)
if params:
return scheme,host,path+"?"+params
else:
return scheme,host,path
def mkRequest(url,data=None,headers={}):
""" create a urlib2.Request """
if data:
data = urllib.urlencode(data)
return urllib2.Request(url,data,headers)
#-------------------------------------------------------------------------------
# http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/146306
#-------------------------------------------------------------------------------
def post_multipart(host, selector, fields, files):
"""
Post fields and files to an http host as multipart/form-data.
fields is a sequence of (name, value) elements for regular form fields.
files is a sequence of (name, filename, value) elements for data to be uploaded as files
Return the server's response page.
"""
content_type, body = encode_multipart_formdata(fields, files)
headers = {'Content-Type': content_type,
'Content-Length': str(len(body))}
r = urllib2.Request("http://%s%s" % (host, selector), body, headers)
return urllib2.urlopen(r).read()
def encode_multipart_formdata(fields, files):
"""
fields is a sequence of (name, value) elements for regular form fields.
files is a sequence of (name, filename, value) elements for data to be uploaded as files
Return (content_type, body) ready for httplib.HTTP instance
"""
mimetools._prefix = "some-random-string-you-like" # vincent patch : http://mail.python.org/pipermail/python-list/2006-December/420360.html
BOUNDARY = mimetools.choose_boundary()
CRLF = '\r\n'
L = []
for (key, value) in fields:
L.append('--' + BOUNDARY)
L.append('Content-Disposition: form-data; name="%s"' % key)
L.append('')
L.append(value)
for (key, filename, value) in files:
L.append('--' + BOUNDARY)
L.append('Content-Disposition: form-data; name="%s"; filename="%s"' % (key, filename))
L.append('Content-Type: %s' % get_content_type(filename))
L.append('')
L.append(value)
L.append('--' + BOUNDARY + '--')
L.append('')
body = CRLF.join(L)
content_type = 'multipart/form-data; boundary=%s' % BOUNDARY
return content_type, body
def get_content_type(filename):
return mimetypes.guess_type(filename)[0] or 'application/octet-stream'
#-------------------------------------------------------------------------------
#===============================================================================
# classes
#===============================================================================
class PycasaWebException(Exception):
pass
###############################################################################
class GoogleConnection(object):
###############################################################################
__URLAUTHENT = "https://www.google.com/accounts/ServiceLoginAuth?service=lh2&passive=true&continue=http%3A%2F%2Fpicasaweb.google.com%2F"
__URLLIST = "http://picasaweb.google.com/api/urls?version=1"
__user=None
user=property(lambda s: s.__user)
__urlGallery=None
__urlPost=None
urlGallery=property(lambda s: s.__urlGallery)
urlPost=property(lambda s: s.__urlPost)
def __init__(self,u,p):
u=utf8(u)
p=utf8(p)
self.__user = u
headers = {"Content-type": "application/x-www-form-urlencoded",}
data = { "null":"Sign in",
"Email":u,
"Passwd":p,
"service":"lh2",
"passive":"true",
"continue":"http://picasaweb.google.com/",
}
request = mkRequest(GoogleConnection.__URLAUTHENT,data,headers) # POST
response = opener.open(request)
buf=response.read()
try:
p1=buf.index('.replace("')+10
p2=buf.index('")',p1)
location=buf[p1:p2]
except:
raise PycasaWebException("unable to connect (bad account ?)")
if "SetSID?" in location: # it's a recent account
location= location.replace("\u003d","=")
elif "invitationRequired" in location:
raise PycasaWebException("Your picasaweb account is not open, you must log in on picasaweb")
# 11/02/2007 : add this line to make good "=" always
location = location.replace("\u003d","=")
#~ # Process the redirect to Valid authent
request = mkRequest(location)
response = opener.open(request)
buf=response.read() # garbage
# Get XML/RSS of "picasa urls"
try:
request = mkRequest(GoogleConnection.__URLLIST)
response = opener.open(request)
xml=response.read()
except urllib2.HTTPError:
raise PycasaWebException("Picasaweb is not activated, you must sign in on the web")
# Parse XML of "picasa urls" and store the needed ones
root=parseString(xml).documentElement
for i in (root.getElementsByTagName("channel")[0]).getElementsByTagName("item"):
title = getText( i.getElementsByTagName("title")[0] )
link = getText( i.getElementsByTagName("link")[0] )
if title == "gallery":
self.__urlGallery = link
if title == "post": # can be obtained only if authentified !
self.__urlPost = link
#~ print title,"=",link
assert self.__urlPost!=None, "can't get POST url"
###############################################################################
class PycasaWeb:
###############################################################################
def __init__(self, user,password):
""" Create a PycasaWeb instance for the account user/password """
#~ try:
#~ self.__gc=GoogleConnection(user,password)
#~ except Exception,m:
#~ raise PycasaWebException(m)
self.__gc=GoogleConnection(user,password)
def getAlbums(self):
""" Get a list of PicasaAlbum available on this account """
l=[]
request = mkRequest(self.__gc.urlGallery)
response = opener.open(request)
xml=response.read()
root=parseString(xml).documentElement
GPHOTO = "http://picasaweb.google.com/lh/picasaweb"
for i in root.getElementsByTagName("item"):
name = getText(i.getElementsByTagName("title")[0]) # title
id = getText(i.getElementsByTagName("gphoto:id")[0]) # gphoto:id
l.append( PicasaAlbum(self.__gc,id,name) )
return l
def createAlbum(self,name,description="",date=None, public=True):
""" Create an Album on picasaweb, return the PicasaAlbum instance """
name=utf8(name)
description=utf8(description)
if date==None:
date = datetime.now()
dat = date.strftime("%d MONTH %Y %H:%M:%S GMT") # TODO : GMT timezone
# replace MONTH by a english month (because in french : it makes an error)
dat=dat.replace("MONTH","jan,feb,mar,apr,may,jun,jui,aug,sep,oct,nov,dec".split(",")[date.month-1])
xml="""
%s
%s
%s
%s
%s
createAlbum
""" % (
name,
description,
dat,
public and "Public" or "Private",
self.__gc.user
)
content_type, body = encode_multipart_formdata([("xml",xml)], [])
headers = {'Content-Type': content_type,
'Content-Length': str(len(body)),
}
request = urllib2.Request(self.__gc.urlPost, body, headers)
response = opener.open(request)
xml=response.read()
id = getResultFromXml(xml) # obtain ID
return PicasaAlbum(self.__gc,id,name)
###############################################################################
class PicasaAlbum(object):
###############################################################################
__name=None
name=property(lambda s: s.__name)
__id=None
__gc=None
def __init__(self,gc,id,name):
""" should be only called by PycasaWeb """
self.__gc = gc
self.__name = name
self.__id = utf8(id)
def uploadPhoto(self,filename,description=""):
""" Upload a picture on this album, return the ID of the new picture """
filename=utf8(filename)
description=utf8(description)
if os.path.isfile(filename):
uid=abs(id(filename))
name = os.path.basename(filename)
xml="""
%s
%s
createAndAppendPhotoToAlbum
-
%s
%s
%d
0.000000
pycasaweb
""" % (self.__gc.user,self.__id,name,description,uid )
content_type, body = encode_multipart_formdata([("xml",xml)], [(uid,filename,open(filename,"rb").read() )])
headers = {'Content-Type': content_type,
'Content-Length': str(len(body)),
}
request = urllib2.Request(self.__gc.urlPost, body, headers)
response = opener.open(request)
xml=response.read()
return getResultFromXml(xml) # obtain ID
else:
raise PycasaWebException("File doesn't exists")
def deleteAlbum(self):
xml="""
%s
%s
deleteAlbum
""" % (self.__gc.user,self.__id )
content_type, body = encode_multipart_formdata([("xml",xml)], [])
headers = {'Content-Type': content_type,
'Content-Length': str(len(body)),
}
request = urllib2.Request(self.__gc.urlPost, body, headers)
response = opener.open(request)
xml=response.read()
return getResultFromXml(xml) # obtain true
def __repr__(self):
return "" % (self.__id,self.__name)
if __name__=="__main__":
picasa = PycasaWeb("manatlan@gmail.com","xxxxx")
p="/home/manatlan/Desktop/fotaux/p20060401_090621.jpg"
album=picasa.createAlbum("TEST")
album.uploadPhoto(p)
for a in picasa.getAlbums():
if a.name == "TEST":
a.uploadPhoto(p)
break
a.deleteAlbum()
print picasa.getAlbums()