r68441 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r68440‎ | r68441 | r68442 >
Date:04:23, 23 June 2010
Author:bawolff
Status:ok
Tags:
Comment:
Experiment with adding support for multivalued metadata tags while still
being backwards compatible.
Modified paths:
  • /branches/img_metadata/phase3/includes/Exif.php (modified) (history)
  • /branches/img_metadata/phase3/includes/api/ApiQueryImageInfo.php (modified) (history)
  • /branches/img_metadata/phase3/includes/filerepo/File.php (modified) (history)
  • /branches/img_metadata/phase3/includes/filerepo/ForeignAPIFile.php (modified) (history)
  • /branches/img_metadata/phase3/includes/media/Bitmap.php (modified) (history)
  • /branches/img_metadata/phase3/includes/media/Generic.php (modified) (history)
  • /branches/img_metadata/phase3/includes/media/IPTC.php (modified) (history)
  • /branches/img_metadata/phase3/includes/media/Jpeg.php (modified) (history)
  • /branches/img_metadata/phase3/languages/messages/MessagesEn.php (modified) (history)
  • /branches/img_metadata/phase3/languages/messages/MessagesQqq.php (modified) (history)

Diff [purge]

Index: branches/img_metadata/phase3/includes/Exif.php
@@ -95,6 +95,13 @@
9696 * Constructor
9797 *
9898 * @param $file String: filename.
 99+ * @fixme the following are broke:
 100+ * SubjectArea, ExifVersion, Flashpix Version (probably any Exif:Undefined with non 1 count)
 101+ * SceneType, FileSource, MakerNote (we really don't want makerNote though),
 102+ * ComponenentsConfiguration, and possibly others pending better testing.
 103+ *
 104+ * DigitalZoomRatio = 0/0 is rejected. need to determine if thats valid.
 105+ * possibly should treat 0/0 = 0. need to read exif spec on that.
99106 */
100107 function __construct( $file ) {
101108 /**
@@ -211,7 +218,7 @@
212219 'DigitalZoomRatio' => Exif::RATIONAL, # Digital zoom ration
213220 'FocalLengthIn35mmFilm' => Exif::SHORT, # Focal length in 35 mm film
214221 'SceneCaptureType' => Exif::SHORT, # Scene capture type #p49
215 - 'GainControl' => Exif::RATIONAL, # Scene control #p49-50
 222+ 'GainControl' => Exif::SHORT, # Scene control #p49-50
216223 'Contrast' => Exif::SHORT, # Contrast #p50
217224 'Saturation' => Exif::SHORT, # Saturation #p50
218225 'Sharpness' => Exif::SHORT, # Sharpness #p50
@@ -271,6 +278,7 @@
272279 */
273280 $this->mRawExifData = $data ? $data : array();
274281 $this->makeFilteredData();
 282+ $this->collapseData();
275283 $this->makeFormattedData();
276284 $this->debugFile( __FUNCTION__, false );
277285 }
@@ -279,31 +287,101 @@
280288 * Make $this->mFilteredExifData
281289 */
282290 function makeFilteredData() {
283 - $this->mFilteredExifData = $this->mRawExifData;
 291+ $this->mFilteredExifData = Array();
284292
285 - foreach( array_keys( $this->mFilteredExifData ) as $section ) {
 293+ foreach ( array_keys( $this->mRawExifData ) as $section ) {
286294 if ( !in_array( $section, array_keys( $this->mExifTags ) ) ) {
287295 $this->debug( $section , __FUNCTION__, "'$section' is not a valid Exif section" );
288 - unset( $this->mFilteredExifData[$section] );
289296 continue;
290297 }
291298
292 - foreach( array_keys( $this->mFilteredExifData[$section] ) as $tag ) {
 299+ foreach ( array_keys( $this->mRawExifData[$section] ) as $tag ) {
293300 if ( !in_array( $tag, array_keys( $this->mExifTags[$section] ) ) ) {
294301 $this->debug( $tag, __FUNCTION__, "'$tag' is not a valid tag in '$section'" );
295 - unset( $this->mFilteredExifData[$section][$tag] );
296302 continue;
297303 }
298 - $value = $this->mFilteredExifData[$section][$tag];
299 - if( !$this->validate( $section, $tag, $value ) ) {
 304+
 305+ $this->mFilteredExifData[$tag] = $this->mRawExifData[$section][$tag];
 306+ // This is ok, as the tags in the different sections do not conflict.
 307+ // except in computed and thumbnail section, which we don't use.
 308+
 309+ $value = $this->mRawExifData[$section][$tag];
 310+ if ( !$this->validate( $section, $tag, $value ) ) {
300311 $this->debug( $value, __FUNCTION__, "'$tag' contained invalid data" );
301 - unset( $this->mFilteredExifData[$section][$tag] );
 312+ unset( $this->mFilteredExifData[$tag] );
302313 }
303314 }
304315 }
305316 }
306317
307318 /**
 319+ * Collapse some fields together.
 320+ * This converts some fields from exif form, to a more friendly form.
 321+ * For example GPS lattitude to a single number.
 322+ *
 323+ * The rationale behind this is that we're storing data, not presenting to the user
 324+ * For example a longitude is a single number describing how far away you are from
 325+ * the prime meridian. Well it might be nice to split it up into minutes and seconds
 326+ * for the user, it doesn't really make sense to split a single number into 4 parts
 327+ * for storage. (degrees, minutes, second, direction vs single floating point number).
 328+ *
 329+ * Other things this might do (not really sure if they make sense or not):
 330+ * Dates -> mediawiki date format.
 331+ * convert values that can be in different units to be in one standardized unit.
 332+ *
 333+ * As an alternative approach, some of this could be done in the validate phase
 334+ * if we make up our own types like Exif::DATE.
 335+ */
 336+ function collapseData( ) {
 337+
 338+ $this->exifGPStoNumber( 'GPSLatitude' );
 339+ $this->exifGPStoNumber( 'GPSDestLatitude' );
 340+ $this->exifGPStoNumber( 'GPSLongitude' );
 341+ $this->exifGPStoNumber( 'GPSDestLongitude' );
 342+
 343+ if ( isset( $this->mFilteredExifData['GPSAltitude'] ) && isset( $this->mFilteredExifData['GPSAltitudeRef'] ) ) {
 344+ if ( $this->mFilteredExifData['GPSAltitudeRef'] === 1 ) {
 345+ $this->mFilteredExifData['GPSAltitude'] *= - 1;
 346+ }
 347+ unset( $this->mFilteredExifData );
 348+ }
 349+
 350+ }
 351+ /**
 352+ * Convert gps in exif form to a single floating point number
 353+ * for example 10 degress 20`40`` S -> -10.34444
 354+ * @param String $prop a gps coordinate exif tag name (like GPSLongitude)
 355+ */
 356+ function exifGPStoNumber ( $prop ) {
 357+ $loc =& $this->mFilteredExifData[$prop];
 358+ $dir =& $this->mFilteredExifData[$prop . 'Ref'];
 359+ $res = false;
 360+
 361+ if ( isset( $loc ) && isset( $dir ) && ( $dir === 'N' || $dir === 'S' || $dir === 'E' || $dir === 'W' ) ) {
 362+ list( $num, $denom ) = explode( '/', $loc[0] );
 363+ $res = $num / $denom;
 364+ list( $num, $denom ) = explode( '/', $loc[1] );
 365+ $res += ( $num / $denom ) * ( 1 / 60 );
 366+ list( $num, $denom ) = explode( '/', $loc[2] );
 367+ $res += ( $num / $denom ) * ( 1 / 3600 );
 368+
 369+ if ( $dir === 'S' || $dir === 'W' ) {
 370+ $res *= - 1; // make negative
 371+ }
 372+ }
 373+
 374+ // update the exif records.
 375+
 376+ if ( $res !== false ) { // using !== as $res could potentially be 0
 377+ $this->mFilteredExifData[$prop] = $res;
 378+ unset( $this->mFilteredExifData[$prop . 'Ref'] );
 379+ } else { // if invalid
 380+ unset( $this->mFilteredExifData[$prop] );
 381+ unset( $this->mFilteredExifData[$prop . 'Ref'] );
 382+ }
 383+ }
 384+
 385+ /**
308386 * @todo document
309387 */
310388 function makeFormattedData( ) {
@@ -601,468 +679,546 @@
602680 function getFormattedData() {
603681 global $wgLang;
604682
605 - $sections =& $this->mExif;
 683+ $tags =& $this->mExif;
606684
607 - $resolutionunit = !isset( $sections['IFD0']['ResolutionUnit'] ) || $sections['IFD0']['ResolutionUnit'] == 2 ? 2 : 3;
608 - unset( $sections['IFD0']['ResolutionUnit'] );
 685+ $resolutionunit = !isset( $tags['ResolutionUnit'] ) || $tags['ResolutionUnit'] == 2 ? 2 : 3;
 686+ unset( $tags['ResolutionUnit'] );
609687
610 - foreach( $sections as $section => &$tags ) {
611 - foreach( $tags as $tag => $val ) {
612 - switch( $tag ) {
613 - case 'Compression':
614 - switch( $val ) {
615 - case 1: case 6:
616 - $tags[$tag] = $this->msg( $tag, $val );
617 - break;
618 - default:
619 - $tags[$tag] = $val;
620 - break;
621 - }
622 - break;
 688+ foreach ( $tags as $tag => &$vals ) {
623689
624 - case 'PhotometricInterpretation':
625 - switch( $val ) {
626 - case 2: case 6:
627 - $tags[$tag] = $this->msg( $tag, $val );
 690+ // This seems ugly to wrap non-array's in an array just to unwrap again,
 691+ // especially when most of the time it is not an array
 692+ if ( !is_array( $tags[$tag] ) ) {
 693+ $vals = Array( $vals );
 694+ }
 695+
 696+ // _type is a special value to say what array type
 697+ if ( isset( $tags[$tag]['_type'] ) ) {
 698+ $type = $tags[$tag]['_type'];
 699+ unset( $vals['_type'] );
 700+ } else {
 701+ $type = 'ul'; // default unorcdered list.
 702+ }
 703+
 704+ foreach ( $vals as &$val ) {
 705+
 706+ switch( $tag ) {
 707+ case 'Compression':
 708+ switch( $val ) {
 709+ case 1: case 6:
 710+ $val = $this->msg( $tag, $val );
 711+ break;
 712+ default:
 713+ $val = $val;
 714+ break;
 715+ }
628716 break;
629 - default:
630 - $tags[$tag] = $val;
631 - break;
632 - }
633 - break;
634717
635 - case 'Orientation':
636 - switch( $val ) {
637 - case 1: case 2: case 3: case 4: case 5: case 6: case 7: case 8:
638 - $tags[$tag] = $this->msg( $tag, $val );
 718+ case 'PhotometricInterpretation':
 719+ switch( $val ) {
 720+ case 2: case 6:
 721+ $val = $this->msg( $tag, $val );
 722+ break;
 723+ default:
 724+ $val = $val;
 725+ break;
 726+ }
639727 break;
640 - default:
641 - $tags[$tag] = $val;
642 - break;
643 - }
644 - break;
645728
646 - case 'PlanarConfiguration':
647 - switch( $val ) {
648 - case 1: case 2:
649 - $tags[$tag] = $this->msg( $tag, $val );
 729+ case 'Orientation':
 730+ switch( $val ) {
 731+ case 1: case 2: case 3: case 4: case 5: case 6: case 7: case 8:
 732+ $val = $this->msg( $tag, $val );
 733+ break;
 734+ default:
 735+ $val = $val;
 736+ break;
 737+ }
650738 break;
651 - default:
652 - $tags[$tag] = $val;
653 - break;
654 - }
655 - break;
656739
657 - // TODO: YCbCrSubSampling
658 - // TODO: YCbCrPositioning
659 -
660 - case 'XResolution':
661 - case 'YResolution':
662 - switch( $resolutionunit ) {
663 - case 2:
664 - $tags[$tag] = $this->msg( 'XYResolution', 'i', $this->formatNum( $val ) );
 740+ case 'PlanarConfiguration':
 741+ switch( $val ) {
 742+ case 1: case 2:
 743+ $val = $this->msg( $tag, $val );
665744 break;
666 - case 3:
667 - $this->msg( 'XYResolution', 'c', $this->formatNum( $val ) );
668 - break;
669745 default:
670 - $tags[$tag] = $val;
 746+ $val = $val;
671747 break;
672 - }
673 - break;
 748+ }
 749+ break;
674750
675 - // TODO: YCbCrCoefficients #p27 (see annex E)
676 - case 'ExifVersion': case 'FlashPixVersion':
677 - $tags[$tag] = "$val"/100;
678 - break;
 751+ // TODO: YCbCrSubSampling
 752+ // TODO: YCbCrPositioning
679753
680 - case 'ColorSpace':
681 - switch( $val ) {
682 - case 1: case 'FFFF.H':
683 - $tags[$tag] = $this->msg( $tag, $val );
 754+ case 'XResolution':
 755+ case 'YResolution':
 756+ switch( $resolutionunit ) {
 757+ case 2:
 758+ $val = $this->msg( 'XYResolution', 'i', $this->formatNum( $val ) );
 759+ break;
 760+ case 3:
 761+ $this->msg( 'XYResolution', 'c', $this->formatNum( $val ) );
 762+ break;
 763+ default:
 764+ $val = $val;
 765+ break;
 766+ }
684767 break;
685 - default:
686 - $tags[$tag] = $val;
 768+
 769+ // TODO: YCbCrCoefficients #p27 (see annex E)
 770+ case 'ExifVersion': case 'FlashpixVersion':
 771+ $val = "$val" / 100;
687772 break;
688 - }
689 - break;
690773
691 - case 'ComponentsConfiguration':
692 - switch( $val ) {
693 - case 0: case 1: case 2: case 3: case 4: case 5: case 6:
694 - $tags[$tag] = $this->msg( $tag, $val );
 774+ case 'ColorSpace':
 775+ switch( $val ) {
 776+ case 1: case 'FFFF.H':
 777+ $val = $this->msg( $tag, $val );
 778+ break;
 779+ default:
 780+ $val = $val;
 781+ break;
 782+ }
695783 break;
696 - default:
697 - $tags[$tag] = $val;
 784+
 785+ case 'ComponentsConfiguration':
 786+ switch( $val ) {
 787+ case 0: case 1: case 2: case 3: case 4: case 5: case 6:
 788+ $val = $this->msg( $tag, $val );
 789+ break;
 790+ default:
 791+ $val = $val;
 792+ break;
 793+ }
698794 break;
699 - }
700 - break;
701795
702 - case 'DateTime':
703 - case 'DateTimeOriginal':
704 - case 'DateTimeDigitized':
705 - if( $val == '0000:00:00 00:00:00' ) {
706 - $tags[$tag] = wfMsg('exif-unknowndate');
707 - } elseif( preg_match( '/^(?:\d{4}):(?:\d\d):(?:\d\d) (?:\d\d):(?:\d\d):(?:\d\d)$/', $val ) ) {
708 - $tags[$tag] = $wgLang->timeanddate( wfTimestamp(TS_MW, $val) );
709 - }
710 - break;
 796+ case 'DateTime':
 797+ case 'DateTimeOriginal':
 798+ case 'DateTimeDigitized':
 799+ if ( $val == '0000:00:00 00:00:00' ) {
 800+ $val = wfMsg( 'exif-unknowndate' );
 801+ } elseif ( preg_match( '/^(?:\d{4}):(?:\d\d):(?:\d\d) (?:\d\d):(?:\d\d):(?:\d\d)$/', $val ) ) {
 802+ $val = $wgLang->timeanddate( wfTimestamp( TS_MW, $val ) );
 803+ }
 804+ break;
711805
712 - case 'ExposureProgram':
713 - switch( $val ) {
714 - case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7: case 8:
715 - $tags[$tag] = $this->msg( $tag, $val );
 806+ case 'ExposureProgram':
 807+ switch( $val ) {
 808+ case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7: case 8:
 809+ $val = $this->msg( $tag, $val );
 810+ break;
 811+ default:
 812+ $val = $val;
 813+ break;
 814+ }
716815 break;
717 - default:
718 - $tags[$tag] = $val;
 816+
 817+ case 'SubjectDistance':
 818+ $val = $this->msg( $tag, '', $this->formatNum( $val ) );
719819 break;
720 - }
721 - break;
722820
723 - case 'SubjectDistance':
724 - $tags[$tag] = $this->msg( $tag, '', $this->formatNum( $val ) );
725 - break;
 821+ case 'MeteringMode':
 822+ switch( $val ) {
 823+ case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7: case 255:
 824+ $val = $this->msg( $tag, $val );
 825+ break;
 826+ default:
 827+ $val = $val;
 828+ break;
 829+ }
 830+ break;
726831
727 - case 'MeteringMode':
728 - switch( $val ) {
729 - case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7: case 255:
730 - $tags[$tag] = $this->msg( $tag, $val );
 832+ case 'LightSource':
 833+ switch( $val ) {
 834+ case 0: case 1: case 2: case 3: case 4: case 9: case 10: case 11:
 835+ case 12: case 13: case 14: case 15: case 17: case 18: case 19: case 20:
 836+ case 21: case 22: case 23: case 24: case 255:
 837+ $val = $this->msg( $tag, $val );
 838+ break;
 839+ default:
 840+ $val = $val;
 841+ break;
 842+ }
731843 break;
732 - default:
733 - $tags[$tag] = $val;
 844+
 845+ case 'Flash':
 846+ $flashDecode = array(
 847+ 'fired' => $val & bindec( '00000001' ),
 848+ 'return' => ( $val & bindec( '00000110' ) ) >> 1,
 849+ 'mode' => ( $val & bindec( '00011000' ) ) >> 3,
 850+ 'function' => ( $val & bindec( '00100000' ) ) >> 5,
 851+ 'redeye' => ( $val & bindec( '01000000' ) ) >> 6,
 852+// 'reserved' => ($val & bindec( '10000000' )) >> 7,
 853+ );
 854+
 855+ # We do not need to handle unknown values since all are used.
 856+ foreach ( $flashDecode as $subTag => $subValue ) {
 857+ # We do not need any message for zeroed values.
 858+ if ( $subTag != 'fired' && $subValue == 0 ) {
 859+ continue;
 860+ }
 861+ $fullTag = $tag . '-' . $subTag ;
 862+ $flashMsgs[] = $this->msg( $fullTag, $subValue );
 863+ }
 864+ $val = $wgLang->commaList( $flashMsgs );
734865 break;
735 - }
736 - break;
737866
738 - case 'LightSource':
739 - switch( $val ) {
740 - case 0: case 1: case 2: case 3: case 4: case 9: case 10: case 11:
741 - case 12: case 13: case 14: case 15: case 17: case 18: case 19: case 20:
742 - case 21: case 22: case 23: case 24: case 255:
743 - $tags[$tag] = $this->msg( $tag, $val );
 867+ case 'FocalPlaneResolutionUnit':
 868+ switch( $val ) {
 869+ case 2:
 870+ $val = $this->msg( $tag, $val );
 871+ break;
 872+ default:
 873+ $val = $val;
 874+ break;
 875+ }
744876 break;
745 - default:
746 - $tags[$tag] = $val;
 877+
 878+ case 'SensingMethod':
 879+ switch( $val ) {
 880+ case 1: case 2: case 3: case 4: case 5: case 7: case 8:
 881+ $val = $this->msg( $tag, $val );
 882+ break;
 883+ default:
 884+ $val = $val;
 885+ break;
 886+ }
747887 break;
748 - }
749 - break;
750888
751 - case 'Flash':
752 - $flashDecode = array(
753 - 'fired' => $val & bindec( '00000001' ),
754 - 'return' => ($val & bindec( '00000110' )) >> 1,
755 - 'mode' => ($val & bindec( '00011000' )) >> 3,
756 - 'function' => ($val & bindec( '00100000' )) >> 5,
757 - 'redeye' => ($val & bindec( '01000000' )) >> 6,
758 -// 'reserved' => ($val & bindec( '10000000' )) >> 7,
759 - );
 889+ case 'FileSource':
 890+ switch( $val ) {
 891+ case 3:
 892+ $val = $this->msg( $tag, $val );
 893+ break;
 894+ default:
 895+ $val = $val;
 896+ break;
 897+ }
 898+ break;
760899
761 - # We do not need to handle unknown values since all are used.
762 - foreach( $flashDecode as $subTag => $subValue ) {
763 - # We do not need any message for zeroed values.
764 - if( $subTag != 'fired' && $subValue == 0) {
765 - continue;
 900+ case 'SceneType':
 901+ switch( $val ) {
 902+ case 1:
 903+ $val = $this->msg( $tag, $val );
 904+ break;
 905+ default:
 906+ $val = $val;
 907+ break;
766908 }
767 - $fullTag = $tag . '-' . $subTag ;
768 - $flashMsgs[] = $this->msg( $fullTag, $subValue );
769 - }
770 - $tags[$tag] = $wgLang->commaList( $flashMsgs );
771 - break;
 909+ break;
772910
773 - case 'FocalPlaneResolutionUnit':
774 - switch( $val ) {
775 - case 2:
776 - $tags[$tag] = $this->msg( $tag, $val );
 911+ case 'CustomRendered':
 912+ switch( $val ) {
 913+ case 0: case 1:
 914+ $val = $this->msg( $tag, $val );
 915+ break;
 916+ default:
 917+ $val = $val;
 918+ break;
 919+ }
777920 break;
778 - default:
779 - $tags[$tag] = $val;
 921+
 922+ case 'ExposureMode':
 923+ switch( $val ) {
 924+ case 0: case 1: case 2:
 925+ $val = $this->msg( $tag, $val );
 926+ break;
 927+ default:
 928+ $val = $val;
 929+ break;
 930+ }
780931 break;
781 - }
782 - break;
783932
784 - case 'SensingMethod':
785 - switch( $val ) {
786 - case 1: case 2: case 3: case 4: case 5: case 7: case 8:
787 - $tags[$tag] = $this->msg( $tag, $val );
 933+ case 'WhiteBalance':
 934+ switch( $val ) {
 935+ case 0: case 1:
 936+ $val = $this->msg( $tag, $val );
 937+ break;
 938+ default:
 939+ $val = $val;
 940+ break;
 941+ }
788942 break;
789 - default:
790 - $tags[$tag] = $val;
 943+
 944+ case 'SceneCaptureType':
 945+ switch( $val ) {
 946+ case 0: case 1: case 2: case 3:
 947+ $val = $this->msg( $tag, $val );
 948+ break;
 949+ default:
 950+ $val = $val;
 951+ break;
 952+ }
791953 break;
792 - }
793 - break;
794954
795 - case 'FileSource':
796 - switch( $val ) {
797 - case 3:
798 - $tags[$tag] = $this->msg( $tag, $val );
 955+ case 'GainControl':
 956+ switch( $val ) {
 957+ case 0: case 1: case 2: case 3: case 4:
 958+ $val = $this->msg( $tag, $val );
 959+ break;
 960+ default:
 961+ $val = $val;
 962+ break;
 963+ }
799964 break;
800 - default:
801 - $tags[$tag] = $val;
 965+
 966+ case 'Contrast':
 967+ switch( $val ) {
 968+ case 0: case 1: case 2:
 969+ $val = $this->msg( $tag, $val );
 970+ break;
 971+ default:
 972+ $val = $val;
 973+ break;
 974+ }
802975 break;
803 - }
804 - break;
805976
806 - case 'SceneType':
807 - switch( $val ) {
808 - case 1:
809 - $tags[$tag] = $this->msg( $tag, $val );
 977+ case 'Saturation':
 978+ switch( $val ) {
 979+ case 0: case 1: case 2:
 980+ $val = $this->msg( $tag, $val );
 981+ break;
 982+ default:
 983+ $val = $val;
 984+ break;
 985+ }
810986 break;
811 - default:
812 - $tags[$tag] = $val;
813 - break;
814 - }
815 - break;
816987
817 - case 'CustomRendered':
818 - switch( $val ) {
819 - case 0: case 1:
820 - $tags[$tag] = $this->msg( $tag, $val );
 988+ case 'Sharpness':
 989+ switch( $val ) {
 990+ case 0: case 1: case 2:
 991+ $val = $this->msg( $tag, $val );
 992+ break;
 993+ default:
 994+ $val = $val;
 995+ break;
 996+ }
821997 break;
822 - default:
823 - $tags[$tag] = $val;
824 - break;
825 - }
826 - break;
827998
828 - case 'ExposureMode':
829 - switch( $val ) {
830 - case 0: case 1: case 2:
831 - $tags[$tag] = $this->msg( $tag, $val );
 999+ case 'SubjectDistanceRange':
 1000+ switch( $val ) {
 1001+ case 0: case 1: case 2: case 3:
 1002+ $val = $this->msg( $tag, $val );
 1003+ break;
 1004+ default:
 1005+ $val = $val;
 1006+ break;
 1007+ }
8321008 break;
833 - default:
834 - $tags[$tag] = $val;
835 - break;
836 - }
837 - break;
8381009
839 - case 'WhiteBalance':
840 - switch( $val ) {
841 - case 0: case 1:
842 - $tags[$tag] = $this->msg( $tag, $val );
 1010+ //The GPS...Ref values are kept for compatability, probably won't be reached.
 1011+ case 'GPSLatitudeRef':
 1012+ case 'GPSDestLatitudeRef':
 1013+ switch( $val ) {
 1014+ case 'N': case 'S':
 1015+ $val = $this->msg( 'GPSLatitude', $val );
 1016+ break;
 1017+ default:
 1018+ $val = $val;
 1019+ break;
 1020+ }
8431021 break;
844 - default:
845 - $tags[$tag] = $val;
846 - break;
847 - }
848 - break;
8491022
850 - case 'SceneCaptureType':
851 - switch( $val ) {
852 - case 0: case 1: case 2: case 3:
853 - $tags[$tag] = $this->msg( $tag, $val );
 1023+ case 'GPSLongitudeRef':
 1024+ case 'GPSDestLongitudeRef':
 1025+ switch( $val ) {
 1026+ case 'E': case 'W':
 1027+ $val = $this->msg( 'GPSLongitude', $val );
 1028+ break;
 1029+ default:
 1030+ $val = $val;
 1031+ break;
 1032+ }
8541033 break;
855 - default:
856 - $tags[$tag] = $val;
857 - break;
858 - }
859 - break;
8601034
861 - case 'GainControl':
862 - switch( $val ) {
863 - case 0: case 1: case 2: case 3: case 4:
864 - $tags[$tag] = $this->msg( $tag, $val );
 1035+ case 'GPSStatus':
 1036+ switch( $val ) {
 1037+ case 'A': case 'V':
 1038+ $val = $this->msg( $tag, $val );
 1039+ break;
 1040+ default:
 1041+ $val = $val;
 1042+ break;
 1043+ }
8651044 break;
866 - default:
867 - $tags[$tag] = $val;
868 - break;
869 - }
870 - break;
8711045
872 - case 'Contrast':
873 - switch( $val ) {
874 - case 0: case 1: case 2:
875 - $tags[$tag] = $this->msg( $tag, $val );
 1046+ case 'GPSMeasureMode':
 1047+ switch( $val ) {
 1048+ case 2: case 3:
 1049+ $val = $this->msg( $tag, $val );
 1050+ break;
 1051+ default:
 1052+ $val = $val;
 1053+ break;
 1054+ }
8761055 break;
877 - default:
878 - $tags[$tag] = $val;
879 - break;
880 - }
881 - break;
8821056
883 - case 'Saturation':
884 - switch( $val ) {
885 - case 0: case 1: case 2:
886 - $tags[$tag] = $this->msg( $tag, $val );
 1057+
 1058+ case 'GPSTrackRef':
 1059+ case 'GPSImgDirectionRef':
 1060+ case 'GPSDestBearingRef':
 1061+ switch( $val ) {
 1062+ case 'T': case 'M':
 1063+ $val = $this->msg( 'GPSDirection', $val );
 1064+ break;
 1065+ default:
 1066+ $val = $val;
 1067+ break;
 1068+ }
8871069 break;
888 - default:
889 - $tags[$tag] = $val;
890 - break;
891 - }
892 - break;
8931070
894 - case 'Sharpness':
895 - switch( $val ) {
896 - case 0: case 1: case 2:
897 - $tags[$tag] = $this->msg( $tag, $val );
 1071+ case 'GPSDateStamp':
 1072+ $val = $wgLang->date( substr( $val, 0, 4 ) . substr( $val, 5, 2 ) . substr( $val, 8, 2 ) . '000000' );
8981073 break;
899 - default:
900 - $tags[$tag] = $val;
901 - break;
902 - }
903 - break;
9041074
905 - case 'SubjectDistanceRange':
906 - switch( $val ) {
907 - case 0: case 1: case 2: case 3:
908 - $tags[$tag] = $this->msg( $tag, $val );
 1075+ case 'GPSAltitudeRef':
 1076+ switch( $val ) {
 1077+ case 0: case 1:
 1078+ $tags[$tag] = $this->msg( 'GPSAltitude', $val );
 1079+ break;
 1080+ default:
 1081+ $tags[$tag] = $val;
 1082+ break;
 1083+ }
9091084 break;
910 - default:
911 - $tags[$tag] = $val;
912 - break;
913 - }
914 - break;
9151085
916 - case 'GPSLatitudeRef':
917 - case 'GPSDestLatitudeRef':
918 - switch( $val ) {
919 - case 'N': case 'S':
920 - $tags[$tag] = $this->msg( 'GPSLatitude', $val );
 1086+ case 'GPSLatitude':
 1087+ case 'GPSDestLatitude':
 1088+ $val = $this->formatCoords( $val, 'latitude' );
9211089 break;
922 - default:
923 - $tags[$tag] = $val;
 1090+ case 'GPSLongitude':
 1091+ case 'GPSDestLongitude':
 1092+ $val = $this->formatCoords( $val, 'longitude' );
9241093 break;
925 - }
926 - break;
9271094
928 - case 'GPSLongitudeRef':
929 - case 'GPSDestLongitudeRef':
930 - switch( $val ) {
931 - case 'E': case 'W':
932 - $tags[$tag] = $this->msg( 'GPSLongitude', $val );
 1095+ case 'GPSSpeedRef':
 1096+ switch( $val ) {
 1097+ case 'K': case 'M': case 'N':
 1098+ $tags[$tag] = $this->msg( 'GPSSpeed', $val );
 1099+ break;
 1100+ default:
 1101+ $tags[$tag] = $val;
 1102+ break;
 1103+ }
9331104 break;
934 - default:
935 - $tags[$tag] = $val;
936 - break;
937 - }
938 - break;
9391105
940 - case 'GPSAltitudeRef':
941 - switch( $val ) {
942 - case 0: case 1:
943 - $tags[$tag] = $this->msg( 'GPSAltitude', $val );
 1106+ case 'GPSDestDistanceRef':
 1107+ switch( $val ) {
 1108+ case 'K': case 'M': case 'N':
 1109+ $tags[$tag] = $this->msg( 'GPSDestDistance', $val );
 1110+ break;
 1111+ default:
 1112+ $tags[$tag] = $val;
 1113+ break;
 1114+ }
9441115 break;
945 - default:
946 - $tags[$tag] = $val;
947 - break;
948 - }
949 - break;
9501116
951 - case 'GPSLatitude':
952 - case 'GPSDestLatitude':
953 - case 'GPSLongitude':
954 - case 'GPSDestLongitude':
955 - $tags[$tag] = $this->formatCoords( $val );
956 - break;
9571117
958 - case 'GPSStatus':
959 - switch( $val ) {
960 - case 'A': case 'V':
961 - $tags[$tag] = $this->msg( $tag, $val );
 1118+ // This is not in the Exif standard, just a special
 1119+ // case for our purposes which enables wikis to wikify
 1120+ // the make, model and software name to link to their articles.
 1121+ case 'Make':
 1122+ case 'Model':
 1123+ case 'Software':
 1124+ $val = $this->msg( $tag, '', $val );
9621125 break;
963 - default:
964 - $tags[$tag] = $val;
 1126+
 1127+ case 'ExposureTime':
 1128+ // Show the pretty fraction as well as decimal version
 1129+ $val = wfMsg( 'exif-exposuretime-format',
 1130+ $this->formatFraction( $val ), $this->formatNum( $val ) );
9651131 break;
966 - }
967 - break;
9681132
969 - case 'GPSMeasureMode':
970 - switch( $val ) {
971 - case 2: case 3:
972 - $tags[$tag] = $this->msg( $tag, $val );
 1133+ case 'FNumber':
 1134+ $val = wfMsg( 'exif-fnumber-format',
 1135+ $this->formatNum( $val ) );
9731136 break;
974 - default:
975 - $tags[$tag] = $val;
976 - break;
977 - }
978 - break;
9791137
980 - case 'GPSSpeedRef':
981 - switch( $val ) {
982 - case 'K': case 'M': case 'N':
983 - $tags[$tag] = $this->msg( 'GPSSpeed', $val );
 1138+ case 'FocalLength':
 1139+ $val = wfMsg( 'exif-focallength-format',
 1140+ $this->formatNum( $val ) );
9841141 break;
985 - default:
986 - $tags[$tag] = $val;
987 - break;
988 - }
989 - break;
9901142
991 - case 'GPSDestDistanceRef':
992 - switch( $val ) {
993 - case 'K': case 'M': case 'N':
994 - $tags[$tag] = $this->msg( 'GPSDestDistance', $val );
 1143+ // Do not transform fields with pure text.
 1144+ // For some languages the formatNum() conversion results to wrong output like
 1145+ // foo,bar@example,com or foo٫bar@example٫com
 1146+ case 'ImageDescription':
 1147+ case 'Artist':
 1148+ case 'Copyright':
 1149+ $val = htmlspecialchars( $val );
9951150 break;
996 - default:
997 - $tags[$tag] = $val;
998 - break;
999 - }
1000 - break;
10011151
1002 - case 'GPSTrackRef':
1003 - case 'GPSImgDirectionRef':
1004 - case 'GPSDestBearingRef':
1005 - switch( $val ) {
1006 - case 'T': case 'M':
1007 - $tags[$tag] = $this->msg( 'GPSDirection', $val );
 1152+ // Here begins the non-exif data
 1153+ case 'JPEGFileComment':
 1154+ $val = htmlspecialchars( $val );
10081155 break;
 1156+
10091157 default:
1010 - $tags[$tag] = $val;
 1158+ $val = $this->formatNum( $val );
10111159 break;
10121160 }
1013 - break;
 1161+ }
 1162+ // End formatting values, start flattening arrays.
 1163+ $vals = $this->flattenArray( $vals, $type );
10141164
1015 - case 'GPSDateStamp':
1016 - $tags[$tag] = $wgLang->date( substr( $val, 0, 4 ) . substr( $val, 5, 2 ) . substr( $val, 8, 2 ) . '000000' );
1017 - break;
 1165+ }
 1166+ return $this->mExif;
 1167+ }
10181168
1019 - // This is not in the Exif standard, just a special
1020 - // case for our purposes which enables wikis to wikify
1021 - // the make, model and software name to link to their articles.
1022 - case 'Make':
1023 - case 'Model':
1024 - case 'Software':
1025 - $tags[$tag] = $this->msg( $tag, '', $val );
1026 - break;
 1169+ /**
 1170+ * A function to collapse multivalued tags into a single value.
 1171+ * This turns an array of (for example) authors into a bulleted list.
 1172+ * This might be used outside of this class.
 1173+ * @public
 1174+ *
 1175+ * @param $vals Array array of values
 1176+ * @param $type Type of array (either lang, ul, ol).
 1177+ * lang = language assoc array with keys being the lang code
 1178+ * ul = unorded list, ol = ordered list
 1179+ * type can also come from the '_type' member of $vals.
 1180+ * @return String single value (in wiki-syntax).
 1181+ */
 1182+ public static function flattenArray( $vals, $type = 'ul' ) {
10271183
1028 - case 'ExposureTime':
1029 - // Show the pretty fraction as well as decimal version
1030 - $tags[$tag] = wfMsg( 'exif-exposuretime-format',
1031 - $this->formatFraction( $val ), $this->formatNum( $val ) );
1032 - break;
 1184+ if ( isset( $vals['_type'] ) ) {
 1185+ $type = $vals['_type'];
 1186+ }
10331187
1034 - case 'FNumber':
1035 - $tags[$tag] = wfMsg( 'exif-fnumber-format',
1036 - $this->formatNum( $val ) );
1037 - break;
1038 -
1039 - case 'FocalLength':
1040 - $tags[$tag] = wfMsg( 'exif-focallength-format',
1041 - $this->formatNum( $val ) );
1042 - break;
1043 -
1044 - // Do not transform fields with pure text.
1045 - // For some languages the formatNum() conversion results to wrong output like
1046 - // foo,bar@example,com or foo٫bar@example٫com
1047 - case 'ImageDescription':
1048 - case 'Artist':
1049 - case 'Copyright':
1050 - $tags[$tag] = htmlspecialchars( $val );
1051 - break;
1052 - //Here begins the non-exif data
1053 - case 'JPEGFileComment':
1054 - $tags[$tag] = htmlspecialchars( $val );
1055 - break;
1056 -
 1188+ if ( !is_array( $vals ) ) {
 1189+ return $vals; // do nothing if not an array;
 1190+ }
 1191+ elseif ( count( $vals ) === 1 && $type !== 'lang' ) {
 1192+ return $vals[0];
 1193+ }
 1194+ elseif ( count( $vals ) === 0 ) {
 1195+ return ""; // paranoia. This should never happen
 1196+ wfDebug( __METHOD__ . ' metadata array with 0 elements!' );
 1197+ }
 1198+ else {
 1199+ switch( $type ) {
 1200+ case 'lang':
 1201+ // fixme incomplete
 1202+ // should place x-default, content language, user language
 1203+ // first. then the others, hidden by defualt.
 1204+ // also should use much better markup.
 1205+ $content = "";
 1206+ if ( $vals['x-default'] ) {
 1207+ $content .= "\n*" . $vals['x-default'];
 1208+ unset( $vals['x-default'] );
 1209+ }
 1210+ foreach ( $vals as $lang => $content ) {
 1211+ $content = "\n*<span lang=\"$lang\">"
 1212+ . "'''$lang''' $content</span>";
 1213+ }
 1214+ return $content;
 1215+ case 'ol':
 1216+ return "\n#" . implode( "\n#", $vals );
 1217+ case 'ul':
10571218 default:
1058 - $tags[$tag] = $this->formatNum( $val );
1059 - break;
 1219+ return "\n*" . implode( "\n*", $vals );
10601220 }
10611221 }
1062 - }
1063 -
1064 - return $this->mExif;
10651222 }
1066 -
10671223 /**
10681224 * Convenience function for getFormattedData()
10691225 *
@@ -1162,19 +1318,37 @@
11631319 * @private
11641320 *
11651321 * @param $coords Array: degrees, minutes and seconds
1166 - * @param $ref String: reference direction (N/S/E/W), optional
 1322+ * @param $type String: latitude or longitude (for if its a NWS or E)
11671323 * @return mixed A floating point number or whatever we were fed
11681324 */
1169 - function formatCoords( $coords, $ref = null ) {
1170 - list($deg, $min, $sec) = $coords;
1171 - $deg = $this->formatNum($deg);
1172 - $min = $this->formatNum($min);
1173 - $sec = $this->formatNum($sec);
1174 - $out = $deg . "°";
1175 - if ($min) $out .= " " . $min . "'";
1176 - if ($sec) $out .= " " . $sec . '"';
1177 - if ($ref) $out .= " " . $ref;
1178 - return $out;
 1325+ function formatCoords( $coord, $type ) {
 1326+ $ref = '';
 1327+ if ( $coord < 0 ) {
 1328+ if ( $type === 'latitude' ) {
 1329+ $ref = 'S';
 1330+ }
 1331+ elseif ( $type === 'longitude' ) {
 1332+ $ref = 'W';
 1333+ }
 1334+ }
 1335+ else {
 1336+ if ( $type === 'latitude' ) {
 1337+ $ref = 'N';
 1338+ }
 1339+ elseif ( $type === 'longitude' ) {
 1340+ $ref = 'E';
 1341+ }
 1342+ }
 1343+
 1344+ $deg = floor( $coord );
 1345+ $min = floor( ( $coord - $deg ) * 60.0 );
 1346+ $sec = round( ( ( $coord - $deg ) - $min / 60 ) * 3600, 2 );
 1347+
 1348+ $deg = $this->formatNum( $deg );
 1349+ $min = $this->formatNum( $min );
 1350+ $sec = $this->formatNum( $sec );
 1351+
 1352+ return wfMsg( 'exif-coordinate-format', $deg, $min, $sec, $ref, $coord );
11791353 }
11801354
11811355 }
Index: branches/img_metadata/phase3/includes/filerepo/File.php
@@ -268,6 +268,26 @@
269269 public function getMetadata() { return false; }
270270
271271 /**
 272+ * get versioned metadata
 273+ *
 274+ * @param $metadata Mixed Array or String of (serialized) metadata
 275+ * @param $version integer version number.
 276+ * @return Array containing metadata, or what was passed to it on fail (unserializing if not array)
 277+ */
 278+ public function convertMetadataVersion($metadata, $version) {
 279+ $handler = $this->getHandler();
 280+ if (!is_array($metadata)) {
 281+ //just to make the return type consistant
 282+ $metadata = unserialize( $metadata );
 283+ }
 284+ if ( $handler ) {
 285+ return $handler->convertMetadataVersion($metadata, $version);
 286+ } else {
 287+ return $metadata;
 288+ }
 289+ }
 290+
 291+ /**
272292 * Return the bit depth of the file
273293 * Overridden by LocalFile
274294 * STUB
Index: branches/img_metadata/phase3/includes/filerepo/ForeignAPIFile.php
@@ -20,7 +20,13 @@
2121 $data = $repo->fetchImageQuery( array(
2222 'titles' => 'File:' . $title->getText(),
2323 'iiprop' => 'timestamp|user|comment|url|size|sha1|metadata|mime',
24 - 'prop' => 'imageinfo' ) );
 24+ 'prop' => 'imageinfo',
 25+ 'iimetadataversion' => Exif::version()
 26+ ) );
 27+ // Note to self/fixme: Using Exif::version() here is obviously not good, as
 28+ // metadata is handler specific. Original plan was to use handler->getMetadataVersion()
 29+ // but that doesn't work, because we don't know mime type yet, thus don't know handler yet.
 30+ // need to think of a better way of doing this.
2531
2632 $info = $repo->getImageInfo( $data );
2733
Index: branches/img_metadata/phase3/includes/api/ApiQueryImageInfo.php
@@ -122,7 +122,7 @@
123123 {
124124 $gotOne = true;
125125 $fit = $this->addPageSubItem( $pageId,
126 - self::getInfo( $img, $prop, $result, $scale ) );
 126+ self::getInfo( $img, $prop, $result, $scale, $params['metadataversion'] ) );
127127 if ( !$fit ) {
128128 if ( count( $pageIds[NS_IMAGE] ) == 1 ) {
129129 // See the 'the user is screwed' comment above
@@ -151,7 +151,7 @@
152152 break;
153153 }
154154 $fit = $this->addPageSubItem( $pageId,
155 - self::getInfo( $oldie, $prop, $result ) );
 155+ self::getInfo( $oldie, $prop, $result, null, $params['metadataversion'] ) );
156156 if ( !$fit ) {
157157 if ( count( $pageIds[NS_IMAGE] ) == 1 ) {
158158 $this->setContinueEnumParameter( 'start',
@@ -191,7 +191,7 @@
192192 * @param $scale Array containing 'width' and 'height' items, or null
193193 * @return Array: result array
194194 */
195 - static function getInfo( $file, $prop, $result, $scale = null ) {
 195+ static function getInfo( $file, $prop, $result, $scale = null, $version = 0 ) {
196196 $vals = array();
197197 if ( isset( $prop['timestamp'] ) ) {
198198 $vals['timestamp'] = wfTimestamp( TS_ISO_8601, $file->getTimestamp() );
@@ -239,8 +239,11 @@
240240 $vals['sha1'] = wfBaseConvert( $file->getSha1(), 36, 16, 40 );
241241 }
242242 if ( isset( $prop['metadata'] ) ) {
243 - $metadata = $file->getMetadata();
244 - $vals['metadata'] = $metadata ? self::processMetaData( unserialize( $metadata ), $result ) : null;
 243+ $metadata = unserialize( $file->getMetadata() );
 244+ if ( $version !== 0 ) {
 245+ $metadata = $file->convertMetadataVersion( $metadata, $version );
 246+ }
 247+ $vals['metadata'] = $metadata ? self::processMetaData( $metadata, $result ) : null;
245248 }
246249 if ( isset( $prop['mime'] ) ) {
247250 $vals['mime'] = $file->getMimeType();
@@ -307,6 +310,11 @@
308311 ApiBase::PARAM_TYPE => 'integer',
309312 ApiBase::PARAM_DFLT => - 1
310313 ),
 314+ 'metadataversion' => array(
 315+ ApiBase::PARAM_TYPE => 'integer',
 316+ ApiBase::PARAM_DFLT => 1,
 317+ ApiBase::PARAM_MIN => 0,
 318+ ),
311319 'continue' => null,
312320 );
313321 }
@@ -341,6 +349,8 @@
342350 'urlwidth' => array( "If {$p}prop=url is set, a URL to an image scaled to this width will be returned.",
343351 'Only the current version of the image can be scaled' ),
344352 'urlheight' => "Similar to {$p}urlwidth. Cannot be used without {$p}urlwidth",
 353+ 'metadataversion' => array( "Version of metadata to use. if 0 is specified, use latest version.",
 354+ "Defaults to '1' for bacwards compatability" ),
345355 'continue' => 'When more results are available, use this to continue',
346356 );
347357 }
Index: branches/img_metadata/phase3/includes/media/Bitmap.php
@@ -424,16 +424,14 @@
425425 $formatted = $format->getFormattedData();
426426 // Sort fields into visible and collapsed
427427 $visibleFields = $this->visibleMetadataFields();
428 - foreach ( $formatted as $section => $tags ) {
429 - foreach ( $tags as $name => $value ) {
430 - $tag = strtolower( $name );
431 - self::addMeta( $result,
432 - in_array( $tag, $visibleFields ) ? 'visible' : 'collapsed',
433 - 'exif',
434 - $tag,
435 - $value
436 - );
437 - }
 428+ foreach ( $formatted as $name => $value ) {
 429+ $tag = strtolower( $name );
 430+ self::addMeta( $result,
 431+ in_array( $tag, $visibleFields ) ? 'visible' : 'collapsed',
 432+ 'exif',
 433+ $tag,
 434+ $value
 435+ );
438436 }
439437 return $result;
440438 }
Index: branches/img_metadata/phase3/includes/media/Generic.php
@@ -87,6 +87,33 @@
8888 function getMetadata( $image, $path ) { return ''; }
8989
9090 /**
 91+ * Get metadata version.
 92+ * @return integer version
 93+ * @todo Originally this was going to be used by ForeignAPIFile, but currently does nothing.
 94+ */
 95+ function getMetadataVersion () { return 1; }
 96+
 97+ /**
 98+ * Convert metadata version.
 99+ *
 100+ * By default just returns $metadata, but can be used to allow
 101+ * media handlers to convert between metadata versions.
 102+ *
 103+ * @param $metadata Mixed String or Array metadata array (serialized if string)
 104+ * @param $version Integer target version
 105+ * @return Array serialized metadata in specified version, or $metadata on fail.
 106+ */
 107+ function convertMetadataVersion( $metadata, $version = 1 ) {
 108+ if ( !is_array( $metadata ) ) {
 109+ //unserialize to keep return parameter consistent.
 110+ wfSuppressWarnings();
 111+ return unserialize( $metadata );
 112+ wfRestoreWarnings();
 113+ }
 114+ return $metadata;
 115+ }
 116+
 117+ /**
91118 * Get a string describing the type of metadata, for display purposes.
92119 */
93120 function getMetadataType( $image ) { return false; }
Index: branches/img_metadata/phase3/includes/media/IPTC.php
@@ -63,8 +63,6 @@
6464 $val = self::convIPTCHelper( $val, $charset );
6565 }
6666
67 - // for now. Probably should keep it as an array perhaps
68 - $data = $wgLang->commaList( $data );
6967 } else {
7068 $data = self::convIPTCHelper ( $data, $charset );
7169 }
Index: branches/img_metadata/phase3/includes/media/Jpeg.php
@@ -27,4 +27,27 @@
2828 return '0';
2929 }
3030 }
 31+
 32+ function convertMetadataVersion( $metadata, $version = 1 ) {
 33+ // basically flattens arrays.
 34+ if ( $version != 1 ) {
 35+ return $metadata;
 36+ }
 37+
 38+ if ( !is_array( $metadata ) ) {
 39+ $metadata = unserialize( $metadata );
 40+ }
 41+ if ( !isset( $metadata['MEDIAWIKI_EXIF_VERSION'] ) || $metadata['MEDIAWIKI_EXIF_VERSION'] != 2 ) {
 42+ return $metadata;
 43+ }
 44+
 45+ foreach ( $metadata as &$val ) {
 46+ if ( is_array( $val ) ) {
 47+ $val = formatExif::flattenArray( $val );
 48+ }
 49+ }
 50+ $metadata['MEDIAWIKI_EXIF_VERSION'] = $version;
 51+ return $metadata;
 52+ }
 53+ function getMetadataVersion () { return Exif::version(); }
3154 }
Index: branches/img_metadata/phase3/languages/messages/MessagesQqq.php
@@ -3196,6 +3196,7 @@
31973197 See also Wikipedia on [http://en.wikipedia.org/wiki/Focal_length#In_photography focal length].',
31983198 'exif-gpslatitude' => '{{Identical|Latitude}}',
31993199 'exif-gpslongitude' => '{{Identical|Longitude}}',
 3200+'exif-coordinate-format' => 'For formatting GPS latitude coordinates. $1 is degrees, $2 is minutes, $3 is seconds (up to two decimal places), $4 is direction (N, S, W, or E), $5 is coordinate as a single positive or negative real number.',
32003201 'exif-jpegfilecomment' => 'This is not a true exif tag, but the contents of the JPEG COM segment. This often contains a file source, but can potentially contain any comment about the file',
32013202
32023203 # EXIF attributes
Index: branches/img_metadata/phase3/languages/messages/MessagesEn.php
@@ -3757,6 +3757,7 @@
37583758 'exif-gpsareainformation' => 'Name of GPS area',
37593759 'exif-gpsdatestamp' => 'GPS date',
37603760 'exif-gpsdifferential' => 'GPS differential correction',
 3761+'exif-coordinate-format' => '$1° $2′ $3″ $4',
37613762 'exif-jpegfilecomment' => 'JPEG file comment',
37623763
37633764 # Make & model, can be wikified in order to link to the camera and model name

Status & tagging log