主にプログラミングに関して。Python, .NET Framework(C#), JavaScript, その他いくらか。
記事にあるサンプルやコードは要検証。使用に際しては責任を負いかねます

Piexif: Python Exif Library

                
tags: python
 PythonでExifをいじるライブラリを書いていて、ドキュメントも作ってだいぶ体裁が整ってきた。PyPIでの区分をalphaからstableに一気に移行し、バージョンも1.0へ引き上げた。
 GoogleAppEngineで写真のアップローダを作るときにExifの扱いで不便を感じ、そのあとでも何度かPythonでExifを扱うことに不便を感じたので自分でPiexifというライブラリを書いてみた。その結果、Stackoverflowにある質問に対してもスマートな解法を提供できるライブラリができたので紹介がてらそのあたりを書いてみる。
https://github.com/hMatoba/Piexif

 まず基本的な部分について
- Pythonの2系と3系の両方で動作(2.7, 3.3, 3.4への対応)
- ドキュメント完備
- Travis CIでビルドテスト結果が確認可能
- ピュアPythonなのでどこへでも容易に移植



・Exifを消す:http://stackoverflow.com/questions/19786301/python-remove-exif-info-from-images
 JPEGのExifを消したいという要望がある。最近でははてなフォトライフというサービスで、アップロードした写真のExifを消すかどうかの設定ができるようになったとかなってないとか。
 PythonでExifを消したければPILを使って画像データだけを使って新しくファイルを作るとか、Gexiv2を使うという解法がある。PILでのやり方はテクニックを駆使していて、シンプルな解法ではない。Gexiv2ならStackoverflowでわかりやすいやり方が紹介されているが、このライブラリのドキュメントは行方不明。
 Piexifでは下記のように。シンプル。
piexif.remove("foo.jpg")



・PIL(Pillow)での編集によってExifが欠落するのを防ぐ:http://stackoverflow.com/questions/400788/resize-image-in-python-without-losing-exif-data
 PILではイメージを保存するとき、ただ保存するだけだとExifが欠落する。Exifを欠落させないやり方も実はあるが、Exifの編集ができず前のイメージそのままで引き継ぐので実画像データと解像度などで矛盾が出るだろう。JPEGにExifを埋め込むライブラリはいくつかあるみたいだがPILとは連携しないようだ。それらを使うと編集した画像を保存→Exifを埋め込んで保存というように保存が二度になっていて非合理。
 PiexifはPILと連携もできるので、保存を二度するという非合理はない。
from PIL import Image
import piexif

im = Image.open(filename)
exif_dict = piexif.load(im.info["exif"])
# process im and exif_dict...
w, h = im.size
exif_dict["0th"][piexif.ImageIFD.XResolution] = (w, 1)
exif_dict["0th"][piexif.ImageIFD.YResolution] = (h, 1)
exif_bytes = piexif.dump(exif_dict)
im.save(new_file, "jpeg", exif=exif_bytes)


 PILでの画像編集とExifの扱いという内容において、「Exifのオリエンテーションタグで指定されている方向を画像に反映したい」という要望もある。これの解法を探すといくつか出てくるが、方向を反映させた画像からExifが抜け落ちていたり、PILと連携できず画像を二度保存する非合理な方法だったり。
 Piexifでの解法。
from PIL import Image
import piexif

def rotate_jpeg(filename):
img = Image.open(filename)
if "exif" in img.info:
exif_dict = piexif.load(img.info["exif"])

if piexif.ImageIFD.Orientation in exif_dict["0th"]:
orientation = exif_dict["0th"].pop(piexif.ImageIFD.Orientation)
exif_bytes = piexif.dump(exif_dict)

if orientation == 2:
img = img.transpose(Image.FLIP_LEFT_RIGHT)
elif orientation == 3:
img = img.rotate(180)
elif orientation == 4:
img = img.rotate(180).transpose(Image.FLIP_LEFT_RIGHT)
elif orientation == 5:
img = img.rotate(-90).transpose(Image.FLIP_LEFT_RIGHT)
elif orientation == 6:
img = img.rotate(-90)
elif orientation == 7:
img = img.rotate(90).transpose(Image.FLIP_LEFT_RIGHT)
elif orientation == 8:
img = img.rotate(90)

img.save(filename, exif=exif_bytes)



 PiexifはPythonでExifをいじるライブラリとしてまだ新しいものなのでユーザは少ない。だがPixabayというサービスの中の人から、pyexiv2をやめてpiexifを採用すると報告が来た。pyexiv2はPythonのExifライブラリとして著名だし標準のような位置にいる。おそらくpyexiv2の使用においてどこかで不便を感じていたんだろう。たとえばちょっと古くてPythonの3系への対応計画が立っていなかったりする。
 そんなわけでExifをいじりたくなったらPiexifの使用をご一考。

Python: virtualenvを使って開発中のプロジェクト専用のPython環境を構築

                
tags: python
 趣味でPython開発に使っているPTVSを導入したVisualStudioではvirtualenvが使えるということだった。virtualenvをまだ使ったことがなかったが、せっかくなのでこれを機会に使ってみる。まずはVisualStudio外で。
公式ドキュメント:https://virtualenv.pypa.io/en/latest/index.html


 virtualenvを使う利点は任意のプロジェクト専用のPython環境を用意できるというところだろう。今Pythonユーザは3系への移行の途中にあって、たぶん開発に使っているPCには2.xと3.xが混在している。ターミナルに”python”と打ってどのバージョンのPythonが実行されるかは設定次第だが、プロジェクトを変えるたびにそのあたりの設定まで変えるのは手間がかかる。だからプロジェクトごとに専用のPythonバージョンをあててしまおうということでvirtualenvが使える。あとサードのライブラリがまっさらな状態の環境が用意できるのでパッケージ間の依存関係に困らされることも回避できるだろう。
 virtualenvを実行するときに利点について再度触れていく。virtualenvはWindowsでも使えるので今回はWindowsで。

 まずはインストール。virtualenvはPythonパッケージの一つなのでpipを使ってインストールが楽。
pip install virtualenv

 virtualenvの使用準備ができたので適当なプロジェクトのPython環境を作ってみる。myというディレクトリにhello.pyという適当なPythonスクリプトを用意する。これをPython 2.7で実行するものだということにする。
hello.py
import sys

print(sys.version)


 まずvirtualenvによる仮想環境の準備。myディレクトリで下記のコマンドを実行。Python2.7を使うように、コマンドのオプションでEXEへのパスを渡してある。
virtualenv -p c:\python27\python.exe .
コマンドの実行でmyディレクトリ内には新たなディレクトリとしてInclude、Lib、Scriptsが作成される。Scripts内にactivate.batがあるのでコマンドプロンプトで実行すると”C:\users\h\desktop\my”と表示されていたカレントディレクトリの表示が”(my) C:\users\h\desktop\my”と表示されるようになる。これでこのプロジェクト用のPython環境が実行されていることになる。ためしにこの環境の実行下で”python hello.py”とやってみれば、そのPCのデフォルトのPythonが3.4であったとしても、Python 2.7が実行されるのがわかる。
 ぼくのPCにはPythonの2.7と3.4が入っているが、デフォルトは3.4である。”python”とコマンドを打てば通常はPython 3.4が実行されるが、今回構築した環境の実行下ではPython 2.7が実行される。最初にPython 2.7を使うように指定したから、この環境下では3.4ではなく2.7が出てくる。
 ディレクトリScripts内にpipがあるので必要なものはこれを使ってインストール。そうすればこのプロジェクトのディレクトリLib下にあるsite-packagesにインストールされる。PC自体のPython環境にインストールされるわけではないので、このプロジェクト用に特定のバージョンを使ったりできる。


 あとは環境変数としてPYTHONPATHとかが設定されていると、そっちまでパスに加えられてスクリプトを探しに行ってしまうので、適宜実行時にパスを書き換え。Scripts\activate.batのパス設定部分を必要に応じて書き換えたり。
set "PYTHONPATH="
set "PATH=%VIRTUAL_ENV%\Scripts;"

 virtualenvを使えばプロジェクトに任意のPython環境を構築できる。Pythonやらパッケージやらで様々なバージョンを使い分ける手段として使っていける。

Dockerを使ってWindows上にPythonサーバのテスト環境を用意する(Nginx, Gunicorn, Tornado)

                
 Windows上で手軽にGunicornを使ったPythonサーバのテストができる環境が欲しくなったのでDockerをこねくりまわして作ってみる。Windows上なのでBoot2Dockerを使う。Pythonサーバは今回はTornado+GunicornをNginxで動かす。今回はなんとか動作確認できるまでにこぎつけるようにしたので、細かい部分はもっとうまい書き方があるかもしれない。


 以下の二つのファイル(PythonスクリプトとNginx設定ファイル)がWindowsのユーザディレクトリの真下の適当な名前のディレクトリに入っているとする。そんな場所に置いているのは、WindowsのユーザディレクトリがDockerにマウントされるようになっていてユーザディレクトリから浅いところだと都合がいいから(c:\Users\ - /c/Users/)。
hello.py
import os

import tornado.web
import tornado.wsgi


class HelloHandler(tornado.web.RequestHandler):
def get(self):
self.write("Hello.")


settings = {
"static_path": os.path.join(os.path.dirname(__file__), "static"),
}

application = tornado.web.Application([
("/", HelloHandler),
], **settings)

wsgi_app = tornado.wsgi.WSGIAdapter(application)


hello.conf(Nginxの設定ファイル)
server {
listen 80 default;
server_name example.org;
access_log /var/log/nginx/example.log;

location / {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}


 Boot2Dockerをインストールして立ち上げる。とりあえずポピュラーなubuntuをサーバ環境として使うことにする。
> docker pull ubuntu:latest

 落としたubuntuイメージからコンテナを作って、ubuntu上で最低限のものをインストールする。あと不必要なnginxのファイルを消しておく。
> docker run -it --name ubuntu0 ubuntu bash

apt-get update
apt-get install -y nginx
apt-get install -y python3-pip
rm /etc/nginx/sites-enabled/default

上記が済んだらctrl+dでコンテナ内からexit。このコンテナを新しいイメージとしてコミットする。
> docker commit ubuntu0 py_im
これで私的Pythonサーバ開発の環境が用意できた。このイメージはpipが入っているのでいろんなPython開発の環境を手早く用意できるだろう。ここからはTornadoとGunicornを使う今回の目的に向けて整えていく。

 DockerでPythonスクリプトとNginx設定ファイルが置かれたディレクトリへ移動する。
> cd /c/Users/john/hello
 そのディレクトリにDockerfileを用意する。Dockerfileとはコンテナ内で実行するコマンドなどを記述したテキストファイル。
FROM py_im

ADD * /usr/
WORKDIR /usr/
RUN pip3 install gunicorn
RUN pip3 install tornado
EXPOSE 80
RUN cp hello.conf /etc/nginx/conf.d/
RUN /etc/init.d/nginx reload
CMD gunicorn hello:wsgi_app



 Dockerfileを書いたのでビルドする。
> docker build -t py_im0 .

 p0というコンテナ名でpy_im0がオリジナルイメージのコンテナを走らせる。Dockerのポートからコンテナのポートへアクセスを提供してもらうように設定する。
> docker run -d -p 80:80 --name p0 py_im0

 コンテナ内部でNginxを走らせる。
> docker exec p0 nginx

 Windowsのブラウザでアクセスしてみる(IPはデフォルトで192.168.59.103)。
1502230110464.jpg

動作確認終了。Pythonスクリプトを更新したときにコンテナのビルド以降を再実施すれば手軽にテストが実行できる。
 あとはプロジェクト固有のDockerへの命令(コンテナではなくDocker自体)をスクリプトとしてまとめるなど。


 最後に改めてなぜDockerを使ったかを。
 Windows上でUNIXサーバがほしくなった場合、Dockerが出てくる以前の選択肢はVMWareやVirtualBoxで仮想マシンを用意することだった。ただ仮想マシンはその中でおこなった操作が基本的に残り、変更以前の状態を用意するのはイメージを用意したりマシンを立ち上げなおしたりでなにかしら手間がかかった。
 Dockerではすでに起動しているLinuxのカーネルを使ってコンテナのOS(仮想マシンでのゲストOS的なもの)が動作するので立ち上げなおしの手間はほとんどないし、OSイメージの用意やチューンのコマンドもテキストファイルにまとめておいて実行できる。うまくまとめればワンタッチで手早く使い捨ての環境が用意できるということだ。
 パッケージの依存などを気にしながらテストを行いたい場合、ある程度使っていていろんなパッケージが入り乱れている仮想マシンより、使い捨てを繰り返していて最低限のパッケージしか入っていないコンテナ環境のほうがエラー時のデバッグがしやすい。そんなわけで今回はDockerを選んだ。

参考******
https://docs.docker.com/installation/windows/
http://www.atmarkit.co.jp/ait/articles/1406/10/news031.html
http://www.atmarkit.co.jp/ait/articles/1407/08/news031.html

Python: herokuでTornadoで非同期じゃないサーバをやってみる

                
tags: python heroku
 GoogleAppEngineのPythonがいまだに3.x対応しない。アルファ版とかそんな気配も見せずに公式コミュニティの該当トピックはクローズされていたり、そんな状態でPHPがGAEの開発言語でサポートされたりと、3.x対応はする気がないように見受けられる。では3.4もいけるherokuはどうなのかと試みてみる。
 ただherokuへ行く前にGAEについてもちょっと。相変わらず3.x対応はしないものの、自分の趣味程度に使うにはおそれおおいぐらいにGAEはいい環境をくれると思う。データベースは独自のNoSQLではあるがSQLモドキが使えるし、SQLなしでも簡単に使えるようになっている。無料範囲内での作成可能なアプリの数やデータストアの容量も結構な量。標準データストアは1GBだし、大容量ファイルを入れるBlobストアは5GBである。

 まあ試し程度にherokuでアプリをデプロイしてみる。herokuで最初のアプリをデプロイしてみる記事は多くあるので細かいことはここには書かない。ただTornadoで非同期ではないサーバを立てているものは見つからなかったのでそこらへんだけ。

 TornadoによるアプリをGunicornサーバで。Procfileには以下のように。
web: gunicorn main:wsgi_app
コマンドに出てきているmainファイルとその中のwsgi_appを定義する。
import os

import tornado.web
import tornado.wsgi


class HelloHandler(tornado.web.RequestHandler):
def get(self):
self.write("Hello.")


settings = {
"static_path": os.path.join(os.path.dirname(__file__), "static"),
}

application = tornado.web.Application([
("/", HelloHandler),
], **settings)

wsgi_app = tornado.wsgi.WSGIAdapter(application)

上記のように書けばherokuでもTornadoのノンブロッキングでない他のフレームワークと同様の仕組みで動くWebアプリができる。

 ぼくは今はPythonを書くのはほぼWindows上だ。動くものはWindows上で動かしている。しかし今回使ったGunicornはUNIXサーバなのでWindows上でこのアプリのローカルテストができない。テストに適したスマートなUNIXサーバの準備方法を考える。
 以前に触ってみたDockerが助けてくれそうに思える。プレーンな実行環境を用意して、必要なものだけインストールして、書いたコードをそこに入れて動かしてみる、一連のテストが終わったらその環境を破棄する、コード編集後またプレーンな実行環境を用意してテスト……と繰り返す。プレーンな実行環境というわけでなければ仮想マシンで適当なOSを動かしっぱなしにすればいいんだけど。Dockerならプレーンな環境の用意が楽だし、必要最小限しかリソースを割かなくて済むだろう。
 今後Dockerでテスト環境の用意をやってみることにする。

Python: GoogleAppEngineでTornadoを使ったアプリ

                
tags: python
 ちょいとサーバにのっけたいPythonスクリプトがあった。GoogleAppEngineの無料割当枠がまだまだ残っているのでそれを使うとして、フレームワークはどれで書こうか。GoogleAppEngine標準のwebapp2に加えて公式でDjangoやFlaskのデモが試せるし、Tornadoも公式でなくTornadoのほうでだがデモが配布されている、ただしTornado自慢のノンブロッキングではないが。
 半日足らずの遊びなのであんまり時間はかけたくない。Flaskはまだ使ったことないがほんの1、2時間程度で書けそうだし迷った。だがTornadoをそれなり触っているので、とりあえずそれが本当にGoogleAppEngineで動くか試したかったのでTornadoにした。

 ぼくはたいていをWindows 7でやっている。だからGoogleAppEngineに上げるPythonアプリのローカルテストもWindowsでやっている。そのローカルテストでPythonバージョンが2.7.9、GAEバージョンの1.9.17でエラーが出たので調べてみたが、ローカルテストのみで出るバグがあるらしい。"no module named _ctypes"と出力された。該当ファイルは見つけることができたのでおそらくパスの問題だろう。次のGAEリリースで修正されると見つけたので今回はいじらず放っておくことにする。
 Tornadoは最新のTornado4.1をpipでインストールし、今回作るプロジェクトディレクトリにスクリプトが入ったtornadoディレクトリをコピーした。
 それに加えて今回のメインであるサーバにのっけたい自作のPythonライブラリもプロジェクトディレクトリにコピーした。あとはこれまでのGAEアプリケーションと同様にアプリケーション名をGAEサイトで登録したりapp.yamlを作ったり。

 そしてGAEで動かすTornadoスクリプト。アップロードされたJPEGファイルからExifを辞書型データに起こし、それをJSON文字列にしてクライアントに返すもの。
import json
import logging

import tornado.web
import tornado.wsgi
import piexif


class RHandler(tornado.web.RequestHandler):
def post(self):
jpg_data = self.request.body
logging.info(jpg_data[:10])
try:
exif_dict = piexif.load(jpg_data)
except:
self.set_status(400)
return self.write("Wrong jpeg")
self.add_header("Content-Type", "application/json")
thumbnail = exif_dict.pop("thumbnail")
data_d = {}
for ifd in exif_dict:
data_d[ifd] = {piexif.TAGS[ifd][tag]["name"]:exif_dict[ifd][tag]
for tag in exif_dict[ifd]}
data_d["thumbnail"] = thumbnail
data = json.dumps(data_d, encoding="latin1")
return self.write(data)


application = tornado.web.Application([
(r"/p", RHandler),
])

application = tornado.wsgi.WSGIAdapter(application)


下記が今回の製作物を上げたもの。
http://piexif-demo.appspot.com/demo
プロフィール

h

Author:h

最新記事
リンク
作ったものなど
月別アーカイブ
カテゴリ
タグリスト

検索フォーム
Amazon