http://events.php.gr.jp/events/show/96
はじめにPHP でバイナリ処理の話をあまり聞かないので、あえてニッチな所を狙って発表させて頂きます。
PHP というより、バイナリ入門といった要素が強いですが、 多分…、いつかお役に立つ日が来ると思います。どうか、ご容赦ください。 一応、自己紹介
まずは、バイナリの定義
なので、本発表では、バイナリファイルの事を、 テキストエディタで開いて読めない文字とか記号が表示されるようなファイル。 という事にしておきます。 バイナリの実例% hexdump -C aria.gif 00000000 47 49 46 38 39 61 c8 00 96 00 f7 00 00 00 00 00 |GIF89a..........| 00000010 ff ff ff 96 53 58 29 1b 1c e6 b0 b8 b2 69 76 37 |....SX)......iv7| 00000020 26 29 d6 96 a1 cb c6 c7 34 1c 22 48 31 38 2b 21 |&)......4."H18+!| <略> GIF ファイルですね。 $ hexdump -C kuriboo.png 00000000 89 50 4e 47 0d 0a 1a 0a 00 00 00 0d 49 48 44 52 |.PNG........IHDR| 00000010 00 00 00 c0 00 00 00 e0 08 06 00 00 00 55 70 69 |.............Upi| 00000020 31 00 00 00 04 73 42 49 54 08 08 08 08 7c 08 64 |1....sBIT....|.d| <略> PNG です。 先頭の4文字を見るとファイルの種類が大体分かるようになっていて、その後ろにはよく分からないデータが続いてます。 普通はここで読むのを諦めるのですが、このよく分からないデータを PHP で解釈して、欲しいデータを抜き出す方法について説明します。 PHP とバイナリPHP の string 型でバイナリ処理が簡単に出来るよ! というのが今回、紹介する Tips の肝です。 (注) PHP6 では UTF-8 対応したり取りやめたりと怪しいので、今回の発表はとりあえず、PHP5(PHP4 も多分大丈夫) only での話しだと思ってください。 本当に PHP の string 型でバイナリ処理できるの?まずは確認。 \0 は?C言語が典型ですが、\0 を文字列の(最後を表す)終端マークとして使う処理系も結構あるので、 string型をバイナリデータとして使う場合、間に \0 が入る事で途中で切れないか。
$s = "This is TEST\n"; $s{3} = "\0"; echo strlen($s)."\n"; echo $s; 13 Thi is TEST
8bitスルー?
bit 表現 |--------+ |XNNNNNNN| この X の bit を特別扱いされる懸念。 +--------+ $s = ' '; $s{3} = 'A'; $s{4} = chr(ord('A') | 0x80); // chr, ord は後で説明します var_dump(bin2hex($s)); string(10) "20202041c1"
バイナリを取り込んでそのまま出力
$data = file_get_contents($argv[1]); echo $data; % php echo.php saitama.jpg > output.dat % md5sum saitama.jpg output.dat 06f741dca38937df3702f6759aead28b saitama.jpg 06f741dca38937df3702f6759aead28b output.dat
byte処理
これだけ分かれば大丈夫。(今回の発表では、☆のついた関数が出てきます) pack 関連は次回発表の機会があれば… さて、JPEG で試してみます。 JPEG の解析
初めの一歩
% hexdump -C aria.jpg 00000000 ff d8 ff e0 00 10 4a 46 49 46 00 01 01 01 00 60 |......JFIF.....`| 00000010 00 60 00 00 ff e1 00 22 45 78 69 66 00 00 49 49 |.`....."Exif..II| 00000020 2a 00 08 00 00 00 01 00 00 51 04 00 01 00 00 00 |*........Q......| <略>
マーカ・コード 長さ データ FFxx(16bit) 16bit 可変サイズ
00000000 ff d8 ff e0 00 10 ... ~~~~~ ~~~~~~~~~~~~~~~ SOI APP0
JPEG を marker で分割してみる
ffd8
function marker($a, $b) { return chr($a).chr($b); } while($marker = $bs->getBytes(2)) { switch ($marker) { case marker(0xFF, 0xD8): // SOS $length = $data = null; break; case marker(0xFF, 0xE0): // APP0 case marker(0xFF, 0xE1): // APP1 $length = $bs->getValue(2); $data = $bs->getBytes($length - 2); break; case marker(0xFF, 0xDA): // SOS include RST $length = strlen($jpegdata) - $bs->getCursor() - 2; $data = $bs->getBytes($length); break; case marker(0xFF, 0xD9): // EOI $length = $data = null; $done = true; break; }
string(4) "ffd8" SOI (開始マーカ) string(4) "ffe1" APP1 (情報) string(4) "ffdb" DQT (量子化テーブル定義) string(4) "ffc0" SOF0 標準DCT圧縮 string(4) "ffc4" DHT (ハフマンテーブル情報) string(4) "ffda" SOS エンコードされたイメージデータ (DRI 含む) string(4) "ffd9" EOI (終了マーカ) 欲しい情報を調べるiPhone で写真を撮ると、GPS 情報が付くらしいので、抽出してみよう。
Little Endian ?
Exif の分解
foreach ($jpeg_chunk as $chunk) { if ($chunk['marker'] == marker(0xFF, 0xE1)) { $data = $chunk['data']; if (substr($data, 0, 4) == 'Exif') {
$exif_data = substr($data, 4); // Exif の文字列より後ろのデータ $bs_exif = new ByteStream($exif_data); // Exif 冒頭のタグ $tag = $bs_exif->getBytes(2); $tiff_header = $bs_exif->getBytes(2); if ($data == 'MM') { $order_le = false; // モトローラ形式(big endian) } else if ($data == 'II') { $order_le = true; // インテル形式(little endian) } $tiff_id = $bs_exif->getBytes(2); $pointer_to_0th_IFD = $bs_exif->getValue(4, $order_le); // IDF の分解 $exif_tag_num = $bs_exif->getValue(2, $order_le); for ($i=0; $i< $exif_tag_num; $i++) { $exif_tag = $bs_exif->getBytes(2); $exif_type = $bs_exif->getValue(2, $order_le); $exif_number = $bs_exif->getValue(4, $order_le); $exif_offset = $bs_exif->getValue(4, $order_le); } $pointer_to_next_IFD = $bs_exif->getValue(2, $order_le); GPSInfo の分解
XXX: tiff header=MM XXX: tiff id=002a XXX: 0th IFD pointer(8) XXX exif tag(010f,2, 6, 146) XXX exif tag(0110,2, 11, 152) XXX exif tag(0112,3, 1, 65536) XXX exif tag(011a,5, 1, 164) XXX exif tag(011b,5, 1, 172) XXX exif tag(0128,3, 1, 131072) XXX exif tag(0131,2, 6, 180) XXX exif tag(0132,2, 20, 186) XXX exif tag(0213,3, 1, 65536) XXX exif tag(8769,4, 1, 206) XXX exif tag(8825,4, 1, 544) XXX: gps_tag_num=7 XXX gps tag(0001,2, 2, N) XXX gps tag(0002,5, 3, 634) XXX gps tag(0003,2, 2, E) XXX gps tag(0004,5, 3, 658) XXX gps tag(0007,5, 3, 682) XXX gps tag(0010,2, 2, T) XXX gps tag(0011,5, 1, 706) XXX: pointer_to_next_IFD=0
bit処理は…
今回のまとめ
蛇足的な Tips
次回予告
あ…
以上ですありがとうございました。 日記でまとめ
|