1<?php 2// (c) Copyright by authors of the Tiki Wiki CMS Groupware Project 3// 4// All Rights Reserved. See copyright.txt for details and a complete list of authors. 5// Licensed under the GNU LESSER GENERAL PUBLIC LICENSE. See license.txt for details. 6// $Id$ 7 8//this script may only be included - so its better to die if called directly. 9if (strpos($_SERVER['SCRIPT_NAME'], basename(__FILE__)) !== false) { 10 header('location: index.php'); 11 exit; 12} 13/* 14 * Manipulates EXIF metadata included within a file 15 */ 16class Exif 17{ 18 /** 19 * Legend, label, suffix and format information for each field 20 * See www.cipa.jp/english/hyoujunka/kikaku/pdf/DC-008-2010_E.pdf for specification source for Exif version 2.3 21 * 22 * @var array 23 */ 24 var $specs = [ 25 //the PHP function exif_read_data adds the FILE group when returning exif data 26 'FILE' => [ 27 'FileDateTime' => [ 28 'label' => 'Data Extraction Time', 29 ], 30 'FileSize' => [ 31 'suffix' => 'bytes', 32 ], 33 'FileType' => [ 34 //from http://php.net/manual/function.exif-imagetype.php 35 'options' => [ 36 '1' => 'GIF', 37 '2' => 'JPEG', 38 '3' => 'PNG', 39 '4' => 'SWF', 40 '5' => 'PSD', 41 '6' => 'BMP', 42 '7' => 'TIFF II (Intel byte order)', 43 '8' => 'TIFF MM (Motorola byte order)', 44 '9' => 'JPC', 45 '10' => 'JP2', 46 '11' => 'JPX', 47 '12' => 'JB2', 48 '13' => 'SWC', 49 '14' => 'IFF', 50 '15' => 'WBMP', 51 '16' => 'XBM', 52 '17' => 'ICO', 53 ], 54 ], 55 ], 56 'COMPUTED' => [ 57 'html' => [ 58 'label' => 'HTML', 59 ], 60 'Height' => [ 61 'suffix' => 'pixels', 62 ], 63 'Width' => [ 64 'suffix' => 'pixels', 65 ], 66 'IsColor' => [ 67 'options' => [ 68 0 => 'No', 69 1 => 'Yes', 70 ], 71 ], 72 'ByteOrderMotorola' => [ 73 'options' => [ 74 0 => 'No', 75 1 => 'Yes', 76 ], 77 ], 78 'Thumbnail.FileType' => [ 79 'label' => 'Thumbnail File Type', 80 //from http://php.net/manual/function.exif-imagetype.php 81 'options' => [ 82 '1' => 'GIF', 83 '2' => 'JPEG', 84 '3' => 'PNG', 85 '4' => 'SWF', 86 '5' => 'PSD', 87 '6' => 'BMP', 88 '7' => 'TIFF II (Intel byte order)', 89 '8' => 'TIFF MM (Motorola byte order)', 90 '9' => 'JPC', 91 '10' => 'JP2', 92 '11' => 'JPX', 93 '12' => 'JB2', 94 '13' => 'SWC', 95 '14' => 'IFF', 96 '15' => 'WBMP', 97 '16' => 'XBM', 98 '17' => 'ICO', 99 ], 100 ], 101 'Thumbnail.MimeType' => [ 102 'label' => 'Thumbnail Mime Type', 103 ], 104 ], 105 'IFD0' => [ 106 'ImageWidth' => [ 107 'suffix' => 'pixels', 108 ], 109 'ImageLength' => [ 110 'suffix' => 'pixels', 111 ], 112 'Compression' => [ 113 'options' => [ 114 '1' => 'Uncompressed', 115 '2' => 'CCITT 1D', 116 '3' => 'T4/Group 3 Fax', 117 '4' => 'T6/Group 4 Fax', 118 '5' => 'LZW', 119 '6' => 'JPEG (old-style)', 120 '7' => 'JPEG', 121 '8' => 'Adobe Deflate', 122 '9' => 'JBIG B&W', 123 '10' => 'JBIG Color', 124 '99' => 'JPEG', 125 '262' => 'Kodak 262', 126 '32766' => 'Next', 127 '32767' => 'Sony ARW Compressed', 128 '32769' => 'Packed RAW', 129 '32770' => 'Samsung SRW Compressed', 130 '32771' => 'CCIRLEW', 131 '32773' => 'PackBits', 132 '32809' => 'Thunderscan', 133 '32867' => 'Kodak KDC Compressed', 134 '32895' => 'IT8CTPAD', 135 '32896' => 'IT8LW', 136 '32897' => 'IT8MP', 137 '32898' => 'IT8BL', 138 '32908' => 'PixarFilm', 139 '32909' => 'PixarLog', 140 '32946' => 'Deflate', 141 '32947' => 'DCS', 142 '34661' => 'JBIG', 143 '34676' => 'SGILog', 144 '34677' => 'SGILog24', 145 '34712' => 'JPEG 2000', 146 '34713' => 'Nikon NEF Compressed', 147 '34715' => 'JBIG2 TIFF FX', 148 '34718' => 'Microsoft Document Imaging (MDI) Binary Level Codec', 149 '34719' => 'Microsoft Document Imaging (MDI) Progressive Transform Codec', 150 '34720' => 'Microsoft Document Imaging (MDI) Vector', 151 '65000' => 'Kodak DCR Compressed', 152 '65535' => 'Pentax PEF Compressed)', 153 ], 154 ], 155 'PhotometricInterpretation' => [ 156 'options' => [ 157 '0' => 'WhiteIsZero', 158 '1' => 'BlackIsZero', 159 '2' => 'RGB', 160 '3' => 'RGB Palette', 161 '4' => 'Transparency Mask', 162 '5' => 'CMYK', 163 '6' => 'YCbCr', 164 '8' => 'CIELab', 165 '9' => 'ICCLab', 166 '10' => 'ITULab', 167 '32803' => 'Color Filter Array', 168 '32844' => 'Pixar LogL', 169 '32845' => 'Pixar LogLuv', 170 '34892' => 'Linear Raw', 171 ], 172 ], 173 'Orientation' => [ 174 'options' => [ 175 '1' => 'Horizontal (normal)', 176 '2' => 'Mirror horizontal', 177 '3' => 'Rotate 180', 178 '4' => 'Mirror vertical', 179 '5' => 'Mirror horizontal and rotate 270 CW', 180 '6' => 'Rotate 90 CW', 181 '7' => 'Mirror horizontal and rotate 90 CW', 182 '8' => 'Rotate 270 CW', 183 ], 184 ], 185 'XResolution' => [ 186 'format' => 'rational', 187 'suffix' => 'pixels (dots) per unit', 188 ], 189 'YResolution' => [ 190 'format' => 'rational', 191 'suffix' => 'pixels (dots) per unit', 192 ], 193 'PlanarConfiguration' => [ 194 'options' => [ 195 '1' => 'Chunky', 196 '2' => 'Planar', 197 ], 198 ], 199 'ResolutionUnit' => [ 200 'options' => [ 201 '2' => 'inch', 202 '3' => 'cm', 203 ], 204 ], 205 'WhitePoint' => [ 206 'format' => 'rational', 207 ], 208 'PrimaryChromaticities' => [ 209 'format' => 'rational', 210 ], 211 'YCbCrCoefficients' => [ 212 'format' => 'rational', 213 ], 214 'YCbCrSubSampling' => [ 215 'options' => [ 216 '1 1' => 'YCbCr4:4:4 (1 1)', 217 '1 2' => 'YCbCr4:4:0 (1 2)', 218 '1 4' => 'YCbCr4:4:1 (1 4)', 219 '2 1' => 'YCbCr4:2:2 (2 1)', 220 '2 2' => 'YCbCr4:2:0 (2 2)', 221 '2 4' => 'YCbCr4:2:1 (2 4)', 222 '4 1' => 'YCbCr4:1:1 (4 1)', 223 '4 2' => 'YCbCr4:1:0 (4 2)', 224 ], 225 ], 226 'YCbCrPositioning' => [ 227 'options' => [ 228 1 => 'Centered', 229 2 => 'Co-sited', 230 ], 231 ], 232 'ReferenceBlackWhite' => [ 233 'format' => 'rational', 234 ], 235 'JPEGInterchangeFormat' => [ 236 'label' => 'Thumbnail Offset', 237 ], 238 'JPEGInterchangeFormatLength' => [ 239 'label' => 'Thumbnail Length', 240 ], 241 'Exif_IFD_Pointer' => [ 242 'label' => 'EXIF IFD Pointer', 243 ], 244 'GPS_IFD_Pointer' => [ 245 'label' => 'GPS IFD Pointer', 246 ], ], 247 'EXIF' => [ 248 'ExposureTime' => [ 249 'format' => 'rational', 250 'suffix' => 'seconds', 251 ], 252 'FNumber' => [ 253 'format' => 'rational', 254 ], 255 'ExposureProgram' => [ 256 'options' => [ 257 '0' => 'Not Defined', 258 '1' => 'Manual', 259 '2' => 'Program AE', 260 '3' => 'Aperture-priority AE', 261 '4' => 'Shutter speed priority AE', 262 '5' => 'Creative (Slow speed)', 263 '6' => 'Action (High speed)', 264 '7' => 'Portrait', 265 '8' => 'Landscape', 266 '9' => 'Bulb', 267 ], 268 ], 269 'SensitivityType' => [ 270 'options' => [ 271 '0' => 'Unknown', 272 '1' => 'Standard Output Sensitivity', 273 '2' => 'Recommended Exposure Index', 274 '3' => 'ISO Speed', 275 '4' => 'Standard Output Sensitivity and Recommended Exposure Index', 276 '5' => 'Standard Output Sensitivity and ISO Speed', 277 '6' => 'Recommended Exposure Index and ISO Speed', 278 '7' => 'Standard Output Sensitivity, Recommended Exposure Index and ISO Speed', 279 ], 280 ], 281 'ComponentsConfiguration' => [ 282 'binary' => true, 283 'options' => [ 284 '00' => '- ', 285 '01' => 'Y ', 286 '02' => 'Cb', 287 '03' => 'Cr', 288 '04' => 'R ', 289 '05' => 'G ', 290 '06' => 'B ', 291 ], 292 ], 293 'CompressedBitsPerPixel' => [ 294 'format' => 'rational', 295 ], 296 'ShutterSpeedValue' => [ 297 'format' => 'rational', 298 'suffix' => 'APEX' 299 ], 300 'ApertureValue' => [ 301 'format' => 'rational', 302 'suffix' => 'APEX' 303 ], 304 'BrightnessValue' => [ 305 'format' => 'rational', 306 'suffix' => 'APEX' 307 ], 308 'ExposureBiasValue' => [ 309 'format' => 'rational', 310 'suffix' => 'APEX' 311 ], 312 'MaxApertureValue' => [ 313 'format' => 'rational', 314 'suffix' => 'APEX' 315 ], 316 'SubjectDistance' => [ 317 'format' => 'rational', 318 'suffix' => 'meters' 319 ], 320 'MeteringMode' => [ 321 'options' => [ 322 '0' => 'Unknown', 323 '1' => 'Average', 324 '2' => 'Center-weighted average', 325 '3' => 'Spot', 326 '4' => 'Multi-spot', 327 '5' => 'Multi-segment', 328 '6' => 'Partial', 329 '255' => 'Other', 330 ], 331 ], 332 'LightSource' => [ 333 'options' => [ 334 '0' => 'Unknown', 335 '1' => 'Daylight', 336 '2' => 'Fluorescent', 337 '3' => 'Tungsten (Incandescent)', 338 '4' => 'Flash', 339 '9' => 'Fine Weather', 340 '10' => 'Cloudy', 341 '11' => 'Shade', 342 '12' => 'Daylight Fluorescent', 343 '13' => 'Day White Fluorescent', 344 '14' => 'Cool White Fluorescent', 345 '15' => 'White Fluorescent', 346 '16' => 'Warm White Fluorescent', 347 '17' => 'Standard Light A', 348 '18' => 'Standard Light B', 349 '19' => 'Standard Light C', 350 '20' => 'D55', 351 '21' => 'D65', 352 '22' => 'D75', 353 '23' => 'D50', 354 '24' => 'ISO Studio Tungsten', 355 '255' => 'Other', 356 ], 357 ], 358 'Flash' => [ 359 'options' => [ 360 '0' => 'No Flash', 361 '1' => 'Fired', 362 '5' => 'Fired, Return not detected', 363 '7' => 'Fired, Return detected', 364 '8' => 'On, Did not fire', 365 '9' => 'On, Fired', 366 '13' => 'On, Return not detected', 367 '15' => 'On, Return detected', 368 '16' => 'Off, Did not fire', 369 '20' => 'Off, Did not fire, Return not detected', 370 '24' => 'Auto, Did not fire', 371 '25' => 'Auto, Fired', 372 '29' => 'Auto, Fired, Return not detected', 373 '31' => 'Auto, Fired, Return detected', 374 '32' => 'No flash function', 375 '48' => 'Off, No flash function', 376 '65' => 'Fired, Red-eye reduction', 377 '69' => 'Fired, Red-eye reduction, Return not detected', 378 '71' => 'Fired, Red-eye reduction, Return detected', 379 '73' => 'On, Red-eye reduction', 380 '77' => 'On, Red-eye reduction, Return not detected', 381 '79' => 'On, Red-eye reduction, Return detected', 382 '80' => 'Off, Red-eye reduction', 383 '88' => 'Auto, Did not fire, Red-eye reduction', 384 '89' => 'Auto, Fired, Red-eye reduction', 385 '93' => 'Auto, Fired, Red-eye reduction, Return not detected', 386 '95' => 'Auto, Fired, Red-eye reduction, Return detected', 387 ], 388 ], 389 'FocalLength' => [ 390 'format' => 'rational', 391 'suffix' => 'mm', 392 ], 393 /* //don't convert since already converted by PHP 394 'UserComment' => array( 395 'binary' => true, 396 ),*/ 397 'ColorSpace' => [ 398 'options' => [ 399 '1' => 'sRGB', 400 '65533' => 'Wide Gamut RGB', 401 '65534' => 'ICC Profile', 402 '65535' => 'Uncalibrated', 403 ], 404 ], 405 'ExifImageWidth' => [ 406 'label' => 'Image Width', 407 'suffix' => 'pixels', 408 ], 409 'ExifImageLength' => [ 410 'label' => 'Image Height', 411 'suffix' => 'pixels', 412 ], 413 'FlashEnergy' => [ 414 'format' => 'rational', 415 'suffix' => 'BCPS', 416 ], 417 'FocalPlaneXResolution' => [ 418 'format' => 'rational', 419 'suffix' => 'pixels per unit', 420 ], 421 'FocalPlaneYResolution' => [ 422 'format' => 'rational', 423 'suffix' => 'pixels per unit', 424 ], 425 'FocalPlaneResolutionUnit' => [ 426 'options' => [ 427 1 => 'None', 428 2 => 'inch', 429 3 => 'cm', 430 4 => 'mm', 431 5 => 'um', 432 ], 433 ], 434 'ExposureIndex' => [ 435 'format' => 'rational', 436 ], 437 'SensingMethod' => [ 438 'options' => [ 439 1 => 'Not defined', 440 2 => 'One-chip color area', 441 3 => 'Two-chip color area', 442 4 => 'Three-chip color area', 443 5 => 'Color sequential area', 444 7 => 'Trilinear', 445 8 => 'Color sequential linear', 446 ], 447 ], 448 'FileSource' => [ 449 'binary' => true, 450 'options' => [ 451 '00' => 'Others', 452 '01' => 'Film Scanner ', 453 '02' => 'Reflection Print Scanner', 454 '03' => 'Digital Camera', 455 ], 456 ], 457 'SceneType' => [ 458 'binary' => true, 459 'options' => [ 460 '01' => 'Directly photographed', 461 ], 462 ], 463 'CFAPattern' => [ 464 'binary' => true, 465 'options' => [ 466 '00' => 'Red', 467 '01' => 'Green', 468 '02' => 'Blue', 469 '03' => 'Cyan', 470 '04' => 'Magenta', 471 '05' => 'Yellow', 472 '06' => 'White', 473 ], 474 ], 475 'CustomRendered' => [ 476 'options' => [ 477 '0' => 'Normal', 478 '1' => 'Custom', 479 ], 480 ], 481 'ExposureMode' => [ 482 'options' => [ 483 '0' => 'Auto', 484 '1' => 'Manual', 485 '2' => 'Auto bracket', 486 ], 487 ], 488 'WhiteBalance' => [ 489 'options' => [ 490 '0' => 'Auto', 491 '1' => 'Manual', 492 ], 493 ], 494 'SceneCaptureType' => [ 495 'options' => [ 496 '0' => 'Standard', 497 '1' => 'Landscape', 498 '2' => 'Portrait', 499 '3' => 'Night', 500 ], 501 ], 502 'GainControl' => [ 503 'options' => [ 504 '0' => 'None', 505 '1' => 'Low gain up', 506 '2' => 'High gain up', 507 '3' => 'Low gain down', 508 '4' => 'High gain down', 509 ], 510 ], 511 'Contrast' => [ 512 'options' => [ 513 '0' => 'Normal', 514 '1' => 'Low', 515 '2' => 'High', 516 ], 517 ], 518 'Saturation' => [ 519 'options' => [ 520 '0' => 'Normal', 521 '1' => 'Low', 522 '2' => 'High', 523 ], 524 ], 525 'Sharpness' => [ 526 'options' => [ 527 '0' => 'Normal', 528 '1' => 'Soft', 529 '2' => 'Hard', 530 ], 531 ], 532 'SubjectDistanceRange' => [ 533 'options' => [ 534 '0' => 'Unknown', 535 '1' => 'Macro', 536 '2' => 'Close', 537 '3' => 'Distant', 538 ], 539 ], 540 //some legitimate exif fields are apparently only partially handled by php 541 //an undefined tag is returned instead of the label 542 'UndefinedTag:0xA431' => [ 543 'label' => 'Serial Number', 544 ], 545 'UndefinedTag:0xA432' => [ 546 'label' => 'Lens Specification', 547 'suffix' => 'Min - max focal length in mm; Min - max Fnumber' 548 ], 549 'UndefinedTag:0xA434' => [ 550 'label' => 'Lens Model', 551 ], 552 ], 553 'GPS' => [ 554 'GPSVersion' => [ 555 'binary' => true, 556 ], 557 'GPSLatitudeRef' => [ 558 'options' => [ 559 'N' => 'North (+)', 560 'S' => 'South (-)', 561 ], 562 ], 563 'GPSLatitude' => [ 564 'format' => 'rational', 565 ], 566 'GPSLongitudeRef' => [ 567 'options' => [ 568 'E' => 'East (+)', 569 'W' => 'West (-)', 570 ], 571 ], 572 'GPSLongitude' => [ 573 'format' => 'rational', 574 ], 575 'GPSAltitudeRef' => [ 576 'binary' => true, 577 'options' => [ 578 '00' => 'Meters above sea level', 579 '01' => 'Meters below sea level', 580 ], 581 ], 582 'GPSAltitude' => [ 583 'format' => 'rational', 584 ], 585 'GPSTimeStamp' => [ 586 'format' => 'rational', 587 'suffix' => '(24-hour clock)', 588 ], 589 'GPSStatus' => [ 590 'options' => [ 591 'A' => 'Measurement Active', 592 'V' => 'Measurement Void', 593 ], 594 ], 595 'GPSMeasureMode' => [ 596 'options' => [ 597 '2' => '2-Dimensional Measurement', 598 '3' => '3-Dimensional Measurement', 599 ], 600 ], 601 'GPSSpeedRef' => [ 602 'options' => [ 603 'K' => 'km/h', 604 'M' => 'mph', 605 'N' => 'knots', 606 ], 607 ], 608 'GPSSpeed' => [ 609 'format' => 'rational', 610 ], 611 'GPSTrackRef' => [ 612 'options' => [ 613 'M' => 'Magnetic North', 614 'T' => 'True North', 615 ], 616 ], 617 'GPSTrack' => [ 618 'format' => 'rational', 619 ], 620 'GPSImgDirectionRef' => [ 621 'options' => [ 622 'M' => 'Magnetic North', 623 'T' => 'True North', 624 ], 625 ], 626 'GPSImgDirection' => [ 627 'format' => 'rational', 628 ], 629 'GPSDifferential' => [ 630 'options' => [ 631 '0' => 'No Corection', 632 'T' => 'Differential Corrected', 633 ], 634 ], 635 'GPSDestLatitudeRef' => [ 636 'options' => [ 637 'N' => 'North (+)', 638 'S' => 'South (-)', 639 ], 640 ], 641 'GPSDestLatitude' => [ 642 'format' => 'rational', 643 ], 644 'GPSDestLongitudeRef' => [ 645 'options' => [ 646 'E' => 'East (+)', 647 'W' => 'West (-)', 648 ], 649 ], 650 'GPSDestLongitude' => [ 651 'format' => 'rational', 652 ], 653 'GPSDestBearingRef' => [ 654 'options' => [ 655 'M' => 'Magnetic North', 656 'T' => 'True North', 657 ], 658 ], 659 'GPSDestBearing' => [ 660 'format' => 'rational', 661 ], 662 'GPSDestDistanceRef' => [ 663 'options' => [ 664 'K' => 'Kilometers', 665 'M' => 'Miles', 666 'N' => 'Nautical Miles', 667 ], 668 ], 669 'GPSDestDistance' => [ 670 'format' => 'rational', 671 ], 672 'GPSHPositioningError' => [ 673 'format' => 'rational', 674 ], 675 ], 676 ]; 677 678 /** 679 * Process raw EXIF data by converting binary or hex information, replacing legend codes with their meanings, 680 * fixing labels, etc. 681 * 682 * @param array $exifraw Array of raw EXIF data 683 * 684 * @return array $exif Array of processed EXIF data, including label, newval and suffix values 685 * for each field 686 */ 687 function processRawData($exifraw) 688 { 689 $filter = new Zend\Filter\Word\CamelCaseToSeparator(); 690 //array of tags to match exif array from file 691 foreach ($exifraw as $group => $fields) { 692 foreach ($fields as $name => $field) { 693 if (isset($field)) { 694 //store raw value 695 $exif[$group][$name]['rawval'] = $field; 696 697 //thumbnail and ifd0 groups share the same specifications 698 $groupmask = $group == 'THUMBNAIL' ? 'IFD0' : $group; 699 700 //get tag data from $specs array 701 if (isset($this->specs[$groupmask][$name])) { 702 //shorten the variable 703 $specname = $this->specs[$groupmask][$name]; 704 705 //convert binary values 706 if (isset($specname['binary']) && $specname['binary']) { 707 $exif[$group][$name]['rawval'] = bin2hex($exif[$group][$name]['rawval']); 708 } 709 710 //start processing rawval into newval 711 if (is_array($exif[$group][$name]['rawval'])) { 712 $exif[$group][$name]['newval'] = $this->processArray($exif[$group][$name]['rawval'], $name); 713 //perform division for rational fields, but only if not an array 714 } elseif (isset($specname['format']) && $specname['format'] == 'rational') { 715 $exif[$group][$name]['newval'] = $this->divide($field); 716 //move rest of rawvals into newvals 717 } else { 718 $exif[$group][$name]['newval'] = $exif[$group][$name]['rawval']; 719 } 720 721 //now determine display values using option values where they exist 722 if (isset($specname['options'])) { 723 //first handle special cases 724 if ($name == 'ComponentsConfiguration') { 725 $str = $exif[$group][$name]['newval']; 726 $opt = $specname['options']; 727 $disp = $opt[substr($str, 0, 2)] . ' ' . $opt[substr($str, 2, 2)] . ' ' 728 . $opt[substr($str, 4, 2)] . ' ' . $opt[substr($str, 6, 2)]; 729 $exif[$group][$name]['newval'] = $disp; 730 } elseif ($name == 'CFAPattern') { 731 $str = $exif[$group][$name]['newval']; 732 $opt = $specname['options']; 733 $disp = '[' . $opt[substr($str, 8, 2)] . ', ' . $opt[substr($str, 10, 2)] . '] [' 734 . $opt[substr($str, 12, 2)] . ', ' . $opt[substr($str, 14, 2)] . ']'; 735 $exif[$group][$name]['newval'] = $disp; 736 } else { 737 $exif[$group][$name]['newval'] = $specname['options'][$exif[$group][$name]['newval']]; 738 } 739 } 740 741 //fix labels 742 if (isset($specname['label'])) { 743 $exif[$group][$name]['label'] = $specname['label']; 744 } else { 745 //create reading-friendly labels from camel case tags 746 $exif[$group][$name]['label'] = $filter->filter($name); 747 } 748 749 if (isset($specname['suffix']) && ! is_array($exif[$group][$name]['newval'])) { 750 $exif[$group][$name]['suffix'] = $specname['suffix']; 751 } 752 } else { 753 //those not covered in $specs 754 $exif[$group][$name]['newval'] = $exif[$group][$name]['rawval']; 755 //create reading-friendly labels from camel case tags 756 $exif[$group][$name]['label'] = $filter->filter($name); 757 } 758 } 759 } 760 } 761 //*******Special Handling*********// 762 //file name is computed by PHP and is meaningless when file is stored in tiki, 763 //and dialog box has real name in title so not needed 764 unset($exif['FILE']['FileName']); 765 //file date is also computed by PHP and represents the time the metadata was extracted. This data is included 766 //elsewhere and is not needed here 767 unset($exif['FILE']['FileDateTime']); 768 //No processing of maker notes yet as specific code is needed for each manufacturer 769 //Blank out field since it is very long and will distort the dialog box 770 if (! empty($exif['EXIF']['MakerNote']['newval'])) { 771 $exif['EXIF']['MakerNote']['newval'] = '(Not processed)'; 772 unset($exif['EXIF']['MakerNote']['rawval']); 773 } 774 if (isset($exif['MAKERNOTE'])) { 775 $exif['MAKERNOTE'] = []; 776 $exif['MAKERNOTE']['Note']['label'] = ""; 777 $exif['MAKERNOTE']['Note']['newval'] = "(Not processed)"; 778 } 779 //Interpret GPSVersion field 780 if (isset($exif['GPS']['GPSVersion'])) { 781 $exif['GPS']['GPSVersion']['newval'] = ''; 782 $len = strlen($exif['GPS']['GPSVersion']['rawval']); 783 for ($i = 0; $i < $len; $i = $i + 2) { 784 if ($i > 0) { 785 $exif['GPS']['GPSVersion']['newval'] .= '.'; 786 } 787 $exif['GPS']['GPSVersion']['newval'] .= (int) substr($exif['GPS']['GPSVersion']['rawval'], $i, 2); 788 } 789 } 790 //PHP already converts UserComment in the Computed group so use that value 791 if (isset($exif['EXIF']['UserComment']) && isset($exif['COMPUTED']['UserComment'])) { 792 $exif['EXIF']['UserComment']['newval'] = $exif['COMPUTED']['UserComment']['rawval']; 793 unset($exif['COMPUTED']['UserComment']); 794 } 795 //PHP already converts the FNumber in the Computed group so use that value 796 if (isset($exif['EXIF']['FNumber']) && isset($exif['COMPUTED']['ApertureFNumber'])) { 797 $exif['EXIF']['FNumber']['newval'] = $exif['COMPUTED']['ApertureFNumber']['rawval']; 798 unset($exif['COMPUTED']['ApertureFNumber']); 799 } 800 801 return $exif; 802 } 803 804 /** 805 * Perform division on values in a rational format, e.g. '100/5'. Accepts arrays or single values 806 * 807 * @param string or array $fractionString 808 * 809 * @return array|float 810 */ 811 function divide($fractionString) 812 { 813 if (! is_array($fractionString)) { 814 $fraction = explode('/', $fractionString); 815 $ret = $fraction[0] / $fraction[1]; 816 } else { 817 foreach ($fractionString as $fs) { 818 $fraction = explode('/', $fs); 819 $ret[] = $fraction[0] / $fraction[1]; 820 } 821 } 822 return $ret; 823 } 824 825 /** 826 * Deal with field values that are arrays, giving unique treatment to certain unique fields, otherwise 827 * converting to a string 828 * 829 * @param array $array field value 830 * @param string $fieldname field name used to identify where unique treatment should be applied 831 * 832 * @return string $ret field array converted into a string 833 */ 834 function processArray($array, $fieldname) 835 { 836 $ret = ''; 837 if ($fieldname == 'GPSLatitude' || $fieldname == 'GPSLongitude') { 838 $calcarray = $this->divide($array); 839 $ret = $calcarray[0] + (($calcarray[1] + ($calcarray[2] / 60)) / 60); 840 } elseif ($fieldname == 'GPSTimeStamp') { 841 $array = $this->divide($array); 842 $ret = $array[0] . ':' . $array[1] . ':' . $array[2]; 843 } elseif ($fieldname == 'UndefinedTag:0xA432') { 844 $array = $this->divide($array); 845 $ret = $array[0] . ' - ' . $array[1] . '; ' . $array[2] . ' - ' . $array[3]; 846 } else { 847 foreach ($array as $value) { 848 $ret .= $value . '; '; 849 } 850 $ret .= tra('(values not interpreted)'); 851 } 852 return $ret; 853 } 854} 855