[ Index ] |
PHP Cross Reference of DokuWiki |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * JPEG metadata reader/writer 4 * 5 * @license BSD <http://www.opensource.org/licenses/bsd-license.php> 6 * @link http://github.com/sd/jpeg-php 7 * @author Sebastian Delmont <sdelmont@zonageek.com> 8 * @author Andreas Gohr <andi@splitbrain.org> 9 * @author Hakan Sandell <hakan.sandell@mydata.se> 10 * @todo Add support for Maker Notes, Extend for GIF and PNG metadata 11 */ 12 13 // Original copyright notice: 14 // 15 // Copyright (c) 2003 Sebastian Delmont <sdelmont@zonageek.com> 16 // All rights reserved. 17 // 18 // Redistribution and use in source and binary forms, with or without 19 // modification, are permitted provided that the following conditions 20 // are met: 21 // 1. Redistributions of source code must retain the above copyright 22 // notice, this list of conditions and the following disclaimer. 23 // 2. Redistributions in binary form must reproduce the above copyright 24 // notice, this list of conditions and the following disclaimer in the 25 // documentation and/or other materials provided with the distribution. 26 // 3. Neither the name of the author nor the names of its contributors 27 // may be used to endorse or promote products derived from this software 28 // without specific prior written permission. 29 // 30 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 31 // IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 32 // TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 33 // PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 34 // HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 35 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 36 // TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 37 // PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 38 // LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 39 // NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 40 // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE 41 42 class JpegMeta { 43 var $_fileName; 44 var $_fp = null; 45 var $_fpout = null; 46 var $_type = 'unknown'; 47 48 var $_markers; 49 var $_info; 50 51 52 /** 53 * Constructor 54 * 55 * @author Sebastian Delmont <sdelmont@zonageek.com> 56 * 57 * @param $fileName 58 */ 59 function __construct($fileName) { 60 61 $this->_fileName = $fileName; 62 63 $this->_fp = null; 64 $this->_type = 'unknown'; 65 66 unset($this->_info); 67 unset($this->_markers); 68 } 69 70 /** 71 * Returns all gathered info as multidim array 72 * 73 * @author Sebastian Delmont <sdelmont@zonageek.com> 74 */ 75 function & getRawInfo() { 76 $this->_parseAll(); 77 78 if ($this->_markers == null) { 79 return false; 80 } 81 82 return $this->_info; 83 } 84 85 /** 86 * Returns basic image info 87 * 88 * @author Sebastian Delmont <sdelmont@zonageek.com> 89 */ 90 function & getBasicInfo() { 91 $this->_parseAll(); 92 93 $info = array(); 94 95 if ($this->_markers == null) { 96 return false; 97 } 98 99 $info['Name'] = $this->_info['file']['Name']; 100 if (isset($this->_info['file']['Url'])) { 101 $info['Url'] = $this->_info['file']['Url']; 102 $info['NiceSize'] = "???KB"; 103 } else { 104 $info['Size'] = $this->_info['file']['Size']; 105 $info['NiceSize'] = $this->_info['file']['NiceSize']; 106 } 107 108 if (@isset($this->_info['sof']['Format'])) { 109 $info['Format'] = $this->_info['sof']['Format'] . " JPEG"; 110 } else { 111 $info['Format'] = $this->_info['sof']['Format'] . " JPEG"; 112 } 113 114 if (@isset($this->_info['sof']['ColorChannels'])) { 115 $info['ColorMode'] = ($this->_info['sof']['ColorChannels'] > 1) ? "Color" : "B&W"; 116 } 117 118 $info['Width'] = $this->getWidth(); 119 $info['Height'] = $this->getHeight(); 120 $info['DimStr'] = $this->getDimStr(); 121 122 $dates = $this->getDates(); 123 124 $info['DateTime'] = $dates['EarliestTime']; 125 $info['DateTimeStr'] = $dates['EarliestTimeStr']; 126 127 $info['HasThumbnail'] = $this->hasThumbnail(); 128 129 return $info; 130 } 131 132 133 /** 134 * Convinience function to access nearly all available Data 135 * through one function 136 * 137 * @author Andreas Gohr <andi@splitbrain.org> 138 * 139 * @param array|string $fields field name or array with field names 140 * @return bool|string 141 */ 142 function getField($fields) { 143 if(!is_array($fields)) $fields = array($fields); 144 $info = false; 145 foreach($fields as $field){ 146 if(strtolower(substr($field,0,5)) == 'iptc.'){ 147 $info = $this->getIPTCField(substr($field,5)); 148 }elseif(strtolower(substr($field,0,5)) == 'exif.'){ 149 $info = $this->getExifField(substr($field,5)); 150 }elseif(strtolower(substr($field,0,4)) == 'xmp.'){ 151 $info = $this->getXmpField(substr($field,4)); 152 }elseif(strtolower(substr($field,0,5)) == 'file.'){ 153 $info = $this->getFileField(substr($field,5)); 154 }elseif(strtolower(substr($field,0,5)) == 'date.'){ 155 $info = $this->getDateField(substr($field,5)); 156 }elseif(strtolower($field) == 'simple.camera'){ 157 $info = $this->getCamera(); 158 }elseif(strtolower($field) == 'simple.raw'){ 159 return $this->getRawInfo(); 160 }elseif(strtolower($field) == 'simple.title'){ 161 $info = $this->getTitle(); 162 }elseif(strtolower($field) == 'simple.shutterspeed'){ 163 $info = $this->getShutterSpeed(); 164 }else{ 165 $info = $this->getExifField($field); 166 } 167 if($info != false) break; 168 } 169 170 if($info === false) $info = ''; 171 if(is_array($info)){ 172 if(isset($info['val'])){ 173 $info = $info['val']; 174 }else{ 175 $arr = array(); 176 foreach($info as $part){ 177 if(is_array($part)){ 178 if(isset($part['val'])){ 179 $arr[] = $part['val']; 180 }else{ 181 $arr[] = join(', ',$part); 182 } 183 }else{ 184 $arr[] = $part; 185 } 186 } 187 $info = join(', ',$arr); 188 } 189 } 190 return trim($info); 191 } 192 193 /** 194 * Convinience function to set nearly all available Data 195 * through one function 196 * 197 * @author Andreas Gohr <andi@splitbrain.org> 198 * 199 * @param string $field field name 200 * @param string $value 201 * @return bool success or fail 202 */ 203 function setField($field, $value) { 204 if(strtolower(substr($field,0,5)) == 'iptc.'){ 205 return $this->setIPTCField(substr($field,5),$value); 206 }elseif(strtolower(substr($field,0,5)) == 'exif.'){ 207 return $this->setExifField(substr($field,5),$value); 208 }else{ 209 return $this->setExifField($field,$value); 210 } 211 } 212 213 /** 214 * Convinience function to delete nearly all available Data 215 * through one function 216 * 217 * @author Andreas Gohr <andi@splitbrain.org> 218 * 219 * @param string $field field name 220 * @return bool 221 */ 222 function deleteField($field) { 223 if(strtolower(substr($field,0,5)) == 'iptc.'){ 224 return $this->deleteIPTCField(substr($field,5)); 225 }elseif(strtolower(substr($field,0,5)) == 'exif.'){ 226 return $this->deleteExifField(substr($field,5)); 227 }else{ 228 return $this->deleteExifField($field); 229 } 230 } 231 232 /** 233 * Return a date field 234 * 235 * @author Andreas Gohr <andi@splitbrain.org> 236 * 237 * @param string $field 238 * @return false|string 239 */ 240 function getDateField($field) { 241 if (!isset($this->_info['dates'])) { 242 $this->_info['dates'] = $this->getDates(); 243 } 244 245 if (isset($this->_info['dates'][$field])) { 246 return $this->_info['dates'][$field]; 247 } 248 249 return false; 250 } 251 252 /** 253 * Return a file info field 254 * 255 * @author Andreas Gohr <andi@splitbrain.org> 256 * 257 * @param string $field field name 258 * @return false|string 259 */ 260 function getFileField($field) { 261 if (!isset($this->_info['file'])) { 262 $this->_parseFileInfo(); 263 } 264 265 if (isset($this->_info['file'][$field])) { 266 return $this->_info['file'][$field]; 267 } 268 269 return false; 270 } 271 272 /** 273 * Return the camera info (Maker and Model) 274 * 275 * @author Andreas Gohr <andi@splitbrain.org> 276 * @todo handle makernotes 277 * 278 * @return false|string 279 */ 280 function getCamera(){ 281 $make = $this->getField(array('Exif.Make','Exif.TIFFMake')); 282 $model = $this->getField(array('Exif.Model','Exif.TIFFModel')); 283 $cam = trim("$make $model"); 284 if(empty($cam)) return false; 285 return $cam; 286 } 287 288 /** 289 * Return shutter speed as a ratio 290 * 291 * @author Joe Lapp <joe.lapp@pobox.com> 292 * 293 * @return string 294 */ 295 function getShutterSpeed() { 296 if (!isset($this->_info['exif'])) { 297 $this->_parseMarkerExif(); 298 } 299 if(!isset($this->_info['exif']['ExposureTime'])){ 300 return ''; 301 } 302 303 $field = $this->_info['exif']['ExposureTime']; 304 if($field['den'] == 1) return $field['num']; 305 return $field['num'].'/'.$field['den']; 306 } 307 308 /** 309 * Return an EXIF field 310 * 311 * @author Sebastian Delmont <sdelmont@zonageek.com> 312 * 313 * @param string $field field name 314 * @return false|string 315 */ 316 function getExifField($field) { 317 if (!isset($this->_info['exif'])) { 318 $this->_parseMarkerExif(); 319 } 320 321 if ($this->_markers == null) { 322 return false; 323 } 324 325 if (isset($this->_info['exif'][$field])) { 326 return $this->_info['exif'][$field]; 327 } 328 329 return false; 330 } 331 332 /** 333 * Return an XMP field 334 * 335 * @author Hakan Sandell <hakan.sandell@mydata.se> 336 * 337 * @param string $field field name 338 * @return false|string 339 */ 340 function getXmpField($field) { 341 if (!isset($this->_info['xmp'])) { 342 $this->_parseMarkerXmp(); 343 } 344 345 if ($this->_markers == null) { 346 return false; 347 } 348 349 if (isset($this->_info['xmp'][$field])) { 350 return $this->_info['xmp'][$field]; 351 } 352 353 return false; 354 } 355 356 /** 357 * Return an Adobe Field 358 * 359 * @author Sebastian Delmont <sdelmont@zonageek.com> 360 * 361 * @param string $field field name 362 * @return false|string 363 */ 364 function getAdobeField($field) { 365 if (!isset($this->_info['adobe'])) { 366 $this->_parseMarkerAdobe(); 367 } 368 369 if ($this->_markers == null) { 370 return false; 371 } 372 373 if (isset($this->_info['adobe'][$field])) { 374 return $this->_info['adobe'][$field]; 375 } 376 377 return false; 378 } 379 380 /** 381 * Return an IPTC field 382 * 383 * @author Sebastian Delmont <sdelmont@zonageek.com> 384 * 385 * @param string $field field name 386 * @return false|string 387 */ 388 function getIPTCField($field) { 389 if (!isset($this->_info['iptc'])) { 390 $this->_parseMarkerAdobe(); 391 } 392 393 if ($this->_markers == null) { 394 return false; 395 } 396 397 if (isset($this->_info['iptc'][$field])) { 398 return $this->_info['iptc'][$field]; 399 } 400 401 return false; 402 } 403 404 /** 405 * Set an EXIF field 406 * 407 * @author Sebastian Delmont <sdelmont@zonageek.com> 408 * @author Joe Lapp <joe.lapp@pobox.com> 409 * 410 * @param string $field field name 411 * @param string $value 412 * @return bool 413 */ 414 function setExifField($field, $value) { 415 if (!isset($this->_info['exif'])) { 416 $this->_parseMarkerExif(); 417 } 418 419 if ($this->_markers == null) { 420 return false; 421 } 422 423 if ($this->_info['exif'] == false) { 424 $this->_info['exif'] = array(); 425 } 426 427 // make sure datetimes are in correct format 428 if(strlen($field) >= 8 && strtolower(substr($field, 0, 8)) == 'datetime') { 429 if(strlen($value) < 8 || $value[4] != ':' || $value[7] != ':') { 430 $value = date('Y:m:d H:i:s', strtotime($value)); 431 } 432 } 433 434 $this->_info['exif'][$field] = $value; 435 436 return true; 437 } 438 439 /** 440 * Set an Adobe Field 441 * 442 * @author Sebastian Delmont <sdelmont@zonageek.com> 443 * 444 * @param string $field field name 445 * @param string $value 446 * @return bool 447 */ 448 function setAdobeField($field, $value) { 449 if (!isset($this->_info['adobe'])) { 450 $this->_parseMarkerAdobe(); 451 } 452 453 if ($this->_markers == null) { 454 return false; 455 } 456 457 if ($this->_info['adobe'] == false) { 458 $this->_info['adobe'] = array(); 459 } 460 461 $this->_info['adobe'][$field] = $value; 462 463 return true; 464 } 465 466 /** 467 * Calculates the multiplier needed to resize the image to the given 468 * dimensions 469 * 470 * @author Andreas Gohr <andi@splitbrain.org> 471 * 472 * @param int $maxwidth 473 * @param int $maxheight 474 * @return float|int 475 */ 476 function getResizeRatio($maxwidth,$maxheight=0){ 477 if(!$maxheight) $maxheight = $maxwidth; 478 479 $w = $this->getField('File.Width'); 480 $h = $this->getField('File.Height'); 481 482 $ratio = 1; 483 if($w >= $h){ 484 if($w >= $maxwidth){ 485 $ratio = $maxwidth/$w; 486 }elseif($h > $maxheight){ 487 $ratio = $maxheight/$h; 488 } 489 }else{ 490 if($h >= $maxheight){ 491 $ratio = $maxheight/$h; 492 }elseif($w > $maxwidth){ 493 $ratio = $maxwidth/$w; 494 } 495 } 496 return $ratio; 497 } 498 499 500 /** 501 * Set an IPTC field 502 * 503 * @author Sebastian Delmont <sdelmont@zonageek.com> 504 * 505 * @param string $field field name 506 * @param string $value 507 * @return bool 508 */ 509 function setIPTCField($field, $value) { 510 if (!isset($this->_info['iptc'])) { 511 $this->_parseMarkerAdobe(); 512 } 513 514 if ($this->_markers == null) { 515 return false; 516 } 517 518 if ($this->_info['iptc'] == false) { 519 $this->_info['iptc'] = array(); 520 } 521 522 $this->_info['iptc'][$field] = $value; 523 524 return true; 525 } 526 527 /** 528 * Delete an EXIF field 529 * 530 * @author Sebastian Delmont <sdelmont@zonageek.com> 531 * 532 * @param string $field field name 533 * @return bool 534 */ 535 function deleteExifField($field) { 536 if (!isset($this->_info['exif'])) { 537 $this->_parseMarkerAdobe(); 538 } 539 540 if ($this->_markers == null) { 541 return false; 542 } 543 544 if ($this->_info['exif'] != false) { 545 unset($this->_info['exif'][$field]); 546 } 547 548 return true; 549 } 550 551 /** 552 * Delete an Adobe field 553 * 554 * @author Sebastian Delmont <sdelmont@zonageek.com> 555 * 556 * @param string $field field name 557 * @return bool 558 */ 559 function deleteAdobeField($field) { 560 if (!isset($this->_info['adobe'])) { 561 $this->_parseMarkerAdobe(); 562 } 563 564 if ($this->_markers == null) { 565 return false; 566 } 567 568 if ($this->_info['adobe'] != false) { 569 unset($this->_info['adobe'][$field]); 570 } 571 572 return true; 573 } 574 575 /** 576 * Delete an IPTC field 577 * 578 * @author Sebastian Delmont <sdelmont@zonageek.com> 579 * 580 * @param string $field field name 581 * @return bool 582 */ 583 function deleteIPTCField($field) { 584 if (!isset($this->_info['iptc'])) { 585 $this->_parseMarkerAdobe(); 586 } 587 588 if ($this->_markers == null) { 589 return false; 590 } 591 592 if ($this->_info['iptc'] != false) { 593 unset($this->_info['iptc'][$field]); 594 } 595 596 return true; 597 } 598 599 /** 600 * Get the image's title, tries various fields 601 * 602 * @param int $max maximum number chars (keeps words) 603 * @return false|string 604 * 605 * @author Andreas Gohr <andi@splitbrain.org> 606 */ 607 function getTitle($max=80){ 608 // try various fields 609 $cap = $this->getField(array('Iptc.Headline', 610 'Iptc.Caption', 611 'Xmp.dc:title', 612 'Exif.UserComment', 613 'Exif.TIFFUserComment', 614 'Exif.TIFFImageDescription', 615 'File.Name')); 616 if (empty($cap)) return false; 617 618 if(!$max) return $cap; 619 // Shorten to 80 chars (keeping words) 620 $new = preg_replace('/\n.+$/','',wordwrap($cap, $max)); 621 if($new != $cap) $new .= '...'; 622 623 return $new; 624 } 625 626 /** 627 * Gather various date fields 628 * 629 * @author Sebastian Delmont <sdelmont@zonageek.com> 630 * 631 * @return array|bool 632 */ 633 function getDates() { 634 $this->_parseAll(); 635 if ($this->_markers == null) { 636 if (@isset($this->_info['file']['UnixTime'])) { 637 $dates = array(); 638 $dates['FileModified'] = $this->_info['file']['UnixTime']; 639 $dates['Time'] = $this->_info['file']['UnixTime']; 640 $dates['TimeSource'] = 'FileModified'; 641 $dates['TimeStr'] = date("Y-m-d H:i:s", $this->_info['file']['UnixTime']); 642 $dates['EarliestTime'] = $this->_info['file']['UnixTime']; 643 $dates['EarliestTimeSource'] = 'FileModified'; 644 $dates['EarliestTimeStr'] = date("Y-m-d H:i:s", $this->_info['file']['UnixTime']); 645 $dates['LatestTime'] = $this->_info['file']['UnixTime']; 646 $dates['LatestTimeSource'] = 'FileModified'; 647 $dates['LatestTimeStr'] = date("Y-m-d H:i:s", $this->_info['file']['UnixTime']); 648 return $dates; 649 } 650 return false; 651 } 652 653 $dates = array(); 654 655 $latestTime = 0; 656 $latestTimeSource = ""; 657 $earliestTime = time(); 658 $earliestTimeSource = ""; 659 660 if (@isset($this->_info['exif']['DateTime'])) { 661 $dates['ExifDateTime'] = $this->_info['exif']['DateTime']; 662 663 $aux = $this->_info['exif']['DateTime']; 664 $aux[4] = "-"; 665 $aux[7] = "-"; 666 $t = strtotime($aux); 667 668 if ($t && $t > $latestTime) { 669 $latestTime = $t; 670 $latestTimeSource = "ExifDateTime"; 671 } 672 673 if ($t && $t < $earliestTime) { 674 $earliestTime = $t; 675 $earliestTimeSource = "ExifDateTime"; 676 } 677 } 678 679 if (@isset($this->_info['exif']['DateTimeOriginal'])) { 680 $dates['ExifDateTimeOriginal'] = $this->_info['exif']['DateTimeOriginal']; 681 682 $aux = $this->_info['exif']['DateTimeOriginal']; 683 $aux[4] = "-"; 684 $aux[7] = "-"; 685 $t = strtotime($aux); 686 687 if ($t && $t > $latestTime) { 688 $latestTime = $t; 689 $latestTimeSource = "ExifDateTimeOriginal"; 690 } 691 692 if ($t && $t < $earliestTime) { 693 $earliestTime = $t; 694 $earliestTimeSource = "ExifDateTimeOriginal"; 695 } 696 } 697 698 if (@isset($this->_info['exif']['DateTimeDigitized'])) { 699 $dates['ExifDateTimeDigitized'] = $this->_info['exif']['DateTimeDigitized']; 700 701 $aux = $this->_info['exif']['DateTimeDigitized']; 702 $aux[4] = "-"; 703 $aux[7] = "-"; 704 $t = strtotime($aux); 705 706 if ($t && $t > $latestTime) { 707 $latestTime = $t; 708 $latestTimeSource = "ExifDateTimeDigitized"; 709 } 710 711 if ($t && $t < $earliestTime) { 712 $earliestTime = $t; 713 $earliestTimeSource = "ExifDateTimeDigitized"; 714 } 715 } 716 717 if (@isset($this->_info['iptc']['DateCreated'])) { 718 $dates['IPTCDateCreated'] = $this->_info['iptc']['DateCreated']; 719 720 $aux = $this->_info['iptc']['DateCreated']; 721 $aux = substr($aux, 0, 4) . "-" . substr($aux, 4, 2) . "-" . substr($aux, 6, 2); 722 $t = strtotime($aux); 723 724 if ($t && $t > $latestTime) { 725 $latestTime = $t; 726 $latestTimeSource = "IPTCDateCreated"; 727 } 728 729 if ($t && $t < $earliestTime) { 730 $earliestTime = $t; 731 $earliestTimeSource = "IPTCDateCreated"; 732 } 733 } 734 735 if (@isset($this->_info['file']['UnixTime'])) { 736 $dates['FileModified'] = $this->_info['file']['UnixTime']; 737 738 $t = $this->_info['file']['UnixTime']; 739 740 if ($t && $t > $latestTime) { 741 $latestTime = $t; 742 $latestTimeSource = "FileModified"; 743 } 744 745 if ($t && $t < $earliestTime) { 746 $earliestTime = $t; 747 $earliestTimeSource = "FileModified"; 748 } 749 } 750 751 $dates['Time'] = $earliestTime; 752 $dates['TimeSource'] = $earliestTimeSource; 753 $dates['TimeStr'] = date("Y-m-d H:i:s", $earliestTime); 754 $dates['EarliestTime'] = $earliestTime; 755 $dates['EarliestTimeSource'] = $earliestTimeSource; 756 $dates['EarliestTimeStr'] = date("Y-m-d H:i:s", $earliestTime); 757 $dates['LatestTime'] = $latestTime; 758 $dates['LatestTimeSource'] = $latestTimeSource; 759 $dates['LatestTimeStr'] = date("Y-m-d H:i:s", $latestTime); 760 761 return $dates; 762 } 763 764 /** 765 * Get the image width, tries various fields 766 * 767 * @author Sebastian Delmont <sdelmont@zonageek.com> 768 * 769 * @return false|string 770 */ 771 function getWidth() { 772 if (!isset($this->_info['sof'])) { 773 $this->_parseMarkerSOF(); 774 } 775 776 if ($this->_markers == null) { 777 return false; 778 } 779 780 if (isset($this->_info['sof']['ImageWidth'])) { 781 return $this->_info['sof']['ImageWidth']; 782 } 783 784 if (!isset($this->_info['exif'])) { 785 $this->_parseMarkerExif(); 786 } 787 788 if (isset($this->_info['exif']['PixelXDimension'])) { 789 return $this->_info['exif']['PixelXDimension']; 790 } 791 792 return false; 793 } 794 795 /** 796 * Get the image height, tries various fields 797 * 798 * @author Sebastian Delmont <sdelmont@zonageek.com> 799 * 800 * @return false|string 801 */ 802 function getHeight() { 803 if (!isset($this->_info['sof'])) { 804 $this->_parseMarkerSOF(); 805 } 806 807 if ($this->_markers == null) { 808 return false; 809 } 810 811 if (isset($this->_info['sof']['ImageHeight'])) { 812 return $this->_info['sof']['ImageHeight']; 813 } 814 815 if (!isset($this->_info['exif'])) { 816 $this->_parseMarkerExif(); 817 } 818 819 if (isset($this->_info['exif']['PixelYDimension'])) { 820 return $this->_info['exif']['PixelYDimension']; 821 } 822 823 return false; 824 } 825 826 /** 827 * Get an dimension string for use in img tag 828 * 829 * @author Sebastian Delmont <sdelmont@zonageek.com> 830 * 831 * @return false|string 832 */ 833 function getDimStr() { 834 if ($this->_markers == null) { 835 return false; 836 } 837 838 $w = $this->getWidth(); 839 $h = $this->getHeight(); 840 841 return "width='" . $w . "' height='" . $h . "'"; 842 } 843 844 /** 845 * Checks for an embedded thumbnail 846 * 847 * @author Sebastian Delmont <sdelmont@zonageek.com> 848 * 849 * @param string $which possible values: 'any', 'exif' or 'adobe' 850 * @return false|string 851 */ 852 function hasThumbnail($which = 'any') { 853 if (($which == 'any') || ($which == 'exif')) { 854 if (!isset($this->_info['exif'])) { 855 $this->_parseMarkerExif(); 856 } 857 858 if ($this->_markers == null) { 859 return false; 860 } 861 862 if (isset($this->_info['exif']) && is_array($this->_info['exif'])) { 863 if (isset($this->_info['exif']['JFIFThumbnail'])) { 864 return 'exif'; 865 } 866 } 867 } 868 869 if ($which == 'adobe') { 870 if (!isset($this->_info['adobe'])) { 871 $this->_parseMarkerAdobe(); 872 } 873 874 if ($this->_markers == null) { 875 return false; 876 } 877 878 if (isset($this->_info['adobe']) && is_array($this->_info['adobe'])) { 879 if (isset($this->_info['adobe']['ThumbnailData'])) { 880 return 'exif'; 881 } 882 } 883 } 884 885 return false; 886 } 887 888 /** 889 * Send embedded thumbnail to browser 890 * 891 * @author Sebastian Delmont <sdelmont@zonageek.com> 892 * 893 * @param string $which possible values: 'any', 'exif' or 'adobe' 894 * @return bool 895 */ 896 function sendThumbnail($which = 'any') { 897 $data = null; 898 899 if (($which == 'any') || ($which == 'exif')) { 900 if (!isset($this->_info['exif'])) { 901 $this->_parseMarkerExif(); 902 } 903 904 if ($this->_markers == null) { 905 return false; 906 } 907 908 if (isset($this->_info['exif']) && is_array($this->_info['exif'])) { 909 if (isset($this->_info['exif']['JFIFThumbnail'])) { 910 $data =& $this->_info['exif']['JFIFThumbnail']; 911 } 912 } 913 } 914 915 if (($which == 'adobe') || ($data == null)){ 916 if (!isset($this->_info['adobe'])) { 917 $this->_parseMarkerAdobe(); 918 } 919 920 if ($this->_markers == null) { 921 return false; 922 } 923 924 if (isset($this->_info['adobe']) && is_array($this->_info['adobe'])) { 925 if (isset($this->_info['adobe']['ThumbnailData'])) { 926 $data =& $this->_info['adobe']['ThumbnailData']; 927 } 928 } 929 } 930 931 if ($data != null) { 932 header("Content-type: image/jpeg"); 933 echo $data; 934 return true; 935 } 936 937 return false; 938 } 939 940 /** 941 * Save changed Metadata 942 * 943 * @author Sebastian Delmont <sdelmont@zonageek.com> 944 * @author Andreas Gohr <andi@splitbrain.org> 945 * 946 * @param string $fileName file name or empty string for a random name 947 * @return bool 948 */ 949 function save($fileName = "") { 950 if ($fileName == "") { 951 $tmpName = tempnam(dirname($this->_fileName),'_metatemp_'); 952 $this->_writeJPEG($tmpName); 953 if (file_exists($tmpName)) { 954 return io_rename($tmpName, $this->_fileName); 955 } 956 } else { 957 return $this->_writeJPEG($fileName); 958 } 959 return false; 960 } 961 962 /*************************************************************/ 963 /* PRIVATE FUNCTIONS (Internal Use Only!) */ 964 /*************************************************************/ 965 966 /*************************************************************/ 967 function _dispose($fileName = "") { 968 $this->_fileName = $fileName; 969 970 $this->_fp = null; 971 $this->_type = 'unknown'; 972 973 unset($this->_markers); 974 unset($this->_info); 975 } 976 977 /*************************************************************/ 978 function _readJPEG() { 979 unset($this->_markers); 980 //unset($this->_info); 981 $this->_markers = array(); 982 //$this->_info = array(); 983 984 $this->_fp = @fopen($this->_fileName, 'rb'); 985 if ($this->_fp) { 986 if (file_exists($this->_fileName)) { 987 $this->_type = 'file'; 988 } 989 else { 990 $this->_type = 'url'; 991 } 992 } else { 993 $this->_fp = null; 994 return false; // ERROR: Can't open file 995 } 996 997 // Check for the JPEG signature 998 $c1 = ord(fgetc($this->_fp)); 999 $c2 = ord(fgetc($this->_fp)); 1000 1001 if ($c1 != 0xFF || $c2 != 0xD8) { // (0xFF + SOI) 1002 $this->_markers = null; 1003 return false; // ERROR: File is not a JPEG 1004 } 1005 1006 $count = 0; 1007 1008 $done = false; 1009 $ok = true; 1010 1011 while (!$done) { 1012 $capture = false; 1013 1014 // First, skip any non 0xFF bytes 1015 $discarded = 0; 1016 $c = ord(fgetc($this->_fp)); 1017 while (!feof($this->_fp) && ($c != 0xFF)) { 1018 $discarded++; 1019 $c = ord(fgetc($this->_fp)); 1020 } 1021 // Then skip all 0xFF until the marker byte 1022 do { 1023 $marker = ord(fgetc($this->_fp)); 1024 } while (!feof($this->_fp) && ($marker == 0xFF)); 1025 1026 if (feof($this->_fp)) { 1027 return false; // ERROR: Unexpected EOF 1028 } 1029 if ($discarded != 0) { 1030 return false; // ERROR: Extraneous data 1031 } 1032 1033 $length = ord(fgetc($this->_fp)) * 256 + ord(fgetc($this->_fp)); 1034 if (feof($this->_fp)) { 1035 return false; // ERROR: Unexpected EOF 1036 } 1037 if ($length < 2) { 1038 return false; // ERROR: Extraneous data 1039 } 1040 $length = $length - 2; // The length we got counts itself 1041 1042 switch ($marker) { 1043 case 0xC0: // SOF0 1044 case 0xC1: // SOF1 1045 case 0xC2: // SOF2 1046 case 0xC9: // SOF9 1047 case 0xE0: // APP0: JFIF data 1048 case 0xE1: // APP1: EXIF or XMP data 1049 case 0xED: // APP13: IPTC / Photoshop data 1050 $capture = true; 1051 break; 1052 case 0xDA: // SOS: Start of scan... the image itself and the last block on the file 1053 $capture = false; 1054 $length = -1; // This field has no length... it includes all data until EOF 1055 $done = true; 1056 break; 1057 default: 1058 $capture = true;//false; 1059 break; 1060 } 1061 1062 $this->_markers[$count] = array(); 1063 $this->_markers[$count]['marker'] = $marker; 1064 $this->_markers[$count]['length'] = $length; 1065 1066 if ($capture) { 1067 if ($length) 1068 $this->_markers[$count]['data'] = fread($this->_fp, $length); 1069 else 1070 $this->_markers[$count]['data'] = ""; 1071 } 1072 elseif (!$done) { 1073 $result = @fseek($this->_fp, $length, SEEK_CUR); 1074 // fseek doesn't seem to like HTTP 'files', but fgetc has no problem 1075 if (!($result === 0)) { 1076 for ($i = 0; $i < $length; $i++) { 1077 fgetc($this->_fp); 1078 } 1079 } 1080 } 1081 $count++; 1082 } 1083 1084 if ($this->_fp) { 1085 fclose($this->_fp); 1086 $this->_fp = null; 1087 } 1088 1089 return $ok; 1090 } 1091 1092 /*************************************************************/ 1093 function _parseAll() { 1094 if (!isset($this->_info['file'])) { 1095 $this->_parseFileInfo(); 1096 } 1097 if (!isset($this->_markers)) { 1098 $this->_readJPEG(); 1099 } 1100 1101 if ($this->_markers == null) { 1102 return false; 1103 } 1104 1105 if (!isset($this->_info['jfif'])) { 1106 $this->_parseMarkerJFIF(); 1107 } 1108 if (!isset($this->_info['jpeg'])) { 1109 $this->_parseMarkerSOF(); 1110 } 1111 if (!isset($this->_info['exif'])) { 1112 $this->_parseMarkerExif(); 1113 } 1114 if (!isset($this->_info['xmp'])) { 1115 $this->_parseMarkerXmp(); 1116 } 1117 if (!isset($this->_info['adobe'])) { 1118 $this->_parseMarkerAdobe(); 1119 } 1120 } 1121 1122 /*************************************************************/ 1123 1124 /** 1125 * @param string $outputName 1126 * 1127 * @return bool 1128 */ 1129 function _writeJPEG($outputName) { 1130 $this->_parseAll(); 1131 1132 $wroteEXIF = false; 1133 $wroteAdobe = false; 1134 1135 $this->_fp = @fopen($this->_fileName, 'r'); 1136 if ($this->_fp) { 1137 if (file_exists($this->_fileName)) { 1138 $this->_type = 'file'; 1139 } 1140 else { 1141 $this->_type = 'url'; 1142 } 1143 } else { 1144 $this->_fp = null; 1145 return false; // ERROR: Can't open file 1146 } 1147 1148 $this->_fpout = fopen($outputName, 'wb'); 1149 if (!$this->_fpout) { 1150 $this->_fpout = null; 1151 fclose($this->_fp); 1152 $this->_fp = null; 1153 return false; // ERROR: Can't open output file 1154 } 1155 1156 // Check for the JPEG signature 1157 $c1 = ord(fgetc($this->_fp)); 1158 $c2 = ord(fgetc($this->_fp)); 1159 1160 if ($c1 != 0xFF || $c2 != 0xD8) { // (0xFF + SOI) 1161 return false; // ERROR: File is not a JPEG 1162 } 1163 1164 fputs($this->_fpout, chr(0xFF), 1); 1165 fputs($this->_fpout, chr(0xD8), 1); // (0xFF + SOI) 1166 1167 $count = 0; 1168 1169 $done = false; 1170 $ok = true; 1171 1172 while (!$done) { 1173 // First, skip any non 0xFF bytes 1174 $discarded = 0; 1175 $c = ord(fgetc($this->_fp)); 1176 while (!feof($this->_fp) && ($c != 0xFF)) { 1177 $discarded++; 1178 $c = ord(fgetc($this->_fp)); 1179 } 1180 // Then skip all 0xFF until the marker byte 1181 do { 1182 $marker = ord(fgetc($this->_fp)); 1183 } while (!feof($this->_fp) && ($marker == 0xFF)); 1184 1185 if (feof($this->_fp)) { 1186 $ok = false; 1187 break; // ERROR: Unexpected EOF 1188 } 1189 if ($discarded != 0) { 1190 $ok = false; 1191 break; // ERROR: Extraneous data 1192 } 1193 1194 $length = ord(fgetc($this->_fp)) * 256 + ord(fgetc($this->_fp)); 1195 if (feof($this->_fp)) { 1196 $ok = false; 1197 break; // ERROR: Unexpected EOF 1198 } 1199 if ($length < 2) { 1200 $ok = false; 1201 break; // ERROR: Extraneous data 1202 } 1203 $length = $length - 2; // The length we got counts itself 1204 1205 unset($data); 1206 if ($marker == 0xE1) { // APP1: EXIF data 1207 $data =& $this->_createMarkerEXIF(); 1208 $wroteEXIF = true; 1209 } 1210 elseif ($marker == 0xED) { // APP13: IPTC / Photoshop data 1211 $data =& $this->_createMarkerAdobe(); 1212 $wroteAdobe = true; 1213 } 1214 elseif ($marker == 0xDA) { // SOS: Start of scan... the image itself and the last block on the file 1215 $done = true; 1216 } 1217 1218 if (!$wroteEXIF && (($marker < 0xE0) || ($marker > 0xEF))) { 1219 if (isset($this->_info['exif']) && is_array($this->_info['exif'])) { 1220 $exif =& $this->_createMarkerEXIF(); 1221 $this->_writeJPEGMarker(0xE1, strlen($exif), $exif, 0); 1222 unset($exif); 1223 } 1224 $wroteEXIF = true; 1225 } 1226 1227 if (!$wroteAdobe && (($marker < 0xE0) || ($marker > 0xEF))) { 1228 if ((isset($this->_info['adobe']) && is_array($this->_info['adobe'])) 1229 || (isset($this->_info['iptc']) && is_array($this->_info['iptc']))) { 1230 $adobe =& $this->_createMarkerAdobe(); 1231 $this->_writeJPEGMarker(0xED, strlen($adobe), $adobe, 0); 1232 unset($adobe); 1233 } 1234 $wroteAdobe = true; 1235 } 1236 1237 $origLength = $length; 1238 if (isset($data)) { 1239 $length = strlen($data); 1240 } 1241 1242 if ($marker != -1) { 1243 $this->_writeJPEGMarker($marker, $length, $data, $origLength); 1244 } 1245 } 1246 1247 if ($this->_fp) { 1248 fclose($this->_fp); 1249 $this->_fp = null; 1250 } 1251 1252 if ($this->_fpout) { 1253 fclose($this->_fpout); 1254 $this->_fpout = null; 1255 } 1256 1257 return $ok; 1258 } 1259 1260 /*************************************************************/ 1261 1262 /** 1263 * @param integer $marker 1264 * @param integer $length 1265 * @param string $data 1266 * @param integer $origLength 1267 * 1268 * @return bool 1269 */ 1270 function _writeJPEGMarker($marker, $length, &$data, $origLength) { 1271 if ($length <= 0) { 1272 return false; 1273 } 1274 1275 fputs($this->_fpout, chr(0xFF), 1); 1276 fputs($this->_fpout, chr($marker), 1); 1277 fputs($this->_fpout, chr((($length + 2) & 0x0000FF00) >> 8), 1); 1278 fputs($this->_fpout, chr((($length + 2) & 0x000000FF) >> 0), 1); 1279 1280 if (isset($data)) { 1281 // Copy the generated data 1282 fputs($this->_fpout, $data, $length); 1283 1284 if ($origLength > 0) { // Skip the original data 1285 $result = @fseek($this->_fp, $origLength, SEEK_CUR); 1286 // fseek doesn't seem to like HTTP 'files', but fgetc has no problem 1287 if ($result != 0) { 1288 for ($i = 0; $i < $origLength; $i++) { 1289 fgetc($this->_fp); 1290 } 1291 } 1292 } 1293 } else { 1294 if ($marker == 0xDA) { // Copy until EOF 1295 while (!feof($this->_fp)) { 1296 $data = fread($this->_fp, 1024 * 16); 1297 fputs($this->_fpout, $data, strlen($data)); 1298 } 1299 } else { // Copy only $length bytes 1300 $data = @fread($this->_fp, $length); 1301 fputs($this->_fpout, $data, $length); 1302 } 1303 } 1304 1305 return true; 1306 } 1307 1308 /** 1309 * Gets basic info from the file - should work with non-JPEGs 1310 * 1311 * @author Sebastian Delmont <sdelmont@zonageek.com> 1312 * @author Andreas Gohr <andi@splitbrain.org> 1313 */ 1314 function _parseFileInfo() { 1315 if (file_exists($this->_fileName) && is_file($this->_fileName)) { 1316 $this->_info['file'] = array(); 1317 $this->_info['file']['Name'] = utf8_decodeFN(\dokuwiki\Utf8\PhpString::basename($this->_fileName)); 1318 $this->_info['file']['Path'] = fullpath($this->_fileName); 1319 $this->_info['file']['Size'] = filesize($this->_fileName); 1320 if ($this->_info['file']['Size'] < 1024) { 1321 $this->_info['file']['NiceSize'] = $this->_info['file']['Size'] . 'B'; 1322 } elseif ($this->_info['file']['Size'] < (1024 * 1024)) { 1323 $this->_info['file']['NiceSize'] = round($this->_info['file']['Size'] / 1024) . 'KB'; 1324 } elseif ($this->_info['file']['Size'] < (1024 * 1024 * 1024)) { 1325 $this->_info['file']['NiceSize'] = round($this->_info['file']['Size'] / (1024*1024)) . 'MB'; 1326 } else { 1327 $this->_info['file']['NiceSize'] = $this->_info['file']['Size'] . 'B'; 1328 } 1329 $this->_info['file']['UnixTime'] = filemtime($this->_fileName); 1330 1331 // get image size directly from file 1332 if ($size = getimagesize($this->_fileName)) { 1333 $this->_info['file']['Width'] = $size[0]; 1334 $this->_info['file']['Height'] = $size[1]; 1335 1336 // set mime types and formats 1337 // http://php.net/manual/en/function.getimagesize.php 1338 // http://php.net/manual/en/function.image-type-to-mime-type.php 1339 switch ($size[2]) { 1340 case 1: 1341 $this->_info['file']['Mime'] = 'image/gif'; 1342 $this->_info['file']['Format'] = 'GIF'; 1343 break; 1344 case 2: 1345 $this->_info['file']['Mime'] = 'image/jpeg'; 1346 $this->_info['file']['Format'] = 'JPEG'; 1347 break; 1348 case 3: 1349 $this->_info['file']['Mime'] = 'image/png'; 1350 $this->_info['file']['Format'] = 'PNG'; 1351 break; 1352 case 4: 1353 $this->_info['file']['Mime'] = 'application/x-shockwave-flash'; 1354 $this->_info['file']['Format'] = 'SWF'; 1355 break; 1356 case 5: 1357 $this->_info['file']['Mime'] = 'image/psd'; 1358 $this->_info['file']['Format'] = 'PSD'; 1359 break; 1360 case 6: 1361 $this->_info['file']['Mime'] = 'image/bmp'; 1362 $this->_info['file']['Format'] = 'BMP'; 1363 break; 1364 case 7: 1365 $this->_info['file']['Mime'] = 'image/tiff'; 1366 $this->_info['file']['Format'] = 'TIFF (Intel)'; 1367 break; 1368 case 8: 1369 $this->_info['file']['Mime'] = 'image/tiff'; 1370 $this->_info['file']['Format'] = 'TIFF (Motorola)'; 1371 break; 1372 case 9: 1373 $this->_info['file']['Mime'] = 'application/octet-stream'; 1374 $this->_info['file']['Format'] = 'JPC'; 1375 break; 1376 case 10: 1377 $this->_info['file']['Mime'] = 'image/jp2'; 1378 $this->_info['file']['Format'] = 'JP2'; 1379 break; 1380 case 11: 1381 $this->_info['file']['Mime'] = 'application/octet-stream'; 1382 $this->_info['file']['Format'] = 'JPX'; 1383 break; 1384 case 12: 1385 $this->_info['file']['Mime'] = 'application/octet-stream'; 1386 $this->_info['file']['Format'] = 'JB2'; 1387 break; 1388 case 13: 1389 $this->_info['file']['Mime'] = 'application/x-shockwave-flash'; 1390 $this->_info['file']['Format'] = 'SWC'; 1391 break; 1392 case 14: 1393 $this->_info['file']['Mime'] = 'image/iff'; 1394 $this->_info['file']['Format'] = 'IFF'; 1395 break; 1396 case 15: 1397 $this->_info['file']['Mime'] = 'image/vnd.wap.wbmp'; 1398 $this->_info['file']['Format'] = 'WBMP'; 1399 break; 1400 case 16: 1401 $this->_info['file']['Mime'] = 'image/xbm'; 1402 $this->_info['file']['Format'] = 'XBM'; 1403 break; 1404 default: 1405 $this->_info['file']['Mime'] = 'image/unknown'; 1406 } 1407 } 1408 } else { 1409 $this->_info['file'] = array(); 1410 $this->_info['file']['Name'] = \dokuwiki\Utf8\PhpString::basename($this->_fileName); 1411 $this->_info['file']['Url'] = $this->_fileName; 1412 } 1413 1414 return true; 1415 } 1416 1417 /*************************************************************/ 1418 function _parseMarkerJFIF() { 1419 if (!isset($this->_markers)) { 1420 $this->_readJPEG(); 1421 } 1422 1423 if ($this->_markers == null || $this->_isMarkerDisabled(('jfif'))) { 1424 return false; 1425 } 1426 1427 try { 1428 $data = null; 1429 $count = count($this->_markers); 1430 for ($i = 0; $i < $count; $i++) { 1431 if ($this->_markers[$i]['marker'] == 0xE0) { 1432 $signature = $this->_getFixedString($this->_markers[$i]['data'], 0, 4); 1433 if ($signature == 'JFIF') { 1434 $data =& $this->_markers[$i]['data']; 1435 break; 1436 } 1437 } 1438 } 1439 1440 if ($data == null) { 1441 $this->_info['jfif'] = false; 1442 return false; 1443 } 1444 1445 $this->_info['jfif'] = array(); 1446 1447 $vmaj = $this->_getByte($data, 5); 1448 $vmin = $this->_getByte($data, 6); 1449 1450 $this->_info['jfif']['Version'] = sprintf('%d.%02d', $vmaj, $vmin); 1451 1452 $units = $this->_getByte($data, 7); 1453 switch ($units) { 1454 case 0: 1455 $this->_info['jfif']['Units'] = 'pixels'; 1456 break; 1457 case 1: 1458 $this->_info['jfif']['Units'] = 'dpi'; 1459 break; 1460 case 2: 1461 $this->_info['jfif']['Units'] = 'dpcm'; 1462 break; 1463 default: 1464 $this->_info['jfif']['Units'] = 'unknown'; 1465 break; 1466 } 1467 1468 $xdens = $this->_getShort($data, 8); 1469 $ydens = $this->_getShort($data, 10); 1470 1471 $this->_info['jfif']['XDensity'] = $xdens; 1472 $this->_info['jfif']['YDensity'] = $ydens; 1473 1474 $thumbx = $this->_getByte($data, 12); 1475 $thumby = $this->_getByte($data, 13); 1476 1477 $this->_info['jfif']['ThumbnailWidth'] = $thumbx; 1478 $this->_info['jfif']['ThumbnailHeight'] = $thumby; 1479 } catch(Exception $e) { 1480 $this->_handleMarkerParsingException($e); 1481 $this->_info['jfif'] = false; 1482 return false; 1483 } 1484 1485 return true; 1486 } 1487 1488 /*************************************************************/ 1489 function _parseMarkerSOF() { 1490 if (!isset($this->_markers)) { 1491 $this->_readJPEG(); 1492 } 1493 1494 if ($this->_markers == null || $this->_isMarkerDisabled(('sof'))) { 1495 return false; 1496 } 1497 1498 try { 1499 $data = null; 1500 $count = count($this->_markers); 1501 for ($i = 0; $i < $count; $i++) { 1502 switch ($this->_markers[$i]['marker']) { 1503 case 0xC0: // SOF0 1504 case 0xC1: // SOF1 1505 case 0xC2: // SOF2 1506 case 0xC9: // SOF9 1507 $data =& $this->_markers[$i]['data']; 1508 $marker = $this->_markers[$i]['marker']; 1509 break; 1510 } 1511 } 1512 1513 if ($data == null) { 1514 $this->_info['sof'] = false; 1515 return false; 1516 } 1517 1518 $pos = 0; 1519 $this->_info['sof'] = array(); 1520 1521 switch ($marker) { 1522 case 0xC0: // SOF0 1523 $format = 'Baseline'; 1524 break; 1525 case 0xC1: // SOF1 1526 $format = 'Progessive'; 1527 break; 1528 case 0xC2: // SOF2 1529 $format = 'Non-baseline'; 1530 break; 1531 case 0xC9: // SOF9 1532 $format = 'Arithmetic'; 1533 break; 1534 default: 1535 return false; 1536 } 1537 1538 $this->_info['sof']['Format'] = $format; 1539 $this->_info['sof']['SamplePrecision'] = $this->_getByte($data, $pos + 0); 1540 $this->_info['sof']['ImageHeight'] = $this->_getShort($data, $pos + 1); 1541 $this->_info['sof']['ImageWidth'] = $this->_getShort($data, $pos + 3); 1542 $this->_info['sof']['ColorChannels'] = $this->_getByte($data, $pos + 5); 1543 } catch(Exception $e) { 1544 $this->_handleMarkerParsingException($e); 1545 $this->_info['sof'] = false; 1546 return false; 1547 } 1548 1549 return true; 1550 } 1551 1552 /** 1553 * Parses the XMP data 1554 * 1555 * @author Hakan Sandell <hakan.sandell@mydata.se> 1556 */ 1557 function _parseMarkerXmp() { 1558 if (!isset($this->_markers)) { 1559 $this->_readJPEG(); 1560 } 1561 1562 if ($this->_markers == null || $this->_isMarkerDisabled(('xmp'))) { 1563 return false; 1564 } 1565 1566 try { 1567 $data = null; 1568 $count = count($this->_markers); 1569 for ($i = 0; $i < $count; $i++) { 1570 if ($this->_markers[$i]['marker'] == 0xE1) { 1571 $signature = $this->_getFixedString($this->_markers[$i]['data'], 0, 29); 1572 if ($signature == "http://ns.adobe.com/xap/1.0/\0") { 1573 $data = substr($this->_markers[$i]['data'], 29); 1574 break; 1575 } 1576 } 1577 } 1578 1579 if ($data == null) { 1580 $this->_info['xmp'] = false; 1581 return false; 1582 } 1583 1584 $parser = xml_parser_create(); 1585 xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0); 1586 xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, 1); 1587 $result = xml_parse_into_struct($parser, $data, $values, $tags); 1588 xml_parser_free($parser); 1589 1590 if ($result == 0) { 1591 $this->_info['xmp'] = false; 1592 return false; 1593 } 1594 1595 $this->_info['xmp'] = array(); 1596 $count = count($values); 1597 for ($i = 0; $i < $count; $i++) { 1598 if ($values[$i]['tag'] == 'rdf:Description' && $values[$i]['type'] == 'open') { 1599 1600 while ((++$i < $count) && ($values[$i]['tag'] != 'rdf:Description')) { 1601 $this->_parseXmpNode($values, $i, $this->_info['xmp'][$values[$i]['tag']], $count); 1602 } 1603 } 1604 } 1605 } catch (Exception $e) { 1606 $this->_handleMarkerParsingException($e); 1607 $this->_info['xmp'] = false; 1608 return false; 1609 } 1610 1611 return true; 1612 } 1613 1614 /** 1615 * Parses XMP nodes by recursion 1616 * 1617 * @author Hakan Sandell <hakan.sandell@mydata.se> 1618 * 1619 * @param array $values 1620 * @param int $i 1621 * @param mixed $meta 1622 * @param integer $count 1623 */ 1624 function _parseXmpNode($values, &$i, &$meta, $count) { 1625 if ($values[$i]['type'] == 'close') return; 1626 1627 if ($values[$i]['type'] == 'complete') { 1628 // Simple Type property 1629 $meta = $values[$i]['value'] ?? ''; 1630 return; 1631 } 1632 1633 $i++; 1634 if ($i >= $count) return; 1635 1636 if ($values[$i]['tag'] == 'rdf:Bag' || $values[$i]['tag'] == 'rdf:Seq') { 1637 // Array property 1638 $meta = array(); 1639 while ($values[++$i]['tag'] == 'rdf:li') { 1640 $this->_parseXmpNode($values, $i, $meta[], $count); 1641 } 1642 $i++; // skip closing Bag/Seq tag 1643 1644 } elseif ($values[$i]['tag'] == 'rdf:Alt') { 1645 // Language Alternative property, only the first (default) value is used 1646 if ($values[$i]['type'] == 'open') { 1647 $i++; 1648 $this->_parseXmpNode($values, $i, $meta, $count); 1649 while ((++$i < $count) && ($values[$i]['tag'] != 'rdf:Alt')); 1650 $i++; // skip closing Alt tag 1651 } 1652 1653 } else { 1654 // Structure property 1655 $meta = array(); 1656 $startTag = $values[$i-1]['tag']; 1657 do { 1658 $this->_parseXmpNode($values, $i, $meta[$values[$i]['tag']], $count); 1659 } while ((++$i < $count) && ($values[$i]['tag'] != $startTag)); 1660 } 1661 } 1662 1663 /*************************************************************/ 1664 function _parseMarkerExif() { 1665 if (!isset($this->_markers)) { 1666 $this->_readJPEG(); 1667 } 1668 1669 if ($this->_markers == null || $this->_isMarkerDisabled(('exif'))) { 1670 return false; 1671 } 1672 1673 try { 1674 $data = null; 1675 $count = count($this->_markers); 1676 for ($i = 0; $i < $count; $i++) { 1677 if ($this->_markers[$i]['marker'] == 0xE1) { 1678 $signature = $this->_getFixedString($this->_markers[$i]['data'], 0, 6); 1679 if ($signature == "Exif\0\0") { 1680 $data =& $this->_markers[$i]['data']; 1681 break; 1682 } 1683 } 1684 } 1685 1686 if ($data == null) { 1687 $this->_info['exif'] = false; 1688 return false; 1689 } 1690 $pos = 6; 1691 $this->_info['exif'] = array(); 1692 1693 // We don't increment $pos after this because Exif uses offsets relative to this point 1694 1695 $byteAlign = $this->_getShort($data, $pos + 0); 1696 1697 if ($byteAlign == 0x4949) { // "II" 1698 $isBigEndian = false; 1699 } elseif ($byteAlign == 0x4D4D) { // "MM" 1700 $isBigEndian = true; 1701 } else { 1702 return false; // Unexpected data 1703 } 1704 1705 $alignCheck = $this->_getShort($data, $pos + 2, $isBigEndian); 1706 if ($alignCheck != 0x002A) // That's the expected value 1707 return false; // Unexpected data 1708 1709 if ($isBigEndian) { 1710 $this->_info['exif']['ByteAlign'] = "Big Endian"; 1711 } else { 1712 $this->_info['exif']['ByteAlign'] = "Little Endian"; 1713 } 1714 1715 $offsetIFD0 = $this->_getLong($data, $pos + 4, $isBigEndian); 1716 if ($offsetIFD0 < 8) 1717 return false; // Unexpected data 1718 1719 $offsetIFD1 = $this->_readIFD($data, $pos, $offsetIFD0, $isBigEndian, 'ifd0'); 1720 if ($offsetIFD1 != 0) 1721 $this->_readIFD($data, $pos, $offsetIFD1, $isBigEndian, 'ifd1'); 1722 } catch(Exception $e) { 1723 $this->_handleMarkerParsingException($e); 1724 $this->_info['exif'] = false; 1725 return false; 1726 } 1727 1728 return true; 1729 } 1730 1731 /*************************************************************/ 1732 1733 /** 1734 * @param mixed $data 1735 * @param integer $base 1736 * @param integer $offset 1737 * @param boolean $isBigEndian 1738 * @param string $mode 1739 * 1740 * @return int 1741 */ 1742 function _readIFD($data, $base, $offset, $isBigEndian, $mode) { 1743 $EXIFTags = $this->_exifTagNames($mode); 1744 1745 $numEntries = $this->_getShort($data, $base + $offset, $isBigEndian); 1746 $offset += 2; 1747 1748 $exifTIFFOffset = 0; 1749 $exifTIFFLength = 0; 1750 $exifThumbnailOffset = 0; 1751 $exifThumbnailLength = 0; 1752 1753 for ($i = 0; $i < $numEntries; $i++) { 1754 $tag = $this->_getShort($data, $base + $offset, $isBigEndian); 1755 $offset += 2; 1756 $type = $this->_getShort($data, $base + $offset, $isBigEndian); 1757 $offset += 2; 1758 $count = $this->_getLong($data, $base + $offset, $isBigEndian); 1759 $offset += 4; 1760 1761 if (($type < 1) || ($type > 12)) 1762 return false; // Unexpected Type 1763 1764 $typeLengths = array( -1, 1, 1, 2, 4, 8, 1, 1, 2, 4, 8, 4, 8 ); 1765 1766 $dataLength = $typeLengths[$type] * $count; 1767 if ($dataLength > 4) { 1768 $dataOffset = $this->_getLong($data, $base + $offset, $isBigEndian); 1769 $rawValue = $this->_getFixedString($data, $base + $dataOffset, $dataLength); 1770 } else { 1771 $rawValue = $this->_getFixedString($data, $base + $offset, $dataLength); 1772 } 1773 $offset += 4; 1774 1775 switch ($type) { 1776 case 1: // UBYTE 1777 if ($count == 1) { 1778 $value = $this->_getByte($rawValue, 0); 1779 } else { 1780 $value = array(); 1781 for ($j = 0; $j < $count; $j++) 1782 $value[$j] = $this->_getByte($rawValue, $j); 1783 } 1784 break; 1785 case 2: // ASCII 1786 $value = $rawValue; 1787 break; 1788 case 3: // USHORT 1789 if ($count == 1) { 1790 $value = $this->_getShort($rawValue, 0, $isBigEndian); 1791 } else { 1792 $value = array(); 1793 for ($j = 0; $j < $count; $j++) 1794 $value[$j] = $this->_getShort($rawValue, $j * 2, $isBigEndian); 1795 } 1796 break; 1797 case 4: // ULONG 1798 if ($count == 1) { 1799 $value = $this->_getLong($rawValue, 0, $isBigEndian); 1800 } else { 1801 $value = array(); 1802 for ($j = 0; $j < $count; $j++) 1803 $value[$j] = $this->_getLong($rawValue, $j * 4, $isBigEndian); 1804 } 1805 break; 1806 case 5: // URATIONAL 1807 if ($count == 1) { 1808 $a = $this->_getLong($rawValue, 0, $isBigEndian); 1809 $b = $this->_getLong($rawValue, 4, $isBigEndian); 1810 $value = array(); 1811 $value['val'] = 0; 1812 $value['num'] = $a; 1813 $value['den'] = $b; 1814 if (($a != 0) && ($b != 0)) { 1815 $value['val'] = $a / $b; 1816 } 1817 } else { 1818 $value = array(); 1819 for ($j = 0; $j < $count; $j++) { 1820 $a = $this->_getLong($rawValue, $j * 8, $isBigEndian); 1821 $b = $this->_getLong($rawValue, ($j * 8) + 4, $isBigEndian); 1822 $value = array(); 1823 $value[$j]['val'] = 0; 1824 $value[$j]['num'] = $a; 1825 $value[$j]['den'] = $b; 1826 if (($a != 0) && ($b != 0)) 1827 $value[$j]['val'] = $a / $b; 1828 } 1829 } 1830 break; 1831 case 6: // SBYTE 1832 if ($count == 1) { 1833 $value = $this->_getByte($rawValue, 0); 1834 } else { 1835 $value = array(); 1836 for ($j = 0; $j < $count; $j++) 1837 $value[$j] = $this->_getByte($rawValue, $j); 1838 } 1839 break; 1840 case 7: // UNDEFINED 1841 $value = $rawValue; 1842 break; 1843 case 8: // SSHORT 1844 if ($count == 1) { 1845 $value = $this->_getShort($rawValue, 0, $isBigEndian); 1846 } else { 1847 $value = array(); 1848 for ($j = 0; $j < $count; $j++) 1849 $value[$j] = $this->_getShort($rawValue, $j * 2, $isBigEndian); 1850 } 1851 break; 1852 case 9: // SLONG 1853 if ($count == 1) { 1854 $value = $this->_getLong($rawValue, 0, $isBigEndian); 1855 } else { 1856 $value = array(); 1857 for ($j = 0; $j < $count; $j++) 1858 $value[$j] = $this->_getLong($rawValue, $j * 4, $isBigEndian); 1859 } 1860 break; 1861 case 10: // SRATIONAL 1862 if ($count == 1) { 1863 $a = $this->_getLong($rawValue, 0, $isBigEndian); 1864 $b = $this->_getLong($rawValue, 4, $isBigEndian); 1865 $value = array(); 1866 $value['val'] = 0; 1867 $value['num'] = $a; 1868 $value['den'] = $b; 1869 if (($a != 0) && ($b != 0)) 1870 $value['val'] = $a / $b; 1871 } else { 1872 $value = array(); 1873 for ($j = 0; $j < $count; $j++) { 1874 $a = $this->_getLong($rawValue, $j * 8, $isBigEndian); 1875 $b = $this->_getLong($rawValue, ($j * 8) + 4, $isBigEndian); 1876 $value = array(); 1877 $value[$j]['val'] = 0; 1878 $value[$j]['num'] = $a; 1879 $value[$j]['den'] = $b; 1880 if (($a != 0) && ($b != 0)) 1881 $value[$j]['val'] = $a / $b; 1882 } 1883 } 1884 break; 1885 case 11: // FLOAT 1886 $value = $rawValue; 1887 break; 1888 1889 case 12: // DFLOAT 1890 $value = $rawValue; 1891 break; 1892 default: 1893 return false; // Unexpected Type 1894 } 1895 1896 $tagName = ''; 1897 if (($mode == 'ifd0') && ($tag == 0x8769)) { // ExifIFDOffset 1898 $this->_readIFD($data, $base, $value, $isBigEndian, 'exif'); 1899 } elseif (($mode == 'ifd0') && ($tag == 0x8825)) { // GPSIFDOffset 1900 $this->_readIFD($data, $base, $value, $isBigEndian, 'gps'); 1901 } elseif (($mode == 'ifd1') && ($tag == 0x0111)) { // TIFFStripOffsets 1902 $exifTIFFOffset = $value; 1903 } elseif (($mode == 'ifd1') && ($tag == 0x0117)) { // TIFFStripByteCounts 1904 $exifTIFFLength = $value; 1905 } elseif (($mode == 'ifd1') && ($tag == 0x0201)) { // TIFFJFIFOffset 1906 $exifThumbnailOffset = $value; 1907 } elseif (($mode == 'ifd1') && ($tag == 0x0202)) { // TIFFJFIFLength 1908 $exifThumbnailLength = $value; 1909 } elseif (($mode == 'exif') && ($tag == 0xA005)) { // InteropIFDOffset 1910 $this->_readIFD($data, $base, $value, $isBigEndian, 'interop'); 1911 } 1912 // elseif (($mode == 'exif') && ($tag == 0x927C)) { // MakerNote 1913 // } 1914 else { 1915 if (isset($EXIFTags[$tag])) { 1916 $tagName = $EXIFTags[$tag]; 1917 if (isset($this->_info['exif'][$tagName])) { 1918 if (!is_array($this->_info['exif'][$tagName])) { 1919 $aux = array(); 1920 $aux[0] = $this->_info['exif'][$tagName]; 1921 $this->_info['exif'][$tagName] = $aux; 1922 } 1923 1924 $this->_info['exif'][$tagName][count($this->_info['exif'][$tagName])] = $value; 1925 } else { 1926 $this->_info['exif'][$tagName] = $value; 1927 } 1928 } 1929 /* 1930 else { 1931 echo sprintf("<h1>Unknown tag %02x (t: %d l: %d) %s in %s</h1>", $tag, $type, $count, $mode, $this->_fileName); 1932 // Unknown Tags will be ignored!!! 1933 // That's because the tag might be a pointer (like the Exif tag) 1934 // and saving it without saving the data it points to might 1935 // create an invalid file. 1936 } 1937 */ 1938 } 1939 } 1940 1941 if (($exifThumbnailOffset > 0) && ($exifThumbnailLength > 0)) { 1942 $this->_info['exif']['JFIFThumbnail'] = $this->_getFixedString($data, $base + $exifThumbnailOffset, $exifThumbnailLength); 1943 } 1944 1945 if (($exifTIFFOffset > 0) && ($exifTIFFLength > 0)) { 1946 $this->_info['exif']['TIFFStrips'] = $this->_getFixedString($data, $base + $exifTIFFOffset, $exifTIFFLength); 1947 } 1948 1949 $nextOffset = $this->_getLong($data, $base + $offset, $isBigEndian); 1950 return $nextOffset; 1951 } 1952 1953 /*************************************************************/ 1954 function & _createMarkerExif() { 1955 $data = null; 1956 $count = count($this->_markers); 1957 for ($i = 0; $i < $count; $i++) { 1958 if ($this->_markers[$i]['marker'] == 0xE1) { 1959 $signature = $this->_getFixedString($this->_markers[$i]['data'], 0, 6); 1960 if ($signature == "Exif\0\0") { 1961 $data =& $this->_markers[$i]['data']; 1962 break; 1963 } 1964 } 1965 } 1966 1967 if (!isset($this->_info['exif'])) { 1968 return false; 1969 } 1970 1971 $data = "Exif\0\0"; 1972 $pos = 6; 1973 $offsetBase = 6; 1974 1975 if (isset($this->_info['exif']['ByteAlign']) && ($this->_info['exif']['ByteAlign'] == "Big Endian")) { 1976 $isBigEndian = true; 1977 $aux = "MM"; 1978 $pos = $this->_putString($data, $pos, $aux); 1979 } else { 1980 $isBigEndian = false; 1981 $aux = "II"; 1982 $pos = $this->_putString($data, $pos, $aux); 1983 } 1984 $pos = $this->_putShort($data, $pos, 0x002A, $isBigEndian); 1985 $pos = $this->_putLong($data, $pos, 0x00000008, $isBigEndian); // IFD0 Offset is always 8 1986 1987 $ifd0 =& $this->_getIFDEntries($isBigEndian, 'ifd0'); 1988 $ifd1 =& $this->_getIFDEntries($isBigEndian, 'ifd1'); 1989 1990 $pos = $this->_writeIFD($data, $pos, $offsetBase, $ifd0, $isBigEndian, true); 1991 $pos = $this->_writeIFD($data, $pos, $offsetBase, $ifd1, $isBigEndian, false); 1992 1993 return $data; 1994 } 1995 1996 /*************************************************************/ 1997 1998 /** 1999 * @param mixed $data 2000 * @param integer $pos 2001 * @param integer $offsetBase 2002 * @param array $entries 2003 * @param boolean $isBigEndian 2004 * @param boolean $hasNext 2005 * 2006 * @return mixed 2007 */ 2008 function _writeIFD(&$data, $pos, $offsetBase, &$entries, $isBigEndian, $hasNext) { 2009 $tiffData = null; 2010 $tiffDataOffsetPos = -1; 2011 2012 $entryCount = count($entries); 2013 2014 $dataPos = $pos + 2 + ($entryCount * 12) + 4; 2015 $pos = $this->_putShort($data, $pos, $entryCount, $isBigEndian); 2016 2017 for ($i = 0; $i < $entryCount; $i++) { 2018 $tag = $entries[$i]['tag']; 2019 $type = $entries[$i]['type']; 2020 2021 if ($type == -99) { // SubIFD 2022 $pos = $this->_putShort($data, $pos, $tag, $isBigEndian); 2023 $pos = $this->_putShort($data, $pos, 0x04, $isBigEndian); // LONG 2024 $pos = $this->_putLong($data, $pos, 0x01, $isBigEndian); // Count = 1 2025 $pos = $this->_putLong($data, $pos, $dataPos - $offsetBase, $isBigEndian); 2026 2027 $dataPos = $this->_writeIFD($data, $dataPos, $offsetBase, $entries[$i]['value'], $isBigEndian, false); 2028 } elseif ($type == -98) { // TIFF Data 2029 $pos = $this->_putShort($data, $pos, $tag, $isBigEndian); 2030 $pos = $this->_putShort($data, $pos, 0x04, $isBigEndian); // LONG 2031 $pos = $this->_putLong($data, $pos, 0x01, $isBigEndian); // Count = 1 2032 $tiffDataOffsetPos = $pos; 2033 $pos = $this->_putLong($data, $pos, 0x00, $isBigEndian); // For Now 2034 $tiffData =& $entries[$i]['value'] ; 2035 } else { // Regular Entry 2036 $pos = $this->_putShort($data, $pos, $tag, $isBigEndian); 2037 $pos = $this->_putShort($data, $pos, $type, $isBigEndian); 2038 $pos = $this->_putLong($data, $pos, $entries[$i]['count'], $isBigEndian); 2039 if (strlen($entries[$i]['value']) > 4) { 2040 $pos = $this->_putLong($data, $pos, $dataPos - $offsetBase, $isBigEndian); 2041 $dataPos = $this->_putString($data, $dataPos, $entries[$i]['value']); 2042 } else { 2043 $val = str_pad($entries[$i]['value'], 4, "\0"); 2044 $pos = $this->_putString($data, $pos, $val); 2045 } 2046 } 2047 } 2048 2049 if ($tiffData != null) { 2050 $this->_putLong($data, $tiffDataOffsetPos, $dataPos - $offsetBase, $isBigEndian); 2051 $dataPos = $this->_putString($data, $dataPos, $tiffData); 2052 } 2053 2054 if ($hasNext) { 2055 $pos = $this->_putLong($data, $pos, $dataPos - $offsetBase, $isBigEndian); 2056 } else { 2057 $pos = $this->_putLong($data, $pos, 0, $isBigEndian); 2058 } 2059 2060 return $dataPos; 2061 } 2062 2063 /*************************************************************/ 2064 2065 /** 2066 * @param boolean $isBigEndian 2067 * @param string $mode 2068 * 2069 * @return array 2070 */ 2071 function & _getIFDEntries($isBigEndian, $mode) { 2072 $EXIFNames = $this->_exifTagNames($mode); 2073 $EXIFTags = $this->_exifNameTags($mode); 2074 $EXIFTypeInfo = $this->_exifTagTypes($mode); 2075 2076 $ifdEntries = array(); 2077 $entryCount = 0; 2078 2079 foreach($EXIFNames as $tag => $name) { 2080 $type = $EXIFTypeInfo[$tag][0]; 2081 $count = $EXIFTypeInfo[$tag][1]; 2082 $value = null; 2083 2084 if (($mode == 'ifd0') && ($tag == 0x8769)) { // ExifIFDOffset 2085 if (isset($this->_info['exif']['EXIFVersion'])) { 2086 $value =& $this->_getIFDEntries($isBigEndian, "exif"); 2087 $type = -99; 2088 } 2089 else { 2090 $value = null; 2091 } 2092 } elseif (($mode == 'ifd0') && ($tag == 0x8825)) { // GPSIFDOffset 2093 if (isset($this->_info['exif']['GPSVersionID'])) { 2094 $value =& $this->_getIFDEntries($isBigEndian, "gps"); 2095 $type = -99; 2096 } else { 2097 $value = null; 2098 } 2099 } elseif (($mode == 'ifd1') && ($tag == 0x0111)) { // TIFFStripOffsets 2100 if (isset($this->_info['exif']['TIFFStrips'])) { 2101 $value =& $this->_info['exif']['TIFFStrips']; 2102 $type = -98; 2103 } else { 2104 $value = null; 2105 } 2106 } elseif (($mode == 'ifd1') && ($tag == 0x0117)) { // TIFFStripByteCounts 2107 if (isset($this->_info['exif']['TIFFStrips'])) { 2108 $value = strlen($this->_info['exif']['TIFFStrips']); 2109 } else { 2110 $value = null; 2111 } 2112 } elseif (($mode == 'ifd1') && ($tag == 0x0201)) { // TIFFJFIFOffset 2113 if (isset($this->_info['exif']['JFIFThumbnail'])) { 2114 $value =& $this->_info['exif']['JFIFThumbnail']; 2115 $type = -98; 2116 } else { 2117 $value = null; 2118 } 2119 } elseif (($mode == 'ifd1') && ($tag == 0x0202)) { // TIFFJFIFLength 2120 if (isset($this->_info['exif']['JFIFThumbnail'])) { 2121 $value = strlen($this->_info['exif']['JFIFThumbnail']); 2122 } else { 2123 $value = null; 2124 } 2125 } elseif (($mode == 'exif') && ($tag == 0xA005)) { // InteropIFDOffset 2126 if (isset($this->_info['exif']['InteroperabilityIndex'])) { 2127 $value =& $this->_getIFDEntries($isBigEndian, "interop"); 2128 $type = -99; 2129 } else { 2130 $value = null; 2131 } 2132 } elseif (isset($this->_info['exif'][$name])) { 2133 $origValue =& $this->_info['exif'][$name]; 2134 2135 // This makes it easier to process variable size elements 2136 if (!is_array($origValue) || isset($origValue['val'])) { 2137 unset($origValue); // Break the reference 2138 $origValue = array($this->_info['exif'][$name]); 2139 } 2140 $origCount = count($origValue); 2141 2142 if ($origCount == 0 ) { 2143 $type = -1; // To ignore this field 2144 } 2145 2146 $value = " "; 2147 2148 switch ($type) { 2149 case 1: // UBYTE 2150 if ($count == 0) { 2151 $count = $origCount; 2152 } 2153 2154 $j = 0; 2155 while (($j < $count) && ($j < $origCount)) { 2156 2157 $this->_putByte($value, $j, $origValue[$j]); 2158 $j++; 2159 } 2160 2161 while ($j < $count) { 2162 $this->_putByte($value, $j, 0); 2163 $j++; 2164 } 2165 break; 2166 case 2: // ASCII 2167 $v = strval($origValue[0]); 2168 if (($count != 0) && (strlen($v) > $count)) { 2169 $v = substr($v, 0, $count); 2170 } 2171 elseif (($count > 0) && (strlen($v) < $count)) { 2172 $v = str_pad($v, $count, "\0"); 2173 } 2174 2175 $count = strlen($v); 2176 2177 $this->_putString($value, 0, $v); 2178 break; 2179 case 3: // USHORT 2180 if ($count == 0) { 2181 $count = $origCount; 2182 } 2183 2184 $j = 0; 2185 while (($j < $count) && ($j < $origCount)) { 2186 $this->_putShort($value, $j * 2, $origValue[$j], $isBigEndian); 2187 $j++; 2188 } 2189 2190 while ($j < $count) { 2191 $this->_putShort($value, $j * 2, 0, $isBigEndian); 2192 $j++; 2193 } 2194 break; 2195 case 4: // ULONG 2196 if ($count == 0) { 2197 $count = $origCount; 2198 } 2199 2200 $j = 0; 2201 while (($j < $count) && ($j < $origCount)) { 2202 $this->_putLong($value, $j * 4, $origValue[$j], $isBigEndian); 2203 $j++; 2204 } 2205 2206 while ($j < $count) { 2207 $this->_putLong($value, $j * 4, 0, $isBigEndian); 2208 $j++; 2209 } 2210 break; 2211 case 5: // URATIONAL 2212 if ($count == 0) { 2213 $count = $origCount; 2214 } 2215 2216 $j = 0; 2217 while (($j < $count) && ($j < $origCount)) { 2218 $v = $origValue[$j]; 2219 if (is_array($v)) { 2220 $a = $v['num']; 2221 $b = $v['den']; 2222 } 2223 else { 2224 $a = 0; 2225 $b = 0; 2226 // TODO: Allow other types and convert them 2227 } 2228 $this->_putLong($value, $j * 8, $a, $isBigEndian); 2229 $this->_putLong($value, ($j * 8) + 4, $b, $isBigEndian); 2230 $j++; 2231 } 2232 2233 while ($j < $count) { 2234 $this->_putLong($value, $j * 8, 0, $isBigEndian); 2235 $this->_putLong($value, ($j * 8) + 4, 0, $isBigEndian); 2236 $j++; 2237 } 2238 break; 2239 case 6: // SBYTE 2240 if ($count == 0) { 2241 $count = $origCount; 2242 } 2243 2244 $j = 0; 2245 while (($j < $count) && ($j < $origCount)) { 2246 $this->_putByte($value, $j, $origValue[$j]); 2247 $j++; 2248 } 2249 2250 while ($j < $count) { 2251 $this->_putByte($value, $j, 0); 2252 $j++; 2253 } 2254 break; 2255 case 7: // UNDEFINED 2256 $v = strval($origValue[0]); 2257 if (($count != 0) && (strlen($v) > $count)) { 2258 $v = substr($v, 0, $count); 2259 } 2260 elseif (($count > 0) && (strlen($v) < $count)) { 2261 $v = str_pad($v, $count, "\0"); 2262 } 2263 2264 $count = strlen($v); 2265 2266 $this->_putString($value, 0, $v); 2267 break; 2268 case 8: // SSHORT 2269 if ($count == 0) { 2270 $count = $origCount; 2271 } 2272 2273 $j = 0; 2274 while (($j < $count) && ($j < $origCount)) { 2275 $this->_putShort($value, $j * 2, $origValue[$j], $isBigEndian); 2276 $j++; 2277 } 2278 2279 while ($j < $count) { 2280 $this->_putShort($value, $j * 2, 0, $isBigEndian); 2281 $j++; 2282 } 2283 break; 2284 case 9: // SLONG 2285 if ($count == 0) { 2286 $count = $origCount; 2287 } 2288 2289 $j = 0; 2290 while (($j < $count) && ($j < $origCount)) { 2291 $this->_putLong($value, $j * 4, $origValue[$j], $isBigEndian); 2292 $j++; 2293 } 2294 2295 while ($j < $count) { 2296 $this->_putLong($value, $j * 4, 0, $isBigEndian); 2297 $j++; 2298 } 2299 break; 2300 case 10: // SRATIONAL 2301 if ($count == 0) { 2302 $count = $origCount; 2303 } 2304 2305 $j = 0; 2306 while (($j < $count) && ($j < $origCount)) { 2307 $v = $origValue[$j]; 2308 if (is_array($v)) { 2309 $a = $v['num']; 2310 $b = $v['den']; 2311 } 2312 else { 2313 $a = 0; 2314 $b = 0; 2315 // TODO: Allow other types and convert them 2316 } 2317 2318 $this->_putLong($value, $j * 8, $a, $isBigEndian); 2319 $this->_putLong($value, ($j * 8) + 4, $b, $isBigEndian); 2320 $j++; 2321 } 2322 2323 while ($j < $count) { 2324 $this->_putLong($value, $j * 8, 0, $isBigEndian); 2325 $this->_putLong($value, ($j * 8) + 4, 0, $isBigEndian); 2326 $j++; 2327 } 2328 break; 2329 case 11: // FLOAT 2330 if ($count == 0) { 2331 $count = $origCount; 2332 } 2333 2334 $j = 0; 2335 while (($j < $count) && ($j < $origCount)) { 2336 $v = strval($origValue[$j]); 2337 if (strlen($v) > 4) { 2338 $v = substr($v, 0, 4); 2339 } 2340 elseif (strlen($v) < 4) { 2341 $v = str_pad($v, 4, "\0"); 2342 } 2343 $this->_putString($value, $j * 4, $v); 2344 $j++; 2345 } 2346 2347 while ($j < $count) { 2348 $v = "\0\0\0\0"; 2349 $this->_putString($value, $j * 4, $v); 2350 $j++; 2351 } 2352 break; 2353 case 12: // DFLOAT 2354 if ($count == 0) { 2355 $count = $origCount; 2356 } 2357 2358 $j = 0; 2359 while (($j < $count) && ($j < $origCount)) { 2360 $v = strval($origValue[$j]); 2361 if (strlen($v) > 8) { 2362 $v = substr($v, 0, 8); 2363 } 2364 elseif (strlen($v) < 8) { 2365 $v = str_pad($v, 8, "\0"); 2366 } 2367 $this->_putString($value, $j * 8, $v); 2368 $j++; 2369 } 2370 2371 while ($j < $count) { 2372 $v = "\0\0\0\0\0\0\0\0"; 2373 $this->_putString($value, $j * 8, $v); 2374 $j++; 2375 } 2376 break; 2377 default: 2378 $value = null; 2379 break; 2380 } 2381 } 2382 2383 if ($value != null) { 2384 $ifdEntries[$entryCount] = array(); 2385 $ifdEntries[$entryCount]['tag'] = $tag; 2386 $ifdEntries[$entryCount]['type'] = $type; 2387 $ifdEntries[$entryCount]['count'] = $count; 2388 $ifdEntries[$entryCount]['value'] = $value; 2389 2390 $entryCount++; 2391 } 2392 } 2393 2394 return $ifdEntries; 2395 } 2396 /*************************************************************/ 2397 function _handleMarkerParsingException($e) { 2398 \dokuwiki\ErrorHandler::logException($e, $this->_fileName); 2399 } 2400 2401 /*************************************************************/ 2402 function _isMarkerDisabled($name) { 2403 if (!isset($this->_info)) return false; 2404 return isset($this->_info[$name]) && $this->_info[$name] === false; 2405 } 2406 2407 /*************************************************************/ 2408 function _parseMarkerAdobe() { 2409 if (!isset($this->_markers)) { 2410 $this->_readJPEG(); 2411 } 2412 2413 if ($this->_markers == null || $this->_isMarkerDisabled('adobe')) { 2414 return false; 2415 } 2416 try { 2417 $data = null; 2418 $count = count($this->_markers); 2419 for ($i = 0; $i < $count; $i++) { 2420 if ($this->_markers[$i]['marker'] == 0xED) { 2421 $signature = $this->_getFixedString($this->_markers[$i]['data'], 0, 14); 2422 if ($signature == "Photoshop 3.0\0") { 2423 $data =& $this->_markers[$i]['data']; 2424 break; 2425 } 2426 } 2427 } 2428 2429 if ($data == null) { 2430 $this->_info['adobe'] = false; 2431 $this->_info['iptc'] = false; 2432 return false; 2433 } 2434 $pos = 14; 2435 $this->_info['adobe'] = array(); 2436 $this->_info['adobe']['raw'] = array(); 2437 $this->_info['iptc'] = array(); 2438 2439 $datasize = strlen($data); 2440 2441 while ($pos < $datasize) { 2442 $signature = $this->_getFixedString($data, $pos, 4); 2443 if ($signature != '8BIM') 2444 return false; 2445 $pos += 4; 2446 2447 $type = $this->_getShort($data, $pos); 2448 $pos += 2; 2449 2450 $strlen = $this->_getByte($data, $pos); 2451 $pos += 1; 2452 $header = ''; 2453 for ($i = 0; $i < $strlen; $i++) { 2454 $header .= $data[$pos + $i]; 2455 } 2456 $pos += $strlen + 1 - ($strlen % 2); // The string is padded to even length, counting the length byte itself 2457 2458 $length = $this->_getLong($data, $pos); 2459 $pos += 4; 2460 2461 $basePos = $pos; 2462 2463 switch ($type) { 2464 case 0x0404: // Caption (IPTC Data) 2465 $pos = $this->_readIPTC($data, $pos); 2466 if ($pos == false) 2467 return false; 2468 break; 2469 case 0x040A: // CopyrightFlag 2470 $this->_info['adobe']['CopyrightFlag'] = $this->_getByte($data, $pos); 2471 $pos += $length; 2472 break; 2473 case 0x040B: // ImageURL 2474 $this->_info['adobe']['ImageURL'] = $this->_getFixedString($data, $pos, $length); 2475 $pos += $length; 2476 break; 2477 case 0x040C: // Thumbnail 2478 $aux = $this->_getLong($data, $pos); 2479 $pos += 4; 2480 if ($aux == 1) { 2481 $this->_info['adobe']['ThumbnailWidth'] = $this->_getLong($data, $pos); 2482 $pos += 4; 2483 $this->_info['adobe']['ThumbnailHeight'] = $this->_getLong($data, $pos); 2484 $pos += 4; 2485 2486 $pos += 16; // Skip some data 2487 2488 $this->_info['adobe']['ThumbnailData'] = $this->_getFixedString($data, $pos, $length - 28); 2489 $pos += $length - 28; 2490 } 2491 break; 2492 default: 2493 break; 2494 } 2495 2496 // We save all blocks, even those we recognized 2497 $label = sprintf('8BIM_0x%04x', $type); 2498 $this->_info['adobe']['raw'][$label] = array(); 2499 $this->_info['adobe']['raw'][$label]['type'] = $type; 2500 $this->_info['adobe']['raw'][$label]['header'] = $header; 2501 $this->_info['adobe']['raw'][$label]['data'] =& $this->_getFixedString($data, $basePos, $length); 2502 2503 $pos = $basePos + $length + ($length % 2); // Even padding 2504 } 2505 } catch(Exception $e) { 2506 $this->_handleMarkerParsingException($e); 2507 $this->_info['adobe'] = false; 2508 $this->_info['iptc'] = false; 2509 return false; 2510 } 2511 } 2512 2513 /*************************************************************/ 2514 function _readIPTC(&$data, $pos = 0) { 2515 $totalLength = strlen($data); 2516 2517 $IPTCTags = $this->_iptcTagNames(); 2518 2519 while ($pos < ($totalLength - 5)) { 2520 $signature = $this->_getShort($data, $pos); 2521 if ($signature != 0x1C02) 2522 return $pos; 2523 $pos += 2; 2524 2525 $type = $this->_getByte($data, $pos); 2526 $pos += 1; 2527 $length = $this->_getShort($data, $pos); 2528 $pos += 2; 2529 2530 $basePos = $pos; 2531 $label = ''; 2532 2533 if (isset($IPTCTags[$type])) { 2534 $label = $IPTCTags[$type]; 2535 } else { 2536 $label = sprintf('IPTC_0x%02x', $type); 2537 } 2538 2539 if ($label != '') { 2540 if (isset($this->_info['iptc'][$label])) { 2541 if (!is_array($this->_info['iptc'][$label])) { 2542 $aux = array(); 2543 $aux[0] = $this->_info['iptc'][$label]; 2544 $this->_info['iptc'][$label] = $aux; 2545 } 2546 $this->_info['iptc'][$label][ count($this->_info['iptc'][$label]) ] = $this->_getFixedString($data, $pos, $length); 2547 } else { 2548 $this->_info['iptc'][$label] = $this->_getFixedString($data, $pos, $length); 2549 } 2550 } 2551 2552 $pos = $basePos + $length; // No padding 2553 } 2554 return $pos; 2555 } 2556 2557 /*************************************************************/ 2558 function & _createMarkerAdobe() { 2559 if (isset($this->_info['iptc'])) { 2560 if (!isset($this->_info['adobe'])) { 2561 $this->_info['adobe'] = array(); 2562 } 2563 if (!isset($this->_info['adobe']['raw'])) { 2564 $this->_info['adobe']['raw'] = array(); 2565 } 2566 if (!isset($this->_info['adobe']['raw']['8BIM_0x0404'])) { 2567 $this->_info['adobe']['raw']['8BIM_0x0404'] = array(); 2568 } 2569 $this->_info['adobe']['raw']['8BIM_0x0404']['type'] = 0x0404; 2570 $this->_info['adobe']['raw']['8BIM_0x0404']['header'] = "Caption"; 2571 $this->_info['adobe']['raw']['8BIM_0x0404']['data'] =& $this->_writeIPTC(); 2572 } 2573 2574 if (isset($this->_info['adobe']['raw']) && (count($this->_info['adobe']['raw']) > 0)) { 2575 $data = "Photoshop 3.0\0"; 2576 $pos = 14; 2577 2578 reset($this->_info['adobe']['raw']); 2579 foreach ($this->_info['adobe']['raw'] as $value){ 2580 $pos = $this->_write8BIM( 2581 $data, 2582 $pos, 2583 $value['type'], 2584 $value['header'], 2585 $value['data'] ); 2586 } 2587 } 2588 2589 return $data; 2590 } 2591 2592 /*************************************************************/ 2593 2594 /** 2595 * @param mixed $data 2596 * @param integer $pos 2597 * 2598 * @param string $type 2599 * @param string $header 2600 * @param mixed $value 2601 * 2602 * @return int|mixed 2603 */ 2604 function _write8BIM(&$data, $pos, $type, $header, &$value) { 2605 $signature = "8BIM"; 2606 2607 $pos = $this->_putString($data, $pos, $signature); 2608 $pos = $this->_putShort($data, $pos, $type); 2609 2610 $len = strlen($header); 2611 2612 $pos = $this->_putByte($data, $pos, $len); 2613 $pos = $this->_putString($data, $pos, $header); 2614 if (($len % 2) == 0) { // Even padding, including the length byte 2615 $pos = $this->_putByte($data, $pos, 0); 2616 } 2617 2618 $len = strlen($value); 2619 $pos = $this->_putLong($data, $pos, $len); 2620 $pos = $this->_putString($data, $pos, $value); 2621 if (($len % 2) != 0) { // Even padding 2622 $pos = $this->_putByte($data, $pos, 0); 2623 } 2624 return $pos; 2625 } 2626 2627 /*************************************************************/ 2628 function & _writeIPTC() { 2629 $data = " "; 2630 $pos = 0; 2631 2632 $IPTCNames =& $this->_iptcNameTags(); 2633 2634 foreach($this->_info['iptc'] as $label => $value) { 2635 $value =& $this->_info['iptc'][$label]; 2636 $type = -1; 2637 2638 if (isset($IPTCNames[$label])) { 2639 $type = $IPTCNames[$label]; 2640 } 2641 elseif (substr($label, 0, 7) == "IPTC_0x") { 2642 $type = hexdec(substr($label, 7, 2)); 2643 } 2644 2645 if ($type != -1) { 2646 if (is_array($value)) { 2647 $vcnt = count($value); 2648 for ($i = 0; $i < $vcnt; $i++) { 2649 $pos = $this->_writeIPTCEntry($data, $pos, $type, $value[$i]); 2650 } 2651 } 2652 else { 2653 $pos = $this->_writeIPTCEntry($data, $pos, $type, $value); 2654 } 2655 } 2656 } 2657 2658 return $data; 2659 } 2660 2661 /*************************************************************/ 2662 2663 /** 2664 * @param mixed $data 2665 * @param integer $pos 2666 * 2667 * @param string $type 2668 * @param mixed $value 2669 * 2670 * @return int|mixed 2671 */ 2672 function _writeIPTCEntry(&$data, $pos, $type, &$value) { 2673 $pos = $this->_putShort($data, $pos, 0x1C02); 2674 $pos = $this->_putByte($data, $pos, $type); 2675 $pos = $this->_putShort($data, $pos, strlen($value)); 2676 $pos = $this->_putString($data, $pos, $value); 2677 2678 return $pos; 2679 } 2680 2681 /*************************************************************/ 2682 function _exifTagNames($mode) { 2683 $tags = array(); 2684 2685 if ($mode == 'ifd0') { 2686 $tags[0x010E] = 'ImageDescription'; 2687 $tags[0x010F] = 'Make'; 2688 $tags[0x0110] = 'Model'; 2689 $tags[0x0112] = 'Orientation'; 2690 $tags[0x011A] = 'XResolution'; 2691 $tags[0x011B] = 'YResolution'; 2692 $tags[0x0128] = 'ResolutionUnit'; 2693 $tags[0x0131] = 'Software'; 2694 $tags[0x0132] = 'DateTime'; 2695 $tags[0x013B] = 'Artist'; 2696 $tags[0x013E] = 'WhitePoint'; 2697 $tags[0x013F] = 'PrimaryChromaticities'; 2698 $tags[0x0211] = 'YCbCrCoefficients'; 2699 $tags[0x0212] = 'YCbCrSubSampling'; 2700 $tags[0x0213] = 'YCbCrPositioning'; 2701 $tags[0x0214] = 'ReferenceBlackWhite'; 2702 $tags[0x8298] = 'Copyright'; 2703 $tags[0x8769] = 'ExifIFDOffset'; 2704 $tags[0x8825] = 'GPSIFDOffset'; 2705 } 2706 if ($mode == 'ifd1') { 2707 $tags[0x00FE] = 'TIFFNewSubfileType'; 2708 $tags[0x00FF] = 'TIFFSubfileType'; 2709 $tags[0x0100] = 'TIFFImageWidth'; 2710 $tags[0x0101] = 'TIFFImageHeight'; 2711 $tags[0x0102] = 'TIFFBitsPerSample'; 2712 $tags[0x0103] = 'TIFFCompression'; 2713 $tags[0x0106] = 'TIFFPhotometricInterpretation'; 2714 $tags[0x0107] = 'TIFFThreshholding'; 2715 $tags[0x0108] = 'TIFFCellWidth'; 2716 $tags[0x0109] = 'TIFFCellLength'; 2717 $tags[0x010A] = 'TIFFFillOrder'; 2718 $tags[0x010E] = 'TIFFImageDescription'; 2719 $tags[0x010F] = 'TIFFMake'; 2720 $tags[0x0110] = 'TIFFModel'; 2721 $tags[0x0111] = 'TIFFStripOffsets'; 2722 $tags[0x0112] = 'TIFFOrientation'; 2723 $tags[0x0115] = 'TIFFSamplesPerPixel'; 2724 $tags[0x0116] = 'TIFFRowsPerStrip'; 2725 $tags[0x0117] = 'TIFFStripByteCounts'; 2726 $tags[0x0118] = 'TIFFMinSampleValue'; 2727 $tags[0x0119] = 'TIFFMaxSampleValue'; 2728 $tags[0x011A] = 'TIFFXResolution'; 2729 $tags[0x011B] = 'TIFFYResolution'; 2730 $tags[0x011C] = 'TIFFPlanarConfiguration'; 2731 $tags[0x0122] = 'TIFFGrayResponseUnit'; 2732 $tags[0x0123] = 'TIFFGrayResponseCurve'; 2733 $tags[0x0128] = 'TIFFResolutionUnit'; 2734 $tags[0x0131] = 'TIFFSoftware'; 2735 $tags[0x0132] = 'TIFFDateTime'; 2736 $tags[0x013B] = 'TIFFArtist'; 2737 $tags[0x013C] = 'TIFFHostComputer'; 2738 $tags[0x0140] = 'TIFFColorMap'; 2739 $tags[0x0152] = 'TIFFExtraSamples'; 2740 $tags[0x0201] = 'TIFFJFIFOffset'; 2741 $tags[0x0202] = 'TIFFJFIFLength'; 2742 $tags[0x0211] = 'TIFFYCbCrCoefficients'; 2743 $tags[0x0212] = 'TIFFYCbCrSubSampling'; 2744 $tags[0x0213] = 'TIFFYCbCrPositioning'; 2745 $tags[0x0214] = 'TIFFReferenceBlackWhite'; 2746 $tags[0x8298] = 'TIFFCopyright'; 2747 $tags[0x9286] = 'TIFFUserComment'; 2748 } elseif ($mode == 'exif') { 2749 $tags[0x829A] = 'ExposureTime'; 2750 $tags[0x829D] = 'FNumber'; 2751 $tags[0x8822] = 'ExposureProgram'; 2752 $tags[0x8824] = 'SpectralSensitivity'; 2753 $tags[0x8827] = 'ISOSpeedRatings'; 2754 $tags[0x8828] = 'OECF'; 2755 $tags[0x9000] = 'EXIFVersion'; 2756 $tags[0x9003] = 'DateTimeOriginal'; 2757 $tags[0x9004] = 'DateTimeDigitized'; 2758 $tags[0x9101] = 'ComponentsConfiguration'; 2759 $tags[0x9102] = 'CompressedBitsPerPixel'; 2760 $tags[0x9201] = 'ShutterSpeedValue'; 2761 $tags[0x9202] = 'ApertureValue'; 2762 $tags[0x9203] = 'BrightnessValue'; 2763 $tags[0x9204] = 'ExposureBiasValue'; 2764 $tags[0x9205] = 'MaxApertureValue'; 2765 $tags[0x9206] = 'SubjectDistance'; 2766 $tags[0x9207] = 'MeteringMode'; 2767 $tags[0x9208] = 'LightSource'; 2768 $tags[0x9209] = 'Flash'; 2769 $tags[0x920A] = 'FocalLength'; 2770 $tags[0x927C] = 'MakerNote'; 2771 $tags[0x9286] = 'UserComment'; 2772 $tags[0x9290] = 'SubSecTime'; 2773 $tags[0x9291] = 'SubSecTimeOriginal'; 2774 $tags[0x9292] = 'SubSecTimeDigitized'; 2775 $tags[0xA000] = 'FlashPixVersion'; 2776 $tags[0xA001] = 'ColorSpace'; 2777 $tags[0xA002] = 'PixelXDimension'; 2778 $tags[0xA003] = 'PixelYDimension'; 2779 $tags[0xA004] = 'RelatedSoundFile'; 2780 $tags[0xA005] = 'InteropIFDOffset'; 2781 $tags[0xA20B] = 'FlashEnergy'; 2782 $tags[0xA20C] = 'SpatialFrequencyResponse'; 2783 $tags[0xA20E] = 'FocalPlaneXResolution'; 2784 $tags[0xA20F] = 'FocalPlaneYResolution'; 2785 $tags[0xA210] = 'FocalPlaneResolutionUnit'; 2786 $tags[0xA214] = 'SubjectLocation'; 2787 $tags[0xA215] = 'ExposureIndex'; 2788 $tags[0xA217] = 'SensingMethod'; 2789 $tags[0xA300] = 'FileSource'; 2790 $tags[0xA301] = 'SceneType'; 2791 $tags[0xA302] = 'CFAPattern'; 2792 } elseif ($mode == 'interop') { 2793 $tags[0x0001] = 'InteroperabilityIndex'; 2794 $tags[0x0002] = 'InteroperabilityVersion'; 2795 $tags[0x1000] = 'RelatedImageFileFormat'; 2796 $tags[0x1001] = 'RelatedImageWidth'; 2797 $tags[0x1002] = 'RelatedImageLength'; 2798 } elseif ($mode == 'gps') { 2799 $tags[0x0000] = 'GPSVersionID'; 2800 $tags[0x0001] = 'GPSLatitudeRef'; 2801 $tags[0x0002] = 'GPSLatitude'; 2802 $tags[0x0003] = 'GPSLongitudeRef'; 2803 $tags[0x0004] = 'GPSLongitude'; 2804 $tags[0x0005] = 'GPSAltitudeRef'; 2805 $tags[0x0006] = 'GPSAltitude'; 2806 $tags[0x0007] = 'GPSTimeStamp'; 2807 $tags[0x0008] = 'GPSSatellites'; 2808 $tags[0x0009] = 'GPSStatus'; 2809 $tags[0x000A] = 'GPSMeasureMode'; 2810 $tags[0x000B] = 'GPSDOP'; 2811 $tags[0x000C] = 'GPSSpeedRef'; 2812 $tags[0x000D] = 'GPSSpeed'; 2813 $tags[0x000E] = 'GPSTrackRef'; 2814 $tags[0x000F] = 'GPSTrack'; 2815 $tags[0x0010] = 'GPSImgDirectionRef'; 2816 $tags[0x0011] = 'GPSImgDirection'; 2817 $tags[0x0012] = 'GPSMapDatum'; 2818 $tags[0x0013] = 'GPSDestLatitudeRef'; 2819 $tags[0x0014] = 'GPSDestLatitude'; 2820 $tags[0x0015] = 'GPSDestLongitudeRef'; 2821 $tags[0x0016] = 'GPSDestLongitude'; 2822 $tags[0x0017] = 'GPSDestBearingRef'; 2823 $tags[0x0018] = 'GPSDestBearing'; 2824 $tags[0x0019] = 'GPSDestDistanceRef'; 2825 $tags[0x001A] = 'GPSDestDistance'; 2826 } 2827 2828 return $tags; 2829 } 2830 2831 /*************************************************************/ 2832 function _exifTagTypes($mode) { 2833 $tags = array(); 2834 2835 if ($mode == 'ifd0') { 2836 $tags[0x010E] = array(2, 0); // ImageDescription -> ASCII, Any 2837 $tags[0x010F] = array(2, 0); // Make -> ASCII, Any 2838 $tags[0x0110] = array(2, 0); // Model -> ASCII, Any 2839 $tags[0x0112] = array(3, 1); // Orientation -> SHORT, 1 2840 $tags[0x011A] = array(5, 1); // XResolution -> RATIONAL, 1 2841 $tags[0x011B] = array(5, 1); // YResolution -> RATIONAL, 1 2842 $tags[0x0128] = array(3, 1); // ResolutionUnit -> SHORT 2843 $tags[0x0131] = array(2, 0); // Software -> ASCII, Any 2844 $tags[0x0132] = array(2, 20); // DateTime -> ASCII, 20 2845 $tags[0x013B] = array(2, 0); // Artist -> ASCII, Any 2846 $tags[0x013E] = array(5, 2); // WhitePoint -> RATIONAL, 2 2847 $tags[0x013F] = array(5, 6); // PrimaryChromaticities -> RATIONAL, 6 2848 $tags[0x0211] = array(5, 3); // YCbCrCoefficients -> RATIONAL, 3 2849 $tags[0x0212] = array(3, 2); // YCbCrSubSampling -> SHORT, 2 2850 $tags[0x0213] = array(3, 1); // YCbCrPositioning -> SHORT, 1 2851 $tags[0x0214] = array(5, 6); // ReferenceBlackWhite -> RATIONAL, 6 2852 $tags[0x8298] = array(2, 0); // Copyright -> ASCII, Any 2853 $tags[0x8769] = array(4, 1); // ExifIFDOffset -> LONG, 1 2854 $tags[0x8825] = array(4, 1); // GPSIFDOffset -> LONG, 1 2855 } 2856 if ($mode == 'ifd1') { 2857 $tags[0x00FE] = array(4, 1); // TIFFNewSubfileType -> LONG, 1 2858 $tags[0x00FF] = array(3, 1); // TIFFSubfileType -> SHORT, 1 2859 $tags[0x0100] = array(4, 1); // TIFFImageWidth -> LONG (or SHORT), 1 2860 $tags[0x0101] = array(4, 1); // TIFFImageHeight -> LONG (or SHORT), 1 2861 $tags[0x0102] = array(3, 3); // TIFFBitsPerSample -> SHORT, 3 2862 $tags[0x0103] = array(3, 1); // TIFFCompression -> SHORT, 1 2863 $tags[0x0106] = array(3, 1); // TIFFPhotometricInterpretation -> SHORT, 1 2864 $tags[0x0107] = array(3, 1); // TIFFThreshholding -> SHORT, 1 2865 $tags[0x0108] = array(3, 1); // TIFFCellWidth -> SHORT, 1 2866 $tags[0x0109] = array(3, 1); // TIFFCellLength -> SHORT, 1 2867 $tags[0x010A] = array(3, 1); // TIFFFillOrder -> SHORT, 1 2868 $tags[0x010E] = array(2, 0); // TIFFImageDescription -> ASCII, Any 2869 $tags[0x010F] = array(2, 0); // TIFFMake -> ASCII, Any 2870 $tags[0x0110] = array(2, 0); // TIFFModel -> ASCII, Any 2871 $tags[0x0111] = array(4, 0); // TIFFStripOffsets -> LONG (or SHORT), Any (one per strip) 2872 $tags[0x0112] = array(3, 1); // TIFFOrientation -> SHORT, 1 2873 $tags[0x0115] = array(3, 1); // TIFFSamplesPerPixel -> SHORT, 1 2874 $tags[0x0116] = array(4, 1); // TIFFRowsPerStrip -> LONG (or SHORT), 1 2875 $tags[0x0117] = array(4, 0); // TIFFStripByteCounts -> LONG (or SHORT), Any (one per strip) 2876 $tags[0x0118] = array(3, 0); // TIFFMinSampleValue -> SHORT, Any (SamplesPerPixel) 2877 $tags[0x0119] = array(3, 0); // TIFFMaxSampleValue -> SHORT, Any (SamplesPerPixel) 2878 $tags[0x011A] = array(5, 1); // TIFFXResolution -> RATIONAL, 1 2879 $tags[0x011B] = array(5, 1); // TIFFYResolution -> RATIONAL, 1 2880 $tags[0x011C] = array(3, 1); // TIFFPlanarConfiguration -> SHORT, 1 2881 $tags[0x0122] = array(3, 1); // TIFFGrayResponseUnit -> SHORT, 1 2882 $tags[0x0123] = array(3, 0); // TIFFGrayResponseCurve -> SHORT, Any (2^BitsPerSample) 2883 $tags[0x0128] = array(3, 1); // TIFFResolutionUnit -> SHORT, 1 2884 $tags[0x0131] = array(2, 0); // TIFFSoftware -> ASCII, Any 2885 $tags[0x0132] = array(2, 20); // TIFFDateTime -> ASCII, 20 2886 $tags[0x013B] = array(2, 0); // TIFFArtist -> ASCII, Any 2887 $tags[0x013C] = array(2, 0); // TIFFHostComputer -> ASCII, Any 2888 $tags[0x0140] = array(3, 0); // TIFFColorMap -> SHORT, Any (3 * 2^BitsPerSample) 2889 $tags[0x0152] = array(3, 0); // TIFFExtraSamples -> SHORT, Any (SamplesPerPixel - 3) 2890 $tags[0x0201] = array(4, 1); // TIFFJFIFOffset -> LONG, 1 2891 $tags[0x0202] = array(4, 1); // TIFFJFIFLength -> LONG, 1 2892 $tags[0x0211] = array(5, 3); // TIFFYCbCrCoefficients -> RATIONAL, 3 2893 $tags[0x0212] = array(3, 2); // TIFFYCbCrSubSampling -> SHORT, 2 2894 $tags[0x0213] = array(3, 1); // TIFFYCbCrPositioning -> SHORT, 1 2895 $tags[0x0214] = array(5, 6); // TIFFReferenceBlackWhite -> RATIONAL, 6 2896 $tags[0x8298] = array(2, 0); // TIFFCopyright -> ASCII, Any 2897 $tags[0x9286] = array(2, 0); // TIFFUserComment -> ASCII, Any 2898 } elseif ($mode == 'exif') { 2899 $tags[0x829A] = array(5, 1); // ExposureTime -> RATIONAL, 1 2900 $tags[0x829D] = array(5, 1); // FNumber -> RATIONAL, 1 2901 $tags[0x8822] = array(3, 1); // ExposureProgram -> SHORT, 1 2902 $tags[0x8824] = array(2, 0); // SpectralSensitivity -> ASCII, Any 2903 $tags[0x8827] = array(3, 0); // ISOSpeedRatings -> SHORT, Any 2904 $tags[0x8828] = array(7, 0); // OECF -> UNDEFINED, Any 2905 $tags[0x9000] = array(7, 4); // EXIFVersion -> UNDEFINED, 4 2906 $tags[0x9003] = array(2, 20); // DateTimeOriginal -> ASCII, 20 2907 $tags[0x9004] = array(2, 20); // DateTimeDigitized -> ASCII, 20 2908 $tags[0x9101] = array(7, 4); // ComponentsConfiguration -> UNDEFINED, 4 2909 $tags[0x9102] = array(5, 1); // CompressedBitsPerPixel -> RATIONAL, 1 2910 $tags[0x9201] = array(10, 1); // ShutterSpeedValue -> SRATIONAL, 1 2911 $tags[0x9202] = array(5, 1); // ApertureValue -> RATIONAL, 1 2912 $tags[0x9203] = array(10, 1); // BrightnessValue -> SRATIONAL, 1 2913 $tags[0x9204] = array(10, 1); // ExposureBiasValue -> SRATIONAL, 1 2914 $tags[0x9205] = array(5, 1); // MaxApertureValue -> RATIONAL, 1 2915 $tags[0x9206] = array(5, 1); // SubjectDistance -> RATIONAL, 1 2916 $tags[0x9207] = array(3, 1); // MeteringMode -> SHORT, 1 2917 $tags[0x9208] = array(3, 1); // LightSource -> SHORT, 1 2918 $tags[0x9209] = array(3, 1); // Flash -> SHORT, 1 2919 $tags[0x920A] = array(5, 1); // FocalLength -> RATIONAL, 1 2920 $tags[0x927C] = array(7, 0); // MakerNote -> UNDEFINED, Any 2921 $tags[0x9286] = array(7, 0); // UserComment -> UNDEFINED, Any 2922 $tags[0x9290] = array(2, 0); // SubSecTime -> ASCII, Any 2923 $tags[0x9291] = array(2, 0); // SubSecTimeOriginal -> ASCII, Any 2924 $tags[0x9292] = array(2, 0); // SubSecTimeDigitized -> ASCII, Any 2925 $tags[0xA000] = array(7, 4); // FlashPixVersion -> UNDEFINED, 4 2926 $tags[0xA001] = array(3, 1); // ColorSpace -> SHORT, 1 2927 $tags[0xA002] = array(4, 1); // PixelXDimension -> LONG (or SHORT), 1 2928 $tags[0xA003] = array(4, 1); // PixelYDimension -> LONG (or SHORT), 1 2929 $tags[0xA004] = array(2, 13); // RelatedSoundFile -> ASCII, 13 2930 $tags[0xA005] = array(4, 1); // InteropIFDOffset -> LONG, 1 2931 $tags[0xA20B] = array(5, 1); // FlashEnergy -> RATIONAL, 1 2932 $tags[0xA20C] = array(7, 0); // SpatialFrequencyResponse -> UNDEFINED, Any 2933 $tags[0xA20E] = array(5, 1); // FocalPlaneXResolution -> RATIONAL, 1 2934 $tags[0xA20F] = array(5, 1); // FocalPlaneYResolution -> RATIONAL, 1 2935 $tags[0xA210] = array(3, 1); // FocalPlaneResolutionUnit -> SHORT, 1 2936 $tags[0xA214] = array(3, 2); // SubjectLocation -> SHORT, 2 2937 $tags[0xA215] = array(5, 1); // ExposureIndex -> RATIONAL, 1 2938 $tags[0xA217] = array(3, 1); // SensingMethod -> SHORT, 1 2939 $tags[0xA300] = array(7, 1); // FileSource -> UNDEFINED, 1 2940 $tags[0xA301] = array(7, 1); // SceneType -> UNDEFINED, 1 2941 $tags[0xA302] = array(7, 0); // CFAPattern -> UNDEFINED, Any 2942 } elseif ($mode == 'interop') { 2943 $tags[0x0001] = array(2, 0); // InteroperabilityIndex -> ASCII, Any 2944 $tags[0x0002] = array(7, 4); // InteroperabilityVersion -> UNKNOWN, 4 2945 $tags[0x1000] = array(2, 0); // RelatedImageFileFormat -> ASCII, Any 2946 $tags[0x1001] = array(4, 1); // RelatedImageWidth -> LONG (or SHORT), 1 2947 $tags[0x1002] = array(4, 1); // RelatedImageLength -> LONG (or SHORT), 1 2948 } elseif ($mode == 'gps') { 2949 $tags[0x0000] = array(1, 4); // GPSVersionID -> BYTE, 4 2950 $tags[0x0001] = array(2, 2); // GPSLatitudeRef -> ASCII, 2 2951 $tags[0x0002] = array(5, 3); // GPSLatitude -> RATIONAL, 3 2952 $tags[0x0003] = array(2, 2); // GPSLongitudeRef -> ASCII, 2 2953 $tags[0x0004] = array(5, 3); // GPSLongitude -> RATIONAL, 3 2954 $tags[0x0005] = array(2, 2); // GPSAltitudeRef -> ASCII, 2 2955 $tags[0x0006] = array(5, 1); // GPSAltitude -> RATIONAL, 1 2956 $tags[0x0007] = array(5, 3); // GPSTimeStamp -> RATIONAL, 3 2957 $tags[0x0008] = array(2, 0); // GPSSatellites -> ASCII, Any 2958 $tags[0x0009] = array(2, 2); // GPSStatus -> ASCII, 2 2959 $tags[0x000A] = array(2, 2); // GPSMeasureMode -> ASCII, 2 2960 $tags[0x000B] = array(5, 1); // GPSDOP -> RATIONAL, 1 2961 $tags[0x000C] = array(2, 2); // GPSSpeedRef -> ASCII, 2 2962 $tags[0x000D] = array(5, 1); // GPSSpeed -> RATIONAL, 1 2963 $tags[0x000E] = array(2, 2); // GPSTrackRef -> ASCII, 2 2964 $tags[0x000F] = array(5, 1); // GPSTrack -> RATIONAL, 1 2965 $tags[0x0010] = array(2, 2); // GPSImgDirectionRef -> ASCII, 2 2966 $tags[0x0011] = array(5, 1); // GPSImgDirection -> RATIONAL, 1 2967 $tags[0x0012] = array(2, 0); // GPSMapDatum -> ASCII, Any 2968 $tags[0x0013] = array(2, 2); // GPSDestLatitudeRef -> ASCII, 2 2969 $tags[0x0014] = array(5, 3); // GPSDestLatitude -> RATIONAL, 3 2970 $tags[0x0015] = array(2, 2); // GPSDestLongitudeRef -> ASCII, 2 2971 $tags[0x0016] = array(5, 3); // GPSDestLongitude -> RATIONAL, 3 2972 $tags[0x0017] = array(2, 2); // GPSDestBearingRef -> ASCII, 2 2973 $tags[0x0018] = array(5, 1); // GPSDestBearing -> RATIONAL, 1 2974 $tags[0x0019] = array(2, 2); // GPSDestDistanceRef -> ASCII, 2 2975 $tags[0x001A] = array(5, 1); // GPSDestDistance -> RATIONAL, 1 2976 } 2977 2978 return $tags; 2979 } 2980 2981 /*************************************************************/ 2982 function _exifNameTags($mode) { 2983 $tags = $this->_exifTagNames($mode); 2984 return $this->_names2Tags($tags); 2985 } 2986 2987 /*************************************************************/ 2988 function _iptcTagNames() { 2989 $tags = array(); 2990 $tags[0x14] = 'SuplementalCategories'; 2991 $tags[0x19] = 'Keywords'; 2992 $tags[0x78] = 'Caption'; 2993 $tags[0x7A] = 'CaptionWriter'; 2994 $tags[0x69] = 'Headline'; 2995 $tags[0x28] = 'SpecialInstructions'; 2996 $tags[0x0F] = 'Category'; 2997 $tags[0x50] = 'Byline'; 2998 $tags[0x55] = 'BylineTitle'; 2999 $tags[0x6E] = 'Credit'; 3000 $tags[0x73] = 'Source'; 3001 $tags[0x74] = 'CopyrightNotice'; 3002 $tags[0x05] = 'ObjectName'; 3003 $tags[0x5A] = 'City'; 3004 $tags[0x5C] = 'Sublocation'; 3005 $tags[0x5F] = 'ProvinceState'; 3006 $tags[0x65] = 'CountryName'; 3007 $tags[0x67] = 'OriginalTransmissionReference'; 3008 $tags[0x37] = 'DateCreated'; 3009 $tags[0x0A] = 'CopyrightFlag'; 3010 3011 return $tags; 3012 } 3013 3014 /*************************************************************/ 3015 function & _iptcNameTags() { 3016 $tags = $this->_iptcTagNames(); 3017 return $this->_names2Tags($tags); 3018 } 3019 3020 /*************************************************************/ 3021 function _names2Tags($tags2Names) { 3022 $names2Tags = array(); 3023 3024 foreach($tags2Names as $tag => $name) { 3025 $names2Tags[$name] = $tag; 3026 } 3027 3028 return $names2Tags; 3029 } 3030 3031 /*************************************************************/ 3032 3033 /** 3034 * @param $data 3035 * @param integer $pos 3036 * 3037 * @return int 3038 */ 3039 function _getByte(&$data, $pos) { 3040 if (!isset($data[$pos])) { 3041 throw new Exception("Requested byte at ".$pos.". Reading outside of file's boundaries."); 3042 } 3043 3044 return ord($data[$pos]); 3045 } 3046 3047 /*************************************************************/ 3048 3049 /** 3050 * @param mixed $data 3051 * @param integer $pos 3052 * 3053 * @param mixed $val 3054 * 3055 * @return int 3056 */ 3057 function _putByte(&$data, $pos, $val) { 3058 $val = intval($val); 3059 3060 $data[$pos] = chr($val); 3061 3062 return $pos + 1; 3063 } 3064 3065 /*************************************************************/ 3066 function _getShort(&$data, $pos, $bigEndian = true) { 3067 if (!isset($data[$pos]) || !isset($data[$pos + 1])) { 3068 throw new Exception("Requested short at ".$pos.". Reading outside of file's boundaries."); 3069 } 3070 3071 if ($bigEndian) { 3072 return (ord($data[$pos]) << 8) 3073 + ord($data[$pos + 1]); 3074 } else { 3075 return ord($data[$pos]) 3076 + (ord($data[$pos + 1]) << 8); 3077 } 3078 } 3079 3080 /*************************************************************/ 3081 function _putShort(&$data, $pos = 0, $val = 0, $bigEndian = true) { 3082 $val = intval($val); 3083 3084 if ($bigEndian) { 3085 $data[$pos + 0] = chr(($val & 0x0000FF00) >> 8); 3086 $data[$pos + 1] = chr(($val & 0x000000FF) >> 0); 3087 } else { 3088 $data[$pos + 0] = chr(($val & 0x00FF) >> 0); 3089 $data[$pos + 1] = chr(($val & 0xFF00) >> 8); 3090 } 3091 3092 return $pos + 2; 3093 } 3094 3095 /*************************************************************/ 3096 3097 /** 3098 * @param mixed $data 3099 * @param integer $pos 3100 * 3101 * @param bool $bigEndian 3102 * 3103 * @return int 3104 */ 3105 function _getLong(&$data, $pos, $bigEndian = true) { 3106 // Assume that if the start and end bytes are defined, the bytes inbetween are defined as well. 3107 if (!isset($data[$pos]) || !isset($data[$pos + 3])){ 3108 throw new Exception("Requested long at ".$pos.". Reading outside of file's boundaries."); 3109 } 3110 if ($bigEndian) { 3111 return (ord($data[$pos]) << 24) 3112 + (ord($data[$pos + 1]) << 16) 3113 + (ord($data[$pos + 2]) << 8) 3114 + ord($data[$pos + 3]); 3115 } else { 3116 return ord($data[$pos]) 3117 + (ord($data[$pos + 1]) << 8) 3118 + (ord($data[$pos + 2]) << 16) 3119 + (ord($data[$pos + 3]) << 24); 3120 } 3121 } 3122 3123 /*************************************************************/ 3124 3125 /** 3126 * @param mixed $data 3127 * @param integer $pos 3128 * 3129 * @param mixed $val 3130 * @param bool $bigEndian 3131 * 3132 * @return int 3133 */ 3134 function _putLong(&$data, $pos, $val, $bigEndian = true) { 3135 $val = intval($val); 3136 3137 if ($bigEndian) { 3138 $data[$pos + 0] = chr(($val & 0xFF000000) >> 24); 3139 $data[$pos + 1] = chr(($val & 0x00FF0000) >> 16); 3140 $data[$pos + 2] = chr(($val & 0x0000FF00) >> 8); 3141 $data[$pos + 3] = chr(($val & 0x000000FF) >> 0); 3142 } else { 3143 $data[$pos + 0] = chr(($val & 0x000000FF) >> 0); 3144 $data[$pos + 1] = chr(($val & 0x0000FF00) >> 8); 3145 $data[$pos + 2] = chr(($val & 0x00FF0000) >> 16); 3146 $data[$pos + 3] = chr(($val & 0xFF000000) >> 24); 3147 } 3148 3149 return $pos + 4; 3150 } 3151 3152 /*************************************************************/ 3153 function & _getNullString(&$data, $pos) { 3154 $str = ''; 3155 $max = strlen($data); 3156 3157 while ($pos < $max) { 3158 if (!isset($data[$pos])) { 3159 throw new Exception("Requested null-terminated string at offset ".$pos.". File terminated before the null-byte."); 3160 } 3161 if (ord($data[$pos]) == 0) { 3162 return $str; 3163 } else { 3164 $str .= $data[$pos]; 3165 } 3166 $pos++; 3167 } 3168 3169 return $str; 3170 } 3171 3172 /*************************************************************/ 3173 function & _getFixedString(&$data, $pos, $length = -1) { 3174 if ($length == -1) { 3175 $length = strlen($data) - $pos; 3176 } 3177 3178 $rv = substr($data, $pos, $length); 3179 if (strlen($rv) != $length) { 3180 throw new ErrorException(sprintf( 3181 "JPEGMeta failed parsing image metadata of %s. Got %d instead of %d bytes at offset %d.", 3182 $this->_fileName, strlen($rv), $length, $pos 3183 ), 0, E_WARNING); 3184 } 3185 return $rv; 3186 } 3187 3188 /*************************************************************/ 3189 function _putString(&$data, $pos, &$str) { 3190 $len = strlen($str); 3191 for ($i = 0; $i < $len; $i++) { 3192 $data[$pos + $i] = $str[$i]; 3193 } 3194 3195 return $pos + $len; 3196 } 3197 3198 /*************************************************************/ 3199 function _hexDump(&$data, $start = 0, $length = -1) { 3200 if (($length == -1) || (($length + $start) > strlen($data))) { 3201 $end = strlen($data); 3202 } else { 3203 $end = $start + $length; 3204 } 3205 3206 $ascii = ''; 3207 $count = 0; 3208 3209 echo "<tt>\n"; 3210 3211 while ($start < $end) { 3212 if (($count % 16) == 0) { 3213 echo sprintf('%04d', $count) . ': '; 3214 } 3215 3216 $c = ord($data[$start]); 3217 $count++; 3218 $start++; 3219 3220 $aux = dechex($c); 3221 if (strlen($aux) == 1) 3222 echo '0'; 3223 echo $aux . ' '; 3224 3225 if ($c == 60) 3226 $ascii .= '<'; 3227 elseif ($c == 62) 3228 $ascii .= '>'; 3229 elseif ($c == 32) 3230 $ascii .= ' '; 3231 elseif ($c > 32) 3232 $ascii .= chr($c); 3233 else 3234 $ascii .= '.'; 3235 3236 if (($count % 4) == 0) { 3237 echo ' - '; 3238 } 3239 3240 if (($count % 16) == 0) { 3241 echo ': ' . $ascii . "<br>\n"; 3242 $ascii = ''; 3243 } 3244 } 3245 3246 if ($ascii != '') { 3247 while (($count % 16) != 0) { 3248 echo '-- '; 3249 $count++; 3250 if (($count % 4) == 0) { 3251 echo ' - '; 3252 } 3253 } 3254 echo ': ' . $ascii . "<br>\n"; 3255 } 3256 3257 echo "</tt>\n"; 3258 } 3259 3260 /*****************************************************************/ 3261 } 3262 3263 /* vim: set expandtab tabstop=4 shiftwidth=4: */
title
Description
Body
title
Description
Body
title
Description
Body
title
Body