GAE - Pythonで、EXIFの回転情報を元に画像回転させてCloud Storageにアップする。

#はじめに
以前にPILを使ってサーバ側で画像を回転させようとして失敗しました。原因は次のエラー

```
".../PIL-1.1.7/PIL/ImageFile.py", line 476, in _save
    fh = fp.fileno()
UnsupportedOperation: fileno
```
fileno()に何か問題があるらしい。ネットで検索してみると他にもこのエラーに苦戦している人がちらほら見られる。このエラーについてはよくわからないです。

なので今回はこれを打開する案として、GAEの**Images Python API**を使ってサーバ側で回転させてみます。

**Images Python APIとは**
AppEngineが提供している、画像データを操作する機能。画像のサイズ変更、回転、反転、トリミングを行うことがでる。APIは、フォーマット、幅、高さ、カラー値のヒストグラムなど、イメージに関する情報も抽出できる。アプリからデータを受け取ることもGoogle Cloud Storageの 値を使用することもできる。
また、Images APIに加えて、Python 2.7の**Python Imaging Library（PIL）**で提供される変換を使用することもできる。
(詳しくは[こちら](https://cloud.google.com/appengine/docs/standard/python/images/))

#EXIFの取得
##APIのインポート

```python
from google.appengine.api import images
```
##base64をImage APIで開く

```python
binary = base64.b64decode(base64_string)
gae_img = images.Image(binary)
```
##情報を取得
ここからEXIF情報を取得していくが、Image Python APIを使って取得する場合、次の手順に従わないとエラーが出てしまう。

```python
# 1. 画像に対してなんらかの処理(変換)
gae_img.rotate(0)
# 2. 画像に対しての変換を実行
gae_img.execute_transforms(parse_source_metadata=True)
# 3. EXIF情報を取得
exif = gae_img.get_original_metadata()
```

順を追って説明していくと、`gae_img.get_original_metadata()`でいきなりEXIF情報を取得したいところだが、これをするには`gae_img.execute_transforms(parse_source_metadata=True)`でソースイメージのメタデータ(EXIF)を解析する必要がある。
この`gae_img.execute_transforms()`は上でも書いてある通り、"画像に対しての変換を実行する"というメソッドであるから、これをやる前に"画像が変換されていることが前提"である。だからこの手前にわざわざ`gae_img.rotate(0)`という一見無意味な処理を書いている。この`gae_img.rotate()`についてはあとで説明する。

ここまでで`print exif`すると

```
{u'MimeType': 0, u'ColorProfile': False, u'Orientation': 6, u'ColorSpace': 1, u'ImageWidth': 3264, u'ImageLength': 2448}
```
ディクショナリ型で入っていることがわかる。
画像の向きについては`Orientation`という項目で判断できる。

```python
print exif['Orientation']
# "1"から"8"までの数字のどれかが出力される。
```
これでEXIF情報の取得ができた。

# EXIF情報を元に画像を回転させる
上記まででやったことをまとめます。

```python
from google.appengine.api import images

binary = base64.b64decode(base64_string)
gae_img = images.Image(binary)

gae_img.rotate(0)
gae_img.execute_transforms(parse_source_metadata=True)
exif = gae_img.get_original_metadata()
```
ここから`exif`の中の`Orientation`を元に画像を回転させる。

```python
if exif['Orientation'] == 3:
    gae_img.rotate(180)
elif exif['Orientation'] == 6:
    gae_img.rotate(90)
elif exif['Orientation'] == 8:
    gae_img.rotate(270)
else : 
    gae_img.rotate(0)

converted_img = gae_img.execute_transforms(parse_source_metadata=True)
```
`gae_img.rotate()`で画像を90度ずつ回転させることができる。
前述でも述べたように`gae_img.execute_transforms()`の性質により、画像に対して一度は変換を行わないとエラーが出てしまうため、`else`で一度は変換が行われるようにしている。

今回は上下反転や左右反転された画像は送られて来ないものと考えているため、2,4,5,7の場合の処理は書いていない。
そして回転させたあと、画像に加えた変更を実行させるために、`gae_img.execute_transforms(parse_source_metadata=True)`を実行する。

#Cloud Storageにアップロードする
ここからCloud Storageにアップロードして行きます。
手順としては
1. 環境変数を判別
2. 環境ごとに適した場所にファイルを開く
3. 開いた先に書き込む(アップロード)
このようになります。
では実際にコードを見て行きましょう。

```python
import cloudstorage as gcs

def upload_gcs(self, name, type, binary):
    # 環境変数を抽出
    env = os.getenv('SERVER_SOFTWARE')
    # 任意の環境であった場合、Google Cloud Storageにアップロード
    if (env and (env.startswith('Google App Engine/') or env.startswith('Development/'))):
        # FileLogic.GCS_IDには保存する場所を示すurlがあらかじめ入っている。
        file_url = FileLogic.GCS_ID + name
        # Google Cloud StorageのAPIでファイルを開く
        with gcs.open(file_url, 'w', content_type=type, options={'x-goog-acl': 'public-read'}) as gcs_file:
            # 画像の書き込み（アップロード）
            gcs_file.write(binary)
            # FileLogic.GCS_HOST_NAMEにはホスト名があらかじめ入っている
            file_url = FileLogic.GCS_HOST_NAME + file_url
    else:
        # 任意の環境でなかった場合ローカルに書き込む
        file_url = 'local_file/' + name
        with open(file_url, 'w') as file:
            file.write(binary)
    return file_url
```

Google Cloud StorageのAPIでopenし、Cloud Storageバケット内の既存のオブジェクトを開く。この際、`'w'`を渡すことで書き込みモードでファイルを開くことができる。
書き込みモードで任意の場所を開いたら、`write`で画像をアップロードする。