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