Index: branches/img_metadata/phase3/maintenance/language/messages.inc |
— | — | @@ -2923,6 +2923,13 @@ |
2924 | 2924 | 'exif-gpsdestdistance-m', |
2925 | 2925 | 'exif-gpsdestdistance-n', |
2926 | 2926 | ), |
| 2927 | + 'exif-gdop' => array( |
| 2928 | + 'exif-gpsdop-excellent', |
| 2929 | + 'exif-gpsdop-good', |
| 2930 | + 'exif-gpsdop-moderate', |
| 2931 | + 'exif-gpsdop-fair', |
| 2932 | + 'exif-gpsdop-poor', |
| 2933 | + ), |
2927 | 2934 | 'exif-objectcycle' => array( |
2928 | 2935 | 'exif-objectcycle-a', |
2929 | 2936 | 'exif-objectcycle-p', |
— | — | @@ -2932,6 +2939,10 @@ |
2933 | 2940 | 'exif-gpsdirection-t', |
2934 | 2941 | 'exif-gpsdirection-m', |
2935 | 2942 | ), |
| 2943 | + 'exif-ycbcrpositioning' => array( |
| 2944 | + 'exif-ycbcrpositioning-1', |
| 2945 | + 'exif-ycbcrpositioning-2', |
| 2946 | + ), |
2936 | 2947 | 'edit-externally' => array( |
2937 | 2948 | 'edit-externally', |
2938 | 2949 | 'edit-externally-help', |
Index: branches/img_metadata/phase3/maintenance/language/messageTypes.inc |
— | — | @@ -599,6 +599,13 @@ |
600 | 600 | 'exif-gpsdestdistance-n', |
601 | 601 | 'exif-gpsdirection-t', |
602 | 602 | 'exif-gpsdirection-m', |
| 603 | + 'exif-gpsdop-excellent', |
| 604 | + 'exif-gpsdop-good', |
| 605 | + 'exif-gpsdop-moderate', |
| 606 | + 'exif-gpsdop-fair', |
| 607 | + 'exif-gpsdop-poor', |
| 608 | + 'exif-ycbcrpositioning-1', |
| 609 | + 'exif-ycbcrpositioning-2', |
603 | 610 | //non-exif metadata that is still image metadata |
604 | 611 | 'exif-jpegfilecomment', |
605 | 612 | 'exif-keywords', |
Index: branches/img_metadata/phase3/includes/Exif.php |
— | — | @@ -24,7 +24,7 @@ |
25 | 25 | */ |
26 | 26 | |
27 | 27 | /** |
28 | | - * @todo document (e.g. one-sentence class-overview description) |
| 28 | + * Class to extract and validate Exif data from jpeg (and possibly tiff) files. |
29 | 29 | * @ingroup Media |
30 | 30 | */ |
31 | 31 | class Exif { |
— | — | @@ -376,7 +376,7 @@ |
377 | 377 | |
378 | 378 | //GPSVersion(ID) is treated as the wrong type by php exif support. |
379 | 379 | //Go through each byte turning it into a version string. |
380 | | - //For example: "\x00\x00\x02\x02" -> "2.2.0.0" |
| 380 | + //For example: "\x02\x02\x00\x00" -> "2.2.0.0" |
381 | 381 | |
382 | 382 | //Also change exif tag name from GPSVersion (what php exif thinks it is) |
383 | 383 | //to GPSVersionID (what the exif standard thinks it is). |
— | — | @@ -388,7 +388,7 @@ |
389 | 389 | if ( $i !== 0 ) { |
390 | 390 | $newVal .= '.'; |
391 | 391 | } |
392 | | - $newVal .= ord( substr($val, strlen($val) - $i - 1, 1) ); |
| 392 | + $newVal .= ord( substr($val, $i, 1) ); |
393 | 393 | } |
394 | 394 | $this->mFilteredExifData['GPSVersionID'] = $newVal; |
395 | 395 | unset( $this->mFilteredExifData['GPSVersion'] ); |
— | — | @@ -396,12 +396,10 @@ |
397 | 397 | |
398 | 398 | } |
399 | 399 | /** |
400 | | - * do userComment and similar. pg. 34 of exif standard. |
| 400 | + * Do userComment tags and similar. See pg. 34 of exif standard. |
401 | 401 | * basically first 8 bytes is charset, rest is value. |
402 | | - * Needs more testing, as its unclear from exif standard if they mean |
403 | | - * utf-8, utf-16, or something else by 'unicode'. |
| 402 | + * This has not been tested on any shift-JIS strings. |
404 | 403 | * @param $prop String prop name. |
405 | | - * @todo this is in need of testing. |
406 | 404 | */ |
407 | 405 | private function charCodeString ( $prop ) { |
408 | 406 | if ( isset( $this->mFilteredExifData[$prop] ) ) { |
— | — | @@ -421,11 +419,27 @@ |
422 | 420 | switch ($charCode) { |
423 | 421 | case "\x4A\x49\x53\x00\x00\x00\x00\x00": |
424 | 422 | //JIS |
425 | | - $val = iconv("Shift-JIS", "UTF-8//IGNORE", $val); |
| 423 | + $charset = "Shift-JIS"; |
426 | 424 | break; |
427 | | - default: //unicode or undefined. leave as is. |
| 425 | + case "UNICODE\x00": |
| 426 | + $charset = "UTF-16"; |
428 | 427 | break; |
| 428 | + default: //ascii or undefined. |
| 429 | + $charset = ""; |
| 430 | + break; |
429 | 431 | } |
| 432 | + // This could possibly check to see if iconv is really installed |
| 433 | + // or if we're using the compatability wraper in globalFunctions.php |
| 434 | + if ($charset) { |
| 435 | + $val = iconv($charset, 'UTF-8//IGNORE', $val); |
| 436 | + } else { |
| 437 | + // if valid utf-8, assume that, otherwise assume windows-1252 |
| 438 | + $valCopy = $val; |
| 439 | + UtfNormal::quickIsNFCVerify( $valCopy ); //validates $valCopy. |
| 440 | + if ( $valCopy !== $val ) { |
| 441 | + $val = iconv('Windows-1252', 'UTF-8//IGNORE', $val); |
| 442 | + } |
| 443 | + } |
430 | 444 | |
431 | 445 | //trim and check to make sure not only whitespace. |
432 | 446 | $val = trim($val); |
— | — | @@ -437,7 +451,7 @@ |
438 | 452 | } |
439 | 453 | |
440 | 454 | //all's good. |
441 | | - $this->mFilteredData[$prop] = $val; |
| 455 | + $this->mFilteredExifData[$prop] = $val; |
442 | 456 | } |
443 | 457 | } |
444 | 458 | /** |
— | — | @@ -765,7 +779,10 @@ |
766 | 780 | } |
767 | 781 | |
768 | 782 | /** |
769 | | - * @todo document (e.g. one-sentence class-overview description) |
| 783 | + * Format Image metadata values into a human readable form. |
| 784 | + * Note despite the name, this formats more than just exif |
| 785 | + * values. |
| 786 | + * @todo Perhaps rename to FormatMetadata |
770 | 787 | * @ingroup Media |
771 | 788 | */ |
772 | 789 | class FormatExif { |
— | — | @@ -818,6 +835,34 @@ |
819 | 836 | } else { |
820 | 837 | $type = 'ul'; // default unorcdered list. |
821 | 838 | } |
| 839 | + |
| 840 | + //This is done differently as the tag is an array. |
| 841 | + if ($tag == 'GPSTimeStamp' && count($vals) === 3) { |
| 842 | + //hour min sec array |
| 843 | + |
| 844 | + $h = explode('/', $vals[0]); |
| 845 | + $m = explode('/', $vals[1]); |
| 846 | + $s = explode('/', $vals[2]); |
| 847 | + |
| 848 | + // this should already be validated |
| 849 | + // when loaded from file, but it could |
| 850 | + // come from a foreign repo, so be |
| 851 | + // paranoid. |
| 852 | + if ( !isset($h[1]) |
| 853 | + || !isset($m[1]) |
| 854 | + || !isset($s[1]) |
| 855 | + || $h[1] == 0 |
| 856 | + || $m[1] == 0 |
| 857 | + || $s[1] == 0 |
| 858 | + ) { |
| 859 | + continue; |
| 860 | + } |
| 861 | + $tags[$tag] = intval( $h[0] / $h[1] ) |
| 862 | + . ':' . intval( $m[0] / $m[1] ) |
| 863 | + . ':' . str_pad( intval( $s[0] / $s[1] ), 2, '0', STR_PAD_LEFT ); |
| 864 | + continue; |
| 865 | + } |
| 866 | + |
822 | 867 | |
823 | 868 | foreach ( $vals as &$val ) { |
824 | 869 | |
— | — | @@ -867,7 +912,17 @@ |
868 | 913 | break; |
869 | 914 | |
870 | 915 | // TODO: YCbCrSubSampling |
871 | | - // TODO: YCbCrPositioning |
| 916 | + case 'YCbCrPositioning': |
| 917 | + switch ( $val ) { |
| 918 | + case 1: |
| 919 | + case 2: |
| 920 | + $val = $this->msg( $tag, $val ); |
| 921 | + break; |
| 922 | + default: |
| 923 | + $val = $val; |
| 924 | + break; |
| 925 | + } |
| 926 | + break; |
872 | 927 | |
873 | 928 | case 'XResolution': |
874 | 929 | case 'YResolution': |
— | — | @@ -914,12 +969,21 @@ |
915 | 970 | case 'DateTime': |
916 | 971 | case 'DateTimeOriginal': |
917 | 972 | case 'DateTimeDigitized': |
918 | | - if ( $val == '0000:00:00 00:00:00' ) { |
| 973 | + case 'GPSDateStamp': |
| 974 | + if ( $val == '0000:00:00 00:00:00' || $val == ' : : : : ' ) { |
919 | 975 | $val = wfMsg( 'exif-unknowndate' ); |
920 | 976 | } elseif ( preg_match( '/^(?:\d{4}):(?:\d\d):(?:\d\d) (?:\d\d):(?:\d\d):(?:\d\d)$/', $val ) ) { |
921 | 977 | $val = $wgLang->timeanddate( wfTimestamp( TS_MW, $val ) ); |
922 | 978 | } elseif ( preg_match( '/^(?:\d{4}):(?:\d\d):(?:\d\d)$/', $val ) ) { |
923 | | - $val = $wgLang->date( wfTimestamp( TS_MW, $val . ' 00:00:00' ) ); |
| 979 | + // avoid using wfTimestamp here for the pre-1902 photos |
| 980 | + // due to reverse y2k38 bug. $wgLang->timeanddate() is also |
| 981 | + // broken on dates from before 1902 so don't worry about it |
| 982 | + // in the above case (not to mention that most photos from the |
| 983 | + // 1800's don't have a time recorded anyways). |
| 984 | + $val = $wgLang->date( substr( $val, 0, 4 ) |
| 985 | + . substr( $val, 5, 2 ) |
| 986 | + . substr( $val, 8, 2 ) |
| 987 | + . '000000' ); |
924 | 988 | } |
925 | 989 | // else it will just output $val without formatting it. |
926 | 990 | break; |
— | — | @@ -1197,10 +1261,6 @@ |
1198 | 1262 | } |
1199 | 1263 | break; |
1200 | 1264 | |
1201 | | - case 'GPSDateStamp': |
1202 | | - $val = $wgLang->date( substr( $val, 0, 4 ) . substr( $val, 5, 2 ) . substr( $val, 8, 2 ) . '000000' ); |
1203 | | - break; |
1204 | | - |
1205 | 1265 | case 'GPSLatitude': |
1206 | 1266 | case 'GPSDestLatitude': |
1207 | 1267 | $val = $this->formatCoords( $val, 'latitude' ); |
— | — | @@ -1213,10 +1273,10 @@ |
1214 | 1274 | case 'GPSSpeedRef': |
1215 | 1275 | switch( $val ) { |
1216 | 1276 | case 'K': case 'M': case 'N': |
1217 | | - $tags[$tag] = $this->msg( 'GPSSpeed', $val ); |
| 1277 | + $val = $this->msg( 'GPSSpeed', $val ); |
1218 | 1278 | break; |
1219 | 1279 | default: |
1220 | | - $tags[$tag] = $val; |
| 1280 | + $val = $val; |
1221 | 1281 | break; |
1222 | 1282 | } |
1223 | 1283 | break; |
— | — | @@ -1224,15 +1284,31 @@ |
1225 | 1285 | case 'GPSDestDistanceRef': |
1226 | 1286 | switch( $val ) { |
1227 | 1287 | case 'K': case 'M': case 'N': |
1228 | | - $tags[$tag] = $this->msg( 'GPSDestDistance', $val ); |
| 1288 | + $val = $this->msg( 'GPSDestDistance', $val ); |
1229 | 1289 | break; |
1230 | 1290 | default: |
1231 | | - $tags[$tag] = $val; |
| 1291 | + $val = $val; |
1232 | 1292 | break; |
1233 | 1293 | } |
1234 | 1294 | break; |
1235 | 1295 | |
| 1296 | + case 'GPSDOP': |
| 1297 | + // See http://en.wikipedia.org/wiki/Dilution_of_precision_(GPS) |
| 1298 | + if ( $val <= 2 ) { |
| 1299 | + $val = $this->msg( $tag, 'excellent', $this->formatNum( $val ) ); |
| 1300 | + } elseif ( $val <= 5 ) { |
| 1301 | + $val = $this->msg( $tag, 'good', $this->formatNum( $val ) ); |
| 1302 | + } elseif ( $val <= 10 ) { |
| 1303 | + $val = $this->msg( $tag, 'moderate', $this->formatNum( $val ) ); |
| 1304 | + } elseif ( $val <= 20 ) { |
| 1305 | + $val = $this->msg( $tag, 'fair', $this->formatNum( $val ) ); |
| 1306 | + } else { |
| 1307 | + $val = $this->msg( $tag, 'poor', $this->formatNum( $val ) ); |
| 1308 | + } |
| 1309 | + break; |
1236 | 1310 | |
| 1311 | + |
| 1312 | + |
1237 | 1313 | // This is not in the Exif standard, just a special |
1238 | 1314 | // case for our purposes which enables wikis to wikify |
1239 | 1315 | // the make, model and software name to link to their articles. |
— | — | @@ -1450,8 +1526,8 @@ |
1451 | 1527 | /** |
1452 | 1528 | * Calculate the greatest common divisor of two integers. |
1453 | 1529 | * |
1454 | | - * @param $a Integer: FIXME |
1455 | | - * @param $b Integer: FIXME |
| 1530 | + * @param $a Integer: Numerator |
| 1531 | + * @param $b Integer: Denominator |
1456 | 1532 | * @return int |
1457 | 1533 | * @private |
1458 | 1534 | */ |
Index: branches/img_metadata/phase3/includes/media/BitmapMetadataHandler.php |
— | — | @@ -8,6 +8,10 @@ |
9 | 9 | @todo other image formats. |
10 | 10 | */ |
11 | 11 | class BitmapMetadataHandler { |
| 12 | + const MAX_JPEG_SEGMENTS = 200; |
| 13 | + // the max segment is a sanity check. |
| 14 | + // A jpeg file should never even remotely have |
| 15 | + // that many segments. Your average file has about 10. |
12 | 16 | private $filetype; |
13 | 17 | private $filename; |
14 | 18 | private $metadata = Array(); |
— | — | @@ -36,9 +40,10 @@ |
37 | 41 | */ |
38 | 42 | function jpegSegmentSplitter () { |
39 | 43 | $filename = $this->filename; |
| 44 | + $segmentCount = 0; |
40 | 45 | if ( $this->filetype !== 'image/jpeg' ) throw new MWException( "jpegSegmentSplitter called on non-jpeg" ); |
41 | 46 | |
42 | | - $segments = Array( 'XMP_ext' => Array() ); |
| 47 | + $segments = Array( 'XMP_ext' => array(), 'COM' => array() ); |
43 | 48 | |
44 | 49 | if ( !$filename ) throw new MWException( "No filename specified for BitmapMetadataHandler" ); |
45 | 50 | if ( !file_exists( $filename ) || is_dir( $filename ) ) throw new MWException( "Invalid file $filename passed to BitmapMetadataHandler" ); |
— | — | @@ -51,6 +56,11 @@ |
52 | 57 | if ( $buffer !== "\xFF\xD8" ) throw new MWException( "Not a jpeg, no SOI" ); |
53 | 58 | while ( !feof( $fh ) ) { |
54 | 59 | $buffer = fread( $fh, 1 ); |
| 60 | + $segmentCount++; |
| 61 | + if ( $segmentCount > self::MAX_JPEG_SEGMENTS ) { |
| 62 | + // this is just a sanity check |
| 63 | + throw new MWException('Too many jpeg segments. Aborting'); |
| 64 | + } |
55 | 65 | if ( $buffer !== "\xFF" ) { |
56 | 66 | throw new MWException( "Error reading jpeg file marker" ); |
57 | 67 | } |
— | — | @@ -58,7 +68,17 @@ |
59 | 69 | $buffer = fread( $fh, 1 ); |
60 | 70 | if ( $buffer === "\xFE" ) { |
61 | 71 | // COM section -- file comment |
62 | | - $segments["COM"] = self::jpegExtractMarker( $fh ); |
| 72 | + // First see if valid utf-8, |
| 73 | + // if not try to convert it to windows-1252. |
| 74 | + $com = $oldCom = trim( self::jpegExtractMarker( $fh ) ); |
| 75 | + UtfNormal::quickIsNFCVerify( $com ); |
| 76 | + //turns $com to valid utf-8. |
| 77 | + //thus if no change, its utf-8, otherwise its something else. |
| 78 | + if ( $com !== $oldCom ) { |
| 79 | + $oldCom = iconv( 'windows-1252', 'UTF-8//IGNORE', $oldCom ); |
| 80 | + } |
| 81 | + $segments["COM"][] = $oldCom; |
| 82 | + |
63 | 83 | } elseif ( $buffer === "\xE1" ) { |
64 | 84 | // APP1 section (Exif, XMP, and XMP extended) |
65 | 85 | $temp = self::jpegExtractMarker( $fh ); |
— | — | @@ -67,8 +87,6 @@ |
68 | 88 | if ( substr( $temp, 0, 29 ) === "http://ns.adobe.com/xap/1.0/\x00" ) { |
69 | 89 | $segments["XMP"] = $temp; |
70 | 90 | } elseif ( substr( $temp, 0, 35 ) === "http://ns.adobe.com/xmp/extension/\x00" ) { |
71 | | - // fixme - put some limit on this? what if someone |
72 | | - // uploaded a file with 100mb worth of metadata. |
73 | 91 | $segments["XMP_ext"][] = $temp; |
74 | 92 | } |
75 | 93 | } elseif ( $buffer === "\xED" ) { |
— | — | @@ -288,7 +306,7 @@ |
289 | 307 | $meta->getExif(); |
290 | 308 | $seg = Array(); |
291 | 309 | $seg = $meta->JpegSegmentSplitter(); |
292 | | - if ( isset( $seg['COM'] ) ) { |
| 310 | + if ( isset( $seg['COM'] ) && isset( $seg['COM'][0] ) ) { |
293 | 311 | $meta->addMetadata( Array( 'JPEGFileComment' => $seg['COM'] ), 'file-comment' ); |
294 | 312 | } |
295 | 313 | if ( isset( $seg['PSIR'] ) ) { |
Index: branches/img_metadata/phase3/includes/media/IPTC.php |
— | — | @@ -25,14 +25,24 @@ |
26 | 26 | return $data; |
27 | 27 | } |
28 | 28 | |
29 | | - $c = '?'; |
| 29 | + $c = ''; |
30 | 30 | //charset info contained in tag 1:90. |
31 | 31 | if (isset($parsed['1#090']) && isset($parsed['1#090'][0])) { |
32 | 32 | $c = self::getCharset($parsed['1#090'][0]); |
| 33 | + if ($c === false) { |
| 34 | + //Unknown charset. refuse to parse. |
| 35 | + //note: There is a different between |
| 36 | + //unknown and no charset specified. |
| 37 | + return array(); |
| 38 | + } |
33 | 39 | unset( $parsed['1#090'] ); |
34 | 40 | } |
35 | 41 | |
36 | 42 | foreach ( $parsed as $tag => $val ) { |
| 43 | + if ( isset( $val[0] ) && trim($val[0]) == '' ) { |
| 44 | + wfDebugLog('iptc', "IPTC tag $tag had only whitespace as its value."); |
| 45 | + continue; |
| 46 | + } |
37 | 47 | switch( $tag ) { |
38 | 48 | case '2#120': /*IPTC caption. mapped with exif ImageDescription*/ |
39 | 49 | $data['ImageDescription'] = self::convIPTC( $val, $c ); |
— | — | @@ -265,10 +275,11 @@ |
266 | 276 | } |
267 | 277 | |
268 | 278 | if ( substr($date, 0, 4) < "1902" ) { |
269 | | - //We run into the reverse y2k38 bug. |
270 | | - //might do something better later... |
271 | | - wfDebugLog( 'iptc', "IPTC: date is too early (reverse y2k38 bug) ( $date )"); |
272 | | - return null; |
| 279 | + // We run into the reverse y2k38 bug. |
| 280 | + // Avoid using wfTimestamp as it doesn't work for pre-1902 dates |
| 281 | + return substr( $date, 0, 4 ) . ':' |
| 282 | + . substr( $date, 4, 2 ) . ':' |
| 283 | + . substr( $date, 6, 2 ); |
273 | 284 | } |
274 | 285 | |
275 | 286 | $unixTS = wfTimestamp( TS_UNIX, $date . substr( $time, 0, 6 )); |
— | — | @@ -300,10 +311,10 @@ |
301 | 312 | foreach ($data as &$val) { |
302 | 313 | $val = self::convIPTCHelper( $val, $charset ); |
303 | 314 | } |
304 | | - |
305 | 315 | } else { |
306 | 316 | $data = self::convIPTCHelper ( $data, $charset ); |
307 | 317 | } |
| 318 | + |
308 | 319 | return $data; |
309 | 320 | } |
310 | 321 | /** |
— | — | @@ -312,21 +323,21 @@ |
313 | 324 | * @param $charset String: The charset |
314 | 325 | */ |
315 | 326 | private static function convIPTCHelper ( $data, $charset ) { |
316 | | - if ( $charset !== '?' ) { |
| 327 | + if ( $charset ) { |
317 | 328 | $data = iconv($charset, "UTF-8//IGNORE", $data); |
318 | 329 | if ($data === false) { |
319 | 330 | $data = ""; |
320 | | - wfDebug(__METHOD__ . " Error converting iptc data charset $charset to utf-8"); |
| 331 | + wfDebugLog('iptc', __METHOD__ . " Error converting iptc data charset $charset to utf-8"); |
321 | 332 | } |
322 | 333 | } else { |
323 | | - //treat as utf-8 if is valid utf-8. otherwise pretend its iso-8859-1 |
| 334 | + //treat as utf-8 if is valid utf-8. otherwise pretend its windows-1252 |
324 | 335 | // most of the time if there is no 1:90 tag, it is either ascii, latin1, or utf-8 |
325 | 336 | $oldData = $data; |
326 | 337 | UtfNormal::quickIsNFCVerify( $data ); //make $data valid utf-8 |
327 | | - if ($data === $oldData) return $data; |
328 | | - else return self::convIPTCHelper ( $oldData, 'ISO-8859-1' ); //should this be windows-1252? |
| 338 | + if ($data === $oldData) return $data; //if validation didn't change $data |
| 339 | + else return self::convIPTCHelper ( $oldData, 'Windows-1252' ); |
329 | 340 | } |
330 | | - return $data; |
| 341 | + return trim( $data ); |
331 | 342 | } |
332 | 343 | |
333 | 344 | /** |
— | — | @@ -455,9 +466,9 @@ |
456 | 467 | $c = 'CSN_369103'; |
457 | 468 | break; |
458 | 469 | default: |
459 | | - wfDebug(__METHOD__ . 'Unkown charset in iptc 1:90: ' . bin2hex( $tag ) ); |
460 | | - //at this point should we give up and refuse to parse iptc? |
461 | | - $c = '?'; |
| 470 | + wfDebugLog('iptc', __METHOD__ . 'Unkown charset in iptc 1:90: ' . bin2hex( $tag ) ); |
| 471 | + //at this point just give up and refuse to parse iptc? |
| 472 | + $c = false; |
462 | 473 | } |
463 | 474 | return $c; |
464 | 475 | } |
Index: branches/img_metadata/phase3/languages/messages/MessagesEn.php |
— | — | @@ -3634,7 +3634,13 @@ |
3635 | 3635 | * exposuretime |
3636 | 3636 | * fnumber |
3637 | 3637 | * isospeedratings |
3638 | | -* focallength', |
| 3638 | +* focallength |
| 3639 | +* artist |
| 3640 | +* copyright |
| 3641 | +* imagedescription |
| 3642 | +* gpslatitude |
| 3643 | +* gpslongitude |
| 3644 | +* gpsaltitude', |
3639 | 3645 | |
3640 | 3646 | # EXIF tags |
3641 | 3647 | 'exif-imagewidth' => 'Width', |
— | — | @@ -3938,8 +3944,8 @@ |
3939 | 3945 | 'exif-gpslongitude-w' => 'West longitude', |
3940 | 3946 | |
3941 | 3947 | # Pseudotags used for GPSAltitude |
3942 | | -'exif-gpsaltitude-above-sealevel' => '$1 meters above sea level', |
3943 | | -'exif-gpsaltitude-below-sealevel' => '$1 meters below sea level', |
| 3948 | +'exif-gpsaltitude-above-sealevel' => '$1 {{plural:$1|meters|meter}} above sea level', |
| 3949 | +'exif-gpsaltitude-below-sealevel' => '$1 {{plural:$1|meters|meter}} below sea level', |
3944 | 3950 | |
3945 | 3951 | 'exif-gpsstatus-a' => 'Measurement in progress', |
3946 | 3952 | 'exif-gpsstatus-v' => 'Measurement interoperability', |
— | — | @@ -3961,10 +3967,19 @@ |
3962 | 3968 | 'exif-gpsdirection-t' => 'True direction', |
3963 | 3969 | 'exif-gpsdirection-m' => 'Magnetic direction', |
3964 | 3970 | |
| 3971 | +'exif-gpsdop-excellent' => 'Excellent ($1)', |
| 3972 | +'exif-gpsdop-good' => 'Good ($1)', |
| 3973 | +'exif-gpsdop-moderate' => 'Moderate ($1)', |
| 3974 | +'exif-gpsdop-fair' => 'Fair ($1)', |
| 3975 | +'exif-gpsdop-poor' => 'Poor ($1)', |
| 3976 | + |
3965 | 3977 | 'exif-objectcycle-a' => 'Morning only', |
3966 | 3978 | 'exif-objectcycle-p' => 'Evening only', |
3967 | 3979 | 'exif-objectcycle-b' => 'Both morning and evening', |
3968 | 3980 | |
| 3981 | +'exif-ycbcrpositioning-1' => 'Centered', |
| 3982 | +'exif-ycbcrpositioning-2' => 'Co-sited', |
| 3983 | + |
3969 | 3984 | # External editor support |
3970 | 3985 | 'edit-externally' => 'Edit this file using an external application', |
3971 | 3986 | 'edit-externally-help' => '(See the [http://www.mediawiki.org/wiki/Manual:External_editors setup instructions] for more information)', |