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

スポンサーサイト

                
tags:
上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

Python: Python2.7でバイナリデータを処理してたスクリプトを3.4で使えるように書き換えてみる

                
tags: python
 Pythonで画像処理をしたいとなるとPILかそのフォークであるPillowを使うのが主な方法。でもこれでJPEGをリサイズして保存するとEXIF情報が失われてしまう。そんなもんで、EXIFを保持しつつJPEGをリサイズできるモジュールをPython 2.7で使うために以前に書いた。最近はPythonの2.7が3.xほどにメンテされなくなってきたときいたので、とりあえず最新の3.4対応をするように書き換えてみたい。
from PIL import Image
import cStringIO

def save_thumbnail(data, output, size=(200, 200)):
if data[0:2] == "\xff\xd8":
pass
else:
with open(data, 'rb') as f:
data = f.read()
segments = _slice2segments(data)
exif = _get_exif(segments)

buff = cStringIO.StringIO()
buff.write(data)
buff.seek(0)
im = Image.open(buff)
im.thumbnail(size, Image.ANTIALIAS)

if exif is None:
im.save(output, "JPEG")
else:
im_buff = cStringIO.StringIO()
im.save(im_buff, "JPEG")

resized = im_buff.getvalue()
head = resized.find("\xff\xdb")
merged_with_exif = resized[0:head] + exif + resized[head:]

if isinstance(output, str):
with open(output, "w+b") as f:
f.write(merged_with_exif)
elif isinstance(output, cStringIO.OutputType):
output.write(merged_with_exif)

def _slice2segments(data):
head = 0
segments = []

while 1:
if (data[head: head + 2] == "\xff\xd8"):
head += 2
else:
length = int(data[head + 2].encode('hex'), 16) * 256 + int(data[head + 3].encode('hex'), 16)
endPoint = head + length + 2
seg = data[head: endPoint]
segments.append(seg);
head = endPoint

if (head >= len(data)) or (data[head: head + 2] == "\xff\xda"):
break

return segments

def _get_exif(segments):
for seg in segments:
if seg[0:2] == "\xff\xe1":
return seg
return None


 EXIFを保持したままリサイズする方法として、
元のJPEGファイルからEXIFデータ部分を抜き出す→
PILでJPEGのリサイズを行って一度保存→
保存されたファイルに抜き出してあったEXIFを埋め込んで保存、
という流れで処理を行っている。JPEGはテキストファイルではないので、バイナリデータの扱いが必要になる。
 2.xではバイナリデータはstr型として扱われるものだった。
with open("foo.jpg", "rb") as f:
data = f.read()

 上記のようにファイルを読むことで、dataにはstr型でファイルのデータが読み込まれる。たとえば"\xff\xd8......"というように。3.xからは上記のようなことをするとbytes型でデータが得られるようになっている。bytes型では1バイトだけ値を欲しいときはint型で値が得られ、2バイト以上読もうとするとstr型のように値が得られる。ちょっと便利だけどちょっと面倒。2.xではバイナリデータはstr型で扱っていたので、1バイト読もうが2バイト以上読もうが常にstr型。これが差異となるので、この部分の書き換えが必要になる。
>data[0]
255
>data[0:2]
b"\xff\xd8"

参考:http://diveintopython3-ja.rdy.jp/strings.html#byte-arrays

 2.xから3.xになったことでstr型とbytes型が区別されるようになったので、たとえばバイナリデータで任意の部分がff x8という値で連続しているか調べたければ、str型でなくbytes型と比較しなければならない。
#data[0:2] == "\xff\xd8"
data[0:2] == b"\xff\xd8"


 あとはcStringIOを使っていたが、これがStringIOとともにioモジュールの中に統合されていたのでio.BytesIOに書き換えたり。スクリプトはそれなり短いものだったので書き換えはこれまでに書いたものぐらい。
 書き換えはしたが2.7でも3.x環境でも動いてくれるとうれしい。ioモジュールは2.7にも移植されているので問題ない。
 一つさかのぼって、data[0:2] == b"\xff\xd8"と書いた。3.xでは左辺のバイナリデータを読み込んだ変数dataはbytes型だったから、右辺もbytes型にした。2.7では左辺がstr型だが、右辺でbをつけたデータも文字列型と同等のものとして解釈されるので、この書き方で2.xでも3.xでも目的の結果が得られる。
 さらにさかのぼって、2.xではバイナリデータを読み込むとstr型、3.xではbytes型となる点である。バイナリデータを解析していると、「nバイト目のデータがそこからはじまるメタデータの長さを表している」ことがある。そのためにnバイト目の値を十進値で得たかったりするが、3.xでこれをやるならそのままdata[n]で得られるのが2.7ではint(data[n].encode("hex"), 16)とstr型からint型への変換が必要だったり。これを2.xと3.xで共通のスクリプトにしたければ、読み込んだバイナリデータをstr型でなく、3.xのbytes型と同等に扱える型に変換してやればいい。となると下記のように2.xで読み込んだバイナリデータはbytearrayにしてしまえ。
with open("foo.jpg", "rb") as f:
data = f.read()
if isinstance(data, str):
data = bytearray(data)

 bytesでなくbytearrayになったが、今回のように共通のインデクスを使ってアクセスする場合はbytes、bytearrayとも共通の型を返す。両者とも1バイトのみ取り出そうとすればint型が得られるし、2バイト以上なら3.xではbytes型、2.xではstr型を返す。2.xにも2.6以降にもbytesという型が一応付加されたが、これは単にstrのエイリアスで、3.xのbytes型とは異なるものだ。今回は使えないのでbytearrayを使う。

 以上の書き換えをして2.7と3.4で動作の確認がとれたスクリプトが以下。
import io
from PIL import Image


def save_thumbnail(data, output, size=(200, 200)):
if data[0:2] == b"\xff\xd8":
pass else:
with open(data, 'rb') as f:
data = f.read()

if isinstance(data, str):
data = bytearray(data)


segments = _slice2segments(data)
exif = _get_exif(segments)

buff = io.BytesIO()
buff.write(data)
buff.seek(0)
im = Image.open(buff)
im.thumbnail(size, Image.ANTIALIAS)

if exif is None:
im.save(output, "JPEG")
else:
im_buff = io.BytesIO()
im.save(im_buff, "JPEG")

resized = im_buff.getvalue()
head = resized.find(b"\xff\xdb")
merged_with_exif = resized[0:head] + exif + resized[head:]

if isinstance(output, str):
with open(output, "w+b") as f:
f.write(merged_with_exif)
elif isinstance(output, io.BytesIO):
output.write(merged_with_exif)

def _slice2segments(data):
head = 0
segments = []

while 1:
if data[head: head + 2] == b"\xff\xd8":
head += 2
else:
length = data[head + 2] * 256 + data[head + 3]
endPoint = head + length + 2
seg = data[head: endPoint]
segments.append(seg)
head = endPoint

if (head >= len(data)) or (data[head: head + 2] == b"\xff\xda"):
break

return segments

def _get_exif(segments):
for seg in segments:
if seg[0:2] == b"\xff\xe1":
return seg
return None


https://github.com/hMatoba/Python-exifkeeper
            

コメントの投稿

非公開コメント

プロフィール

Matoba

Author:Matoba

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

検索フォーム
Amazon
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。