1<?php 2///////////////////////////////////////////////////////////////// 3/// getID3() by James Heinrich <info@getid3.org> // 4// available at http://getid3.sourceforge.net // 5// or http://www.getid3.org // 6// also https://github.com/JamesHeinrich/getID3 // 7///////////////////////////////////////////////////////////////// 8// See readme.txt for more details // 9///////////////////////////////////////////////////////////////// 10// // 11// module.graphic.jpg.php // 12// module for analyzing JPEG Image files // 13// dependencies: PHP compiled with --enable-exif (optional) // 14// module.tag.xmp.php (optional) // 15// /// 16///////////////////////////////////////////////////////////////// 17 18 19class getid3_jpg extends getid3_handler 20{ 21 22 23 public function Analyze() { 24 $info = &$this->getid3->info; 25 26 $info['fileformat'] = 'jpg'; 27 $info['video']['dataformat'] = 'jpg'; 28 $info['video']['lossless'] = false; 29 $info['video']['bits_per_sample'] = 24; 30 $info['video']['pixel_aspect_ratio'] = (float) 1; 31 32 $this->fseek($info['avdataoffset']); 33 34 $imageinfo = array(); 35 //list($width, $height, $type) = getid3_lib::GetDataImageSize($this->fread($info['filesize']), $imageinfo); 36 list($width, $height, $type) = getimagesize($info['filenamepath'], $imageinfo); // http://www.getid3.org/phpBB3/viewtopic.php?t=1474 37 38 39 if (isset($imageinfo['APP13'])) { 40 // http://php.net/iptcparse 41 // http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/IPTC.html 42 $iptc_parsed = iptcparse($imageinfo['APP13']); 43 if (is_array($iptc_parsed)) { 44 foreach ($iptc_parsed as $iptc_key_raw => $iptc_values) { 45 list($iptc_record, $iptc_tagkey) = explode('#', $iptc_key_raw); 46 $iptc_tagkey = intval(ltrim($iptc_tagkey, '0')); 47 foreach ($iptc_values as $key => $value) { 48 $IPTCrecordName = $this->IPTCrecordName($iptc_record); 49 $IPTCrecordTagName = $this->IPTCrecordTagName($iptc_record, $iptc_tagkey); 50 if (isset($info['iptc']['comments'][$IPTCrecordName][$IPTCrecordTagName])) { 51 $info['iptc']['comments'][$IPTCrecordName][$IPTCrecordTagName][] = $value; 52 } else { 53 $info['iptc']['comments'][$IPTCrecordName][$IPTCrecordTagName] = array($value); 54 } 55 } 56 } 57 } 58 } 59 60 $returnOK = false; 61 switch ($type) { 62 case IMG_JPG: 63 $info['video']['resolution_x'] = $width; 64 $info['video']['resolution_y'] = $height; 65 66 if (isset($imageinfo['APP1'])) { 67 if (function_exists('exif_read_data')) { 68 if (substr($imageinfo['APP1'], 0, 4) == 'Exif') { 69//$this->warning('known issue: https://bugs.php.net/bug.php?id=62523'); 70//return false; 71 set_error_handler(function($errno, $errstr, $errfile, $errline, array $errcontext) { 72 if (!(error_reporting() & $errno)) { 73 // error is not specified in the error_reporting setting, so we ignore it 74 return false; 75 } 76 77 $errcontext['info']['warning'][] = 'Error parsing EXIF data ('.$errstr.')'; 78 }); 79 80 $info['jpg']['exif'] = exif_read_data($info['filenamepath'], null, true, false); 81 82 restore_error_handler(); 83 } else { 84 $this->warning('exif_read_data() cannot parse non-EXIF data in APP1 (expected "Exif", found "'.substr($imageinfo['APP1'], 0, 4).'")'); 85 } 86 } else { 87 $this->warning('EXIF parsing only available when '.(GETID3_OS_ISWINDOWS ? 'php_exif.dll enabled' : 'compiled with --enable-exif')); 88 } 89 } 90 $returnOK = true; 91 break; 92 93 default: 94 break; 95 } 96 97 98 $cast_as_appropriate_keys = array('EXIF', 'IFD0', 'THUMBNAIL'); 99 foreach ($cast_as_appropriate_keys as $exif_key) { 100 if (isset($info['jpg']['exif'][$exif_key])) { 101 foreach ($info['jpg']['exif'][$exif_key] as $key => $value) { 102 $info['jpg']['exif'][$exif_key][$key] = $this->CastAsAppropriate($value); 103 } 104 } 105 } 106 107 108 if (isset($info['jpg']['exif']['GPS'])) { 109 110 if (isset($info['jpg']['exif']['GPS']['GPSVersion'])) { 111 for ($i = 0; $i < 4; $i++) { 112 $version_subparts[$i] = ord(substr($info['jpg']['exif']['GPS']['GPSVersion'], $i, 1)); 113 } 114 $info['jpg']['exif']['GPS']['computed']['version'] = 'v'.implode('.', $version_subparts); 115 } 116 117 if (isset($info['jpg']['exif']['GPS']['GPSDateStamp'])) { 118 $explodedGPSDateStamp = explode(':', $info['jpg']['exif']['GPS']['GPSDateStamp']); 119 $computed_time[5] = (isset($explodedGPSDateStamp[0]) ? $explodedGPSDateStamp[0] : ''); 120 $computed_time[3] = (isset($explodedGPSDateStamp[1]) ? $explodedGPSDateStamp[1] : ''); 121 $computed_time[4] = (isset($explodedGPSDateStamp[2]) ? $explodedGPSDateStamp[2] : ''); 122 123 $computed_time = array(0=>0, 1=>0, 2=>0, 3=>0, 4=>0, 5=>0); 124 if (isset($info['jpg']['exif']['GPS']['GPSTimeStamp']) && is_array($info['jpg']['exif']['GPS']['GPSTimeStamp'])) { 125 foreach ($info['jpg']['exif']['GPS']['GPSTimeStamp'] as $key => $value) { 126 $computed_time[$key] = getid3_lib::DecimalizeFraction($value); 127 } 128 } 129 $info['jpg']['exif']['GPS']['computed']['timestamp'] = gmmktime($computed_time[0], $computed_time[1], $computed_time[2], $computed_time[3], $computed_time[4], $computed_time[5]); 130 } 131 132 if (isset($info['jpg']['exif']['GPS']['GPSLatitude']) && is_array($info['jpg']['exif']['GPS']['GPSLatitude'])) { 133 $direction_multiplier = ((isset($info['jpg']['exif']['GPS']['GPSLatitudeRef']) && ($info['jpg']['exif']['GPS']['GPSLatitudeRef'] == 'S')) ? -1 : 1); 134 foreach ($info['jpg']['exif']['GPS']['GPSLatitude'] as $key => $value) { 135 $computed_latitude[$key] = getid3_lib::DecimalizeFraction($value); 136 } 137 $info['jpg']['exif']['GPS']['computed']['latitude'] = $direction_multiplier * ($computed_latitude[0] + ($computed_latitude[1] / 60) + ($computed_latitude[2] / 3600)); 138 } 139 140 if (isset($info['jpg']['exif']['GPS']['GPSLongitude']) && is_array($info['jpg']['exif']['GPS']['GPSLongitude'])) { 141 $direction_multiplier = ((isset($info['jpg']['exif']['GPS']['GPSLongitudeRef']) && ($info['jpg']['exif']['GPS']['GPSLongitudeRef'] == 'W')) ? -1 : 1); 142 foreach ($info['jpg']['exif']['GPS']['GPSLongitude'] as $key => $value) { 143 $computed_longitude[$key] = getid3_lib::DecimalizeFraction($value); 144 } 145 $info['jpg']['exif']['GPS']['computed']['longitude'] = $direction_multiplier * ($computed_longitude[0] + ($computed_longitude[1] / 60) + ($computed_longitude[2] / 3600)); 146 } 147 if (isset($info['jpg']['exif']['GPS']['GPSAltitudeRef'])) { 148 $info['jpg']['exif']['GPS']['GPSAltitudeRef'] = ord($info['jpg']['exif']['GPS']['GPSAltitudeRef']); // 0 = above sea level; 1 = below sea level 149 } 150 if (isset($info['jpg']['exif']['GPS']['GPSAltitude'])) { 151 $direction_multiplier = (!empty($info['jpg']['exif']['GPS']['GPSAltitudeRef']) ? -1 : 1); // 0 = above sea level; 1 = below sea level 152 $info['jpg']['exif']['GPS']['computed']['altitude'] = $direction_multiplier * getid3_lib::DecimalizeFraction($info['jpg']['exif']['GPS']['GPSAltitude']); 153 } 154 155 } 156 157 158 getid3_lib::IncludeDependency(GETID3_INCLUDEPATH.'module.tag.xmp.php', __FILE__, true); 159 if (isset($info['filenamepath'])) { 160 $image_xmp = new Image_XMP($info['filenamepath']); 161 $xmp_raw = $image_xmp->getAllTags(); 162 foreach ($xmp_raw as $key => $value) { 163 if (strpos($key, ':')) { 164 list($subsection, $tagname) = explode(':', $key); 165 $info['xmp'][$subsection][$tagname] = $this->CastAsAppropriate($value); 166 } else { 167 $this->warning('XMP: expecting "<subsection>:<tagname>", found "'.$key.'"'); 168 } 169 } 170 } 171 172 if (!$returnOK) { 173 unset($info['fileformat']); 174 return false; 175 } 176 return true; 177 } 178 179 180 public function CastAsAppropriate($value) { 181 if (is_array($value)) { 182 return $value; 183 } elseif (preg_match('#^[0-9]+/[0-9]+$#', $value)) { 184 return getid3_lib::DecimalizeFraction($value); 185 } elseif (preg_match('#^[0-9]+$#', $value)) { 186 return getid3_lib::CastAsInt($value); 187 } elseif (preg_match('#^[0-9\.]+$#', $value)) { 188 return (float) $value; 189 } 190 return $value; 191 } 192 193 194 public function IPTCrecordName($iptc_record) { 195 // http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/IPTC.html 196 static $IPTCrecordName = array(); 197 if (empty($IPTCrecordName)) { 198 $IPTCrecordName = array( 199 1 => 'IPTCEnvelope', 200 2 => 'IPTCApplication', 201 3 => 'IPTCNewsPhoto', 202 7 => 'IPTCPreObjectData', 203 8 => 'IPTCObjectData', 204 9 => 'IPTCPostObjectData', 205 ); 206 } 207 return (isset($IPTCrecordName[$iptc_record]) ? $IPTCrecordName[$iptc_record] : ''); 208 } 209 210 211 public function IPTCrecordTagName($iptc_record, $iptc_tagkey) { 212 // http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/IPTC.html 213 static $IPTCrecordTagName = array(); 214 if (empty($IPTCrecordTagName)) { 215 $IPTCrecordTagName = array( 216 1 => array( // IPTC EnvelopeRecord Tags 217 0 => 'EnvelopeRecordVersion', 218 5 => 'Destination', 219 20 => 'FileFormat', 220 22 => 'FileVersion', 221 30 => 'ServiceIdentifier', 222 40 => 'EnvelopeNumber', 223 50 => 'ProductID', 224 60 => 'EnvelopePriority', 225 70 => 'DateSent', 226 80 => 'TimeSent', 227 90 => 'CodedCharacterSet', 228 100 => 'UniqueObjectName', 229 120 => 'ARMIdentifier', 230 122 => 'ARMVersion', 231 ), 232 2 => array( // IPTC ApplicationRecord Tags 233 0 => 'ApplicationRecordVersion', 234 3 => 'ObjectTypeReference', 235 4 => 'ObjectAttributeReference', 236 5 => 'ObjectName', 237 7 => 'EditStatus', 238 8 => 'EditorialUpdate', 239 10 => 'Urgency', 240 12 => 'SubjectReference', 241 15 => 'Category', 242 20 => 'SupplementalCategories', 243 22 => 'FixtureIdentifier', 244 25 => 'Keywords', 245 26 => 'ContentLocationCode', 246 27 => 'ContentLocationName', 247 30 => 'ReleaseDate', 248 35 => 'ReleaseTime', 249 37 => 'ExpirationDate', 250 38 => 'ExpirationTime', 251 40 => 'SpecialInstructions', 252 42 => 'ActionAdvised', 253 45 => 'ReferenceService', 254 47 => 'ReferenceDate', 255 50 => 'ReferenceNumber', 256 55 => 'DateCreated', 257 60 => 'TimeCreated', 258 62 => 'DigitalCreationDate', 259 63 => 'DigitalCreationTime', 260 65 => 'OriginatingProgram', 261 70 => 'ProgramVersion', 262 75 => 'ObjectCycle', 263 80 => 'By-line', 264 85 => 'By-lineTitle', 265 90 => 'City', 266 92 => 'Sub-location', 267 95 => 'Province-State', 268 100 => 'Country-PrimaryLocationCode', 269 101 => 'Country-PrimaryLocationName', 270 103 => 'OriginalTransmissionReference', 271 105 => 'Headline', 272 110 => 'Credit', 273 115 => 'Source', 274 116 => 'CopyrightNotice', 275 118 => 'Contact', 276 120 => 'Caption-Abstract', 277 121 => 'LocalCaption', 278 122 => 'Writer-Editor', 279 125 => 'RasterizedCaption', 280 130 => 'ImageType', 281 131 => 'ImageOrientation', 282 135 => 'LanguageIdentifier', 283 150 => 'AudioType', 284 151 => 'AudioSamplingRate', 285 152 => 'AudioSamplingResolution', 286 153 => 'AudioDuration', 287 154 => 'AudioOutcue', 288 184 => 'JobID', 289 185 => 'MasterDocumentID', 290 186 => 'ShortDocumentID', 291 187 => 'UniqueDocumentID', 292 188 => 'OwnerID', 293 200 => 'ObjectPreviewFileFormat', 294 201 => 'ObjectPreviewFileVersion', 295 202 => 'ObjectPreviewData', 296 221 => 'Prefs', 297 225 => 'ClassifyState', 298 228 => 'SimilarityIndex', 299 230 => 'DocumentNotes', 300 231 => 'DocumentHistory', 301 232 => 'ExifCameraInfo', 302 ), 303 3 => array( // IPTC NewsPhoto Tags 304 0 => 'NewsPhotoVersion', 305 10 => 'IPTCPictureNumber', 306 20 => 'IPTCImageWidth', 307 30 => 'IPTCImageHeight', 308 40 => 'IPTCPixelWidth', 309 50 => 'IPTCPixelHeight', 310 55 => 'SupplementalType', 311 60 => 'ColorRepresentation', 312 64 => 'InterchangeColorSpace', 313 65 => 'ColorSequence', 314 66 => 'ICC_Profile', 315 70 => 'ColorCalibrationMatrix', 316 80 => 'LookupTable', 317 84 => 'NumIndexEntries', 318 85 => 'ColorPalette', 319 86 => 'IPTCBitsPerSample', 320 90 => 'SampleStructure', 321 100 => 'ScanningDirection', 322 102 => 'IPTCImageRotation', 323 110 => 'DataCompressionMethod', 324 120 => 'QuantizationMethod', 325 125 => 'EndPoints', 326 130 => 'ExcursionTolerance', 327 135 => 'BitsPerComponent', 328 140 => 'MaximumDensityRange', 329 145 => 'GammaCompensatedValue', 330 ), 331 7 => array( // IPTC PreObjectData Tags 332 10 => 'SizeMode', 333 20 => 'MaxSubfileSize', 334 90 => 'ObjectSizeAnnounced', 335 95 => 'MaximumObjectSize', 336 ), 337 8 => array( // IPTC ObjectData Tags 338 10 => 'SubFile', 339 ), 340 9 => array( // IPTC PostObjectData Tags 341 10 => 'ConfirmedObjectSize', 342 ), 343 ); 344 345 } 346 return (isset($IPTCrecordTagName[$iptc_record][$iptc_tagkey]) ? $IPTCrecordTagName[$iptc_record][$iptc_tagkey] : $iptc_tagkey); 347 } 348 349} 350