1<?php 2//================================================================================================ 3//================================================================================================ 4//================================================================================================ 5/* 6 Exifer 1.5 7 Extracts EXIF information from digital photos. 8 9 Copyright � 2005 Jake Olefsky 10 http://www.offsky.com/software/exif/index.php 11 jake@olefsky.com 12 13 This program is free software; you can redistribute it and/or modify it under the terms of 14 the GNU General Public License as published by the Free Software Foundation; either version 2 15 of the License, or (at your option) any later version. 16 17 This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 18 without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 19 See the GNU General Public License for more details. http://www.gnu.org/copyleft/gpl.html 20 21 SUMMARY: 22 This script will correctly parse all of the EXIF data included in images taken 23 with digital cameras. It will read the IDF0, IDF1, SubIDF and InteroperabilityIFD 24 fields as well as parsing some of the MakerNote fields that vary depending on 25 camera make and model. This script parses more tags than the internal PHP exif 26 implementation and it will correctly identify and decode what all the values mean. 27 28 This version will correctly parse the MakerNote field for Nikon, Olympus, and Canon 29 digital cameras. Others will follow. 30 31 TESTED WITH: 32 Nikon CoolPix 700 33 Nikon CoolPix 4500 34 Nikon CoolPix 950 35 Nikon Coolpix 5700 36 Canon PowerShot S200 37 Canon PowerShot S110 38 Olympus C2040Z 39 Olympus C960 40 Canon Ixus 41 Canon EOS 300D 42 Canon Digital Rebel 43 Canon EOS 10D 44 Canon PowerShot G2 45 FujiFilm DX 10 46 FujiFilm MX-1200 47 FujiFilm FinePix2400 48 FujiFilm FinePix2600 49 FujiFilm FinePix S602 50 FujiFilm FinePix40i 51 Sony D700 52 Sony Cybershot 53 Kodak DC210 54 Kodak DC240 55 Kodak DC4800 56 Kodak DX3215 57 Ricoh RDC-5300 58 Sanyo VPC-G250 59 Sanyo VPC-SX550 60 Epson 3100z 61 62 If you improve this script or test it with a camera not on my list, please email me a copy of 63 the improvement so everyone can benefit from it. Thanks! 64 65 66 VERSION HISTORY: 67 68 1.0 Sept 23, 2002 69 70 First Public Release 71 72 1.1 Jan 25, 2003 73 74 +Gracefully handled the error case where you pass an empty string to this library 75 +Fixed an inconsistency in the Olympus Camera parsing module 76 +Added support for parsing the MakerNote of Canon images. 77 +Modified how the imagefile is opened so it works for windows machines. 78 +Correctly parses the FocalPlaneResolutionUnit and PhotometricInterpretation fields 79 +Negative rational numbers are properly displayed 80 +Strange old cameras that use Motorola endineness are now properly supported 81 +Tested with several more cameras 82 83 Potential Problem: Negative Shorts and Negative Longs may not be correctly displayed, but I 84 have not yet found an example of negative shorts or longs being used. 85 86 1.2 Mar 30, 2003 87 88 +Fixed an error that was displayed if you edited your image with WinXP's image viewer 89 +Fixed a bug that caused some images saved from 3rd party software to not parse correctly 90 +Changed the ExposureTime tag to display in fractional seconds rather than decimal 91 +Updated the ShutterSpeedValue tag to have the units of 'sec' 92 +Added support for parsing the MakeNote of FujiFilm images 93 +Added support for parsing the MakeNote of Sanyo images 94 +Fixed a bug with parsing some Olympus MakerNote tags 95 +Tested with several more cameras 96 97 1.3 Jun 15, 2003 98 99 +Fixed Canon MakerNote support for some models 100 (Canon has very difficult and inconsistent MakerNote syntax) 101 +Negative signed shorts and negative signed longs are properly displayed 102 +Several more tags are defined 103 +More information in my comments about what each tag is 104 +Parses and Displays GPS information if available 105 +Tested with several more cameras 106 107 1.4 Sept 14, 2003 108 109 +This software is now licensed under the GNU General Public License 110 +Exposure time is now correctly displayed when the numerator is 10 111 +Fixed the calculation and display of ShutterSpeedValue, ApertureValue and MaxApertureValue 112 +Fixed a bug with the GPS code 113 +Tested with several more cameras 114 115 1.5 Feb 18, 2005 116 117 +It now gracefully deals with a passed in file that cannot be found. 118 +Fixed a GPS bug for the parsing of Altitude and other signed rational numbers 119 +Defined more values for Canon cameras. 120 +Added "bulb" detection for ShutterSpeed 121 +Made script loading a little faster and less memory intensive. 122 +Bug fixes 123 +Better error reporting 124 +Graceful failure for files with corrupt exif info. 125 +QuickTime (including iPhoto) messes up the Makernote tag for certain photos (no workaround yet) 126 +Now reads exif information when the jpeg markers are out of order 127 +Gives raw data output for IPTC, COM and APP2 fields which are sometimes set by other applications 128 +Improvements to Nikon Makernote parsing 129*/ 130//================================================================================================ 131//================================================================================================ 132//================================================================================================ 133 134 135 136 137//================================================================================================ 138//================================================================================================ 139// Converts from Intel to Motorola endien. Just reverses the bytes (assumes hex is passed in) 140//================================================================================================ 141//================================================================================================ 142function intel2Moto($intel) { 143 $len = strlen($intel); 144 $moto=""; 145 for($i=0; $i<=$len; $i+=2) { 146 $moto.=substr($intel,$len-$i,2); 147 } 148 return $moto; 149} 150 151//================================================================================================ 152//================================================================================================ 153// Looks up the name of the tag 154//================================================================================================ 155//================================================================================================ 156function lookup_tag($tag) { 157 switch($tag) { 158 159 //used by IFD0 "Camera Tags" 160 case "000b": $tag = "ACDComment";break; //text string up to 999 bytes long 161 case "00fe": $tag = "ImageType";break; //integer -2147483648 to 2147483647 162 case "0106": $tag = "PhotometricInterpret";break; //?? Please send sample image with this tag 163 case "010e": $tag = "ImageDescription";break; //text string up to 999 bytes long 164 case "010f": $tag = "Make";break; //text string up to 999 bytes long 165 case "0110": $tag = "Model";break; //text string up to 999 bytes long 166 case "0112": $tag = "Orientation";break; //integer values 1-9 167 case "0115": $tag = "SamplePerPixel";break; //integer 0-65535 168 case "011a": $tag = "xResolution";break; //positive rational number 169 case "011b": $tag = "yResolution";break; //positive rational number 170 case "011c": $tag = "PlanarConfig";break; //integer values 1-2 171 case "0128": $tag = "ResolutionUnit";break; //integer values 1-3 172 case "0131": $tag = "Software";break; //text string up to 999 bytes long 173 case "0132": $tag = "DateTime";break; //YYYY:MM:DD HH:MM:SS 174 case "013b": $tag = "Artist";break; //text string up to 999 bytes long 175 case "013c": $tag = "HostComputer";break; //text string 176 case "013e": $tag = "WhitePoint";break; //two positive rational numbers 177 case "013f": $tag = "PrimaryChromaticities";break; //six positive rational numbers 178 case "0211": $tag = "YCbCrCoefficients";break; //three positive rational numbers 179 case "0213": $tag = "YCbCrPositioning";break; //integer values 1-2 180 case "0214": $tag = "ReferenceBlackWhite";break; //six positive rational numbers 181 case "8298": $tag = "Copyright";break; //text string up to 999 bytes long 182 case "8649": $tag = "PhotoshopSettings";break; //?? 183 case "8825": $tag = "GPSInfoOffset";break; 184 case "8769": $tag = "ExifOffset";break; //positive integer 185 186 //used by Exif SubIFD "Image Tags" 187 case "829a": $tag = "ExposureTime";break; //seconds or fraction of seconds 1/x 188 case "829d": $tag = "FNumber";break; //positive rational number 189 case "8822": $tag = "ExposureProgram";break; //integer value 1-9 190 case "8824": $tag = "SpectralSensitivity";break; //?? 191 case "8827": $tag = "ISOSpeedRatings";break; //integer 0-65535 192 case "9000": $tag = "ExifVersion";break; //?? 193 case "9003": $tag = "DateTimeOriginal";break; //YYYY:MM:DD HH:MM:SS 194 case "9004": $tag = "DateTimedigitized";break; //YYYY:MM:DD HH:MM:SS 195 case "9101": $tag = "ComponentsConfiguration";break; //?? 196 case "9102": $tag = "CompressedBitsPerPixel";break; //positive rational number 197 case "9201": $tag = "ShutterSpeedValue";break; //seconds or fraction of seconds 1/x 198 case "9202": $tag = "ApertureValue";break; //positive rational number 199 case "9203": $tag = "BrightnessValue";break; //positive rational number 200 case "9204": $tag = "ExposureBiasValue";break; //positive rational number (EV) 201 case "9205": $tag = "MaxApertureValue";break; //positive rational number 202 case "9206": $tag = "SubjectDistance";break; //positive rational number (meters) 203 case "9207": $tag = "MeteringMode";break; //integer 1-6 and 255 204 case "9208": $tag = "LightSource";break; //integer 1-255 205 case "9209": $tag = "Flash";break; //integer 1-255 206 case "920a": $tag = "FocalLength";break; //positive rational number (mm) 207 case "9213": $tag = "ImageHistory";break; //text string up to 999 bytes long 208 case "927c": $tag = "MakerNote";break; //a bunch of data 209 case "9286": $tag = "UserComment";break; //text string 210 case "9290": $tag = "SubsecTime";break; //text string up to 999 bytes long 211 case "9291": $tag = "SubsecTimeOriginal";break; //text string up to 999 bytes long 212 case "9292": $tag = "SubsecTimeDigitized";break; //text string up to 999 bytes long 213 case "a000": $tag = "FlashPixVersion";break; //?? 214 case "a001": $tag = "ColorSpace";break; //values 1 or 65535 215 case "a002": $tag = "ExifImageWidth";break; //ingeter 1-65535 216 case "a003": $tag = "ExifImageHeight";break; //ingeter 1-65535 217 case "a004": $tag = "RelatedSoundFile";break; //text string 12 bytes long 218 case "a005": $tag = "ExifInteroperabilityOffset";break; //positive integer 219 case "a20c": $tag = "SpacialFreqResponse";break; //?? 220 case "a20b": $tag = "FlashEnergy";break; //positive rational number 221 case "a20e": $tag = "FocalPlaneXResolution";break; //positive rational number 222 case "a20f": $tag = "FocalPlaneYResolution";break; //positive rational number 223 case "a210": $tag = "FocalPlaneResolutionUnit";break; //values 1-3 224 case "a214": $tag = "SubjectLocation";break; //two integers 0-65535 225 case "a215": $tag = "ExposureIndex";break; //positive rational number 226 case "a217": $tag = "SensingMethod";break; //values 1-8 227 case "a300": $tag = "FileSource";break; //integer 228 case "a301": $tag = "SceneType";break; //integer 229 case "a302": $tag = "CFAPattern";break; //undefined data type 230 231 case "a401": $tag = "CustomerRender";break; //values 0 or 1 232 case "a402": $tag = "ExposureMode";break; //values 0-2 233 case "a403": $tag = "WhiteBalance";break; //values 0 or 1 234 case "a404": $tag = "DigitalZoomRatio";break; //positive rational number 235 case "a405": $tag = "FocalLengthIn35mmFilm";break; //?? 236 case "a406": $tag = "SceneCaptureMode";break; //values 0-3 237 case "a407": $tag = "GainControl";break; //values 0-4 238 case "a408": $tag = "Contrast";break; //values 0-2 239 case "a409": $tag = "Saturation";break; //values 0-2 240 case "a40a": $tag = "Sharpness";break; //values 0-2 241 242 243 //used by Interoperability IFD 244 case "0001": $tag = "InteroperabilityIndex";break; //text string 3 bytes long 245 case "0002": $tag = "InteroperabilityVersion";break; //datatype undefined 246 case "1000": $tag = "RelatedImageFileFormat";break; //text string up to 999 bytes long 247 case "1001": $tag = "RelatedImageWidth";break; //integer in range 0-65535 248 case "1002": $tag = "RelatedImageLength";break; //integer in range 0-65535 249 250 //used by IFD1 "Thumbnail" 251 case "0100": $tag = "ImageWidth";break; //integer in range 0-65535 252 case "0101": $tag = "ImageLength";break; //integer in range 0-65535 253 case "0102": $tag = "BitsPerSample";break; //integers in range 0-65535 254 case "0103": $tag = "Compression";break; //values 1 or 6 255 case "0106": $tag = "PhotometricInterpretation";break; //values 0-4 256 case "010e": $tag = "ThumbnailDescription";break; //text string up to 999 bytes long 257 case "010f": $tag = "ThumbnailMake";break; //text string up to 999 bytes long 258 case "0110": $tag = "ThumbnailModel";break; //text string up to 999 bytes long 259 case "0111": $tag = "StripOffsets";break; //?? 260 case "0112": $tag = "ThumbnailOrientation";break; //integer 1-9 261 case "0115": $tag = "SamplesPerPixel";break; //?? 262 case "0116": $tag = "RowsPerStrip";break; //?? 263 case "0117": $tag = "StripByteCounts";break; //?? 264 case "011a": $tag = "ThumbnailXResolution";break; //positive rational number 265 case "011b": $tag = "ThumbnailYResolution";break; //positive rational number 266 case "011c": $tag = "PlanarConfiguration";break; //values 1 or 2 267 case "0128": $tag = "ThumbnailResolutionUnit";break; //values 1-3 268 case "0201": $tag = "JpegIFOffset";break; 269 case "0202": $tag = "JpegIFByteCount";break; 270 case "0212": $tag = "YCbCrSubSampling";break; 271 272 //misc 273 case "00ff": $tag = "SubfileType";break; 274 case "012d": $tag = "TransferFunction";break; 275 case "013d": $tag = "Predictor";break; 276 case "0142": $tag = "TileWidth";break; 277 case "0143": $tag = "TileLength";break; 278 case "0144": $tag = "TileOffsets";break; 279 case "0145": $tag = "TileByteCounts";break; 280 case "014a": $tag = "SubIFDs";break; 281 case "015b": $tag = "JPEGTables";break; 282 case "828d": $tag = "CFARepeatPatternDim";break; 283 case "828e": $tag = "CFAPattern";break; 284 case "828f": $tag = "BatteryLevel";break; 285 case "83bb": $tag = "IPTC/NAA";break; 286 case "8773": $tag = "InterColorProfile";break; 287 288 case "8828": $tag = "OECF";break; 289 case "8829": $tag = "Interlace";break; 290 case "882a": $tag = "TimeZoneOffset";break; 291 case "882b": $tag = "SelfTimerMode";break; 292 case "920b": $tag = "FlashEnergy";break; 293 case "920c": $tag = "SpatialFrequencyResponse";break; 294 case "920d": $tag = "Noise";break; 295 case "9211": $tag = "ImageNumber";break; 296 case "9212": $tag = "SecurityClassification";break; 297 case "9214": $tag = "SubjectLocation";break; 298 case "9215": $tag = "ExposureIndex";break; 299 case "9216": $tag = "TIFF/EPStandardID";break; 300 case "a20b": $tag = "FlashEnergy";break; 301 302 default: $tag = "unknown:".$tag;break; 303 } 304 return $tag; 305 306} 307 308//================================================================================================ 309//================================================================================================ 310// Looks up the datatype 311//================================================================================================ 312//================================================================================================ 313function lookup_type(&$type,&$size) { 314 switch($type) { 315 case "0001": $type = "UBYTE";$size=1;break; 316 case "0002": $type = "ASCII";$size=1;break; 317 case "0003": $type = "USHORT";$size=2;break; 318 case "0004": $type = "ULONG";$size=4;break; 319 case "0005": $type = "URATIONAL";$size=8;break; 320 case "0006": $type = "SBYTE";$size=1;break; 321 case "0007": $type = "UNDEFINED";$size=1;break; 322 case "0008": $type = "SSHORT";$size=2;break; 323 case "0009": $type = "SLONG";$size=4;break; 324 case "000a": $type = "SRATIONAL";$size=8;break; 325 case "000b": $type = "FLOAT";$size=4;break; 326 case "000c": $type = "DOUBLE";$size=8;break; 327 default: $type = "error:".$type;$size=0;break; 328 } 329 return $type; 330} 331 332//================================================================================================ 333//================================================================================================ 334// Formats Data for the data type 335//================================================================================================ 336//================================================================================================ 337function formatData($type,$tag,$intel,$data) { 338 339 if($type=="ASCII") { 340 //do nothing 341 } else if($type=="URATIONAL" || $type=="SRATIONAL") { 342 $data = bin2hex($data); 343 if($intel==1) $data = intel2Moto($data); 344 345 if($intel==1) $top = hexdec(substr($data,8,8)); //intel stores them bottom-top 346 else $top = hexdec(substr($data,0,8)); //motorola stores them top-bottom 347 348 if($intel==1) $bottom = hexdec(substr($data,0,8)); //intel stores them bottom-top 349 else $bottom = hexdec(substr($data,8,8)); //motorola stores them top-bottom 350 351 if($type=="SRATIONAL" && $top>2147483647) $top = $top - 4294967296; //this makes the number signed instead of unsigned 352 if($bottom!=0) $data=$top/$bottom; 353 else if($top==0) $data = 0; 354 else $data=$top."/".$bottom; 355 356 if(($tag=="011a" || $tag=="011b") && $bottom==1) { //XResolution YResolution 357 $data=$top." dots per ResolutionUnit"; 358 } else if($tag=="829d") { //FNumber 359 $data="f ".$data; 360 } else if($tag=="9204") { //ExposureBiasValue 361 $data=$data." EV"; 362 } else if($tag=="9205" || $tag=="9202") { //ApertureValue and MaxApertureValue 363 //ApertureValue is given in the APEX Mode. Many thanks to Matthieu Froment for this code 364 //The formula is : Aperture = 2*log2(FNumber) <=> FNumber = e((Aperture.ln(2))/2) 365 $data = exp(($data*log(2))/2); 366 $data = round($data, 1);//Focal is given with a precision of 1 digit. 367 $data="f ".$data; 368 } else if($tag=="920a") { //FocalLength 369 $data=$data." mm"; 370 } else if($tag=="829a" || $tag=="9201") { //ExposureTime or ShutterSpeedValue 371 // The ShutterSpeedValue is given in the APEX mode. Many thanks to Matthieu Froment for this code 372 // The formula is : Shutter = - log2(exposureTime) (Appendix C of EXIF spec.) 373 // Where shutter is in APEX, log2(exposure) = ln(exposure)/ln(2) 374 // So final formula is : exposure = exp(-ln(2).shutter) 375 // The formula can be developed : exposure = 1/(exp(ln(2).shutter)) 376 if($tag=='9201') { 377 $data = exp($data * log(2)); 378 if ($data >= 1000000) { 379 $data = null; // Throw out near-zero value 380 } else if ($data >= 1.99) { 381 $prec = $data >= 38 ? -1 : 0; 382 $data = '1/' . round($data, $prec) . ' sec'; 383 } else if ($data > 0) $data = 1/$data; 384 } 385 else if($tag=='829a' && $top > 0 && $bottom % $top == 0) { 386 $data = '1/' . intval($bottom/$top) . ' sec'; 387 } 388 if (is_float($data)) { 389 if ($data > 0) { 390 $prec = $data < 1 ? 2 : ($data < 10 ? 1 : 0); 391 $data = round($data, $prec) . ' sec'; 392 } else { 393 $data = "Bulb"; 394 } 395 } 396 } 397 398 } else if($type=="USHORT" || $type=="SSHORT" || $type=="ULONG" || $type=="SLONG" || $type=="FLOAT" || $type=="DOUBLE") { 399 $data = bin2hex($data); 400 if($type=="USHORT" || $type=="SSHORT") $data = substr($data,0,4); 401 if($intel==1) $data = intel2Moto($data); 402 $data=hexdec($data); 403 404 if($type=="SSHORT" && $data>32767) $data = $data - 65536; //this makes the number signed instead of unsigned 405 if($type=="SLONG" && $data>2147483647) $data = $data - 4294967296; //this makes the number signed instead of unsigned 406 407 if($tag=="0112") { //Orientation 408 if($data==1) $data = "Normal (O deg)"; 409 else if($data==2) $data = "Mirrored"; 410 else if($data==3) $data = "Upsidedown"; 411 else if($data==4) $data = "Upsidedown Mirrored"; 412 else if($data==5) $data = "90 deg CW Mirrored"; 413 else if($data==6) $data = "90 deg CCW"; 414 else if($data==7) $data = "90 deg CCW Mirrored"; 415 else if($data==8) $data = "90 deg CW"; 416 } else if($tag=="0128" || $tag=="a210" || $tag=="0128") { //ResolutionUnit and FocalPlaneResolutionUnit and ThumbnailResolutionUnit 417 if($data==1) $data = "No Unit"; 418 else if($data==2) $data = "Inch"; 419 else if($data==3) $data = "Centimeter"; 420 } else if($tag=="0213") { //YCbCrPositioning 421 if($data==1) $data = "Center of Pixel Array"; 422 else if($data==2) $data = "Datum Point"; 423 } else if($tag=="8822") { //ExposureProgram 424 if($data==1) $data = "Manual"; 425 else if($data==2) $data = "Program"; 426 else if($data==3) $data = "Aperture Priority"; 427 else if($data==4) $data = "Shutter Priority"; 428 else if($data==5) $data = "Program Creative"; 429 else if($data==6) $data = "Program Action"; 430 else if($data==7) $data = "Portrait"; 431 else if($data==8) $data = "Landscape"; 432 else $data = "Unknown: ".$data; 433 } else if($tag=="9207") { //MeteringMode 434 if($data==0) $data = "Unknown"; 435 else if($data==1) $data = "Average"; 436 else if($data==2) $data = "Center Weighted Average"; 437 else if($data==3) $data = "Spot"; 438 else if($data==4) $data = "Multi-Spot"; 439 else if($data==5) $data = "Multi-Segment"; 440 else if($data==6) $data = "Partial"; 441 else if($data==255) $data = "Other"; 442 else $data = "Unknown: ".$data; 443 } else if($tag=="9208") { //LightSource 444 if($data==0) $data = "Unknown or Auto"; 445 else if($data==1) $data = "Daylight"; 446 else if($data==2) $data = "Fluorescent"; 447 else if($data==3) $data = "Tungsten (Incandescent light)"; 448 else if($data==4) $data = "Flash"; 449 else if($data==9) $data = "Fine Weather"; 450 else if($data==10) $data = "Cloudy Weather"; 451 else if($data==11) $data = "Shade"; 452 else if($data==12) $data = "Daylight Fluorescent (D 5700 - 7100K)"; 453 else if($data==13) $data = "Day White Fluorescent (N 4600 - 5400K)"; 454 else if($data==14) $data = "Cool White Fluorescent (W 3900 -4500K)"; 455 else if($data==15) $data = "White Fluorescent (WW 3200 - 3700K)"; 456 else if($data==10) $data = "Flash"; 457 else if($data==17) $data = "Standard Light A"; 458 else if($data==18) $data = "Standard Light B"; 459 else if($data==19) $data = "Standard Light C"; 460 else if($data==20) $data = "D55"; 461 else if($data==21) $data = "D65"; 462 else if($data==22) $data = "D75"; 463 else if($data==23) $data = "D50"; 464 else if($data==24) $data = "ISO Studio Tungsten"; 465 else if($data==255) $data = "Other"; 466 else $data = "Unknown: ".$data; 467 } else if($tag=="9209") { //Flash 468 if($data==0) $data = "No Flash"; 469 else if($data==1) $data = "Flash"; 470 else if($data==5) $data = "Flash, strobe return light not detected"; 471 else if($data==7) $data = "Flash, strobe return light detected"; 472 else if($data==9) $data = "Compulsory Flash"; 473 else if($data==13) $data = "Compulsory Flash, Return light not detected"; 474 else if($data==15) $data = "Compulsory Flash, Return light detected"; 475 else if($data==16) $data = "No Flash"; 476 else if($data==24) $data = "No Flash"; 477 else if($data==25) $data = "Flash, Auto-Mode"; 478 else if($data==29) $data = "Flash, Auto-Mode, Return light not detected"; 479 else if($data==31) $data = "Flash, Auto-Mode, Return light detected"; 480 else if($data==32) $data = "No Flash"; 481 else if($data==65) $data = "Red Eye"; 482 else if($data==69) $data = "Red Eye, Return light not detected"; 483 else if($data==71) $data = "Red Eye, Return light detected"; 484 else if($data==73) $data = "Red Eye, Compulsory Flash"; 485 else if($data==77) $data = "Red Eye, Compulsory Flash, Return light not detected"; 486 else if($data==79) $data = "Red Eye, Compulsory Flash, Return light detected"; 487 else if($data==89) $data = "Red Eye, Auto-Mode"; 488 else if($data==93) $data = "Red Eye, Auto-Mode, Return light not detected"; 489 else if($data==95) $data = "Red Eye, Auto-Mode, Return light detected"; 490 else $data = "Unknown: ".$data; 491 } else if($tag=="a001") { //ColorSpace 492 if($data==1) $data = "sRGB"; 493 else $data = "Uncalibrated"; 494 } else if($tag=="a002" || $tag=="a003") { //ExifImageWidth/Height 495 $data = $data. " pixels"; 496 } else if($tag=="0103") { //Compression 497 if($data==1) $data = "No Compression"; 498 else if($data==6) $data = "Jpeg Compression"; 499 else $data = "Unknown: ".$data; 500 } else if($tag=="a217") { //SensingMethod 501 if($data==1) $data = "Not defined"; 502 else if($data==2) $data = "One Chip Color Area Sensor"; 503 else if($data==3) $data = "Two Chip Color Area Sensor"; 504 else if($data==4) $data = "Three Chip Color Area Sensor"; 505 else if($data==5) $data = "Color Sequential Area Sensor"; 506 else if($data==7) $data = "Trilinear Sensor"; 507 else if($data==8) $data = "Color Sequential Linear Sensor"; 508 else $data = "Unknown: ".$data; 509 } else if($tag=="0106") { //PhotometricInterpretation 510 if($data==1) $data = "Monochrome"; 511 else if($data==2) $data = "RGB"; 512 else if($data==6) $data = "YCbCr"; 513 else $data = "Unknown: ".$data; 514 } 515 516 } else if($type=="UNDEFINED") { 517 518 if($tag=="9000" || $tag=="a000" || $tag=="0002") { //ExifVersion,FlashPixVersion,InteroperabilityVersion 519 $data="version ".$data/100; 520 } 521 if($tag=="a300") { //FileSource 522 $data = bin2hex($data); 523 $data = str_replace("00","",$data); 524 $data = str_replace("03","Digital Still Camera",$data); 525 } 526 if($tag=="a301") { //SceneType 527 $data = bin2hex($data); 528 $data = str_replace("00","",$data); 529 $data = str_replace("01","Directly Photographed",$data); 530 } 531 if($tag=="9101") { //ComponentsConfiguration 532 $data = bin2hex($data); 533 $data = str_replace("01","Y",$data); 534 $data = str_replace("02","Cb",$data); 535 $data = str_replace("03","Cr",$data); 536 $data = str_replace("04","R",$data); 537 $data = str_replace("05","G",$data); 538 $data = str_replace("06","B",$data); 539 $data = str_replace("00","",$data); 540 } 541 if($tag=="9286") { //UserComment 542 $encoding = rtrim(substr($data, 0, 8)); 543 $data = rtrim(substr($data, 8)); 544 } 545 } else { 546 $data = bin2hex($data); 547 if($intel==1) $data = intel2Moto($data); 548 } 549 550 return $data; 551} 552 553//================================================================================================ 554//================================================================================================ 555// Reads one standard IFD entry 556//================================================================================================ 557//================================================================================================ 558function read_entry(&$result,$in,$seek,$intel,$ifd_name,$globalOffset) { 559 560 if(feof($in)) { //test to make sure we can still read. 561 $result['Errors'] = $result['Errors']+1; 562 return; 563 } 564 565 //2 byte tag 566 $tag = bin2hex(fread( $in, 2 )); 567 if($intel==1) $tag = intel2Moto($tag); 568 $tag_name = lookup_tag($tag); 569 570 //2 byte datatype 571 $type = bin2hex(fread( $in, 2 )); 572 if($intel==1) $type = intel2Moto($type); 573 lookup_type($type,$size); 574 575 //4 byte number of elements 576 $count = bin2hex(fread( $in, 4 )); 577 if($intel==1) $count = intel2Moto($count); 578 $bytesofdata = $size*hexdec($count); 579 580 //4 byte value or pointer to value if larger than 4 bytes 581 $value = fread( $in, 4 ); 582 583 if($bytesofdata<=4) { //if datatype is 4 bytes or less, its the value 584 $data = $value; 585 } else if($bytesofdata<100000) { //otherwise its a pointer to the value, so lets go get it 586 $value = bin2hex($value); 587 if($intel==1) $value = intel2Moto($value); 588 $v = fseek($seek,$globalOffset+hexdec($value)); //offsets are from TIFF header which is 12 bytes from the start of the file 589 if($v==0) { 590 $data = fread($seek, $bytesofdata); 591 } else if($v==-1) { 592 $result['Errors'] = $result['Errors']+1; 593 } 594 } else { //bytesofdata was too big, so the exif had an error 595 $result['Errors'] = $result['Errors']+1; 596 return; 597 } 598 if($tag_name=="MakerNote") { //if its a maker tag, we need to parse this specially 599 $make = $result['IFD0']['Make']; 600 601 if($result['VerboseOutput']==1) { 602 $result[$ifd_name]['MakerNote']['RawData'] = $data; 603 } 604 /* not using stripos for php4 compatibility */ 605 $capmake = strtoupper($make); 606 if(strpos($capmake,'NIKON')!==false) { 607 require_once(dirname(__FILE__) . '/makers/nikon.inc'); 608 parseNikon($data,$result); 609 $result[$ifd_name]['KnownMaker'] = 1; 610 } else if(strpos($capmake,'OLYMPUS')!==false) { 611 require_once(dirname(__FILE__) . '/makers/olympus.inc'); 612 parseOlympus($data,$result,$seek,$globalOffset); 613 $result[$ifd_name]['KnownMaker'] = 1; 614 } else if(strpos($capmake,'Canon')!==false) { 615 require_once(dirname(__FILE__) . '/makers/canon.inc'); 616 parseCanon($data,$result,$seek,$globalOffset); 617 $result[$ifd_name]['KnownMaker'] = 1; 618 } else if(strpos($capmake,'FUJIFILM')!==false) { 619 require_once(dirname(__FILE__) . '/makers/fujifilm.inc'); 620 parseFujifilm($data,$result); 621 $result[$ifd_name]['KnownMaker'] = 1; 622 } else if(strpos($capmake,'SANYO')!==false) { 623 require_once(dirname(__FILE__) . '/makers/sanyo.inc'); 624 parseSanyo($data,$result,$seek,$globalOffset); 625 $result[$ifd_name]['KnownMaker'] = 1; 626 } else { 627 $result[$ifd_name]['KnownMaker'] = 0; 628 } 629 } else if($tag_name=="GPSInfoOffset") { 630 require_once(dirname(__FILE__) . '/makers/gps.inc'); 631 $formated_data = formatData($type,$tag,$intel,$data); 632 $result[$ifd_name]['GPSInfo'] = $formated_data; 633 parseGPS($data,$result,$formated_data,$seek,$globalOffset); 634 } else { 635 //Format the data depending on the type and tag 636 $formated_data = formatData($type,$tag,$intel,$data); 637 638 $result[$ifd_name][$tag_name] = $formated_data; 639 640 if($result['VerboseOutput']==1) { 641 if($type=="URATIONAL" || $type=="SRATIONAL" || $type=="USHORT" || $type=="SSHORT" || $type=="ULONG" || $type=="SLONG" || $type=="FLOAT" || $type=="DOUBLE") { 642 $data = bin2hex($data); 643 if($intel==1) $data = intel2Moto($data); 644 } 645 $result[$ifd_name][$tag_name."_Verbose"]['RawData'] = $data; 646 $result[$ifd_name][$tag_name."_Verbose"]['Type'] = $type; 647 $result[$ifd_name][$tag_name."_Verbose"]['Bytes'] = $bytesofdata; 648 } 649 } 650} 651 652//================================================================================================ 653//================================================================================================ 654// Pass in a file and this reads the EXIF data 655// 656// Usefull resources 657// http://www.ba.wakwak.com/~tsuruzoh/Computer/Digicams/exif-e.html 658// http://www.w3.org/Graphics/JPEG/jfif.txt 659// http://exif.org/ 660// http://www.ozhiker.com/electronics/pjmt/library/list_contents.php4 661// http://www.ozhiker.com/electronics/pjmt/jpeg_info/makernotes.html 662// http://pel.sourceforge.net/ 663// http://us2.php.net/manual/en/function.exif-read-data.php 664//================================================================================================ 665//================================================================================================ 666function read_exif_data_raw($path,$verbose) { 667 668 if($path=='' || $path=='none') return; 669 670 $in = @fopen($path, "rb"); //the b is for windows machines to open in binary mode 671 $seek = @fopen($path, "rb"); //There may be an elegant way to do this with one file handle. 672 673 $globalOffset = 0; 674 675 if(!isset($verbose)) $verbose=0; 676 677 $result['VerboseOutput'] = $verbose; 678 $result['Errors'] = 0; 679 680 if(!$in || !$seek) { //if the path was invalid, this error will catch it 681 $result['Errors'] = 1; 682 $result['Error'][$result['Errors']] = "The file could not be found."; 683 return $result; 684 } 685 686 $GLOBALS['exiferFileSize'] = filesize($path); 687 688 //First 2 bytes of JPEG are 0xFFD8 689 $data = bin2hex(fread( $in, 2 )); 690 if($data=="ffd8") { 691 $result['ValidJpeg'] = 1; 692 } else { 693 $result['ValidJpeg'] = 0; 694 fclose($in); 695 fclose($seek); 696 return $result; 697 } 698 699 $result['ValidIPTCData'] = 0; 700 $result['ValidJFIFData'] = 0; 701 $result['ValidEXIFData'] = 0; 702 $result['ValidAPP2Data'] = 0; 703 $result['ValidCOMData'] = 0; 704 705 //Next 2 bytes are MARKER tag (0xFFE#) 706 $data = bin2hex(fread( $in, 2 )); 707 $size = bin2hex(fread( $in, 2 )); 708 709 //LOOP THROUGH MARKERS TILL YOU GET TO FFE1 (exif marker) 710 // 711 $abortCount = 0; 712 while(!feof($in) && $data!="ffe1" && $data!="ffc0" && $data!="ffd9" && ++$abortCount < 200) { 713 if($data=="ffe0") { //JFIF Marker 714 $result['ValidJFIFData'] = 1; 715 $result['JFIF']['Size'] = hexdec($size); 716 717 if(hexdec($size)-2 > 0) { 718 $data = fread( $in, hexdec($size)-2); 719 $result['JFIF']['Data'] = $data; 720 } 721 722 $result['JFIF']['Identifier'] = substr($data,0,5);; 723 $result['JFIF']['ExtensionCode'] = bin2hex(substr($data,6,1)); 724 725 $globalOffset+=hexdec($size)+2; 726 727 } else if($data=="ffed") { //IPTC Marker 728 $result['ValidIPTCData'] = 1; 729 $result['IPTC']['Size'] = hexdec($size); 730 731 if(hexdec($size)-2 > 0) { 732 $data = fread( $in, hexdec($size)-2); 733 $result['IPTC']['Data'] = $data ; 734 } 735 $globalOffset+=hexdec($size)+2; 736 737 } else if($data=="ffe2") { //EXIF extension Marker 738 $result['ValidAPP2Data'] = 1; 739 $result['APP2']['Size'] = hexdec($size); 740 741 if(hexdec($size)-2 > 0) { 742 $data = fread( $in, hexdec($size)-2); 743 $result['APP2']['Data'] = $data ; 744 } 745 $globalOffset+=hexdec($size)+2; 746 747 } else if($data=="fffe") { //COM extension Marker 748 $result['ValidCOMData'] = 1; 749 $result['COM']['Size'] = hexdec($size); 750 751 if(hexdec($size)-2 > 0) { 752 $data = fread( $in, hexdec($size)-2); 753 $result['COM']['Data'] = $data ; 754 } 755 $globalOffset+=hexdec($size)+2; 756 757 } else if($data=="ffe1") { 758 $result['ValidEXIFData'] = 1; 759 } 760 761 $data = bin2hex(fread( $in, 2 )); 762 $size = bin2hex(fread( $in, 2 )); 763 } 764 //END MARKER LOOP 765 766 if($data=="ffe1") { 767 $result['ValidEXIFData'] = 1; 768 } else { 769 fclose($in); 770 fclose($seek); 771 return $result; 772 } 773 774 //Size of APP1 775 $result['APP1Size'] = hexdec($size); 776 777 //Start of APP1 block starts with "Exif" header (6 bytes) 778 $header = fread( $in, 6 ); 779 780 //Then theres a TIFF header with 2 bytes of endieness (II or MM) 781 $header = fread( $in, 2 ); 782 if($header==="II") { 783 $intel=1; 784 $result['Endien'] = "Intel"; 785 } else if($header==="MM") { 786 $intel=0; 787 $result['Endien'] = "Motorola"; 788 } else { 789 $intel=1; //not sure what the default should be, but this seems reasonable 790 $result['Endien'] = "Unknown"; 791 } 792 793 //2 bytes of 0x002a 794 $tag = bin2hex(fread( $in, 2 )); 795 796 //Then 4 bytes of offset to IFD0 (usually 8 which includes all 8 bytes of TIFF header) 797 $offset = bin2hex(fread( $in, 4 )); 798 if($intel==1) $offset = intel2Moto($offset); 799 800 // Check for extremely large values here 801 if(hexdec($offset) > 100000) { 802 $result['ValidEXIFData'] = 0; 803 fclose($in); 804 fclose($seek); 805 return $result; 806 } 807 808 if(hexdec($offset)>8) $unknown = fread( $in, hexdec($offset)-8); //fixed this bug in 1.3 809 810 //add 12 to the offset to account for TIFF header 811 $globalOffset+=12; 812 813 814 //===========================================================Start of IFD0 815 $num = bin2hex(fread( $in, 2 )); 816 if($intel==1) $num = intel2Moto($num); 817 $num = hexdec($num); 818 $result['IFD0NumTags'] = $num; 819 820 if($num<1000) { //1000 entries is too much and is probably an error. 821 for($i=0;$i<$num;$i++) { 822 read_entry($result,$in,$seek,$intel,"IFD0",$globalOffset); 823 } 824 } else { 825 $result['Errors'] = $result['Errors']+1; 826 $result['Error'][$result['Errors']] = "Illegal size for IFD0"; 827 } 828 829 //store offset to IFD1 830 $offset = bin2hex(fread( $in, 4 )); 831 if($intel==1) $offset = intel2Moto($offset); 832 $result['IFD1Offset'] = hexdec($offset); 833 834 //Check for SubIFD 835 if(!isset($result['IFD0']['ExifOffset']) || $result['IFD0']['ExifOffset']==0) { 836 fclose($in); 837 fclose($seek); 838 return $result; 839 } 840 841 //seek to SubIFD (Value of ExifOffset tag) above. 842 $ExitOffset = $result['IFD0']['ExifOffset']; 843 $v = fseek($in,$globalOffset+$ExitOffset); 844 if($v==-1) { 845 $result['Errors'] = $result['Errors']+1; 846 $result['Error'][$result['Errors']] = "Couldnt Find SubIFD"; 847 } 848 849 //===========================================================Start of SubIFD 850 $num = bin2hex(fread( $in, 2 )); 851 if($intel==1) $num = intel2Moto($num); 852 $num = hexdec($num); 853 $result['SubIFDNumTags'] = $num; 854 855 if($num<1000) { //1000 entries is too much and is probably an error. 856 for($i=0;$i<$num;$i++) { 857 read_entry($result,$in,$seek,$intel,"SubIFD",$globalOffset); 858 } 859 } else { 860 $result['Errors'] = $result['Errors']+1; 861 $result['Error'][$result['Errors']] = "Illegal size for SubIFD"; 862 } 863 864 //Check for IFD1 865 if(!isset($result['IFD1Offset']) || $result['IFD1Offset']==0) { 866 fclose($in); 867 fclose($seek); 868 return $result; 869 } 870 //seek to IFD1 871 $v = fseek($in,$globalOffset+$result['IFD1Offset']); 872 if($v==-1) { 873 $result['Errors'] = $result['Errors']+1; 874 $result['Error'][$result['Errors']] = "Couldnt Find IFD1"; 875 } 876 877 //===========================================================Start of IFD1 878 $num = bin2hex(fread( $in, 2 )); 879 if($intel==1) $num = intel2Moto($num); 880 $num = hexdec($num); 881 $result['IFD1NumTags'] = $num; 882 883 if($num<1000) { //1000 entries is too much and is probably an error. 884 for($i=0;$i<$num;$i++) { 885 read_entry($result,$in,$seek,$intel,"IFD1",$globalOffset); 886 } 887 } else { 888 $result['Errors'] = $result['Errors']+1; 889 $result['Error'][$result['Errors']] = "Illegal size for IFD1"; 890 } 891 //if verbose output is on, stick in the thumbnail raw data 892 if($result['VerboseOutput']==1 && $result['IFD1']['JpegIFOffset']>0 && $result['IFD1']['JpegIFByteCount']>0) { 893 $v = fseek($seek,$globalOffset+$result['IFD1']['JpegIFOffset']); 894 if($v==0) { 895 $data = fread($seek, $result['IFD1']['JpegIFByteCount']); 896 } else if($v==-1) { 897 $result['Errors'] = $result['Errors']+1; 898 } 899 $result['IFD1']["ThumbnailData"] = $data; 900 } 901 902 903 //Check for Interoperability IFD 904 if(!isset($result['SubIFD']['ExifInteroperabilityOffset']) || $result['SubIFD']['ExifInteroperabilityOffset']==0) { 905 fclose($in); 906 fclose($seek); 907 return $result; 908 } 909 //seek to InteroperabilityIFD 910 $v = fseek($in,$globalOffset+$result['SubIFD']['ExifInteroperabilityOffset']); 911 if($v==-1) { 912 $result['Errors'] = $result['Errors']+1; 913 $result['Error'][$result['Errors']] = "Couldnt Find InteroperabilityIFD"; 914 } 915 916 //===========================================================Start of InteroperabilityIFD 917 $num = bin2hex(fread( $in, 2 )); 918 if($intel==1) $num = intel2Moto($num); 919 $num = hexdec($num); 920 $result['InteroperabilityIFDNumTags'] = $num; 921 922 if($num<1000) { //1000 entries is too much and is probably an error. 923 for($i=0;$i<$num;$i++) { 924 read_entry($result,$in,$seek,$intel,"InteroperabilityIFD",$globalOffset); 925 } 926 } else { 927 $result['Errors'] = $result['Errors']+1; 928 $result['Error'][$result['Errors']] = "Illegal size for InteroperabilityIFD"; 929 } 930 fclose($in); 931 fclose($seek); 932 return $result; 933} 934 935//================================================================================================ 936//================================================================================================ 937// Converts a floating point number into a fraction. Many thanks to Matthieu Froment for this code 938//================================================================================================ 939//================================================================================================ 940function ConvertToFraction($v, &$n, &$d) 941{ 942 $MaxTerms = 15; //Limit to prevent infinite loop 943 $MinDivisor = 1E-6; //Limit to prevent divide by zero 944 $MaxError = 1E-8; //How close is enough 945 946 $f = $v; //Initialize fraction being converted 947 948 $n_un = 1; //Initialize fractions with 1/0, 0/1 949 $d_un = 0; 950 $n_deux = 0; 951 $d_deux = 1; 952 953 for ($i = 0;$i<$MaxTerms;$i++) 954 { 955 $a = floor($f); //Get next term 956 $f = $f - $a; //Get new divisor 957 $n = $n_un * $a + $n_deux; //Calculate new fraction 958 $d = $d_un * $a + $d_deux; 959 $n_deux = $n_un; //Save last two fractions 960 $d_deux = $d_un; 961 $n_un = $n; 962 $d_un = $d; 963 964 if ($f < $MinDivisor) //Quit if dividing by zero 965 break; 966 967 if (abs($v - $n / $d) < $MaxError) 968 break; 969 970 $f = 1 / $f; //Take reciprocal 971 } 972} 973?> 974