HOME > ブログ > Google App Engineで画像アップローダ

Google App Engineで画像アップローダ

YaneuraLabsさんのブログ記事「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においては普通なのかな?負荷はかからんのかなぁ~?



| Google App Engine | Comment:2 |
コメント
req
日本語ファイルのアップについて
こんにちは、(AppEngine勉強中の)reqと申します。
こちらの記事とても参考になりました。

一点、WindowsXPで日本語のファイル名の画像を指定したらエラーが出てしまうようなので、
以下のようにすればいけましたということをコメントします:

class Photo_Upload(db.Model):
"""画像格納データストア"""
date = db.DateTimeProperty(auto_now_add=True)
photo = db.BlobProperty()
#変更前↓
# filename = db.StringProperty(multiline=False)
#変更後↓
filename = db.BlobProperty()

・・・

余計なお世話かもですが。。。
失礼します。
| 2009/01/20 15:22:39
jinlingren
は、初コメントヽ(゚ロ゚;)
reqさんコメントありがとうございます。
余計なお世話なんてとんでもない。。。
大変ありがたい指摘ですm(_ _)m

ブログを始めて1年あまり・・・
やっとついた初めてのコメントはやはり嬉しいですなぁ〜ヘ(´ω`)ゞ
| 2009/01/22 16:57:48
コメント投稿












画像リロード
*半角の小英字、数字で構成されています