Google App Engineで画像アップローダ
2008/09/24 13:23:38
YaneuraLabsさんのブログ記事「GAEのアップローダを作ってみた」はファイルアップロードをどうやったらよいかで大変参考になったのだが、画像だけにしぼった時に、
『画像の横幅、縦幅をどうやったら取得できるのか?』
のやり方が分からなかったので画像アップローダを作ってみた。
以下詳細。
なぜそんなことがやりたかったかというと、
・アップされた画像が規定の横幅サイズより大きかったらそのサイズにリサイズ
また、
・大きな画像アップロード時に横幅サイズを指定してそのサイズにリサイズ
というようなことをしたい時にアップした画像の横幅を取得する必要があったのです。
GAEに触るまではPythonなんてやったことがないので慣習とか右も左もわからず、PHPでは簡単な画像周りの処理方法がま~ったくわからず、GAEのImage APIはリサイズや諸々はできても画像情報の取得機能なんてのは備わっておらず、調べてもなかなかそのような情報に出会わず(馴染みのない言語はキーワードの単語も思いつかんorz)四苦八苦して。。。
で、結局以下のようになりました。
「getimageinfo.py」はGoogle Codeにアップされているので恐らく使用しても問題ないと思いますが、
使う際には自己判断にてお願いしますm(_ _)m
(一応「MIT License」となっているみたい)
app.yml
説明省きます。
アプリケーション名は「photo-upload」で、
どんなアクセスでも「app.py」が実行されると設定。
getimageinfo.py
以下のリンク先を参照してください。
http://code.google.com/p/plsamples/source/browse/trunk/GrandMonde/getimageinfo.py
app.py
テンプレートは使用していませんので、「app.py」いちファイルで完結しています。
HTML画面としては3つの画面しかありません。
1.画像参照+アップロードフォーム画面
2.アップロード完了確認画面
3.アップロード画像一覧画面
img.headers['content-type'] = MIMEタイプ
が取得できる →参照
「getimageinfo.py」ファイルの「getImageInfo」関数にそのバイナリをつっこむと、
・MIMEタイプ
・画像の横幅サイズ
・画像の縦幅サイズ
が取得できる。
つまるところたったいま保存した「key()」を知りたいということなのだが、「put()」した後に「key()」ですぐに取得できるみたい。
どの時点で発行しているのかは確認していないので、ひょっとしたら「photo_upload = Photo_Upload()」の時点でかもしれない。
(※10/27追記:データストアにエンティティが保存された時エンティティは個々にユニークなキーを持つらしい。なので「photo_upload.put」の時keyは発行されているみたい)
「math」を使うには「import math」をお忘れなく。
画像の出力は表示の度に動的に書き出しています。
PHPなんかでも同じことできますが、どう考えても負荷がかかるので何か特別な理由でもない限りこんなことしませんが、
GAEではファイルシステムという仕組みではないらしいので画像をアップして所定のフォルダに格納という普通なことができません。
なのでこのような形になるのですが、これはGAEにおいては普通なのかな?負荷はかからんのかなぁ~?
・アップされた画像が規定の横幅サイズより大きかったらそのサイズにリサイズ
また、
・大きな画像アップロード時に横幅サイズを指定してそのサイズにリサイズ
というようなことをしたい時にアップした画像の横幅を取得する必要があったのです。
GAEに触るまではPythonなんてやったことがないので慣習とか右も左もわからず、PHPでは簡単な画像周りの処理方法がま~ったくわからず、GAEのImage APIはリサイズや諸々はできても画像情報の取得機能なんてのは備わっておらず、調べてもなかなかそのような情報に出会わず(馴染みのない言語はキーワードの単語も思いつかんorz)四苦八苦して。。。
で、結局以下のようになりました。
「getimageinfo.py」はGoogle Codeにアップされているので恐らく使用しても問題ないと思いますが、
使う際には自己判断にてお願いしますm(_ _)m
(一応「MIT License」となっているみたい)
app.yml
application: photo-upload version: 1 runtime: python api_version: 1 handlers: - url: /.* script: app.py
説明省きます。
アプリケーション名は「photo-upload」で、
どんなアクセスでも「app.py」が実行されると設定。
getimageinfo.py
転載控えます
以下のリンク先を参照してください。
http://code.google.com/p/plsamples/source/browse/trunk/GrandMonde/getimageinfo.py
app.py
#!/usr/bin/env python
#!-*- coding:utf-8 -*-
"""
画像アップローダ
copyright(C)2008 jinlingren.com
"""
from __future__ import division
import cgi, math
import wsgiref.handlers
from google.appengine.ext import webapp, db
from google.appengine.api import images
from getimageinfo import getImageInfo
# データストア設定
class Photo_Upload(db.Model):
"""画像格納データストア"""
date = db.DateTimeProperty(auto_now_add=True)
photo = db.BlobProperty()
filename = db.StringProperty(multiline=False)
size = db.IntegerProperty()
width = db.IntegerProperty()
height = db.IntegerProperty()
mimetype = db.StringProperty(multiline=False)
# アプリケーション
class Home(webapp.RequestHandler):
def get(self):
"""画像アップロード画面&アップロード完了確認画面"""
output_html = """<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>画像アップローダ</title>
</head>
<body>
<h1>画像アップローダ</h1>
<form action="/" enctype="multipart/form-data" method="post">
<input type="file" name="photo">
<input type="submit" value="アップロード">
</form>
<br>
<br>
<a href="/list">アップロード画像一覧</a>
</body>
</html>"""
self.response.out.write(output_html)
def post(self):
photo = self.request.get("photo")
if photo:
img = self.request.body_file.vars['photo']
bin = db.Blob(photo)
content_type, width, height = getImageInfo(bin)
photo_upload = Photo_Upload()
photo_upload.photo = bin
photo_upload.filename = img.filename
photo_upload.size = len(bin)
photo_upload.width = width
photo_upload.height = height
photo_upload.mimetype = img.headers['content-type']
photo_upload.put()
key = photo_upload.key()
filename = photo_upload.filename
size = photo_upload.size
mimetype = photo_upload.mimetype
size = math.ceil(size / 1024)
output_html = """<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>画像アップローダ</title>
</head>
<body>
<h1>画像アップローダ</h1>
<br>
<img src="/img/%s"><br>
元ファイル名:%s<br>
サイズ:%s KB<br>
横幅:%s pix<br>
縦幅:%s pix<br>
MIMEタイプ:%s<br>
<br>
<br>
<br>
<a href="/">戻る</a>
<br>
<br>
<br>
<a href="/list">アップロード画像一覧</a>
</body>
</html>"""
self.response.out.write(output_html % (str(key),str(filename),str(size),width,height,str(mimetype)))
else:
self.redirect("/")
class List(webapp.RequestHandler):
def get(self):
"""アップロード済み画像一覧"""
pics = Photo_Upload.all().order('-date')
output_html = """<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>画像アップローダ</title>
</head>
<body>
<h1>画像アップローダ</h1>
<br>
<a href="/">戻る</a>
<br>
<br>
<table border="1">"""
for pic in pics:
output_html += "<tr>\n"
output_html += "<td><a href=\"/img/"+str(pic.key())+"\" target=\"_blank\">\n"
output_html += "<img src=\"/img/"+str(pic.key())+"\" width=\"100\"></a></td>\n"
output_html += "<td><a href=\"/delete?key="+str(pic.key())+"\">del</a><br>\n"
output_html += "<br>元ファイル名:"+str(pic.filename)+"<br>\n"
output_html += "サイズ:"+str(math.ceil(pic.size / 1024))+" KB<br>\n"
output_html += "横幅:"+str(pic.width)+" pix<br>\n"
output_html += "縦幅:"+str(pic.height)+" pix<br>\n"
output_html += "MIMEタイプ:"+str(pic.mimetype)+"<br></td>\n"
output_html += "</tr>\n"
output_html += """
</table>
<br>
<br>
<a href="/">戻る</a>
</body>
</html>"""
self.response.out.write(output_html)
class Delete(webapp.RequestHandler):
def get(self):
"""画像削除処理"""
key = self.request.get("key")
if key:
photo = Photo_Upload.get(key)
photo.delete()
self.redirect("/list")
class Img(webapp.RequestHandler):
def get(self,photoid):
"""画像出力処理"""
img = Photo_Upload.get(photoid)
if img:
photo = img.photo
mimetype = img.mimetype
self.response.headers['Content-Type'] = str(mimetype)
self.response.out.write(photo)
return
else:
self.error(404)
return self.response.out.write('404 not found')
def main():
application = webapp.WSGIApplication(
[
('/', Home),
('/list', List),
('/delete', Delete),
('/img/([^/]+)', Img),
],
debug=True)
wsgiref.handlers.CGIHandler().run(application)
if __name__ == "__main__":
main()テンプレートは使用していませんので、「app.py」いちファイルで完結しています。
HTML画面としては3つの画面しかありません。
1.画像参照+アップロードフォーム画面
2.アップロード完了確認画面
3.アップロード画像一覧画面
from __future__ import division「ceil」を使用した際に小数点以下を切り上げてくれないので、新しい除算仕様を有効にする →参照
from getimageinfo import getImageInfo同じディレクトリの「getimageinfo.py」ファイルの「getImageInfo」を関数を読み込む
img = self.request.body_file.vars['photo']img.filename = アップロード元ファイル名
img.headers['content-type'] = MIMEタイプ
が取得できる →参照
bin = db.Blob(photo) content_type, width, height = getImageInfo(bin)送られてきた画像を「db.Blog」を使用してバイナリに変換。
「getimageinfo.py」ファイルの「getImageInfo」関数にそのバイナリをつっこむと、
・MIMEタイプ
・画像の横幅サイズ
・画像の縦幅サイズ
が取得できる。
photo_upload.size = len(bin)バイナリを組み込み関数「len」で画像のサイズを取得(=byte)
photo_upload.put() key = photo_upload.key()ここは少し悩んだのだが、データストアにつっこんだ後、すぐさまそのデータのみを抜き出すってどうやるのだろう?
つまるところたったいま保存した「key()」を知りたいということなのだが、「put()」した後に「key()」ですぐに取得できるみたい。
どの時点で発行しているのかは確認していないので、ひょっとしたら「photo_upload = Photo_Upload()」の時点でかもしれない。
(※10/27追記:データストアにエンティティが保存された時エンティティは個々にユニークなキーを持つらしい。なので「photo_upload.put」の時keyは発行されているみたい)
size = math.ceil(size / 1024)「size」には[byte]サイズが放り込まれているので1024で割って小数点引き上げで[KB]サイズに変換
「math」を使うには「import math」をお忘れなく。
画像の出力は表示の度に動的に書き出しています。
PHPなんかでも同じことできますが、どう考えても負荷がかかるので何か特別な理由でもない限りこんなことしませんが、
GAEではファイルシステムという仕組みではないらしいので画像をアップして所定のフォルダに格納という普通なことができません。
なのでこのような形になるのですが、これはGAEにおいては普通なのかな?負荷はかからんのかなぁ~?