<?php

// http://siisise.net/jpeg.html#format

class ByteStream {
    private 
$_data;
    private 
$_cursor;
    function 
__construct($data$cursor 0) {
        
$this->_data $data;
        
$this->_cursor $cursor;
    }
    function 
getBytes($size) {
        if (
strlen($this->_data) <= $this->_cursor $size) {
//            throw new Exception("strlen($this->_data) <= $this->_cursor + $size");
            
return 0// XXX
        
}
        
$ret substr($this->_data$this->_cursor$size);
        
$this->_cursor += $size;
//var_dump($this->_cursor);
        
return $ret;
    }
    
// order_le: big endian: 0, little endian: 1
    
function getValue($size$order_le false) {
        
$data $this->getBytes($size$order_le);
        if (
$order_le) {
            
$data strrev($data); // be to le order
        
}
        
$value 0;
        for (
$i 0$i $size$i++) {
            
$value <<= 8;
            
$value += ord($data[$i]);
        }
        return 
$value;
    }
    function 
getCursor() {
        return 
$this->_cursor;
    }
    
// TODO: getBytes, getValue と統合する (説明用にあえてこのまま)
    //
    
function getBytesByOffset($offset$size) {
        if (
strlen($this->_data) <= $offset $size) {
            throw new 
Exception("strlen($this->_data) <= $offset + $size");
        }
        
$ret substr($this->_data$offset$size);
        return 
$ret;
    }
    
// order_le: big endian: 0, little endian: 1
    
function getValueByOffset($offset$size$order_le false) {
        
$data $this->getBytesByOffset($offset$size$order_le);
        if (
$order_le) {
            
$ret strrev($ret); // be to le order
        
}
        
$value 0;
        for (
$i 0$i $size$i++) {
            
$value <<= 8;
            
$value += ord($data[$i]);
        }
        return 
$value;
    }
}

function 
marker($a$b) { return chr($a).chr($b); }

$jpegdata file_get_contents($argv[1]);

$bs = new ByteStream($jpegdata);

$jpeg_chunk = array();
$done false;
while(
$marker $bs->getBytes(2)) {
    switch (
$marker) {
      case 
marker(0xFF0xD8): // SOS 
          
$length $data null;
          break;
      default: 
// etc
          
if (ord($marker{0}) != 0xFF) {
              
$marker_hex bin2hex($marker);
              throw new 
Exception("Illegal JPEG marker($marker_hex)");
          }
      case 
marker(0xFF0xE0): // APP0
      
case marker(0xFF0xE1): // APP1
        
$length $bs->getValue(2);
        
$data $bs->getBytes($length 2);
        break;
      case 
marker(0xFF0xDA): // SOS include RST
          
$length strlen($jpegdata) - $bs->getCursor() - 2;
          
$data $bs->getBytes($length);
          break;
      case 
marker(0xFF0xD9): // EOI
          
$length $data null;
          
$done true;
          break;
    }
    
$jpeg_chunk[] = array(
        
'marker' => $marker,
        
'length' => $length,
        
'data' => $data);
    if (
$done) {
        break;
    }
}

function 
exif_tag($a$b) { return chr($a).chr($b); } // marker と同じ

foreach ($jpeg_chunk as $chunk) {
    if (
$chunk['marker'] == marker(0xFF0xE1)) {
        
$data $chunk['data'];
        if (
substr($data04) == 'Exif') {
            
$exif_data substr($data4);
            
// echo $exif_data; // output exit data
            
$bs_exif = new ByteStream($exif_data);
            
// TIFF Header
            
$order_le false// big endian
            
$tag $bs_exif->getBytes(2);
            if (
$tag != exif_tag(0x000x00)) {
                continue; 
// unknown exif
            
}
            
$data $bs_exif->getBytes(2);
            if (
$data == 'MM') {
                
$order_le false// モトローラ形式(big endian)
            
} else if ($data == 'II') {
                
$order_le true// インテル形式(little endian)
            
} else {
                throw new 
Exception("Illegal Exif Tiff Tag($data)");
            }
            echo 
"XXX: tiff header=$data\n";
            
$tiff_id $bs_exif->getBytes(2);
            
$pointer_to_0th_IFD $bs_exif->getValue(4$order_le);
            echo 
"XXX: tiff id=".bin2hex($tiff_id)."\n";
            echo 
"XXX: 0th IFD pointer($pointer_to_0th_IFD)\n";
            if (
$pointer_to_0th_IFD != 8) {
                throw new 
Exception("Sorry. pointe_to_0th_IFD($pointer_to_0th_IFD) must be 8...");
            }
            
// 0th IFD
            
$exif_tag_num $bs_exif->getValue(2$order_le); // while
//            echo "XXX: exif_tag_num=$exif_tag_num\n";
            
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);
                echo 
"XXX exif tag(".bin2hex($exif_tag).",$exif_type$exif_number$exif_offset)\n";
                if (
$exif_tag == exif_tag(0x880x25)) { // 0x8825: GPS Info
                    // 2 = tiff header(MM or II) offset
                    
$bs_gps = new ByteStream($exif_data$exif_offset 2);
                    
// GPS IFD  // 本来は再帰的に処理を書く
                    
$gps_tag_num $bs_gps->getValue(2$order_le); // while
                    
echo "XXX: gps_tag_num=$gps_tag_num\n";
                    for (
$j=0$j$gps_tag_num$j++) {
                        
$gps_tag  $bs_gps->getBytes(2);
                        
$gps_type $bs_gps->getValue(2$order_le);
                        
$gps_number $bs_gps->getValue(4$order_le);
                        switch (
$gps_type) {
                            case 
2// ASCII
                                
$gps_offset $bs_gps->getBytes(4);
                                break;
                            default:
                                
$gps_offset $bs_gps->getValue(4$order_le);
                                break;
                        }
                        echo 
"XXX gps tag(".bin2hex($gps_tag).",$gps_type$gps_number$gps_offset)\n";
                    }
                }
            }
            
$pointer_to_next_IFD $bs_exif->getValue(2$order_le);
            echo 
"XXX: pointer_to_next_IFD=$pointer_to_next_IFD\n";
        }
    }

}