Skip to content

Instantly share code, notes, and snippets.

@jou4
Created September 6, 2011 07:43
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jou4/1196871 to your computer and use it in GitHub Desktop.
Save jou4/1196871 to your computer and use it in GitHub Desktop.
<?php
$exif = new Exif("test.jpg");
$dt = "2011:08:10 12:00:00";
$exif->add_entry("DateTime", TagEntry::FORMAT_ASCII_STRINGS, 19, $dt);
$exif->add_entry("DateTimeOriginal", TagEntry::FORMAT_ASCII_STRINGS, 19, $dt);
$exif->add_entry("DateTimeDigitized", TagEntry::FORMAT_ASCII_STRINGS, 19, $dt);
file_put_contents("test2.jpg", $exif->to_binary());
class TagEntry {
const BYTE_SIZE = 12;
const FORMAT_UNSIGNED_BYTE = 1;
const FORMAT_ASCII_STRINGS = 2;
const FORMAT_UNSIGNED_SHORT = 3;
const FORMAT_UNSIGNED_LONG = 4;
const FORMAT_UNSIGNED_RATIONAL = 5;
const FORMAT_SIGNED_BYTE = 6;
const FORMAT_UNDEFINED = 7;
const FORMAT_SIGNED_SHORT = 8;
const FORMAT_SIGNED_LONG = 9;
const FORMAT_SIGNED_RATIONAL = 10;
const FORMAT_SIGNED_FLOAT = 11;
const FORMAT_DOUBLE_FLOAT = 12;
var $_data_size = array(
self::FORMAT_UNSIGNED_BYTE => 1,
self::FORMAT_ASCII_STRINGS => 1,
self::FORMAT_UNSIGNED_SHORT => 2,
self::FORMAT_UNSIGNED_LONG => 4,
self::FORMAT_UNSIGNED_RATIONAL => 8,
self::FORMAT_SIGNED_BYTE => 1,
self::FORMAT_UNDEFINED => 1,
self::FORMAT_SIGNED_SHORT => 2,
self::FORMAT_SIGNED_LONG => 4,
self::FORMAT_SIGNED_RATIONAL => 8,
self::FORMAT_SIGNED_FLOAT => 4,
self::FORMAT_DOUBLE_FLOAT => 8,
);
var $_tag; // 0xXXXX
var $_format;
var $_num;
var $_val;
var $_offset = 0;
function __construct($tag, $format, $num, $val){
$this->_tag = $tag;
$this->_format = $format;
$this->_num = $num;
$this->_val = $val;
}
function is_over_size(){
$size = $this->size();
return ($size > 4);
}
function size(){
return $this->_data_size[$this->_format] * $this->_num;
}
function to_binary(){
$data = "";
$data .= pack("v", $this->_tag);
$data .= pack("v", $this->_format);
$data .= pack("V", $this->_num);
if($this->_offset){
$data .= pack("V", $this->_offset);
}
else{
$data .= $this->_val;
}
return $data;
}
}
class IFD {
var $_enabled_tags;
var $_entries;
function __construct(){
$this->_enabled_tags = array();
$this->_entries = array();
}
function is_enabled_tag($tag){
return ( ! empty($this->_enabled_tags[$tag]));
}
function add_entry($tag, $format, $num, $val){
if( ! $this->is_enabled_tag($tag)){
return FALSE;
}
$this->_entries[$tag] = new TagEntry($this->_enabled_tags[$tag], $format, $num, $val);
}
function has_entry(){
return (count(array_values($this->_entries)) > 0);
}
function to_binary($base, $has_next_ifd = FALSE){
if( ! $this->has_entry()){
return array(0, "", "", "");
}
$entries = array_values($this->_entries);
$entries_count = count($entries);
// pointer of base + byte of entry_count_area + byte of entries + byte of next_link_area
$data_section_base = $base + 2 + TagEntry::BYTE_SIZE * count($entries) + 4;
$dir_section = "";
$next_link = "";
$data_section = "";
// 2byte - number of entry
$dir_section .= pack("v", $entries_count);
// 12byte * n - entries
foreach($entries as $entry){
if($entry->is_over_size()){
$size = $entry->size();
$val = $entry->_val;
$entry->_offset = $data_section_base;
$data_section_base += $size;
$data_section .= $val;
}
$dir_section .= $entry->to_binary();
}
// 4byte - offset to next ifd
if($has_next_ifd){
$size = strlen($dir_section) + 4 + strlen($data_section);
$next_link .= pack("V", $base + $size);
}
else{
$next_link .= pack("V", 0);
}
return array(strlen($dir_section . $next_link . $data_section), $dir_section, $next_link, $data_section);
}
}
class IFD0 extends IFD {
function __construct(){
parent::__construct();
$this->_enabled_tags = array(
"ImageDescription" => 0x010e,
"Make" => 0x010f,
"Model" => 0x0110,
"Orientation" => 0x0112,
"XResolution" => 0x011a,
"YResolution" => 0x011b,
"ResolutionUnit" => 0x0128,
"Software" => 0x0131,
"DateTime" => 0x0132,
"WhitePoint" => 0x013e,
"PrimaryChromaticities" => 0x013f,
"YCbCrCoefficients" => 0x0211,
"YCbCrPositioning" => 0x0213,
"ReferenceBlackWhite" => 0x0214,
"Copyright" => 0x8298,
"ExifIFDPointer" => 0x8769,
);
}
function to_binary($base, $has_next_ifd = FALSE){
if( ! $this->has_entry()){
return array(0, "", "", "");
}
// dummy entry
$this->add_entry("ExifIFDPointer", TagEntry::FORMAT_UNSIGNED_LONG, 1, pack("V", 0));
list($size, $dir, $link, $data) = parent::to_binary($base, $has_next_ifd);
$exif_ifd_base = $base + $size;
// set real entry
$this->add_entry("ExifIFDPointer", TagEntry::FORMAT_UNSIGNED_LONG, 1, pack("V", $exif_ifd_base));
// recalc
return parent::to_binary($base, $has_next_ifd);
}
}
class IFDExif extends IFD {
function __construct(){
parent::__construct();
$this->_enabled_tags = array(
"ExposureTime" => 0x829a,
"FNumber" => 0x829d,
"ExposureProgram" => 0x8822,
"ISOSpeedRatings" => 0x8827,
"ExifVersion" => 0x9000,
"DateTimeOriginal" => 0x9003,
"DateTimeDigitized" => 0x9004,
"ComponentsConfiguration" => 0x9101,
"CompressedBitsPerPixel" => 0x9102,
"ShutterSpeedValue" => 0x9201,
"ApertureValue" => 0x9202,
"BrightnessValue" => 0x9203,
"ExposureBiasValue" => 0x9204,
"MaxApertureValue" => 0x9205,
"SubjectDistance" => 0x9206,
"MeteringMode" => 0x9207,
"LightSource" => 0x9208,
"Flash" => 0x9209,
"FocalLength" => 0x920a,
"MakerNote" => 0x927c,
"UserComment" => 0x9286,
"SubsecTime" => 0x9290,
"SubsecTimeOriginal" => 0x9291,
"SubsecTimeDigitized" => 0x9292,
"FlashPixVersion" => 0xa000,
"ColorSpace" => 0xa001,
"ExifImageWidth" => 0xa002,
"ExifImageHeight" => 0xa003,
"RelatedSoundFile" => 0xa004,
"InteroperabilityIFDPointer" => 0xa005,
"FocalPlaneXResolution" => 0xa20e,
"FocalPlaneYResolution" => 0xa20f,
"FocalPlaneResolutionUnit" => 0xa210,
"ExposureIndex" => 0xa215,
"SensingMethod" => 0xa217,
"FileSource" => 0xa300,
"SceneType" => 0xa301,
"CFAPattern" => 0xa302,
);
}
}
class IFD1 extends IFD {
function __construct(){
parent::__construct();
$this->_enabled_tags = array(
"ImageWidth" => 0x0100,
"ImageLength" => 0x0101,
"BitsPerSample" => 0x0102,
"Compression" => 0x0103,
"PhotometricInterpretation" => 0x0106,
"StripOffsets" => 0x0111,
"Orientation" => 0x0112,
"SamplesPerPixel" => 0x0115,
"RowsPerStrip" => 0x0116,
"StripByteConunts" => 0x0117,
"XResolution" => 0x011a,
"YResolution" => 0x011b,
"PlanarConfiguration" => 0x011c,
"ResolutionUnit" => 0x0128,
"JpegInterchangeFormat" => 0x0201,
"JpegInterchangeFormatLength" => 0x0202,
"YCbCrCoefficients" => 0x0211,
"YCbCrSubSampling" => 0x0212,
"YCbCrPositioning" => 0x0213,
"ReferenceBlackWhite" => 0x0214,
);
}
}
class Exif {
var $_ifd0;
var $_ifdexif;
var $_ifd1;
var $_path;
function __construct($path){
$this->_ifd0 = new IFD0();
$this->_ifdexif = new IFDExif();
$this->_ifd1 = new IFD1();
$this->_path = $path;
$this->_ifd0->add_entry("Make", TagEntry::FORMAT_ASCII_STRINGS, 4, str_pad("", 4));
$this->_ifd0->add_entry("Model", TagEntry::FORMAT_ASCII_STRINGS, 4, str_pad("", 4));
$this->_ifd0->add_entry("DateTime", TagEntry::FORMAT_ASCII_STRINGS, 19, str_pad("", 19));
$this->_ifdexif->add_entry("DateTimeOriginal", TagEntry::FORMAT_ASCII_STRINGS, 19, str_pad("", 19));
$this->_ifdexif->add_entry("DateTimeDigitized", TagEntry::FORMAT_ASCII_STRINGS, 19, str_pad("", 19));
}
function add_entry($tag, $format, $num, $val){
if($this->_ifd0->is_enabled_tag($tag)){
$this->_ifd0->add_entry($tag, $format, $num, $val);
}
else if($this->_ifdexif->is_enabled_tag($tag)){
$this->_ifdexif->add_entry($tag, $format, $num, $val);
}
else if($this->_ifd1->is_enabled_tag($tag)){
$this->_ifd1->add_entry($tag, $format, $num, $val);
}
else{
return FALSE;
}
}
function to_binary(){
$reader = new Reader($this->_path);
$data = "";
// SOI(2byte)
$data .= $reader->read(2);
// APP0
$marker = $reader->read_unsigned_short();
if($marker == 0xFFE0){
$size = $reader->read_unsigned_short();
$data .= pack("n", $marker);
$data .= pack("n", $size);
$data .= $reader->read($size - 2); // exclude size area
}
else{
// back
$reader->seek(-2);
}
// APP1
$app1 = "";
// Exif header
$app1 .= "Exif" . pack("n", 0x0000);
// Tiff header - intel format
$base = 8;
$app1 .= "II" . pack("v", 0x002A) . pack("V", $base);
$current_base = $base;
// IFD0
list($ifd0_size, $ifd0_dir, $ifd0_link, $ifd0_data) =
$this->_ifd0->to_binary($current_base, $this->_ifd1->has_entry());
$current_base += $ifd0_size;
// ExifIFD
list($ifdexif_size, $ifdexif_dir, $ifdexif_link, $ifdexif_data) = $this->_ifdexif->to_binary($current_base);
$current_base += $ifdexif_size;
// IFD1
list($ifd1_size, $ifd1_dir, $ifd1_link, $ifd1_data) = $this->_ifd1->to_binary($current_base);
$current_base += $ifd1_size;
$app1 .= $ifd0_dir;
if($this->_ifd1->has_entry()){
$app1 .= pack("V", $base + $ifd0_size + $ifdexif_size);
}
else{
$app1 .= $ifd0_link;
}
$app1 .= $ifd0_data;
$app1 .= $ifdexif_dir . $ifdexif_link . $ifdexif_data;
$app1 .= $ifd1_dir . $ifd1_link . $ifd1_data;
if($ifd0_size + $ifdexif_size + $ifd1_size > 0){
// Marker
$data .= pack("n", 0xFFE1);
// App1 size
$data .= pack("n", 2 + strlen($app1));
// App1 data
$data .= $app1;
}
// remain data
$data .= $reader->read();
$reader->close();
return $data;
}
}
class Reader {
const BYTE_ORDER_BE = 1;
const BYTE_ORDER_LE = 2;
function __construct($path = ""){
$this->_byte_order = self::BYTE_ORDER_BE;
if($path){
$this->open($path);
}
}
function open($path){
$this->_fp = fopen($path, "r");
}
function close(){
fclose($this->_fp);
}
function set_byte_order($order){
$this->_byte_order = $order;
}
function get_byte_order($order){
return $this->_byte_order;
}
function is_be(){
return ($this->_byte_order == self::BYTE_ORDER_BE);
}
function is_le(){
return ( ! $this->is_be());
}
function seek($offset){
fseek($this->_fp, $offset, SEEK_CUR);
}
function read($length = 0){
if(feof($this->_fp)){
return NULL;
}
if($length){
return fread($this->_fp, $length);
}
else{
$data = '';
while( ! feof($this->_fp)){
$data .= fread($this->_fp, 4096);
}
return $data;
}
}
function read_hex_string($length){
$data = $this->read($length);
$format = "H*";
return array_shift(unpack($format, $data));
}
function read_byte(){
$data = $this->read(1);
$format = "c";
return array_shift(unpack($format, $data));
}
function read_unsigned_byte(){
$data = $this->read(1);
$format = "C";
return array_shift(unpack($format, $data));
}
function read_unsigned_short(){
$data = $this->read(2);
$format = ($this->is_be()) ? "n" : "v";
return array_shift(unpack($format, $data));
}
function read_unsigned_long(){
$data = $this->read(4);
$format = ($this->is_be()) ? "N" : "V";
return array_shift(unpack($format, $data));
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment