Index: branches/img_metadata/phase3/includes/media/FormatMetadata.php |
— | — | @@ -0,0 +1,916 @@ |
| 2 | +<?php |
| 3 | +/** |
| 4 | + * This program is free software; you can redistribute it and/or modify |
| 5 | + * it under the terms of the GNU General Public License as published by |
| 6 | + * the Free Software Foundation; either version 2 of the License, or |
| 7 | + * (at your option) any later version. |
| 8 | + * |
| 9 | + * This program is distributed in the hope that it will be useful, |
| 10 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 11 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 12 | + * GNU General Public License for more details. |
| 13 | + * |
| 14 | + * You should have received a copy of the GNU General Public License along |
| 15 | + * with this program; if not, write to the Free Software Foundation, Inc., |
| 16 | + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
| 17 | + * http://www.gnu.org/copyleft/gpl.html |
| 18 | + * |
| 19 | + * @ingroup Media |
| 20 | + * @author Ævar Arnfjörð Bjarmason <avarab@gmail.com> |
| 21 | + * @copyright Copyright © 2005, Ævar Arnfjörð Bjarmason, 2009 Brent Garber |
| 22 | + * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License |
| 23 | + * @see http://exif.org/Exif2-2.PDF The Exif 2.2 specification |
| 24 | + * @file |
| 25 | + */ |
| 26 | + |
| 27 | + |
| 28 | +/** |
| 29 | + * Format Image metadata values into a human readable form. |
| 30 | + * |
| 31 | + * Note lots of these messages use the prefix 'exif' even though |
| 32 | + * they may not be exif properties. For example 'exif-ImageDescription' |
| 33 | + * can be the Exif ImageDescription, or it could be the iptc-iim caption |
| 34 | + * property, or it could be the xmp dc:description property. This |
| 35 | + * is because these messages should be independent of how the data is |
| 36 | + * stored, sine the user doesn't care if the description is stored in xmp, |
| 37 | + * exif, etc only that its a description. (Additionally many of these properties |
| 38 | + * are merged together following the MWG standard, such that for example, |
| 39 | + * exif properties override XMP properties that mean the same thing if |
| 40 | + * there is a conflict). |
| 41 | + * |
| 42 | + * It should perhaps use a prefix like 'metadata' instead, but there |
| 43 | + * is already a large number of messages using the 'exif' prefix. |
| 44 | + * |
| 45 | + * @ingroup Media |
| 46 | + */ |
| 47 | +class FormatMetadata { |
| 48 | + |
| 49 | + /** |
| 50 | + * Numbers given by Exif user agents are often magical, that is they |
| 51 | + * should be replaced by a detailed explanation depending on their |
| 52 | + * value which most of the time are plain integers. This function |
| 53 | + * formats Exif (and other metadata) values into human readable form. |
| 54 | + * |
| 55 | + * @param $tags Array: the Exif data to format ( as returned by |
| 56 | + * Exif::getFilteredData() or BitmapMetadataHandler ) |
| 57 | + * @return array |
| 58 | + */ |
| 59 | + public static function getFormattedData( $tags ) { |
| 60 | + global $wgLang; |
| 61 | + |
| 62 | + $resolutionunit = !isset( $tags['ResolutionUnit'] ) || $tags['ResolutionUnit'] == 2 ? 2 : 3; |
| 63 | + unset( $tags['ResolutionUnit'] ); |
| 64 | + |
| 65 | + foreach ( $tags as $tag => &$vals ) { |
| 66 | + |
| 67 | + // This seems ugly to wrap non-array's in an array just to unwrap again, |
| 68 | + // especially when most of the time it is not an array |
| 69 | + if ( !is_array( $tags[$tag] ) ) { |
| 70 | + $vals = Array( $vals ); |
| 71 | + } |
| 72 | + |
| 73 | + // _type is a special value to say what array type |
| 74 | + if ( isset( $tags[$tag]['_type'] ) ) { |
| 75 | + $type = $tags[$tag]['_type']; |
| 76 | + unset( $vals['_type'] ); |
| 77 | + } else { |
| 78 | + $type = 'ul'; // default unorcdered list. |
| 79 | + } |
| 80 | + |
| 81 | + //This is done differently as the tag is an array. |
| 82 | + if ($tag == 'GPSTimeStamp' && count($vals) === 3) { |
| 83 | + //hour min sec array |
| 84 | + |
| 85 | + $h = explode('/', $vals[0]); |
| 86 | + $m = explode('/', $vals[1]); |
| 87 | + $s = explode('/', $vals[2]); |
| 88 | + |
| 89 | + // this should already be validated |
| 90 | + // when loaded from file, but it could |
| 91 | + // come from a foreign repo, so be |
| 92 | + // paranoid. |
| 93 | + if ( !isset($h[1]) |
| 94 | + || !isset($m[1]) |
| 95 | + || !isset($s[1]) |
| 96 | + || $h[1] == 0 |
| 97 | + || $m[1] == 0 |
| 98 | + || $s[1] == 0 |
| 99 | + ) { |
| 100 | + continue; |
| 101 | + } |
| 102 | + $tags[$tag] = intval( $h[0] / $h[1] ) |
| 103 | + . ':' . intval( $m[0] / $m[1] ) |
| 104 | + . ':' . str_pad( intval( $s[0] / $s[1] ), 2, '0', STR_PAD_LEFT ); |
| 105 | + continue; |
| 106 | + } |
| 107 | + |
| 108 | + |
| 109 | + foreach ( $vals as &$val ) { |
| 110 | + |
| 111 | + switch( $tag ) { |
| 112 | + case 'Compression': |
| 113 | + switch( $val ) { |
| 114 | + case 1: case 6: |
| 115 | + $val = self::msg( $tag, $val ); |
| 116 | + break; |
| 117 | + default: |
| 118 | + $val = $val; |
| 119 | + break; |
| 120 | + } |
| 121 | + break; |
| 122 | + |
| 123 | + case 'PhotometricInterpretation': |
| 124 | + switch( $val ) { |
| 125 | + case 2: case 6: |
| 126 | + $val = self::msg( $tag, $val ); |
| 127 | + break; |
| 128 | + default: |
| 129 | + $val = $val; |
| 130 | + break; |
| 131 | + } |
| 132 | + break; |
| 133 | + |
| 134 | + case 'Orientation': |
| 135 | + switch( $val ) { |
| 136 | + case 1: case 2: case 3: case 4: case 5: case 6: case 7: case 8: |
| 137 | + $val = self::msg( $tag, $val ); |
| 138 | + break; |
| 139 | + default: |
| 140 | + $val = $val; |
| 141 | + break; |
| 142 | + } |
| 143 | + break; |
| 144 | + |
| 145 | + case 'PlanarConfiguration': |
| 146 | + switch( $val ) { |
| 147 | + case 1: case 2: |
| 148 | + $val = self::msg( $tag, $val ); |
| 149 | + break; |
| 150 | + default: |
| 151 | + $val = $val; |
| 152 | + break; |
| 153 | + } |
| 154 | + break; |
| 155 | + |
| 156 | + // TODO: YCbCrSubSampling |
| 157 | + case 'YCbCrPositioning': |
| 158 | + switch ( $val ) { |
| 159 | + case 1: |
| 160 | + case 2: |
| 161 | + $val = self::msg( $tag, $val ); |
| 162 | + break; |
| 163 | + default: |
| 164 | + $val = $val; |
| 165 | + break; |
| 166 | + } |
| 167 | + break; |
| 168 | + |
| 169 | + case 'XResolution': |
| 170 | + case 'YResolution': |
| 171 | + switch( $resolutionunit ) { |
| 172 | + case 2: |
| 173 | + $val = self::msg( 'XYResolution', 'i', self::formatNum( $val ) ); |
| 174 | + break; |
| 175 | + case 3: |
| 176 | + $val = self::msg( 'XYResolution', 'c', self::formatNum( $val ) ); |
| 177 | + break; |
| 178 | + default: |
| 179 | + $val = $val; |
| 180 | + break; |
| 181 | + } |
| 182 | + break; |
| 183 | + |
| 184 | + // TODO: YCbCrCoefficients #p27 (see annex E) |
| 185 | + case 'ExifVersion': case 'FlashpixVersion': |
| 186 | + $val = "$val" / 100; |
| 187 | + break; |
| 188 | + |
| 189 | + case 'ColorSpace': |
| 190 | + switch( $val ) { |
| 191 | + case 1: case 'FFFF.H': |
| 192 | + $val = self::msg( $tag, $val ); |
| 193 | + break; |
| 194 | + default: |
| 195 | + $val = $val; |
| 196 | + break; |
| 197 | + } |
| 198 | + break; |
| 199 | + |
| 200 | + case 'ComponentsConfiguration': |
| 201 | + switch( $val ) { |
| 202 | + case 0: case 1: case 2: case 3: case 4: case 5: case 6: |
| 203 | + $val = self::msg( $tag, $val ); |
| 204 | + break; |
| 205 | + default: |
| 206 | + $val = $val; |
| 207 | + break; |
| 208 | + } |
| 209 | + break; |
| 210 | + |
| 211 | + case 'DateTime': |
| 212 | + case 'DateTimeOriginal': |
| 213 | + case 'DateTimeDigitized': |
| 214 | + case 'DateTimeReleased': |
| 215 | + case 'DateTimeExpires': |
| 216 | + case 'GPSDateStamp': |
| 217 | + case 'dc-date': |
| 218 | + case 'DateTimeMetadata': |
| 219 | + if ( $val == '0000:00:00 00:00:00' || $val == ' : : : : ' ) { |
| 220 | + $val = wfMsg( 'exif-unknowndate' ); |
| 221 | + } elseif ( preg_match( '/^(?:\d{4}):(?:\d\d):(?:\d\d) (?:\d\d):(?:\d\d):(?:\d\d)$/', $val ) ) { |
| 222 | + $val = $wgLang->timeanddate( wfTimestamp( TS_MW, $val ) ); |
| 223 | + } elseif ( preg_match( '/^(?:\d{4}):(?:\d\d):(?:\d\d)$/', $val ) ) { |
| 224 | + // avoid using wfTimestamp here for the pre-1902 photos |
| 225 | + // due to reverse y2k38 bug. $wgLang->timeanddate() is also |
| 226 | + // broken on dates from before 1902 so don't worry about it |
| 227 | + // in the above case (not to mention that most photos from the |
| 228 | + // 1800's don't have a time recorded anyways). |
| 229 | + $val = $wgLang->date( substr( $val, 0, 4 ) |
| 230 | + . substr( $val, 5, 2 ) |
| 231 | + . substr( $val, 8, 2 ) |
| 232 | + . '000000' ); |
| 233 | + } |
| 234 | + // else it will just output $val without formatting it. |
| 235 | + break; |
| 236 | + |
| 237 | + case 'ExposureProgram': |
| 238 | + switch( $val ) { |
| 239 | + case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7: case 8: |
| 240 | + $val = self::msg( $tag, $val ); |
| 241 | + break; |
| 242 | + default: |
| 243 | + $val = $val; |
| 244 | + break; |
| 245 | + } |
| 246 | + break; |
| 247 | + |
| 248 | + case 'SubjectDistance': |
| 249 | + $val = self::msg( $tag, '', self::formatNum( $val ) ); |
| 250 | + break; |
| 251 | + |
| 252 | + case 'MeteringMode': |
| 253 | + switch( $val ) { |
| 254 | + case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7: case 255: |
| 255 | + $val = self::msg( $tag, $val ); |
| 256 | + break; |
| 257 | + default: |
| 258 | + $val = $val; |
| 259 | + break; |
| 260 | + } |
| 261 | + break; |
| 262 | + |
| 263 | + case 'LightSource': |
| 264 | + switch( $val ) { |
| 265 | + case 0: case 1: case 2: case 3: case 4: case 9: case 10: case 11: |
| 266 | + case 12: case 13: case 14: case 15: case 17: case 18: case 19: case 20: |
| 267 | + case 21: case 22: case 23: case 24: case 255: |
| 268 | + $val = self::msg( $tag, $val ); |
| 269 | + break; |
| 270 | + default: |
| 271 | + $val = $val; |
| 272 | + break; |
| 273 | + } |
| 274 | + break; |
| 275 | + |
| 276 | + case 'Flash': |
| 277 | + $flashDecode = array( |
| 278 | + 'fired' => $val & bindec( '00000001' ), |
| 279 | + 'return' => ( $val & bindec( '00000110' ) ) >> 1, |
| 280 | + 'mode' => ( $val & bindec( '00011000' ) ) >> 3, |
| 281 | + 'function' => ( $val & bindec( '00100000' ) ) >> 5, |
| 282 | + 'redeye' => ( $val & bindec( '01000000' ) ) >> 6, |
| 283 | +// 'reserved' => ($val & bindec( '10000000' )) >> 7, |
| 284 | + ); |
| 285 | + |
| 286 | + # We do not need to handle unknown values since all are used. |
| 287 | + foreach ( $flashDecode as $subTag => $subValue ) { |
| 288 | + # We do not need any message for zeroed values. |
| 289 | + if ( $subTag != 'fired' && $subValue == 0 ) { |
| 290 | + continue; |
| 291 | + } |
| 292 | + $fullTag = $tag . '-' . $subTag ; |
| 293 | + $flashMsgs[] = self::msg( $fullTag, $subValue ); |
| 294 | + } |
| 295 | + $val = $wgLang->commaList( $flashMsgs ); |
| 296 | + break; |
| 297 | + |
| 298 | + case 'FocalPlaneResolutionUnit': |
| 299 | + switch( $val ) { |
| 300 | + case 2: |
| 301 | + $val = self::msg( $tag, $val ); |
| 302 | + break; |
| 303 | + default: |
| 304 | + $val = $val; |
| 305 | + break; |
| 306 | + } |
| 307 | + break; |
| 308 | + |
| 309 | + case 'SensingMethod': |
| 310 | + switch( $val ) { |
| 311 | + case 1: case 2: case 3: case 4: case 5: case 7: case 8: |
| 312 | + $val = self::msg( $tag, $val ); |
| 313 | + break; |
| 314 | + default: |
| 315 | + $val = $val; |
| 316 | + break; |
| 317 | + } |
| 318 | + break; |
| 319 | + |
| 320 | + case 'FileSource': |
| 321 | + switch( $val ) { |
| 322 | + case 3: |
| 323 | + $val = self::msg( $tag, $val ); |
| 324 | + break; |
| 325 | + default: |
| 326 | + $val = $val; |
| 327 | + break; |
| 328 | + } |
| 329 | + break; |
| 330 | + |
| 331 | + case 'SceneType': |
| 332 | + switch( $val ) { |
| 333 | + case 1: |
| 334 | + $val = self::msg( $tag, $val ); |
| 335 | + break; |
| 336 | + default: |
| 337 | + $val = $val; |
| 338 | + break; |
| 339 | + } |
| 340 | + break; |
| 341 | + |
| 342 | + case 'CustomRendered': |
| 343 | + switch( $val ) { |
| 344 | + case 0: case 1: |
| 345 | + $val = self::msg( $tag, $val ); |
| 346 | + break; |
| 347 | + default: |
| 348 | + $val = $val; |
| 349 | + break; |
| 350 | + } |
| 351 | + break; |
| 352 | + |
| 353 | + case 'ExposureMode': |
| 354 | + switch( $val ) { |
| 355 | + case 0: case 1: case 2: |
| 356 | + $val = self::msg( $tag, $val ); |
| 357 | + break; |
| 358 | + default: |
| 359 | + $val = $val; |
| 360 | + break; |
| 361 | + } |
| 362 | + break; |
| 363 | + |
| 364 | + case 'WhiteBalance': |
| 365 | + switch( $val ) { |
| 366 | + case 0: case 1: |
| 367 | + $val = self::msg( $tag, $val ); |
| 368 | + break; |
| 369 | + default: |
| 370 | + $val = $val; |
| 371 | + break; |
| 372 | + } |
| 373 | + break; |
| 374 | + |
| 375 | + case 'SceneCaptureType': |
| 376 | + switch( $val ) { |
| 377 | + case 0: case 1: case 2: case 3: |
| 378 | + $val = self::msg( $tag, $val ); |
| 379 | + break; |
| 380 | + default: |
| 381 | + $val = $val; |
| 382 | + break; |
| 383 | + } |
| 384 | + break; |
| 385 | + |
| 386 | + case 'GainControl': |
| 387 | + switch( $val ) { |
| 388 | + case 0: case 1: case 2: case 3: case 4: |
| 389 | + $val = self::msg( $tag, $val ); |
| 390 | + break; |
| 391 | + default: |
| 392 | + $val = $val; |
| 393 | + break; |
| 394 | + } |
| 395 | + break; |
| 396 | + |
| 397 | + case 'Contrast': |
| 398 | + switch( $val ) { |
| 399 | + case 0: case 1: case 2: |
| 400 | + $val = self::msg( $tag, $val ); |
| 401 | + break; |
| 402 | + default: |
| 403 | + $val = $val; |
| 404 | + break; |
| 405 | + } |
| 406 | + break; |
| 407 | + |
| 408 | + case 'Saturation': |
| 409 | + switch( $val ) { |
| 410 | + case 0: case 1: case 2: |
| 411 | + $val = self::msg( $tag, $val ); |
| 412 | + break; |
| 413 | + default: |
| 414 | + $val = $val; |
| 415 | + break; |
| 416 | + } |
| 417 | + break; |
| 418 | + |
| 419 | + case 'Sharpness': |
| 420 | + switch( $val ) { |
| 421 | + case 0: case 1: case 2: |
| 422 | + $val = self::msg( $tag, $val ); |
| 423 | + break; |
| 424 | + default: |
| 425 | + $val = $val; |
| 426 | + break; |
| 427 | + } |
| 428 | + break; |
| 429 | + |
| 430 | + case 'SubjectDistanceRange': |
| 431 | + switch( $val ) { |
| 432 | + case 0: case 1: case 2: case 3: |
| 433 | + $val = self::msg( $tag, $val ); |
| 434 | + break; |
| 435 | + default: |
| 436 | + $val = $val; |
| 437 | + break; |
| 438 | + } |
| 439 | + break; |
| 440 | + |
| 441 | + //The GPS...Ref values are kept for compatability, probably won't be reached. |
| 442 | + case 'GPSLatitudeRef': |
| 443 | + case 'GPSDestLatitudeRef': |
| 444 | + switch( $val ) { |
| 445 | + case 'N': case 'S': |
| 446 | + $val = self::msg( 'GPSLatitude', $val ); |
| 447 | + break; |
| 448 | + default: |
| 449 | + $val = $val; |
| 450 | + break; |
| 451 | + } |
| 452 | + break; |
| 453 | + |
| 454 | + case 'GPSLongitudeRef': |
| 455 | + case 'GPSDestLongitudeRef': |
| 456 | + switch( $val ) { |
| 457 | + case 'E': case 'W': |
| 458 | + $val = self::msg( 'GPSLongitude', $val ); |
| 459 | + break; |
| 460 | + default: |
| 461 | + $val = $val; |
| 462 | + break; |
| 463 | + } |
| 464 | + break; |
| 465 | + |
| 466 | + case 'GPSAltitude': |
| 467 | + if ( $val < 0 ) { |
| 468 | + $val = self::msg( 'GPSAltitude', 'below-sealevel', self::formatNum( -$val ) ); |
| 469 | + } else { |
| 470 | + $val = self::msg( 'GPSAltitude', 'above-sealevel', self::formatNum( $val ) ); |
| 471 | + } |
| 472 | + break; |
| 473 | + |
| 474 | + case 'GPSStatus': |
| 475 | + switch( $val ) { |
| 476 | + case 'A': case 'V': |
| 477 | + $val = self::msg( $tag, $val ); |
| 478 | + break; |
| 479 | + default: |
| 480 | + $val = $val; |
| 481 | + break; |
| 482 | + } |
| 483 | + break; |
| 484 | + |
| 485 | + case 'GPSMeasureMode': |
| 486 | + switch( $val ) { |
| 487 | + case 2: case 3: |
| 488 | + $val = self::msg( $tag, $val ); |
| 489 | + break; |
| 490 | + default: |
| 491 | + $val = $val; |
| 492 | + break; |
| 493 | + } |
| 494 | + break; |
| 495 | + |
| 496 | + |
| 497 | + case 'GPSTrackRef': |
| 498 | + case 'GPSImgDirectionRef': |
| 499 | + case 'GPSDestBearingRef': |
| 500 | + switch( $val ) { |
| 501 | + case 'T': case 'M': |
| 502 | + $val = self::msg( 'GPSDirection', $val ); |
| 503 | + break; |
| 504 | + default: |
| 505 | + $val = $val; |
| 506 | + break; |
| 507 | + } |
| 508 | + break; |
| 509 | + |
| 510 | + case 'GPSLatitude': |
| 511 | + case 'GPSDestLatitude': |
| 512 | + $val = self::formatCoords( $val, 'latitude' ); |
| 513 | + break; |
| 514 | + case 'GPSLongitude': |
| 515 | + case 'GPSDestLongitude': |
| 516 | + $val = self::formatCoords( $val, 'longitude' ); |
| 517 | + break; |
| 518 | + |
| 519 | + case 'GPSSpeedRef': |
| 520 | + switch( $val ) { |
| 521 | + case 'K': case 'M': case 'N': |
| 522 | + $val = self::msg( 'GPSSpeed', $val ); |
| 523 | + break; |
| 524 | + default: |
| 525 | + $val = $val; |
| 526 | + break; |
| 527 | + } |
| 528 | + break; |
| 529 | + |
| 530 | + case 'GPSDestDistanceRef': |
| 531 | + switch( $val ) { |
| 532 | + case 'K': case 'M': case 'N': |
| 533 | + $val = self::msg( 'GPSDestDistance', $val ); |
| 534 | + break; |
| 535 | + default: |
| 536 | + $val = $val; |
| 537 | + break; |
| 538 | + } |
| 539 | + break; |
| 540 | + |
| 541 | + case 'GPSDOP': |
| 542 | + // See http://en.wikipedia.org/wiki/Dilution_of_precision_(GPS) |
| 543 | + if ( $val <= 2 ) { |
| 544 | + $val = self::msg( $tag, 'excellent', self::formatNum( $val ) ); |
| 545 | + } elseif ( $val <= 5 ) { |
| 546 | + $val = self::msg( $tag, 'good', self::formatNum( $val ) ); |
| 547 | + } elseif ( $val <= 10 ) { |
| 548 | + $val = self::msg( $tag, 'moderate', self::formatNum( $val ) ); |
| 549 | + } elseif ( $val <= 20 ) { |
| 550 | + $val = self::msg( $tag, 'fair', self::formatNum( $val ) ); |
| 551 | + } else { |
| 552 | + $val = self::msg( $tag, 'poor', self::formatNum( $val ) ); |
| 553 | + } |
| 554 | + break; |
| 555 | + |
| 556 | + |
| 557 | + |
| 558 | + // This is not in the Exif standard, just a special |
| 559 | + // case for our purposes which enables wikis to wikify |
| 560 | + // the make, model and software name to link to their articles. |
| 561 | + case 'Make': |
| 562 | + case 'Model': |
| 563 | + $val = self::msg( $tag, '', $val ); |
| 564 | + break; |
| 565 | + |
| 566 | + case 'Software': |
| 567 | + if ( is_array( $val ) ) { |
| 568 | + //if its a software, version array. |
| 569 | + $val = wfMsg( 'exif-software-version-value', $val[0], $val[1] ); |
| 570 | + } else { |
| 571 | + $val = self::msg( $tag, '', $val ); |
| 572 | + } |
| 573 | + break; |
| 574 | + |
| 575 | + case 'ExposureTime': |
| 576 | + // Show the pretty fraction as well as decimal version |
| 577 | + $val = wfMsg( 'exif-exposuretime-format', |
| 578 | + self::formatFraction( $val ), self::formatNum( $val ) ); |
| 579 | + break; |
| 580 | + case 'ISOSpeedRatings': |
| 581 | + // If its = 65535 that means its at the |
| 582 | + // limit of the size of Exif::short and |
| 583 | + // is really higher. |
| 584 | + if ( $val == '65535' ) { |
| 585 | + $val = self::msg( $tag, 'overflow' ); |
| 586 | + } else { |
| 587 | + $val = self::formatNum( $val ); |
| 588 | + } |
| 589 | + break; |
| 590 | + case 'FNumber': |
| 591 | + $val = wfMsg( 'exif-fnumber-format', |
| 592 | + self::formatNum( $val ) ); |
| 593 | + break; |
| 594 | + |
| 595 | + case 'FocalLength': |
| 596 | + $val = wfMsg( 'exif-focallength-format', |
| 597 | + self::formatNum( $val ) ); |
| 598 | + break; |
| 599 | + |
| 600 | + // Do not transform fields with pure text. |
| 601 | + // For some languages the formatNum() conversion results to wrong output like |
| 602 | + // foo,bar@example,com or foo٫bar@example٫com |
| 603 | + case 'ImageDescription': |
| 604 | + case 'Artist': |
| 605 | + case 'Copyright': |
| 606 | + case 'RelatedSoundFile': |
| 607 | + case 'ImageUniqueID': |
| 608 | + case 'SpectralSensitivity': |
| 609 | + case 'GPSSatellites': |
| 610 | + case 'GPSVersionID': |
| 611 | + case 'GPSMapDatum': |
| 612 | + case 'Keywords': |
| 613 | + case 'CountryDest': |
| 614 | + case 'CountryDestCode': |
| 615 | + case 'ProvinceOrStateDest': |
| 616 | + case 'CityDest': |
| 617 | + case 'SublocationDest': |
| 618 | + case 'ObjectName': |
| 619 | + case 'SpecialInstructions': |
| 620 | + case 'Headline': |
| 621 | + case 'Credit': |
| 622 | + case 'Source': |
| 623 | + case 'EditStatus': |
| 624 | + case 'Urgency': |
| 625 | + case 'FixtureIdentifier': |
| 626 | + case 'LocationDest': |
| 627 | + case 'LocationDestCode': |
| 628 | + case 'Contact': |
| 629 | + case 'Writer': |
| 630 | + case 'JPEGFileComment': |
| 631 | + case 'iimCategory': |
| 632 | + case 'iimSupplementalCategory': |
| 633 | + case 'OriginalTransmissionRef': |
| 634 | + case 'Identifier': |
| 635 | + case 'dc-contributor': |
| 636 | + case 'dc-coverage': |
| 637 | + case 'dc-publisher': |
| 638 | + case 'dc-relation': |
| 639 | + case 'dc-rights': |
| 640 | + case 'dc-source': |
| 641 | + case 'dc-type': |
| 642 | + case 'Lens': |
| 643 | + case 'SerialNumber': |
| 644 | + case 'CameraOwnerName': |
| 645 | + case 'Label': |
| 646 | + case 'Nickname': |
| 647 | + case 'RightsCertificate': |
| 648 | + case 'CopyrightOwner': |
| 649 | + case 'UsageTerms': |
| 650 | + case 'WebStatement': |
| 651 | + case 'OriginalDocumentID': |
| 652 | + case 'LicenseUrl': |
| 653 | + case 'MorePermissionsUrl': |
| 654 | + case 'AttributionUrl': |
| 655 | + case 'PreferredAttributionName': |
| 656 | + |
| 657 | + $val = htmlspecialchars( $val ); |
| 658 | + break; |
| 659 | + |
| 660 | + case 'ObjectCycle': |
| 661 | + switch ( $val ) { |
| 662 | + case 'a': case 'p': case 'b': |
| 663 | + $val = self::msg( $tag, $val ); |
| 664 | + break; |
| 665 | + default: |
| 666 | + $val = htmlspecialchars( $val ); |
| 667 | + break; |
| 668 | + } |
| 669 | + break; |
| 670 | + case 'Copyrighted': |
| 671 | + switch( $val ) { |
| 672 | + case 'True': case 'False': |
| 673 | + $val = self::msg( $tag, $val ); |
| 674 | + break; |
| 675 | + } |
| 676 | + break; |
| 677 | + case 'Rating': |
| 678 | + if ( $val == '-1' ) { |
| 679 | + $val = self::msg( $tag, 'rejected' ); |
| 680 | + } else { |
| 681 | + $val = self::formatNum( $val ); |
| 682 | + } |
| 683 | + break; |
| 684 | + |
| 685 | + case 'LanguageCode': |
| 686 | + $lang = $wgLang->getLanguageName( strtolower( $val ) ); |
| 687 | + if ($lang) { |
| 688 | + $val = htmlspecialchars( $lang ); |
| 689 | + } else { |
| 690 | + $val = htmlspecialchars( $val ); |
| 691 | + } |
| 692 | + break; |
| 693 | + |
| 694 | + default: |
| 695 | + $val = self::formatNum( $val ); |
| 696 | + break; |
| 697 | + } |
| 698 | + } |
| 699 | + // End formatting values, start flattening arrays. |
| 700 | + $vals = self::flattenArray( $vals, $type ); |
| 701 | + |
| 702 | + } |
| 703 | + return $tags; |
| 704 | + } |
| 705 | + |
| 706 | + /** |
| 707 | + * A function to collapse multivalued tags into a single value. |
| 708 | + * This turns an array of (for example) authors into a bulleted list. |
| 709 | + * |
| 710 | + * This is public on the basis it might be useful outside of this class. |
| 711 | + * |
| 712 | + * @param $vals Array array of values |
| 713 | + * @param $type Type of array (either lang, ul, ol). |
| 714 | + * lang = language assoc array with keys being the lang code |
| 715 | + * ul = unorded list, ol = ordered list |
| 716 | + * type can also come from the '_type' member of $vals. |
| 717 | + * @return String single value (in wiki-syntax). |
| 718 | + */ |
| 719 | + public static function flattenArray( $vals, $type = 'ul' ) { |
| 720 | + |
| 721 | + if ( isset( $vals['_type'] ) ) { |
| 722 | + $type = $vals['_type']; |
| 723 | + } |
| 724 | + |
| 725 | + if ( !is_array( $vals ) ) { |
| 726 | + return $vals; // do nothing if not an array; |
| 727 | + } |
| 728 | + elseif ( count( $vals ) === 1 && $type !== 'lang' ) { |
| 729 | + return $vals[0]; |
| 730 | + } |
| 731 | + elseif ( count( $vals ) === 0 ) { |
| 732 | + return ""; // paranoia. This should never happen |
| 733 | + wfDebug( __METHOD__ . ' metadata array with 0 elements!' ); |
| 734 | + } |
| 735 | + /* Fixme: This should hide some of the list entries if there are |
| 736 | + * say more than four. Especially if a field is translated into 20 |
| 737 | + * languages, we don't want to show them all by default |
| 738 | + */ |
| 739 | + else { |
| 740 | + switch( $type ) { |
| 741 | + case 'lang': |
| 742 | + // fixme incomplete |
| 743 | + // should place x-default, content language, user language |
| 744 | + // first. then the others, hidden by defualt. |
| 745 | + // also should use much better markup. |
| 746 | + $content = ""; |
| 747 | + if ( $vals['x-default'] ) { |
| 748 | + $content .= "\n*" . $vals['x-default']; |
| 749 | + unset( $vals['x-default'] ); |
| 750 | + } |
| 751 | + foreach ( $vals as $lang => $item ) { |
| 752 | + global $wgContLang; |
| 753 | + $content .= "\n*<span lang=\"$lang\">" |
| 754 | + . "'''$lang''' $item</span>"; |
| 755 | + } |
| 756 | + return $content; |
| 757 | + case 'ol': |
| 758 | + return "<ol><li>" . implode( "</li>\n<li>", $vals ) . '</li></ol>'; |
| 759 | + case 'ul': |
| 760 | + default: |
| 761 | + return "<ul><li>" . implode( "</li>\n<li>", $vals ) . '</li></ul>'; |
| 762 | + } |
| 763 | + } |
| 764 | + } |
| 765 | + /** |
| 766 | + * Convenience function for getFormattedData() |
| 767 | + * |
| 768 | + * @private |
| 769 | + * |
| 770 | + * @param $tag String: the tag name to pass on |
| 771 | + * @param $val String: the value of the tag |
| 772 | + * @param $arg String: an argument to pass ($1) |
| 773 | + * @return string A wfMsg of "exif-$tag-$val" in lower case |
| 774 | + */ |
| 775 | + static function msg( $tag, $val, $arg = null ) { |
| 776 | + global $wgContLang; |
| 777 | + |
| 778 | + if ($val === '') |
| 779 | + $val = 'value'; |
| 780 | + return wfMsg( $wgContLang->lc( "exif-$tag-$val" ), $arg ); |
| 781 | + } |
| 782 | + |
| 783 | + /** |
| 784 | + * Format a number, convert numbers from fractions into floating point |
| 785 | + * numbers, joins arrays of numbers with commas. |
| 786 | + * |
| 787 | + * @private |
| 788 | + * |
| 789 | + * @param $num Mixed: the value to format |
| 790 | + * @return mixed A floating point number or whatever we were fed |
| 791 | + */ |
| 792 | + static function formatNum( $num ) { |
| 793 | + global $wgLang; |
| 794 | + $m = array(); |
| 795 | + if( is_array($num) ) { |
| 796 | + $out = array(); |
| 797 | + foreach( $num as $number ) { |
| 798 | + $out[] = self::formatNum($number); |
| 799 | + } |
| 800 | + return $wgLang->commaList( $out ); |
| 801 | + } |
| 802 | + if ( preg_match( '/^(-?\d+)\/(\d+)$/', $num, $m ) ) |
| 803 | + return $wgLang->formatNum( $m[2] != 0 ? $m[1] / $m[2] : $num ); |
| 804 | + else |
| 805 | + return $wgLang->formatNum( $num ); |
| 806 | + } |
| 807 | + |
| 808 | + /** |
| 809 | + * Format a rational number, reducing fractions |
| 810 | + * |
| 811 | + * @private |
| 812 | + * |
| 813 | + * @param $num Mixed: the value to format |
| 814 | + * @return mixed A floating point number or whatever we were fed |
| 815 | + */ |
| 816 | + static function formatFraction( $num ) { |
| 817 | + $m = array(); |
| 818 | + if ( preg_match( '/^(-?\d+)\/(\d+)$/', $num, $m ) ) { |
| 819 | + $numerator = intval( $m[1] ); |
| 820 | + $denominator = intval( $m[2] ); |
| 821 | + $gcd = self::gcd( abs( $numerator ), $denominator ); |
| 822 | + if( $gcd != 0 ) { |
| 823 | + // 0 shouldn't happen! ;) |
| 824 | + return self::formatNum( $numerator / $gcd ) . '/' . self::formatNum( $denominator / $gcd ); |
| 825 | + } |
| 826 | + } |
| 827 | + return self::formatNum( $num ); |
| 828 | + } |
| 829 | + |
| 830 | + /** |
| 831 | + * Calculate the greatest common divisor of two integers. |
| 832 | + * |
| 833 | + * @param $a Integer: Numerator |
| 834 | + * @param $b Integer: Denominator |
| 835 | + * @return int |
| 836 | + * @private |
| 837 | + */ |
| 838 | + static function gcd( $a, $b ) { |
| 839 | + /* |
| 840 | + // http://en.wikipedia.org/wiki/Euclidean_algorithm |
| 841 | + // Recursive form would be: |
| 842 | + if( $b == 0 ) |
| 843 | + return $a; |
| 844 | + else |
| 845 | + return gcd( $b, $a % $b ); |
| 846 | + */ |
| 847 | + while( $b != 0 ) { |
| 848 | + $remainder = $a % $b; |
| 849 | + |
| 850 | + // tail recursion... |
| 851 | + $a = $b; |
| 852 | + $b = $remainder; |
| 853 | + } |
| 854 | + return $a; |
| 855 | + } |
| 856 | + |
| 857 | + /** |
| 858 | + * Format a coordinate value, convert numbers from floating point |
| 859 | + * into degree minute second representation. |
| 860 | + * |
| 861 | + * @private |
| 862 | + * |
| 863 | + * @param $coords Array: degrees, minutes and seconds |
| 864 | + * @param $type String: latitude or longitude (for if its a NWS or E) |
| 865 | + * @return mixed A floating point number or whatever we were fed |
| 866 | + */ |
| 867 | + static function formatCoords( $coord, $type ) { |
| 868 | + $ref = ''; |
| 869 | + if ( $coord < 0 ) { |
| 870 | + $nCoord = -$coord; |
| 871 | + if ( $type === 'latitude' ) { |
| 872 | + $ref = 'S'; |
| 873 | + } |
| 874 | + elseif ( $type === 'longitude' ) { |
| 875 | + $ref = 'W'; |
| 876 | + } |
| 877 | + } |
| 878 | + else { |
| 879 | + $nCoord = $coord; |
| 880 | + if ( $type === 'latitude' ) { |
| 881 | + $ref = 'N'; |
| 882 | + } |
| 883 | + elseif ( $type === 'longitude' ) { |
| 884 | + $ref = 'E'; |
| 885 | + } |
| 886 | + } |
| 887 | + |
| 888 | + $deg = floor( $nCoord ); |
| 889 | + $min = floor( ( $nCoord - $deg ) * 60.0 ); |
| 890 | + $sec = round( ( ( $nCoord - $deg ) - $min / 60 ) * 3600, 2 ); |
| 891 | + |
| 892 | + $deg = self::formatNum( $deg ); |
| 893 | + $min = self::formatNum( $min ); |
| 894 | + $sec = self::formatNum( $sec ); |
| 895 | + |
| 896 | + return wfMsg( 'exif-coordinate-format', $deg, $min, $sec, $ref, $coord ); |
| 897 | + } |
| 898 | + |
| 899 | +} |
| 900 | + |
| 901 | +/** For compatability with old FormatExif class |
| 902 | + * which some extensions use. |
| 903 | + * |
| 904 | + *@deprected |
| 905 | + * |
| 906 | +**/ |
| 907 | +class FormatExif { |
| 908 | + var $meta; |
| 909 | + function FormatExif ( $meta ) { |
| 910 | + wfDeprecated(__METHOD__); |
| 911 | + $this->meta = $meta; |
| 912 | + } |
| 913 | + function getFormattedData ( ) { |
| 914 | + return FormatMetadata::getFormattedData( $this->meta ); |
| 915 | + } |
| 916 | + |
| 917 | +} |
Property changes on: branches/img_metadata/phase3/includes/media/FormatMetadata.php |
___________________________________________________________________ |
Added: svn:eol-style |
1 | 918 | + native |