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