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