こつこつと

2008/09/13

FlickrとPicasaへの画像アップロード

お仕事でいろんな写真サイトに手元の写真をアップロードするというのをやる予定です。
それに先駆けて、APIも整備されていて有名なところということで、FlickrとPicasaに挑戦してみた。

基本的に、作るのはDjangoのWebApplicationです。
WebApplicationの場合は、Flickr,Picasaとも、基本的に、ユーザからそれぞれのサイトの
ユーザ名とパスワードを預かるのは、さすがに、なんか気持ち悪いですよね、
というのに対処された認証になっているようです。
なんか世の中には、意外にその手のサンプルが少ないようなので、書いてみる。
まあ、ドキュメント読めば、普通に書いてるんだけど。

FlickrとPicasa(というか、Google)は、どちらもトークンというものを
ユーザ操作によって取得し、それを使ってユーザの画像を操作できます。
#Googleは、OAuthにも対応しているようですが、ライブラリが対応していないっぽいので、
#実績もあるしー、とかいう弱腰で、AuthSub方式を使いました。

とりあえず、どちらのサイトも、流れは以下になるようです。
・まず、各サイトへの認証URLを生成する
・そのURLへのリンクをクリックさせるか、リダイレクトする
・そのサイトで、このアプリから認証を要求されてるけど、承認するか?ページが表示される
・Cookieとかで認証済みなら、ボタン押すだけか、認証画面からログイン
 すると、自分のアプリサイト(事前に指定済み)のURLへリダイレクトされる
 そのとき、GETの引数として、トークンかそれに準ずるもの(flickrでは、frobというらしい)が渡される
・それを使って、永続的に利用できるトークンを取得する(昇格させる)
・それ以降は、それを使って、アップロード等の操作を行う

こっからは、実際のPythonででっちあげたサンプルコード(Djangoでサイト構築をした場合です。)
Flickrは、flickrpyを利用しました。
いまいち、これ以外に、トークンを使った認証にうまく対応しているライブラリを
見つけられませんでした。

とりあえず、FlickrでAPI Keyを取得します。
そのときに、認証後の戻り先のURL指定があるので、
http://xxx.xxx.xxx.xxx/flickr/gotFrob
というようなURLを指定しておきます。このURLでfrobを取得します。

とりあえず、viewだけ貼り付け。urlconfとかは適当に書く必要がもちろんあります。
あとは、Photoというモデルをつかってるけど、たいしたものでないので、省略。
とりあえず、やっつけこーど。さらに、!!!とか書いてるところはチョー適当。
ほんとは、生成したfrobをちゃんと覚えといて、ユーザとひも付けしないとだめだけど、
そこは適当に今は、渡されたfrobからtokenを取得して、ファイルに保存してるだけ。


from flickrapi import FlickrAPI

FLICKR_TOKEN = u'xxxx'
FLICKR_TOKEN_FILE_PATH = u'flickr_token.txt'
FLICKR_API_KEY = u'yyyy'
FLICKR_API_SECRET = u'zzzz'
FLICKR_PERMISSION = u'delete'

def get_flickr_api(token=None):
if token is None:
return FlickrAPI(FLICKR_API_KEY, FLICKR_API_SECRET, format='etree')
else:
return FlickrAPI(FLICKR_API_KEY, FLICKR_API_SECRET, token=token, format='etree')


def flickr_redirect_to_login(request):
flickr = get_flickr_api()
response = flickr.auth_getFrob()
frob = None
if response.attrib['stat'] == u'ok':
frob = response.findtext('frob')
else:
# !!!
return HttpResponse('Error')

return HttpResponseRedirect(flickr.auth_url(perms='delete', frob=frob))

def flickr_got_frob(request):
if request.method == 'GET':
frob = request.GET['frob']
flickr = get_flickr_api()
response = flickr.auth_getToken(frob=frob)
token = None
if response.attrib['stat'] == u'ok':
token = response.findtext('auth/token')
else:
# !!!
return HttpResponse('Error')

token_file = file(FLICKR_TOKEN_FILE_PATH, 'w')
token_file.write(token)
token_file.close()

return render_to_response('flickr/flickr_got_token.html',
dict(frob=frob, token=token))

def flickr_upload(request):
if request.method == 'GET':
photo_id = request.GET['photo_id']
photo = Photo.objects.all().filter(photo_id=photo_id).get()
token_file = file(FLICKR_TOKEN_FILE_PATH, 'r')
FLICKR_TOKEN = token_file.read()
token_file.close()
flickr = get_flickr_api(token=FLICKR_TOKEN)
response = flickr.upload(filename=photo.get_photo_url().encode('utf-8'), title=photo.caption)
flickr_photo_id = None
if response.attrib['stat'] == 'ok':
flickr_photo_id = response.findtext('photoid')
else:
# !!!
return HttpResponse('Error')

return render_to_response('flickr/flickr_upload.html',
dict(photo_id = photo_id, flickr_photo_id = flickr_photo_id))


流れは、
ユーザ操作によって、flickr_redirect_to_loginが呼び出されます。
すると、flickrの認証画面にとばされます。
んで、flickr_got_frobに帰ってきて、frobを使って、flickrからtokenを取得します。
んで、それを使って、flickr_uploadをつかって、アップロードする。

次は、Picasaの場合、同様にDjango前提。
gdata-python-clientで、AuthSubを利用します。
Picasaの場合は、事前にAPI等を取得するわけではなく、URL生成時に戻り先を指定する。
なんかアプリを事前に登録することで、
アプリ名とかがちゃんとGoogle側でも表示されるらしいので、やったほうがいいかも。
Picasaの場合は、frobのようなものはなく、1回限りのtokenという扱いで、それをsessionトークンに昇格させる。

ポイントは、これはバグだろーみたいな投稿もGoogle Codeであったけど、
Picasaの場合は、対アルバムにしかアップロードできないので、
InsertAlbumをとりあえずやってアルバムを作ってやろうとするけど、
こいつが対象のURLを生成する際に、self.emailを利用しようとする。
これは、ClientLogin()を使う場合は、email,passwordを指定しているから問題ないのだけど、
事前取得済みのsession tokenを使う場合、emailはわからない場合が普通。
そういうときは、Googleでは、'default'をURLとして指定すると、ユーザを勝手にGoogle側で決めてくれるようだ。
(これを調べるのに、ずいぶん手間取った。。。)
というわけで、本来なら、ライブラリ側のコード自体を、emailがNoneなら、defaultにするみたいするべきだけど、
ちょっとめんどくさかったので、emailに'default'をさくっと呼び出す前に代入した。
これで、とりあえず、うまくいくようにはなった。ただ、これはあまりにもやっつけすぎるかも。


import gdata.photos.service

PICASA_TOKEN = ''
PICASA_TOKEN_FILE_PATH = u'picasa_token.txt'

def picasa_redirect_to_login(request):
gd_client = gdata.photos.service.PhotosService()
auth_url = gd_client.GenerateAuthSubURL(next='http://xxx.xxx.xxx.xxx/picasa/got_token',
scope='http://picasaweb.google.com/data/',
secure=False, session=True)
return HttpResponseRedirect(auth_url)

def picasa_got_token(request):
if request.method == 'GET':
token = request.GET['token']
gd_client = gdata.photos.service.PhotosService()
gd_client.SetAuthSubToken(token)
gd_client.UpgradeToSessionToken()
token_file = file(PICASA_TOKEN_FILE_PATH, 'w')
token_file.write(gd_client.GetAuthSubToken())
token_file.close()
return HttpResponse('session token is %s' % (gd_client.GetAuthSubToken()))
else:
# !!!
return HttpResponse('Error')

def picasa_upload(request):
if request.method == 'GET':
photo_id = request.GET['photo_id']
import sys
photo = Photo.objects.all().filter(photo_id=photo_id).get()
gd_client = gdata.photos.service.PhotosService()
PICASA_TOKEN = file(PICASA_TOKEN_FILE_PATH, 'r').read()
gd_client.SetAuthSubToken(PICASA_TOKEN)
# bad techs
gd_client.email = 'default'
response = gd_client.InsertAlbum('test', 'test')
album_url = response.GetFeedLink().href
response = gd_client.InsertPhotoSimple(album_url, photo.caption, photo.comment, photo.get_photo_url())
return HttpResponse('picasa photo link is here' % response.GetSelfLink().href)
else:
# !!!
return HttpResponse('Error')


長くなったので、これでやめとこう。
とりあえず、こんな感じ。

0 件のコメント: