2147483647) $top = $top - 4294967296; //this makes the number signed instead of unsigned if($bottom!=0) $data=$top/$bottom; else if($top==0) $data = 0; else $data=$top."/".$bottom; if(($tag=="011a" || $tag=="011b") && $bottom==1) { //XResolution YResolution $data=$top." dots per ResolutionUnit"; } else if($tag=="829a") { //Exposure Time if($top/10 == 1) $data="1/".round($bottom/10, 0)." sec"; else $data=$top."/".$bottom." sec"; } else if($tag=="829d") { //FNumber $data="f ".$data; } else if($tag=="9204") { //ExposureBiasValue $data=$data." EV"; } else if($tag=="9205" || $tag=="9202") { //ApertureValue and MaxApertureValue //ApertureValue is given in the APEX Mode. Many thanks to Matthieu Froment for this code //The formula is : Aperture = 2*log2(FNumber) <=> FNumber = e((Aperture.ln(2))/2) $data = exp(($data*log(2))/2); $data = round($data, 1);//Focal is given with a precision of 1 digit. $data="f ".$data; } else if($tag=="920a") { //FocalLength $data=$data." mm"; } else if($tag=="9201") { //ShutterSpeedValue // The ShutterSpeedValue is given in the APEX mode. Many thanks to Matthieu Froment for this code // The formula is : Shutter = - log2(exposureTime) (Appendix C of EXIF spec.) // Where shutter is in APEX, log2(exposure) = ln(exposure)/ln(2) // So final formula is : exposure = exp(-ln(2).shutter) // The formula can be developed : exposure = 1/(exp(ln(2).shutter)) $data = exp($data * log(2)); if ($data > 1) $data = floor($data); //Drop the decimal. if ($data > 0) { $data = 1/$data; //Final calculation. We now have a floating number. Transform it in a pretty number $n=0;$d=0; ConvertToFraction($data, $n, $d); if ($n>=1 && $d==1) $data = $n." sec"; //To avoid exposure times style 3/1 sec. else $data = $n."/".$d." sec"; } else { $data = "Bulb"; } } } else if($type=="USHORT" || $type=="SSHORT" || $type=="ULONG" || $type=="SLONG" || $type=="FLOAT" || $type=="DOUBLE") { $data = bin2hex($data); if($type=="USHORT" || $type=="SSHORT") $data = substr($data,0,4); if($intel==1) $data = intel2Moto($data); $data=hexdec($data); if($type=="SSHORT" && $data>32767) $data = $data - 65536; //this makes the number signed instead of unsigned if($type=="SLONG" && $data>2147483647) $data = $data - 4294967296; //this makes the number signed instead of unsigned if($tag=="0112") { //Orientation if($data==1) $data = "Normal (O deg)"; else if($data==2) $data = "Mirrored"; else if($data==3) $data = "Upsidedown"; else if($data==4) $data = "Upsidedown Mirrored"; else if($data==5) $data = "90 deg CW Mirrored"; else if($data==6) $data = "90 deg CCW"; else if($data==7) $data = "90 deg CCW Mirrored"; else if($data==8) $data = "90 deg CW"; } else if($tag=="0128" || $tag=="a210" || $tag=="0128") { //ResolutionUnit and FocalPlaneResolutionUnit and ThumbnailResolutionUnit if($data==1) $data = "No Unit"; else if($data==2) $data = "Inch"; else if($data==3) $data = "Centimeter"; } else if($tag=="0213") { //YCbCrPositioning if($data==1) $data = "Center of Pixel Array"; else if($data==2) $data = "Datum Point"; } else if($tag=="8822") { //ExposureProgram if($data==1) $data = "Manual"; else if($data==2) $data = "Program"; else if($data==3) $data = "Aperture Priority"; else if($data==4) $data = "Shutter Priority"; else if($data==5) $data = "Program Creative"; else if($data==6) $data = "Program Action"; else if($data==7) $data = "Portrait"; else if($data==8) $data = "Landscape"; else $data = "Unknown: ".$data; } else if($tag=="9207") { //MeteringMode if($data==0) $data = "Unknown"; else if($data==1) $data = "Average"; else if($data==2) $data = "Center Weighted Average"; else if($data==3) $data = "Spot"; else if($data==4) $data = "Multi-Spot"; else if($data==5) $data = "Multi-Segment"; else if($data==6) $data = "Partial"; else if($data==255) $data = "Other"; else $data = "Unknown: ".$data; } else if($tag=="9208") { //LightSource if($data==0) $data = "Unknown or Auto"; else if($data==1) $data = "Daylight"; else if($data==2) $data = "Fluorescent"; else if($data==3) $data = "Tungsten (Incandescent light)"; else if($data==4) $data = "Flash"; else if($data==9) $data = "Fine Weather"; else if($data==10) $data = "Cloudy Weather"; else if($data==11) $data = "Shade"; else if($data==12) $data = "Daylight Fluorescent (D 5700 - 7100K)"; else if($data==13) $data = "Day White Fluorescent (N 4600 - 5400K)"; else if($data==14) $data = "Cool White Fluorescent (W 3900 -4500K)"; else if($data==15) $data = "White Fluorescent (WW 3200 - 3700K)"; else if($data==10) $data = "Flash"; else if($data==17) $data = "Standard Light A"; else if($data==18) $data = "Standard Light B"; else if($data==19) $data = "Standard Light C"; else if($data==20) $data = "D55"; else if($data==21) $data = "D65"; else if($data==22) $data = "D75"; else if($data==23) $data = "D50"; else if($data==24) $data = "ISO Studio Tungsten"; else if($data==255) $data = "Other"; else $data = "Unknown: ".$data; } else if($tag=="9209") { //Flash if($data==0) $data = "No Flash"; else if($data==1) $data = "Flash"; else if($data==5) $data = "Flash, strobe return light not detected"; else if($data==7) $data = "Flash, strobe return light detected"; else if($data==9) $data = "Compulsory Flash"; else if($data==13) $data = "Compulsory Flash, Return light not detected"; else if($data==15) $data = "Compulsory Flash, Return light detected"; else if($data==16) $data = "No Flash"; else if($data==24) $data = "No Flash"; else if($data==25) $data = "Flash, Auto-Mode"; else if($data==29) $data = "Flash, Auto-Mode, Return light not detected"; else if($data==31) $data = "Flash, Auto-Mode, Return light detected"; else if($data==32) $data = "No Flash"; else if($data==65) $data = "Red Eye"; else if($data==69) $data = "Red Eye, Return light not detected"; else if($data==71) $data = "Red Eye, Return light detected"; else if($data==73) $data = "Red Eye, Compulsory Flash"; else if($data==77) $data = "Red Eye, Compulsory Flash, Return light not detected"; else if($data==79) $data = "Red Eye, Compulsory Flash, Return light detected"; else if($data==89) $data = "Red Eye, Auto-Mode"; else if($data==93) $data = "Red Eye, Auto-Mode, Return light not detected"; else if($data==95) $data = "Red Eye, Auto-Mode, Return light detected"; else $data = "Unknown: ".$data; } else if($tag=="a001") { //ColorSpace if($data==1) $data = "sRGB"; else $data = "Uncalibrated"; } else if($tag=="a002" || $tag=="a003") { //ExifImageWidth/Height $data = $data. " pixels"; } else if($tag=="0103") { //Compression if($data==1) $data = "No Compression"; else if($data==6) $data = "Jpeg Compression"; else $data = "Unknown: ".$data; } else if($tag=="a217") { //SensingMethod if($data==1) $data = "Not defined"; else if($data==2) $data = "One Chip Color Area Sensor"; else if($data==3) $data = "Two Chip Color Area Sensor"; else if($data==4) $data = "Three Chip Color Area Sensor"; else if($data==5) $data = "Color Sequential Area Sensor"; else if($data==7) $data = "Trilinear Sensor"; else if($data==8) $data = "Color Sequential Linear Sensor"; else $data = "Unknown: ".$data; } else if($tag=="0106") { //PhotometricInterpretation if($data==1) $data = "Monochrome"; else if($data==2) $data = "RGB"; else if($data==6) $data = "YCbCr"; else $data = "Unknown: ".$data; } } else if($type=="UNDEFINED") { if($tag=="9000" || $tag=="a000" || $tag=="0002") { //ExifVersion,FlashPixVersion,InteroperabilityVersion $data="version ".$data/100; } if($tag=="a300") { //FileSource $data = bin2hex($data); $data = str_replace("00","",$data); $data = str_replace("03","Digital Still Camera",$data); } if($tag=="a301") { //SceneType $data = bin2hex($data); $data = str_replace("00","",$data); $data = str_replace("01","Directly Photographed",$data); } if($tag=="9101") { //ComponentsConfiguration $data = bin2hex($data); $data = str_replace("01","Y",$data); $data = str_replace("02","Cb",$data); $data = str_replace("03","Cr",$data); $data = str_replace("04","R",$data); $data = str_replace("05","G",$data); $data = str_replace("06","B",$data); $data = str_replace("00","",$data); } if($tag=="9286") { //UserComment $data = rtrim($data); } } else { $data = bin2hex($data); if($intel==1) $data = intel2Moto($data); } return $data; } //================================================================================================ //================================================================================================ // Reads one standard IFD entry //================================================================================================ //================================================================================================ function read_entry(&$result,$in,$seek,$intel,$ifd_name,$globalOffset) { if(feof($in)) { //test to make sure we can still read. $result['Errors'] = $result['Errors']+1; return; } //2 byte tag $tag = bin2hex(fread( $in, 2 )); if($intel==1) $tag = intel2Moto($tag); $tag_name = lookup_tag($tag); //2 byte datatype $type = bin2hex(fread( $in, 2 )); if($intel==1) $type = intel2Moto($type); lookup_type($type,$size); //4 byte number of elements $count = bin2hex(fread( $in, 4 )); if($intel==1) $count = intel2Moto($count); $bytesofdata = $size*hexdec($count); //4 byte value or pointer to value if larger than 4 bytes $value = fread( $in, 4 ); if($bytesofdata<=4) { //if datatype is 4 bytes or less, its the value $data = $value; } else if($bytesofdata<100000) { //otherwise its a pointer to the value, so lets go get it $value = bin2hex($value); if($intel==1) $value = intel2Moto($value); $v = fseek($seek,$globalOffset+hexdec($value)); //offsets are from TIFF header which is 12 bytes from the start of the file if($v==0) { $data = fread($seek, $bytesofdata); } else if($v==-1) { $result['Errors'] = $result['Errors']+1; } } else { //bytesofdata was too big, so the exif had an error $result['Errors'] = $result['Errors']+1; return; } if($tag_name=="MakerNote") { //if its a maker tag, we need to parse this specially $make = $result['IFD0']['Make']; if($result['VerboseOutput']==1) { $result[$ifd_name]['MakerNote']['RawData'] = $data; } if(eregi("NIKON",$make)) { require_once(dirname(__FILE__) . '/makers/nikon.inc'); parseNikon($data,$result); $result[$ifd_name]['KnownMaker'] = 1; } else if(eregi("OLYMPUS",$make)) { require_once(dirname(__FILE__) . '/makers/olympus.inc'); parseOlympus($data,$result,$seek,$globalOffset); $result[$ifd_name]['KnownMaker'] = 1; } else if(eregi("Canon",$make)) { require_once(dirname(__FILE__) . '/makers/canon.inc'); parseCanon($data,$result,$seek,$globalOffset); $result[$ifd_name]['KnownMaker'] = 1; } else if(eregi("FUJIFILM",$make)) { require_once(dirname(__FILE__) . '/makers/fujifilm.inc'); parseFujifilm($data,$result); $result[$ifd_name]['KnownMaker'] = 1; } else if(eregi("SANYO",$make)) { require_once(dirname(__FILE__) . '/makers/sanyo.inc'); parseSanyo($data,$result,$seek,$globalOffset); $result[$ifd_name]['KnownMaker'] = 1; } else { $result[$ifd_name]['KnownMaker'] = 0; } } else if($tag_name=="GPSInfoOffset") { require_once(dirname(__FILE__) . '/makers/gps.inc'); $formated_data = formatData($type,$tag,$intel,$data); $result[$ifd_name]['GPSInfo'] = $formated_data; parseGPS($data,$result,$formated_data,$seek,$globalOffset); } else { //Format the data depending on the type and tag $formated_data = formatData($type,$tag,$intel,$data); $result[$ifd_name][$tag_name] = $formated_data; if($result['VerboseOutput']==1) { if($type=="URATIONAL" || $type=="SRATIONAL" || $type=="USHORT" || $type=="SSHORT" || $type=="ULONG" || $type=="SLONG" || $type=="FLOAT" || $type=="DOUBLE") { $data = bin2hex($data); if($intel==1) $data = intel2Moto($data); } $result[$ifd_name][$tag_name."_Verbose"]['RawData'] = $data; $result[$ifd_name][$tag_name."_Verbose"]['Type'] = $type; $result[$ifd_name][$tag_name."_Verbose"]['Bytes'] = $bytesofdata; } } } //================================================================================================ //================================================================================================ // Pass in a file and this reads the EXIF data // // Usefull resources // http://www.ba.wakwak.com/~tsuruzoh/Computer/Digicams/exif-e.html // http://www.w3.org/Graphics/JPEG/jfif.txt // http://exif.org/ // http://www.ozhiker.com/electronics/pjmt/library/list_contents.php4 // http://www.ozhiker.com/electronics/pjmt/jpeg_info/makernotes.html // http://pel.sourceforge.net/ // http://us2.php.net/manual/en/function.exif-read-data.php //================================================================================================ //================================================================================================ function read_exif_data_raw($path,$verbose) { if($path=='' || $path=='none') return; $in = @fopen($path, "rb"); //the b is for windows machines to open in binary mode $seek = @fopen($path, "rb"); //There may be an elegant way to do this with one file handle. $globalOffset = 0; if(!isset($verbose)) $verbose=0; $result['VerboseOutput'] = $verbose; $result['Errors'] = 0; if(!$in || !$seek) { //if the path was invalid, this error will catch it $result['Errors'] = 1; $result['Error'][$result['Errors']] = "The file could not be found."; return $result; } //First 2 bytes of JPEG are 0xFFD8 $data = bin2hex(fread( $in, 2 )); if($data=="ffd8") { $result['ValidJpeg'] = 1; } else { $result['ValidJpeg'] = 0; fclose($in); fclose($seek); return $result; } $result['ValidIPTCData'] = 0; $result['ValidJFIFData'] = 0; $result['ValidEXIFData'] = 0; $result['ValidAPP2Data'] = 0; $result['ValidCOMData'] = 0; //Next 2 bytes are MARKER tag (0xFFE#) $data = bin2hex(fread( $in, 2 )); $size = bin2hex(fread( $in, 2 )); //LOOP THROUGH MARKERS TILL YOU GET TO FFE1 (exif marker) while(!feof($in) && $data!="ffe1" && $data!="ffc0" && $data!="ffd9") { if($data=="ffe0") { //JFIF Marker $result['ValidJFIFData'] = 1; $result['JFIF']['Size'] = hexdec($size); if(hexdec($size)-2 > 0) { $data = fread( $in, hexdec($size)-2); $result['JFIF']['Data'] = $data; } $result['JFIF']['Identifier'] = substr($data,0,5);; $result['JFIF']['ExtensionCode'] = bin2hex(substr($data,6,1)); $globalOffset+=hexdec($size)+2; } else if($data=="ffed") { //IPTC Marker $result['ValidIPTCData'] = 1; $result['IPTC']['Size'] = hexdec($size); if(hexdec($size)-2 > 0) { $data = fread( $in, hexdec($size)-2); $result['IPTC']['Data'] = $data ; } $globalOffset+=hexdec($size)+2; } else if($data=="ffe2") { //EXIF extension Marker $result['ValidAPP2Data'] = 1; $result['APP2']['Size'] = hexdec($size); if(hexdec($size)-2 > 0) { $data = fread( $in, hexdec($size)-2); $result['APP2']['Data'] = $data ; } $globalOffset+=hexdec($size)+2; } else if($data=="fffe") { //COM extension Marker $result['ValidCOMData'] = 1; $result['COM']['Size'] = hexdec($size); if(hexdec($size)-2 > 0) { $data = fread( $in, hexdec($size)-2); $result['COM']['Data'] = $data ; } $globalOffset+=hexdec($size)+2; } else if($data=="ffe1") { $result['ValidEXIFData'] = 1; } $data = bin2hex(fread( $in, 2 )); $size = bin2hex(fread( $in, 2 )); } //END MARKER LOOP if($data=="ffe1") { $result['ValidEXIFData'] = 1; } else { fclose($in); fclose($seek); return $result; } //Size of APP1 $result['APP1Size'] = hexdec($size); //Start of APP1 block starts with "Exif" header (6 bytes) $header = fread( $in, 6 ); //Then theres a TIFF header with 2 bytes of endieness (II or MM) $header = fread( $in, 2 ); if($header==="II") { $intel=1; $result['Endien'] = "Intel"; } else if($header==="MM") { $intel=0; $result['Endien'] = "Motorola"; } else { $intel=1; //not sure what the default should be, but this seems reasonable $result['Endien'] = "Unknown"; } //2 bytes of 0x002a $tag = bin2hex(fread( $in, 2 )); //Then 4 bytes of offset to IFD0 (usually 8 which includes all 8 bytes of TIFF header) $offset = bin2hex(fread( $in, 4 )); if($intel==1) $offset = intel2Moto($offset); // Check for extremely large values here if(hexdec($offset) > 100000) { $result['ValidEXIFData'] = 0; fclose($in); fclose($seek); return $result; } if(hexdec($offset)>8) $unknown = fread( $in, hexdec($offset)-8); //fixed this bug in 1.3 //add 12 to the offset to account for TIFF header $globalOffset+=12; //===========================================================Start of IFD0 $num = bin2hex(fread( $in, 2 )); if($intel==1) $num = intel2Moto($num); $num = hexdec($num); $result['IFD0NumTags'] = $num; if($num<1000) { //1000 entries is too much and is probably an error. for($i=0;$i<$num;$i++) { read_entry($result,$in,$seek,$intel,"IFD0",$globalOffset); } } else { $result['Errors'] = $result['Errors']+1; $result['Error'][$result['Errors']] = "Illegal size for IFD0"; } //store offset to IFD1 $offset = bin2hex(fread( $in, 4 )); if($intel==1) $offset = intel2Moto($offset); $result['IFD1Offset'] = hexdec($offset); //Check for SubIFD if(!isset($result['IFD0']['ExifOffset']) || $result['IFD0']['ExifOffset']==0) { fclose($in); fclose($seek); return $result; } //seek to SubIFD (Value of ExifOffset tag) above. $ExitOffset = $result['IFD0']['ExifOffset']; $v = fseek($in,$globalOffset+$ExitOffset); if($v==-1) { $result['Errors'] = $result['Errors']+1; $result['Error'][$result['Errors']] = "Couldnt Find SubIFD"; } //===========================================================Start of SubIFD $num = bin2hex(fread( $in, 2 )); if($intel==1) $num = intel2Moto($num); $num = hexdec($num); $result['SubIFDNumTags'] = $num; if($num<1000) { //1000 entries is too much and is probably an error. for($i=0;$i<$num;$i++) { read_entry($result,$in,$seek,$intel,"SubIFD",$globalOffset); } } else { $result['Errors'] = $result['Errors']+1; $result['Error'][$result['Errors']] = "Illegal size for SubIFD"; } //Check for IFD1 if(!isset($result['IFD1Offset']) || $result['IFD1Offset']==0) { fclose($in); fclose($seek); return $result; } //seek to IFD1 $v = fseek($in,$globalOffset+$result['IFD1Offset']); if($v==-1) { $result['Errors'] = $result['Errors']+1; $result['Error'][$result['Errors']] = "Couldnt Find IFD1"; } //===========================================================Start of IFD1 $num = bin2hex(fread( $in, 2 )); if($intel==1) $num = intel2Moto($num); $num = hexdec($num); $result['IFD1NumTags'] = $num; if($num<1000) { //1000 entries is too much and is probably an error. for($i=0;$i<$num;$i++) { read_entry($result,$in,$seek,$intel,"IFD1",$globalOffset); } } else { $result['Errors'] = $result['Errors']+1; $result['Error'][$result['Errors']] = "Illegal size for IFD1"; } //if verbose output is on, stick in the thumbnail raw data if($result['VerboseOutput']==1 && $result['IFD1']['JpegIFOffset']>0 && $result['IFD1']['JpegIFByteCount']>0) { $v = fseek($seek,$globalOffset+$result['IFD1']['JpegIFOffset']); if($v==0) { $data = fread($seek, $result['IFD1']['JpegIFByteCount']); } else if($v==-1) { $result['Errors'] = $result['Errors']+1; } $result['IFD1']["ThumbnailData"] = $data; } //Check for Interoperability IFD if(!isset($result['SubIFD']['ExifInteroperabilityOffset']) || $result['SubIFD']['ExifInteroperabilityOffset']==0) { fclose($in); fclose($seek); return $result; } //seek to InteroperabilityIFD $v = fseek($in,$globalOffset+$result['SubIFD']['ExifInteroperabilityOffset']); if($v==-1) { $result['Errors'] = $result['Errors']+1; $result['Error'][$result['Errors']] = "Couldnt Find InteroperabilityIFD"; } //===========================================================Start of InteroperabilityIFD $num = bin2hex(fread( $in, 2 )); if($intel==1) $num = intel2Moto($num); $num = hexdec($num); $result['InteroperabilityIFDNumTags'] = $num; if($num<1000) { //1000 entries is too much and is probably an error. for($i=0;$i<$num;$i++) { read_entry($result,$in,$seek,$intel,"InteroperabilityIFD",$globalOffset); } } else { $result['Errors'] = $result['Errors']+1; $result['Error'][$result['Errors']] = "Illegal size for InteroperabilityIFD"; } fclose($in); fclose($seek); return $result; } //================================================================================================ //================================================================================================ // Converts a floating point number into a fraction. Many thanks to Matthieu Froment for this code //================================================================================================ //================================================================================================ function ConvertToFraction($v, &$n, &$d) { $MaxTerms = 15; //Limit to prevent infinite loop $MinDivisor = 1E-6; //Limit to prevent divide by zero $MaxError = 1E-8; //How close is enough $f = $v; //Initialize fraction being converted $n_un = 1; //Initialize fractions with 1/0, 0/1 $d_un = 0; $n_deux = 0; $d_deux = 1; for ($i = 0;$i<$MaxTerms;$i++) { $a = floor($f); //Get next term $f = $f - $a; //Get new divisor $n = $n_un * $a + $n_deux; //Calculate new fraction $d = $d_un * $a + $d_deux; $n_deux = $n_un; //Save last two fractions $d_deux = $d_un; $n_un = $n; $d_un = $d; if ($f < $MinDivisor) //Quit if dividing by zero break; if (abs($v - $n / $d) < $MaxError) break; $f = 1 / $f; //Take reciprocal } } ?>