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

スポンサーサイト

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

Python, JavaScript: JSONにJPEGデータを入れてクライアントに渡す、クライアントでそれを表示

                
 JPEGのメタデータであるExifには、文字列や数値データに加え、サムネイルとしてJPEGがバイナリで丸々入るようになっている。サーバにアップロードしたJPEGのExifをクライアントに返したいとき、文字列や数値データはJSONで返してやればそのまま表示もできるし問題ない。じゃあサムネイルとして入っているJPEGデータも一緒に返して表示させたいときはどうするかと考えてみた。

・JPEGもそのまんまJSONに詰め込んでみる
 Pythonではバイナリを扱うのは基本的にbytes型である。そしてbytes型はstr型との相互変換が容易なので、bytes型からJSON文字列を作るのも容易。
 JPEGデータは基本的に下記のような表現で得られるだろう。
b"\xff\xd8......"
 上記をJSON文字列にするためにjson.dumpsに渡したいので、str型に変換する。
data = b"\xff\xd8......".decode("latin-1")
 ちなみにb"\xff"のようなascii文字にない値を上記のようにデコードすると"\u00ff"というユニコードエスケープされた値が得られる。ascii文字に該当する値があるならその文字で表現される(b"\x50"→"P")。JSONの標準エンコードはUTF-8だが、非asciiならユニコードで表しておけばエンコードにおける文字の取り違いもなくなるだろうからなるほどというところ。
 str型ならjson.dumpsに渡すことができるので、それで得られたものをクライアントに返せばいい。
j_str = json.dumps({"thumbnail":data})
j_strはJSON文字列への変換によって'{"thumbnail": "\\u00ff\\u00d8......"}'となる。このthumbnailの値である"\\u00ff\\u00d8......"はJavaScript側でJSONとして扱われてオブジェクトに変換される際、"\u00ff\u00d8......"となってユニコードエスケープされた文字列として扱える。これを1バイトずつcharCodeAtで値を取得していけばJPEGのバイナリデータを復元できる。
var thumbnail = [];
for (var i=0; i<jObj["thumbnail"].length; i++) {
thumbnail[i] = jObj["thumbnail"].charCodeAt(i);
}


Stackoverflowで拾ったBase64エンコードのスクリプトがあるので、それを使って復元したJPEGをImageオブジェクトのsrcに入れられる形にする。
function encode64(input) {
var output = "",
chr1, chr2, chr3 = "",
enc1, enc2, enc3, enc4 = "",
i = 0,
KEY_STR = "ABCDEFGHIJKLMNOP" +
"QRSTUVWXYZabcdef" +
"ghijklmnopqrstuv" +
"wxyz0123456789+/" +
"=";

do {
chr1 = input[i++];
chr2 = input[i++];
chr3 = input[i++];

enc1 = chr1 >> 2;
enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
enc4 = chr3 & 63;

if (isNaN(chr2)) {
enc3 = enc4 = 64;
} else if (isNaN(chr3)) {
enc4 = 64;
}

output += KEY_STR.charAt(enc1) +
KEY_STR.charAt(enc2) +
KEY_STR.charAt(enc3) +
KEY_STR.charAt(enc4);
chr1 = chr2 = chr3 = "";
enc1 = enc2 = enc3 = enc4 = "";
} while (i < input.length);
return output;
};

var image = new Image();
image.src = "data:image/jpeg;base64," + encode64(thumbnail);
$("body").append(image);


 ここまでとりあえず勢いで動くものを書いた。途中、バイナリはサーバ側でBase64エンコードしておいたらどうなるかという考えが頭をチラついたので考えてみる。

 頭にチラついた考えをひとまずおいて書ききったのは、Base64エンコードがデータサイズを肥大化させるからである。しかしこれまでで実際に書いたやり方でもjson.dumpsによるユニコードエスケープによって肥大化が生じている。それなら素直にBase64エンコードしちゃったらどうだろう。Base64エンコードはバイナリデータを64文字の英数字のみを用いて表そうという規格である。これはascii文字に該当するので、これならb"\xff"などのような非ascii文字がデータに含まれなくなるので、ユニコードエスケープはなくなるだろう。JPEGデータのJSON文字列化とBase64エンコード化後のJSON文字列化でサイズ比較をしてみる。
 自分のライブラリにある適当なJPEG画像を読んでみた。それぞれサイズをとってみた。下記のスクリプト。
import base64
import json

with open("foo.jpg", "rb") as f:
data = f.read()

print("original size: " + str(len(data)))

print("base64: " + str(len(json.dumps(base64.b64encode(data).decode("latin-1")))))

print("not base64: " + str(len(json.dumps(data.decode("latin-1")))))

結果出力
original size: 3323371
base64: 4431166
not base64: 13479060

 オリジナルが3.3MBぐらいでbase64エンコード後のJSON文字列化がおおよそ33%増しの44MB、base64エンコードをしていないとおよそ300%上乗せで13.4MB。
 私的結論。JSONにバイナリデータ混ぜたいなら無理せずbase64エンコードしておく。
            

コメントの投稿

非公開コメント

プロフィール

hMatoba

Author:hMatoba
Github

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

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