[4686] | 1 | <?php |
---|
| 2 | /* |
---|
| 3 | * --:: JPEG MetaDatas ::------------------------------------------------------- |
---|
| 4 | * |
---|
| 5 | * Author : Grum |
---|
| 6 | * email : grum at piwigo.org |
---|
| 7 | * website : http://photos.grum.fr |
---|
| 8 | * |
---|
| 9 | * << May the Little SpaceFrog be with you ! >> |
---|
| 10 | * |
---|
| 11 | * |
---|
| 12 | * +-----------------------------------------------------------------------+ |
---|
| 13 | * | JpegMetaData - a PHP based Jpeg Metadata manager | |
---|
| 14 | * +-----------------------------------------------------------------------+ |
---|
| 15 | * | Copyright(C) 2010 Grum - http://www.grum.fr | |
---|
| 16 | * +-----------------------------------------------------------------------+ |
---|
| 17 | * | This program is free software; you can redistribute it and/or modify | |
---|
| 18 | * | it under the terms of the GNU General Public License as published by | |
---|
| 19 | * | the Free Software Foundation | |
---|
| 20 | * | | |
---|
| 21 | * | This program is distributed in the hope that it will be useful, but | |
---|
| 22 | * | WITHOUT ANY WARRANTY; without even the implied warranty of | |
---|
| 23 | * | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
---|
| 24 | * | General Public License for more details. | |
---|
| 25 | * | | |
---|
| 26 | * | You should have received a copy of the GNU General Public License | |
---|
| 27 | * | along with this program; if not, write to the Free Software | |
---|
| 28 | * | Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, | |
---|
| 29 | * | USA. | |
---|
| 30 | * +-----------------------------------------------------------------------+ |
---|
| 31 | * |
---|
| 32 | * |
---|
| 33 | * ----------------------------------------------------------------------------- |
---|
| 34 | * |
---|
| 35 | * The IptcReader class is the dedicated class to read IPTC from the APP13 |
---|
| 36 | * segment |
---|
| 37 | * |
---|
| 38 | * The APP13 segment (the 'photoshop' segment) contain 8BIM blocks. |
---|
| 39 | * The IPTC are stored inside the specific 0x0404 block. If there is more than |
---|
| 40 | * one 8BIM 0x0404 block, the IptcReader reads all blocks and merge IPTC tags. |
---|
| 41 | * |
---|
| 42 | * =======> See HeightBIMReader.class.php to know more about 8BIM blocks <====== |
---|
| 43 | * |
---|
| 44 | * ----------------------------------------------------------------------------- |
---|
| 45 | * |
---|
| 46 | * .. Notes .. |
---|
| 47 | * |
---|
| 48 | * The IptcReader class is derived from the GenericReader class. |
---|
| 49 | * |
---|
| 50 | * ======> See GenericReader.class.php to know more about common methods <====== |
---|
| 51 | * |
---|
| 52 | * This class provides theses public functions : |
---|
| 53 | * - optimizeDateTime |
---|
| 54 | * |
---|
| 55 | * ----------------------------------------------------------------------------- |
---|
| 56 | */ |
---|
| 57 | |
---|
| 58 | require_once(JPEG_METADATA_DIR."Common/Data.class.php"); |
---|
| 59 | require_once(JPEG_METADATA_DIR."Readers/HeightBIMReader.class.php"); |
---|
| 60 | require_once(JPEG_METADATA_DIR."TagDefinitions/IptcTags.class.php"); |
---|
| 61 | |
---|
| 62 | |
---|
| 63 | class IptcReader extends GenericReader |
---|
| 64 | { |
---|
| 65 | const HEADER_1 = "Photoshop 3.0\x00"; |
---|
| 66 | const HEADER_2 = "Adobe_Photoshop2.5:\x00"; |
---|
[6729] | 67 | protected $schema = Schemas::IPTC; |
---|
[4686] | 68 | |
---|
| 69 | private $header = ""; |
---|
| 70 | |
---|
| 71 | /** |
---|
| 72 | * The constructor needs, like the ancestor, the datas to be parsed |
---|
| 73 | * |
---|
| 74 | * @param String $data |
---|
| 75 | */ |
---|
| 76 | function __construct($data) |
---|
| 77 | { |
---|
| 78 | parent::__construct($data); |
---|
| 79 | |
---|
| 80 | /* |
---|
| 81 | * read the header |
---|
| 82 | * if header is a valid header, read entries |
---|
| 83 | */ |
---|
| 84 | if($this->readHeader()) |
---|
| 85 | { |
---|
| 86 | $this->initializeEntries(); |
---|
| 87 | } |
---|
| 88 | } |
---|
| 89 | |
---|
[4904] | 90 | function __destruct() |
---|
| 91 | { |
---|
| 92 | parent::__destruct(); |
---|
| 93 | } |
---|
| 94 | |
---|
| 95 | |
---|
[4686] | 96 | public function toString() |
---|
| 97 | { |
---|
| 98 | $returned="IPTC ; NbEntries: ".sprintf("%02d", $this->nbEntries); |
---|
| 99 | return($returned); |
---|
| 100 | } |
---|
| 101 | |
---|
| 102 | /** |
---|
| 103 | * All IPTC Date & Time are separated into distinct tags |
---|
| 104 | * this function complete "date" tags with the associated "time" tags, and |
---|
| 105 | * delete the "time" tags |
---|
| 106 | * |
---|
| 107 | * Example : |
---|
| 108 | * 0x146 "Date Sent" = 2009/12/24 => tag value : 2009/12/24 00:00:00 |
---|
| 109 | * 0x150 "Time Sent" = 19:43:28 => tag value : 0001/01/01 19:43:28 |
---|
| 110 | * |
---|
| 111 | * Optimize Date merge date & time => tag value : 2009/12/24 19:43:28 |
---|
| 112 | * |
---|
| 113 | */ |
---|
| 114 | public function optimizeDateTime() |
---|
| 115 | { |
---|
| 116 | $assoc=Array( |
---|
| 117 | Array(0x0146, 0x0150), |
---|
| 118 | Array(0x021E, 0x0223), |
---|
| 119 | Array(0x0225, 0x0226), |
---|
| 120 | Array(0x0237, 0x023C), |
---|
| 121 | Array(0x023E, 0x023F), |
---|
| 122 | ); |
---|
| 123 | |
---|
| 124 | foreach($assoc as $val) |
---|
| 125 | { |
---|
| 126 | $tagD=$this->getTagIndexById($val[0]); |
---|
| 127 | $tagT=$this->getTagIndexById($val[1]); |
---|
| 128 | |
---|
| 129 | if($tagD>-1 and $tagT>-1) |
---|
| 130 | { |
---|
| 131 | /* |
---|
| 132 | * can't use the timestamp function because not compatible with php < 5.3 |
---|
| 133 | */ |
---|
[5237] | 134 | if($this->entries[$tagD]->getLabel() instanceof DateTime and |
---|
| 135 | $this->entries[$tagT]->getLabel() instanceof DateTime) |
---|
[5231] | 136 | { |
---|
| 137 | $this->entries[$tagD]->getLabel()->setTime( |
---|
| 138 | (int)$this->entries[$tagT]->getLabel()->format("H"), |
---|
| 139 | (int)$this->entries[$tagT]->getLabel()->format("i"), |
---|
| 140 | (int)$this->entries[$tagT]->getLabel()->format("s") |
---|
| 141 | ); |
---|
| 142 | } |
---|
[4686] | 143 | array_splice($this->entries, $tagT, 1); |
---|
| 144 | } |
---|
[4904] | 145 | unset($tagD); |
---|
| 146 | unset($tagT); |
---|
[4686] | 147 | } |
---|
| 148 | } |
---|
| 149 | |
---|
| 150 | |
---|
| 151 | /** |
---|
| 152 | * initialize the definition for IPTC tags |
---|
| 153 | */ |
---|
| 154 | protected function initializeTagDef() |
---|
| 155 | { |
---|
| 156 | $this->tagDef = new IptcTags(); |
---|
| 157 | } |
---|
| 158 | |
---|
| 159 | /** |
---|
| 160 | * read the header of the APP13 segment, and try to determinate wich kind of |
---|
| 161 | * data are stored |
---|
| 162 | * |
---|
| 163 | * at now, only "Photoshop 3.0" data structure is known |
---|
| 164 | * the "Adobe_Photoshop2.5" data structure is not recognized yet |
---|
| 165 | * |
---|
| 166 | * @return Boolean : true if the header is known |
---|
| 167 | */ |
---|
| 168 | private function readHeader() |
---|
| 169 | { |
---|
| 170 | $this->data->seek(); |
---|
| 171 | $header=$this->data->readASCII(strlen(self::HEADER_1)); |
---|
| 172 | if($header==self::HEADER_1) |
---|
| 173 | { |
---|
| 174 | $this->header=$header; |
---|
| 175 | return(true); |
---|
| 176 | } |
---|
| 177 | |
---|
| 178 | $this->data->seek(); |
---|
| 179 | $header=$this->data->readASCII(strlen(self::HEADER_2)); |
---|
| 180 | if($header==self::HEADER_2) |
---|
| 181 | { |
---|
| 182 | $this->header=$header; |
---|
| 183 | /* |
---|
| 184 | * structure from an HEADER_2 is not known.... |
---|
| 185 | */ |
---|
| 186 | return(false); |
---|
| 187 | } |
---|
| 188 | |
---|
| 189 | return(false); |
---|
| 190 | } |
---|
| 191 | |
---|
| 192 | /** |
---|
| 193 | * reads all the 8BIM blocks of the segment. If the 8BIM block is an IPTC |
---|
| 194 | * block, read all the IPTC entries and set the Tag properties |
---|
| 195 | * |
---|
| 196 | * An entry is a Tag object |
---|
| 197 | * |
---|
| 198 | * Add the entry to the entries array |
---|
| 199 | * |
---|
| 200 | */ |
---|
| 201 | protected function initializeEntries() |
---|
| 202 | { |
---|
| 203 | $blocks=explode("8BIM", $this->data->readASCII()); |
---|
| 204 | foreach($blocks as $key=> $val) |
---|
| 205 | { |
---|
| 206 | $block=new HeightBIMReader("8BIM".$val); |
---|
| 207 | if($block->isValid()) |
---|
| 208 | { |
---|
| 209 | /* merge entries from all 8BIM blocks */ |
---|
| 210 | $this->entries=array_merge($this->entries, $block->getTags()); |
---|
| 211 | } |
---|
| 212 | unset($block); |
---|
| 213 | } |
---|
[4904] | 214 | unset($blocks); |
---|
[4686] | 215 | |
---|
[6949] | 216 | |
---|
| 217 | /* for each entries, convert value to human readable tag value |
---|
| 218 | * |
---|
| 219 | * build a special 'keywords' tag made as an array from all iptc 'keywords' (0x0219) tags found |
---|
| 220 | */ |
---|
| 221 | $keywordsTag=null; |
---|
[4686] | 222 | foreach($this->entries as $key => $tag) |
---|
| 223 | { |
---|
| 224 | $this->setTagProperties($tag); |
---|
[6949] | 225 | if($tag->getId()==0x0219) |
---|
| 226 | { |
---|
| 227 | if(is_null($keywordsTag)) |
---|
| 228 | { |
---|
| 229 | $keywordsTag=new Tag( |
---|
| 230 | 0x0219, |
---|
| 231 | array($tag->getValue()), |
---|
| 232 | $tag->getName(), |
---|
| 233 | array($tag->getLabel()), |
---|
| 234 | "", |
---|
| 235 | $tag->isKnown(), |
---|
| 236 | $tag->isImplemented(), |
---|
| 237 | $tag->isTranslatable(), |
---|
| 238 | $tag->getSchema() |
---|
| 239 | ); |
---|
| 240 | } |
---|
| 241 | else |
---|
| 242 | { |
---|
| 243 | $keywordsTag->setValue(array_merge($keywordsTag->getValue(), array($tag->getValue()))); |
---|
| 244 | $keywordsTag->setLabel(array_merge($keywordsTag->getLabel(), array($tag->getLabel()))); |
---|
| 245 | } |
---|
| 246 | } |
---|
[4686] | 247 | } |
---|
[6949] | 248 | if(!is_null($keywordsTag)) |
---|
| 249 | { |
---|
| 250 | /* |
---|
| 251 | * IPTC 'keywords' is stored like XMP 'xmp.dc:subject' (as a 'seq') |
---|
| 252 | */ |
---|
| 253 | $keywordsTag->setValue( |
---|
| 254 | array( |
---|
| 255 | 'type' => 'seq', |
---|
| 256 | 'values' => $keywordsTag->getValue() |
---|
| 257 | ) |
---|
| 258 | ); |
---|
| 259 | |
---|
| 260 | $keywordsTag->setLabel( |
---|
| 261 | array( |
---|
| 262 | 'type' => 'seq', |
---|
| 263 | 'values' => $keywordsTag->getLabel() |
---|
| 264 | ) |
---|
| 265 | ); |
---|
| 266 | $this->entries[]=$keywordsTag; |
---|
| 267 | unset($keywordsTag); |
---|
| 268 | } |
---|
[4686] | 269 | } |
---|
| 270 | |
---|
| 271 | /** |
---|
| 272 | * Interprets the tag values into 'human readable values' |
---|
| 273 | * |
---|
| 274 | * @param Tag $entry |
---|
| 275 | */ |
---|
| 276 | private function setTagProperties($tag) |
---|
| 277 | { |
---|
| 278 | /* |
---|
| 279 | * if the given tag id is defined, analyzing its values |
---|
| 280 | */ |
---|
| 281 | if($this->tagDef->tagIdExists($tag->getId())) |
---|
| 282 | { |
---|
| 283 | $tagProperties=$this->tagDef->getTagById($tag->getId()); |
---|
| 284 | |
---|
[4698] | 285 | $tag->setKnown(true); |
---|
[4686] | 286 | $tag->setName($tagProperties['tagName']); |
---|
[4698] | 287 | $tag->setImplemented($tagProperties['implemented']); |
---|
| 288 | $tag->setTranslatable($tagProperties['translatable']); |
---|
[6729] | 289 | $tag->setSchema($this->schema); |
---|
[4686] | 290 | |
---|
| 291 | /* |
---|
| 292 | * if there is values defined for the tag, analyze it |
---|
| 293 | */ |
---|
| 294 | if(array_key_exists('tagValues', $tagProperties)) |
---|
| 295 | { |
---|
| 296 | if(array_key_exists($tag->getValue(), $tagProperties['tagValues'])) |
---|
| 297 | { |
---|
| 298 | $tag->setLabel($tagProperties['tagValues'][$tag->getValue()]); |
---|
| 299 | } |
---|
| 300 | else |
---|
| 301 | { |
---|
| 302 | $tag->setLabel("[unknow value 0x".sprintf("%04x", $tag->getValue())."]"); |
---|
| 303 | } |
---|
| 304 | } |
---|
| 305 | else |
---|
| 306 | { |
---|
| 307 | /* |
---|
| 308 | * there is no values defined for the tag, analyzing it with dedicated |
---|
| 309 | * function |
---|
| 310 | */ |
---|
| 311 | $tag->setLabel($this->processSpecialTag($tag->getId(), $tag->getValue(), 0, 0)); |
---|
| 312 | } |
---|
| 313 | } |
---|
| 314 | } |
---|
| 315 | |
---|
| 316 | /** |
---|
| 317 | * this function can be overrided to process special tags |
---|
| 318 | */ |
---|
| 319 | protected function processSpecialTag($tagId, $values, $type, $valuesOffset=0) |
---|
| 320 | { |
---|
| 321 | switch($tagId) |
---|
| 322 | { |
---|
| 323 | /* |
---|
| 324 | * Tags managed |
---|
| 325 | */ |
---|
| 326 | case 0x0105: // 2:05 - Destination |
---|
| 327 | case 0x011E: // 1:30 - Service Identifier |
---|
| 328 | case 0x0128: // 1:40 - Envelope Number |
---|
| 329 | case 0x0132: // 1:50 - Product I.D. |
---|
| 330 | case 0x0205: // 2:05 - Title |
---|
| 331 | case 0x0207: // 2:07 - Edit Status |
---|
| 332 | case 0x020F: // 2:15 - Category |
---|
| 333 | case 0x0214: // 2:20 - Supplemental Category |
---|
| 334 | case 0x0216: // 2:22 - Fixture Identifier |
---|
| 335 | case 0x0219: // 2:25 - Keywords |
---|
| 336 | case 0x021A: // 2:25 - Content Location Code |
---|
| 337 | case 0x021B: // 2:25 - Content Location Name |
---|
| 338 | case 0x0228: // 2:40 - Special Instructions |
---|
| 339 | case 0x0241: // 2:65 - Originating Program |
---|
| 340 | case 0x0246: // 2:70 - Program Version |
---|
| 341 | case 0x0250: // 2:80 - By-line |
---|
| 342 | case 0x0255: // 2:80 - By-line Title |
---|
| 343 | case 0x025A: // 2:90 - City |
---|
| 344 | case 0x025C: // 2:92 - Sublocation |
---|
| 345 | case 0x025F: // 2:95 - Province/State |
---|
| 346 | case 0x0264: // 2:100 - Country Code |
---|
| 347 | case 0x0265: // 2:101 - Country |
---|
| 348 | case 0x0267: // 2:103 - Original Transmission Reference |
---|
| 349 | case 0x0269: // 2:105 - Headline |
---|
| 350 | case 0x026E: // 2:110 - credit |
---|
| 351 | case 0x0273: // 2:115 - source |
---|
| 352 | case 0x0274: // 2:116 - Copyright Notice |
---|
| 353 | case 0x0276: // 2:118 - Contact |
---|
| 354 | case 0x0278: // 2:120 - Description |
---|
| 355 | case 0x027A: // 2:122 - Writer/Editor |
---|
| 356 | case 0x0287: // 2:150 - Language Identifier |
---|
[6949] | 357 | $returned=utf8_encode($values); |
---|
[4686] | 358 | break; |
---|
| 359 | case 0x0114: // 1:20 - File Format |
---|
| 360 | $tag=$this->tagDef->getTagById(0x0114); |
---|
| 361 | $returned=$tag['tagValues.special'][ConvertData::toUShort($values, BYTE_ORDER_BIG_ENDIAN)]; |
---|
[4904] | 362 | unset($tag); |
---|
[4686] | 363 | break; |
---|
| 364 | case 0x0203: // 2:03 - Object Type Reference |
---|
| 365 | case 0x0204: // 2:04 - Intellectual Genre |
---|
| 366 | $returned=explode(":", $values); |
---|
| 367 | break; |
---|
| 368 | case 0x0146: // 1:70 - Date Sent |
---|
| 369 | case 0x021E: // 2:30 - Release Date |
---|
| 370 | case 0x0225: // 2:37 - Expiration Date |
---|
| 371 | case 0x0237: // 2:55 - Date Created |
---|
| 372 | case 0x023E: // 2:62 - Digital Creation Date |
---|
| 373 | $returned=ConvertData::toDateTime($values); |
---|
| 374 | break; |
---|
| 375 | case 0x0150: // 1:80 - Time Sent |
---|
| 376 | case 0x0223: // 2:35 - Release Time |
---|
| 377 | case 0x0226: // 2:38 - Expiration Time |
---|
| 378 | case 0x023C: // 2:60 - Time Created |
---|
| 379 | case 0x023F: // 2:63 - Digital Creation Time |
---|
| 380 | $returned=ConvertData::toDateTime("00010101T".$values); |
---|
| 381 | break; |
---|
| 382 | /* |
---|
| 383 | * Tags not managed |
---|
| 384 | */ |
---|
| 385 | default: |
---|
| 386 | $returned="Not yet implemented;".ConvertData::toHexDump($tagId, ByteType::USHORT)." => ".ConvertData::toHexDump($values, $type, 64)." [$values]"; |
---|
| 387 | break; |
---|
| 388 | } |
---|
| 389 | return($returned); |
---|
| 390 | } |
---|
| 391 | } |
---|
| 392 | |
---|
| 393 | |
---|
| 394 | |
---|
| 395 | |
---|
| 396 | ?> |
---|