r86169 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r86168‎ | r86169 | r86170 >
Date:01:23, 16 April 2011
Author:bawolff
Status:resolved (Comments)
Tags:
Comment:
Merge to trunk everything in img_metadata branch.

Hope I did this in an ok fashion. svn merge --re-integrate was giving me issues
so I just essentially over-wrote my working copy with the version at img_metadata.
Modified paths:
  • /trunk/phase3/docs/hooks.txt (modified) (history)
  • /trunk/phase3/includes/AutoLoader.php (modified) (history)
  • /trunk/phase3/includes/DefaultSettings.php (modified) (history)
  • /trunk/phase3/includes/Exif.php (deleted) (history)
  • /trunk/phase3/includes/ImagePage.php (modified) (history)
  • /trunk/phase3/includes/api/ApiQueryImageInfo.php (modified) (history)
  • /trunk/phase3/includes/filerepo/File.php (modified) (history)
  • /trunk/phase3/includes/filerepo/ForeignAPIFile.php (modified) (history)
  • /trunk/phase3/includes/filerepo/LocalFile.php (modified) (history)
  • /trunk/phase3/includes/media/Bitmap.php (modified) (history)
  • /trunk/phase3/includes/media/BitmapMetadataHandler.php (added) (history)
  • /trunk/phase3/includes/media/Exif.php (added) (history)
  • /trunk/phase3/includes/media/FormatMetadata.php (added) (history)
  • /trunk/phase3/includes/media/GIF.php (modified) (history)
  • /trunk/phase3/includes/media/GIFMetadataExtractor.php (modified) (history)
  • /trunk/phase3/includes/media/Generic.php (modified) (history)
  • /trunk/phase3/includes/media/IPTC.php (added) (history)
  • /trunk/phase3/includes/media/Jpeg.php (added) (history)
  • /trunk/phase3/includes/media/JpegMetadataExtractor.php (added) (history)
  • /trunk/phase3/includes/media/PNG.php (modified) (history)
  • /trunk/phase3/includes/media/PNGMetadataExtractor.php (modified) (history)
  • /trunk/phase3/includes/media/XMP.php (added) (history)
  • /trunk/phase3/includes/media/XMPInfo.php (added) (history)
  • /trunk/phase3/includes/media/XMPValidate.php (added) (history)
  • /trunk/phase3/languages/messages/MessagesEn.php (modified) (history)
  • /trunk/phase3/languages/messages/MessagesFr.php (modified) (history)
  • /trunk/phase3/languages/messages/MessagesFrp.php (modified) (history)
  • /trunk/phase3/languages/messages/MessagesIt.php (modified) (history)
  • /trunk/phase3/languages/messages/MessagesJa.php (modified) (history)
  • /trunk/phase3/languages/messages/MessagesNl.php (modified) (history)
  • /trunk/phase3/languages/messages/MessagesOc.php (modified) (history)
  • /trunk/phase3/languages/messages/MessagesPms.php (modified) (history)
  • /trunk/phase3/languages/messages/MessagesQqq.php (modified) (history)
  • /trunk/phase3/languages/messages/MessagesScn.php (modified) (history)
  • /trunk/phase3/maintenance/language/messageTypes.inc (modified) (history)
  • /trunk/phase3/maintenance/language/messages.inc (modified) (history)
  • /trunk/phase3/maintenance/rebuildImages.php (modified) (history)
  • /trunk/phase3/skins/common/shared.css (modified) (history)

Diff [purge]

Index: trunk/phase3/maintenance/language/messages.inc
@@ -2631,6 +2631,8 @@
26322632 'metadata-expand',
26332633 'metadata-collapse',
26342634 'metadata-fields',
 2635+ 'metadata-langitem',
 2636+ 'metadata-langitem-default',
26352637 ),
26362638 'exif' => array(
26372639 'exif-imagewidth',
@@ -2651,7 +2653,6 @@
26522654 'exif-stripbytecounts',
26532655 'exif-jpeginterchangeformat',
26542656 'exif-jpeginterchangeformatlength',
2655 - 'exif-transferfunction',
26562657 'exif-whitepoint',
26572658 'exif-primarychromaticities',
26582659 'exif-ycbcrcoefficients',
@@ -2670,7 +2671,6 @@
26712672 'exif-compressedbitsperpixel',
26722673 'exif-pixelydimension',
26732674 'exif-pixelxdimension',
2674 - 'exif-makernote',
26752675 'exif-usercomment',
26762676 'exif-relatedsoundfile',
26772677 'exif-datetimeoriginal',
@@ -2685,7 +2685,6 @@
26862686 'exif-exposureprogram',
26872687 'exif-spectralsensitivity',
26882688 'exif-isospeedratings',
2689 - 'exif-oecf',
26902689 'exif-shutterspeedvalue',
26912690 'exif-aperturevalue',
26922691 'exif-brightnessvalue',
@@ -2699,7 +2698,6 @@
27002699 'exif-focallength-format',
27012700 'exif-subjectarea',
27022701 'exif-flashenergy',
2703 - 'exif-spatialfrequencyresponse',
27042702 'exif-focalplanexresolution',
27052703 'exif-focalplaneyresolution',
27062704 'exif-focalplaneresolutionunit',
@@ -2708,7 +2706,6 @@
27092707 'exif-sensingmethod',
27102708 'exif-filesource',
27112709 'exif-scenetype',
2712 - 'exif-cfapattern',
27132710 'exif-customrendered',
27142711 'exif-exposuremode',
27152712 'exif-whitebalance',
@@ -2753,17 +2750,87 @@
27542751 'exif-gpsareainformation',
27552752 'exif-gpsdatestamp',
27562753 'exif-gpsdifferential',
 2754+ 'exif-jpegfilecomment',
 2755+ 'exif-keywords',
 2756+ 'exif-worldregioncreated',
 2757+ 'exif-countrycreated',
 2758+ 'exif-countrycodecreated',
 2759+ 'exif-provinceorstatecreated',
 2760+ 'exif-citycreated',
 2761+ 'exif-sublocationcreated',
 2762+ 'exif-worldregiondest',
 2763+ 'exif-countrydest',
 2764+ 'exif-countrycodedest',
 2765+ 'exif-provinceorstatedest',
 2766+ 'exif-citydest',
 2767+ 'exif-sublocationdest',
27572768 'exif-objectname',
 2769+ 'exif-specialinstructions',
 2770+ 'exif-headline',
 2771+ 'exif-credit',
 2772+ 'exif-source',
 2773+ 'exif-editstatus',
 2774+ 'exif-urgency',
 2775+ 'exif-fixtureidentifier',
 2776+ 'exif-locationdest',
 2777+ 'exif-locationdestcode',
 2778+ 'exif-objectcycle',
 2779+ 'exif-contact',
 2780+ 'exif-writer',
 2781+ 'exif-languagecode',
 2782+ 'exif-iimversion',
 2783+ 'exif-iimcategory',
 2784+ 'exif-iimsupplementalcategory',
 2785+ 'exif-datetimeexpires',
 2786+ 'exif-datetimereleased',
 2787+ 'exif-originaltransmissionref',
 2788+ 'exif-identifier',
 2789+ 'exif-lens',
 2790+ 'exif-serialnumber',
 2791+ 'exif-cameraownername',
 2792+ 'exif-label',
 2793+ 'exif-datetimemetadata',
 2794+ 'exif-nickname',
 2795+ 'exif-rating',
 2796+ 'exif-rightscertificate',
 2797+ 'exif-copyrighted',
 2798+ 'exif-copyrightowner',
 2799+ 'exif-usageterms',
 2800+ 'exif-webstatement',
 2801+ 'exif-originaldocumentid',
 2802+ 'exif-licenseurl',
 2803+ 'exif-morepermissionsurl',
 2804+ 'exif-attributionurl',
 2805+ 'exif-preferredattributionname',
 2806+ 'exif-pngfilecomment',
 2807+ 'exif-disclaimer',
 2808+ 'exif-contentwarning',
 2809+ 'exif-giffilecomment',
 2810+ 'exif-intellectualgenre',
 2811+ 'exif-subjectnewscode',
 2812+ 'exif-scenecode',
 2813+ 'exif-event',
 2814+ 'exif-organisationinimage',
 2815+ 'exif-personinimage',
 2816+ 'exif-originalimageheight',
 2817+ 'exif-originalimagewidth',
27582818 ),
27592819 'exif-values' => array(
27602820 'exif-make-value',
27612821 'exif-model-value',
27622822 'exif-software-value',
 2823+ 'exif-software-version-value',
 2824+ 'exif-contact-value',
 2825+ 'exif-subjectnewscode-value',
27632826 ),
27642827 'exif-compression' => array(
27652828 'exif-compression-1',
27662829 'exif-compression-6',
27672830 ),
 2831+ 'exif-copyrighted' => array(
 2832+ 'exif-copyrighted-true',
 2833+ 'exif-copyrighted-false',
 2834+ ),
27682835 'exif-photometricinterpretation' => array(
27692836 'exif-photometricinterpretation-2',
27702837 'exif-photometricinterpretation-6',
@@ -2791,7 +2858,7 @@
27922859 ),
27932860 'exif-colorspace' => array(
27942861 'exif-colorspace-1',
2795 - 'exif-colorspace-ffff.h',
 2862+ 'exif-colorspace-65535',
27962863 ),
27972864 'exif-componentsconfiguration' => array(
27982865 'exif-componentsconfiguration-0',
@@ -2934,6 +3001,10 @@
29353002 'exif-gpslongitude-e',
29363003 'exif-gpslongitude-w',
29373004 ),
 3005+ 'exif-altituderef' => array(
 3006+ 'exif-gpsaltitude-above-sealevel',
 3007+ 'exif-gpsaltitude-below-sealevel',
 3008+ ),
29383009 'exif-gpsstatus' => array(
29393010 'exif-gpsstatus-a',
29403011 'exif-gpsstatus-v',
@@ -2947,10 +3018,75 @@
29483019 'exif-gpsspeed-m',
29493020 'exif-gpsspeed-n',
29503021 ),
 3022+ 'exif-gpsdestdistanceref' => array(
 3023+ 'exif-gpsdestdistance-k',
 3024+ 'exif-gpsdestdistance-m',
 3025+ 'exif-gpsdestdistance-n',
 3026+ ),
 3027+ 'exif-gdop' => array(
 3028+ 'exif-gpsdop-excellent',
 3029+ 'exif-gpsdop-good',
 3030+ 'exif-gpsdop-moderate',
 3031+ 'exif-gpsdop-fair',
 3032+ 'exif-gpsdop-poor',
 3033+ ),
 3034+ 'exif-objectcycle' => array(
 3035+ 'exif-objectcycle-a',
 3036+ 'exif-objectcycle-p',
 3037+ 'exif-objectcycle-b',
 3038+ ),
29513039 'exif-gpsdirection' => array(
29523040 'exif-gpsdirection-t',
29533041 'exif-gpsdirection-m',
29543042 ),
 3043+ 'exif-ycbcrpositioning' => array(
 3044+ 'exif-ycbcrpositioning-1',
 3045+ 'exif-ycbcrpositioning-2',
 3046+ ),
 3047+ 'exif-dc' => array(
 3048+ 'exif-dc-contributor',
 3049+ 'exif-dc-coverage',
 3050+ 'exif-dc-date',
 3051+ 'exif-dc-publisher',
 3052+ 'exif-dc-relation',
 3053+ 'exif-dc-rights',
 3054+ 'exif-dc-source',
 3055+ 'exif-dc-type',
 3056+ ),
 3057+ 'exif-rating' => array(
 3058+ 'exif-rating-rejected',
 3059+ ),
 3060+ 'exif-isospeedratings' => array(
 3061+ 'exif-isospeedratings-overflow',
 3062+ ),
 3063+ 'exif-maxaperturevalue' => array(
 3064+ 'exif-maxaperturevalue-value',
 3065+ ),
 3066+ 'exif-iimcategory' => array(
 3067+ 'exif-iimcategory-ace',
 3068+ 'exif-iimcategory-clj',
 3069+ 'exif-iimcategory-dis',
 3070+ 'exif-iimcategory-fin',
 3071+ 'exif-iimcategory-edu',
 3072+ 'exif-iimcategory-evn',
 3073+ 'exif-iimcategory-hth',
 3074+ 'exif-iimcategory-hum',
 3075+ 'exif-iimcategory-lab',
 3076+ 'exif-iimcategory-lif',
 3077+ 'exif-iimcategory-pol',
 3078+ 'exif-iimcategory-rel',
 3079+ 'exif-iimcategory-sci',
 3080+ 'exif-iimcategory-soi',
 3081+ 'exif-iimcategory-spo',
 3082+ 'exif-iimcategory-war',
 3083+ 'exif-iimcategory-wea',
 3084+ ),
 3085+ 'exif-urgency' => array(
 3086+ 'exif-urgency-normal',
 3087+ 'exif-urgency-low',
 3088+ 'exif-urgency-high',
 3089+ 'exif-urgency-other',
 3090+ ),
29553091 'edit-externally' => array(
29563092 'edit-externally',
29573093 'edit-externally-help',
@@ -3445,9 +3581,11 @@
34463582 'exif-subjectdistancerange' => '',
34473583 'exif-gpslatitude' => 'Pseudotags used for GPSLatitudeRef and GPSDestLatitudeRef',
34483584 'exif-gpslongitude' => 'Pseudotags used for GPSLongitudeRef and GPSDestLongitudeRef',
 3585+ 'exif-altituderef' => 'Pseudotags used for GPSAltitudeRef',
34493586 'exif-gpsstatus' => '',
34503587 'exif-gpsmeasuremode' => '',
34513588 'exif-gpsspeed' => 'Pseudotags used for GPSSpeedRef',
 3589+ 'exif-gpsdestdistanceref' => 'Pseudotags used for GPSDestDistanceRef',
34523590 'exif-gpsdirection' => 'Pseudotags used for GPSTrackRef, GPSImgDirectionRef and GPSDestBearingRef',
34533591 'edit-externally' => 'External editor support',
34543592 'all' => "'all' in various places, this might be different for inflected languages",
Index: trunk/phase3/maintenance/language/messageTypes.inc
@@ -100,6 +100,7 @@
101101 'exif-make-value',
102102 'exif-model-value',
103103 'exif-software-value',
 104+ 'exif-software-version-value',
104105 'history_copyright',
105106 'licenses',
106107 'loginstart',
@@ -280,18 +281,20 @@
281282 'exif-xyresolution-i',
282283 'exif-xyresolution-c',
283284 'exif-colorspace-1',
284 - 'exif-colorspace-ffff.h',
285285 'exif-componentsconfiguration-1',
286286 'exif-componentsconfiguration-2',
287287 'exif-componentsconfiguration-3',
288288 'exif-componentsconfiguration-4',
289289 'exif-componentsconfiguration-5',
290290 'exif-componentsconfiguration-6',
 291+ 'exif-contact-value',
 292+ 'exif-coordinate-format',
291293 'exif-lightsource-20',
292294 'exif-lightsource-21',
293295 'exif-lightsource-22',
294296 'exif-lightsource-23',
295 - 'exif-filesource-3',
 297+ 'exif-maxaperturevalue-value',
 298+ 'exif-subjectnewscode-value',
296299 'booksources-isbn',
297300 'sp-contributions-explain',
298301 'sorbs',
@@ -389,6 +392,8 @@
390393 'shared-repo-name-wikimediacommons',
391394 'usermessage-template',
392395 'filepage.css',
 396+ 'metadata-langitem',
 397+ 'metadata-langitem-default',
393398 'nocookiesforlogin',
394399 );
395400
@@ -412,7 +417,6 @@
413418 'exif-stripbytecounts',
414419 'exif-jpeginterchangeformat',
415420 'exif-jpeginterchangeformatlength',
416 - 'exif-transferfunction',
417421 'exif-whitepoint',
418422 'exif-primarychromaticities',
419423 'exif-ycbcrcoefficients',
@@ -431,7 +435,6 @@
432436 'exif-compressedbitsperpixel',
433437 'exif-pixelydimension',
434438 'exif-pixelxdimension',
435 - 'exif-makernote',
436439 'exif-usercomment',
437440 'exif-relatedsoundfile',
438441 'exif-datetimeoriginal',
@@ -445,7 +448,6 @@
446449 'exif-exposureprogram',
447450 'exif-spectralsensitivity',
448451 'exif-isospeedratings',
449 - 'exif-oecf',
450452 'exif-shutterspeedvalue',
451453 'exif-aperturevalue',
452454 'exif-brightnessvalue',
@@ -458,7 +460,6 @@
459461 'exif-focallength',
460462 'exif-subjectarea',
461463 'exif-flashenergy',
462 - 'exif-spatialfrequencyresponse',
463464 'exif-focalplanexresolution',
464465 'exif-focalplaneyresolution',
465466 'exif-focalplaneresolutionunit',
@@ -467,7 +468,6 @@
468469 'exif-sensingmethod',
469470 'exif-filesource',
470471 'exif-scenetype',
471 - 'exif-cfapattern',
472472 'exif-customrendered',
473473 'exif-exposuremode',
474474 'exif-whitebalance',
@@ -512,6 +512,7 @@
513513 'exif-gpsareainformation',
514514 'exif-gpsdatestamp',
515515 'exif-gpsdifferential',
 516+ 'exif-colorspace-65535',
516517 'exif-compression-1',
517518 'exif-unknowndate',
518519 'exif-orientation-1',
@@ -598,6 +599,7 @@
599600 'exif-contrast-0',
600601 'exif-contrast-1',
601602 'exif-contrast-2',
 603+ 'exif-filesource-3',
602604 'exif-saturation-0',
603605 'exif-saturation-1',
604606 'exif-saturation-2',
@@ -612,8 +614,8 @@
613615 'exif-gpslatitude-s',
614616 'exif-gpslongitude-e',
615617 'exif-gpslongitude-w',
616 - 'exif-gpsaltitude-0',
617 - 'exif-gpsaltitude-1',
 618+ 'exif-gpsaltitude-above-sealevel',
 619+ 'exif-gpsaltitude-below-sealevel',
618620 'exif-gpsstatus-a',
619621 'exif-gpsstatus-v',
620622 'exif-gpsmeasuremode-2',
@@ -626,5 +628,112 @@
627629 'exif-gpsdestdistance-n',
628630 'exif-gpsdirection-t',
629631 'exif-gpsdirection-m',
 632+ 'exif-gpsdop-excellent',
 633+ 'exif-gpsdop-good',
 634+ 'exif-gpsdop-moderate',
 635+ 'exif-gpsdop-fair',
 636+ 'exif-gpsdop-poor',
 637+ 'exif-ycbcrpositioning-1',
 638+ 'exif-ycbcrpositioning-2',
 639+ //non-exif metadata that is still image metadata
 640+ 'exif-jpegfilecomment',
 641+ 'exif-keywords',
 642+ 'exif-worldregioncreated',
 643+ 'exif-countrycreated',
 644+ 'exif-countrycodecreated',
 645+ 'exif-provinceorstatecreated',
 646+ 'exif-citycreated',
 647+ 'exif-sublocationcreated',
 648+ 'exif-worldregiondest',
 649+ 'exif-countrydest',
 650+ 'exif-countrycodedest',
 651+ 'exif-stateorprovincedest',
 652+ 'exif-citydest',
 653+ 'exif-sublocationdest',
630654 'exif-objectname',
 655+ 'exif-specialinstructions',
 656+ 'exif-headline',
 657+ 'exif-credit',
 658+ 'exif-source',
 659+ 'exif-editstatus',
 660+ 'exif-urgency',
 661+ 'exif-fixtureidentifier',
 662+ 'exif-locationdest',
 663+ 'exif-locationdestcode',
 664+ 'exif-objectcycle',
 665+ 'exif-objectcycle-a',
 666+ 'exif-objectcycle-p',
 667+ 'exif-objectcycle-b',
 668+ 'exif-contact',
 669+ 'exif-writer',
 670+ 'exif-languagecode',
 671+ 'exif-iimversion',
 672+ 'exif-iimcategory',
 673+ 'exif-iimsupplementalcategory',
 674+ 'exif-datetimeexpires',
 675+ 'exif-datetimereleased',
 676+ 'exif-originaltransmissionref',
 677+ 'exif-identifier',
 678+ 'exif-dc-contributor',
 679+ 'exif-dc-coverage',
 680+ 'exif-dc-date',
 681+ 'exif-dc-publisher',
 682+ 'exif-dc-relation',
 683+ 'exif-dc-rights',
 684+ 'exif-dc-source',
 685+ 'exif-dc-type',
 686+ 'exif-lens',
 687+ 'exif-serialnumber',
 688+ 'exif-cameraownername',
 689+ 'exif-label',
 690+ 'exif-datetimemetadata',
 691+ 'exif-nickname',
 692+ 'exif-rating',
 693+ 'exif-rightscertificate',
 694+ 'exif-copyrighted',
 695+ 'exif-copyrightowner',
 696+ 'exif-usageterms',
 697+ 'exif-webstatement',
 698+ 'exif-originaldocumentid',
 699+ 'exif-licenseurl',
 700+ 'exif-morepermissionsurl',
 701+ 'exif-attributionurl',
 702+ 'exif-preferredattributionname',
 703+ 'exif-copyrighted-true',
 704+ 'exif-copyrighted-false',
 705+ 'exif-rating-rejected',
 706+ 'exif-isospeedratings-overflow',
 707+ 'exif-pngfilecomment',
 708+ 'exif-disclaimer',
 709+ 'exif-contentwarning',
 710+ 'exif-giffilecomment',
 711+ 'exif-intellectualgenre',
 712+ 'exif-iimcategory-ace',
 713+ 'exif-iimcategory-clj',
 714+ 'exif-iimcategory-dis',
 715+ 'exif-iimcategory-fin',
 716+ 'exif-iimcategory-edu',
 717+ 'exif-iimcategory-evn',
 718+ 'exif-iimcategory-hth',
 719+ 'exif-iimcategory-hum',
 720+ 'exif-iimcategory-lab',
 721+ 'exif-iimcategory-lif',
 722+ 'exif-iimcategory-pol',
 723+ 'exif-iimcategory-rel',
 724+ 'exif-iimcategory-sci',
 725+ 'exif-iimcategory-soi',
 726+ 'exif-iimcategory-spo',
 727+ 'exif-iimcategory-war',
 728+ 'exif-iimcategory-wea',
 729+ 'exif-subjectnewscode',
 730+ 'exif-scenecode',
 731+ 'exif-event',
 732+ 'exif-organisationinimage',
 733+ 'exif-personinimage',
 734+ 'exif-originalimageheight',
 735+ 'exif-originalimagewidth',
 736+ 'exif-urgency-normal',
 737+ 'exif-urgency-low',
 738+ 'exif-urgency-high',
 739+ 'exif-urgency-other',
631740 );
Index: trunk/phase3/maintenance/rebuildImages.php
@@ -42,6 +42,10 @@
4343 function __construct() {
4444 parent::__construct();
4545
 46+ global $wgUpdateCompatibleMetadata;
 47+ //make sure to update old, but compatible img_metadata fields.
 48+ $wgUpdateCompatibleMetadata = true;
 49+
4650 $this->mDescription = 'Script to update image metadata records';
4751
4852 $this->addOption( 'missing', 'Check for files without associated database record' );
Index: trunk/phase3/skins/common/shared.css
@@ -445,7 +445,7 @@
446446 font-size: 0.8em;
447447 margin-left: 0.5em;
448448 margin-bottom: 0.5em;
449 - width: 300px;
 449+ width: 400px;
450450 }
451451
452452 table.mw_metadata caption {
@@ -468,8 +468,8 @@
469469 table.mw_metadata td, table.mw_metadata th {
470470 text-align: center;
471471 border: 1px solid #aaaaaa;
472 - padding-left: 0.1em;
473 - padding-right: 0.1em;
 472+ padding-left: 5px;
 473+ padding-right: 5px;
474474 }
475475
476476 table.mw_metadata th {
@@ -480,6 +480,14 @@
481481 background-color: #fcfcfc;
482482 }
483483
 484+table.mw_metadata ul.metadata-langlist {
 485+ list-style-type: none;
 486+ list-style-image: none;
 487+ padding-right: 5px;
 488+ padding-left: 5px;
 489+ margin: 0;
 490+}
 491+
484492 /* Galleries */
485493 /* These display attributes look nonsensical, but are needed to support IE and FF2 */
486494 /* Don't forget to update commonPrint.css */
Index: trunk/phase3/docs/hooks.txt
@@ -877,6 +877,14 @@
878878 $url: string value as output (out parameter, can modify)
879879 $query: query options passed to Title::getLocalURL()
880880
 881+'GetMetadataVersion': modify the image metadata version currently in use. This is
 882+ used when requesting image metadata from a ForiegnApiRepo. Media handlers
 883+ that need to have versioned metadata should add an element to the end of
 884+ the version array of the form 'handler_name=version'. Most media handlers
 885+ won't need to do this unless they broke backwards compatibility with a
 886+ previous version of the media handler metadata output.
 887+&$version: Array of version strings
 888+
881889 'GetPreferences': modify user preferences
882890 $user: User whose preferences are being modified.
883891 &$preferences: Preferences description array, to be fed to an HTMLForm object
@@ -1998,5 +2006,14 @@
19992007 $row: The database row for the revision.
20002008 $text: The revision text.
20012009
 2010+'XMPGetInfo': Called when obtaining the list of XMP tags to extract. Can be used to add
 2011+ additional tags to extract.
 2012+&$items: Array containing information on which items to extract. See XMPInfo for details on the format.
 2013+
 2014+'XMPGetResults': Called just before returning the results array of parsing xmp data. Can be
 2015+ used to post-process the results.
 2016+&$data: Array of metadata sections (such as $data['xmp-general']) each section is an array of
 2017+ metadata tags returned (each tag is either a value, or an array of values).
 2018+
20022019 More hooks might be available but undocumented, you can execute
20032020 ./maintenance/findhooks.php to find hidden one.
Index: trunk/phase3/includes/Exif.php
@@ -1,1154 +0,0 @@
2 -<?php
3 -/**
4 - * Exif metadata reader
5 - *
6 - * This program is free software; you can redistribute it and/or modify
7 - * it under the terms of the GNU General Public License as published by
8 - * the Free Software Foundation; either version 2 of the License, or
9 - * (at your option) any later version.
10 - *
11 - * This program is distributed in the hope that it will be useful,
12 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 - * GNU General Public License for more details.
15 - *
16 - * You should have received a copy of the GNU General Public License along
17 - * with this program; if not, write to the Free Software Foundation, Inc.,
18 - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 - * http://www.gnu.org/copyleft/gpl.html
20 - *
21 - * @ingroup Media
22 - * @author Ævar Arnfjörð Bjarmason <avarab@gmail.com>
23 - * @copyright Copyright © 2005, Ævar Arnfjörð Bjarmason
24 - * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License
25 - * @see http://exif.org/Exif2-2.PDF The Exif 2.2 specification
26 - * @file
27 - */
28 -
29 -/**
30 - * @todo document (e.g. one-sentence class-overview description)
31 - * @ingroup Media
32 - */
33 -class Exif {
34 -
35 - const BYTE = 1; //!< An 8-bit (1-byte) unsigned integer.
36 - const ASCII = 2; //!< An 8-bit byte containing one 7-bit ASCII code. The final byte is terminated with NULL.
37 - const SHORT = 3; //!< A 16-bit (2-byte) unsigned integer.
38 - const LONG = 4; //!< A 32-bit (4-byte) unsigned integer.
39 - const RATIONAL = 5; //!< Two LONGs. The first LONG is the numerator and the second LONG expresses the denominator
40 - const UNDEFINED = 7; //!< An 8-bit byte that can take any value depending on the field definition
41 - const SLONG = 9; //!< A 32-bit (4-byte) signed integer (2's complement notation),
42 - const SRATIONAL = 10; //!< Two SLONGs. The first SLONG is the numerator and the second SLONG is the denominator.
43 -
44 - //@{
45 - /* @var array
46 - * @private
47 - */
48 -
49 - /**
50 - * Exif tags grouped by category, the tagname itself is the key and the type
51 - * is the value, in the case of more than one possible value type they are
52 - * separated by commas.
53 - */
54 - var $mExifTags;
55 -
56 - /**
57 - * A one dimentional array of all Exif tags
58 - */
59 - var $mFlatExifTags;
60 -
61 - /**
62 - * The raw Exif data returned by exif_read_data()
63 - */
64 - var $mRawExifData;
65 -
66 - /**
67 - * A Filtered version of $mRawExifData that has been pruned of invalid
68 - * tags and tags that contain content they shouldn't contain according
69 - * to the Exif specification
70 - */
71 - var $mFilteredExifData;
72 -
73 - /**
74 - * Filtered and formatted Exif data, see FormatExif::getFormattedData()
75 - */
76 - var $mFormattedExifData;
77 -
78 - //@}
79 -
80 - //@{
81 - /* @var string
82 - * @private
83 - */
84 -
85 - /**
86 - * The file being processed
87 - */
88 - var $file;
89 -
90 - /**
91 - * The basename of the file being processed
92 - */
93 - var $basename;
94 -
95 - /**
96 - * The private log to log to, e.g. 'exif'
97 - */
98 - var $log = false;
99 -
100 - //@}
101 -
102 - /**
103 - * Constructor
104 - *
105 - * @param $file String: filename.
106 - */
107 - function __construct( $file ) {
108 - /**
109 - * Page numbers here refer to pages in the EXIF 2.2 standard
110 - *
111 - * @link http://exif.org/Exif2-2.PDF The Exif 2.2 specification
112 - */
113 - $this->mExifTags = array(
114 - # TIFF Rev. 6.0 Attribute Information (p22)
115 - 'tiff' => array(
116 - # Tags relating to image structure
117 - 'structure' => array(
118 - 'ImageWidth' => Exif::SHORT.','.Exif::LONG, # Image width
119 - 'ImageLength' => Exif::SHORT.','.Exif::LONG, # Image height
120 - 'BitsPerSample' => Exif::SHORT, # Number of bits per component
121 - # "When a primary image is JPEG compressed, this designation is not"
122 - # "necessary and is omitted." (p23)
123 - 'Compression' => Exif::SHORT, # Compression scheme #p23
124 - 'PhotometricInterpretation' => Exif::SHORT, # Pixel composition #p23
125 - 'Orientation' => Exif::SHORT, # Orientation of image #p24
126 - 'SamplesPerPixel' => Exif::SHORT, # Number of components
127 - 'PlanarConfiguration' => Exif::SHORT, # Image data arrangement #p24
128 - 'YCbCrSubSampling' => Exif::SHORT, # Subsampling ratio of Y to C #p24
129 - 'YCbCrPositioning' => Exif::SHORT, # Y and C positioning #p24-25
130 - 'XResolution' => Exif::RATIONAL, # Image resolution in width direction
131 - 'YResolution' => Exif::RATIONAL, # Image resolution in height direction
132 - 'ResolutionUnit' => Exif::SHORT, # Unit of X and Y resolution #(p26)
133 - ),
134 -
135 - # Tags relating to recording offset
136 - 'offset' => array(
137 - 'StripOffsets' => Exif::SHORT.','.Exif::LONG, # Image data location
138 - 'RowsPerStrip' => Exif::SHORT.','.Exif::LONG, # Number of rows per strip
139 - 'StripByteCounts' => Exif::SHORT.','.Exif::LONG, # Bytes per compressed strip
140 - 'JPEGInterchangeFormat' => Exif::SHORT.','.Exif::LONG, # Offset to JPEG SOI
141 - 'JPEGInterchangeFormatLength' => Exif::SHORT.','.Exif::LONG, # Bytes of JPEG data
142 - ),
143 -
144 - # Tags relating to image data characteristics
145 - 'characteristics' => array(
146 - 'TransferFunction' => Exif::SHORT, # Transfer function
147 - 'WhitePoint' => Exif::RATIONAL, # White point chromaticity
148 - 'PrimaryChromaticities' => Exif::RATIONAL, # Chromaticities of primarities
149 - 'YCbCrCoefficients' => Exif::RATIONAL, # Color space transformation matrix coefficients #p27
150 - 'ReferenceBlackWhite' => Exif::RATIONAL # Pair of black and white reference values
151 - ),
152 -
153 - # Other tags
154 - 'other' => array(
155 - 'DateTime' => Exif::ASCII, # File change date and time
156 - 'ImageDescription' => Exif::ASCII, # Image title
157 - 'Make' => Exif::ASCII, # Image input equipment manufacturer
158 - 'Model' => Exif::ASCII, # Image input equipment model
159 - 'Software' => Exif::ASCII, # Software used
160 - 'Artist' => Exif::ASCII, # Person who created the image
161 - 'Copyright' => Exif::ASCII, # Copyright holder
162 - ),
163 - ),
164 -
165 - # Exif IFD Attribute Information (p30-31)
166 - 'exif' => array(
167 - # Tags relating to version
168 - 'version' => array(
169 - # TODO: NOTE: Nonexistence of this field is taken to mean nonconformance
170 - # to the EXIF 2.1 AND 2.2 standards
171 - 'ExifVersion' => Exif::UNDEFINED, # Exif version
172 - 'FlashpixVersion' => Exif::UNDEFINED, # Supported Flashpix version #p32
173 - ),
174 -
175 - # Tags relating to Image Data Characteristics
176 - 'characteristics' => array(
177 - 'ColorSpace' => Exif::SHORT, # Color space information #p32
178 - ),
179 -
180 - # Tags relating to image configuration
181 - 'configuration' => array(
182 - 'ComponentsConfiguration' => Exif::UNDEFINED, # Meaning of each component #p33
183 - 'CompressedBitsPerPixel' => Exif::RATIONAL, # Image compression mode
184 - 'PixelYDimension' => Exif::SHORT.','.Exif::LONG, # Valid image width
185 - 'PixelXDimension' => Exif::SHORT.','.Exif::LONG, # Valind image height
186 - ),
187 -
188 - # Tags relating to related user information
189 - 'user' => array(
190 - 'MakerNote' => Exif::UNDEFINED, # Manufacturer notes
191 - 'UserComment' => Exif::UNDEFINED, # User comments #p34
192 - ),
193 -
194 - # Tags relating to related file information
195 - 'related' => array(
196 - 'RelatedSoundFile' => Exif::ASCII, # Related audio file
197 - ),
198 -
199 - # Tags relating to date and time
200 - 'dateandtime' => array(
201 - 'DateTimeOriginal' => Exif::ASCII, # Date and time of original data generation #p36
202 - 'DateTimeDigitized' => Exif::ASCII, # Date and time of original data generation
203 - 'SubSecTime' => Exif::ASCII, # DateTime subseconds
204 - 'SubSecTimeOriginal' => Exif::ASCII, # DateTimeOriginal subseconds
205 - 'SubSecTimeDigitized' => Exif::ASCII, # DateTimeDigitized subseconds
206 - ),
207 -
208 - # Tags relating to picture-taking conditions (p31)
209 - 'conditions' => array(
210 - 'ExposureTime' => Exif::RATIONAL, # Exposure time
211 - 'FNumber' => Exif::RATIONAL, # F Number
212 - 'ExposureProgram' => Exif::SHORT, # Exposure Program #p38
213 - 'SpectralSensitivity' => Exif::ASCII, # Spectral sensitivity
214 - 'ISOSpeedRatings' => Exif::SHORT, # ISO speed rating
215 - 'OECF' => Exif::UNDEFINED, # Optoelectronic conversion factor
216 - 'ShutterSpeedValue' => Exif::SRATIONAL, # Shutter speed
217 - 'ApertureValue' => Exif::RATIONAL, # Aperture
218 - 'BrightnessValue' => Exif::SRATIONAL, # Brightness
219 - 'ExposureBiasValue' => Exif::SRATIONAL, # Exposure bias
220 - 'MaxApertureValue' => Exif::RATIONAL, # Maximum land aperture
221 - 'SubjectDistance' => Exif::RATIONAL, # Subject distance
222 - 'MeteringMode' => Exif::SHORT, # Metering mode #p40
223 - 'LightSource' => Exif::SHORT, # Light source #p40-41
224 - 'Flash' => Exif::SHORT, # Flash #p41-42
225 - 'FocalLength' => Exif::RATIONAL, # Lens focal length
226 - 'SubjectArea' => Exif::SHORT, # Subject area
227 - 'FlashEnergy' => Exif::RATIONAL, # Flash energy
228 - 'SpatialFrequencyResponse' => Exif::UNDEFINED, # Spatial frequency response
229 - 'FocalPlaneXResolution' => Exif::RATIONAL, # Focal plane X resolution
230 - 'FocalPlaneYResolution' => Exif::RATIONAL, # Focal plane Y resolution
231 - 'FocalPlaneResolutionUnit' => Exif::SHORT, # Focal plane resolution unit #p46
232 - 'SubjectLocation' => Exif::SHORT, # Subject location
233 - 'ExposureIndex' => Exif::RATIONAL, # Exposure index
234 - 'SensingMethod' => Exif::SHORT, # Sensing method #p46
235 - 'FileSource' => Exif::UNDEFINED, # File source #p47
236 - 'SceneType' => Exif::UNDEFINED, # Scene type #p47
237 - 'CFAPattern' => Exif::UNDEFINED, # CFA pattern
238 - 'CustomRendered' => Exif::SHORT, # Custom image processing #p48
239 - 'ExposureMode' => Exif::SHORT, # Exposure mode #p48
240 - 'WhiteBalance' => Exif::SHORT, # White Balance #p49
241 - 'DigitalZoomRatio' => Exif::RATIONAL, # Digital zoom ration
242 - 'FocalLengthIn35mmFilm' => Exif::SHORT, # Focal length in 35 mm film
243 - 'SceneCaptureType' => Exif::SHORT, # Scene capture type #p49
244 - 'GainControl' => Exif::RATIONAL, # Scene control #p49-50
245 - 'Contrast' => Exif::SHORT, # Contrast #p50
246 - 'Saturation' => Exif::SHORT, # Saturation #p50
247 - 'Sharpness' => Exif::SHORT, # Sharpness #p50
248 - 'DeviceSettingDescription' => Exif::UNDEFINED, # Desice settings description
249 - 'SubjectDistanceRange' => Exif::SHORT, # Subject distance range #p51
250 - ),
251 -
252 - 'other' => array(
253 - 'ImageUniqueID' => Exif::ASCII, # Unique image ID
254 - ),
255 - ),
256 -
257 - # GPS Attribute Information (p52)
258 - 'gps' => array(
259 - 'GPSVersionID' => Exif::BYTE, # GPS tag version
260 - 'GPSLatitudeRef' => Exif::ASCII, # North or South Latitude #p52-53
261 - 'GPSLatitude' => Exif::RATIONAL, # Latitude
262 - 'GPSLongitudeRef' => Exif::ASCII, # East or West Longitude #p53
263 - 'GPSLongitude' => Exif::RATIONAL, # Longitude
264 - 'GPSAltitudeRef' => Exif::BYTE, # Altitude reference
265 - 'GPSAltitude' => Exif::RATIONAL, # Altitude
266 - 'GPSTimeStamp' => Exif::RATIONAL, # GPS time (atomic clock)
267 - 'GPSSatellites' => Exif::ASCII, # Satellites used for measurement
268 - 'GPSStatus' => Exif::ASCII, # Receiver status #p54
269 - 'GPSMeasureMode' => Exif::ASCII, # Measurement mode #p54-55
270 - 'GPSDOP' => Exif::RATIONAL, # Measurement precision
271 - 'GPSSpeedRef' => Exif::ASCII, # Speed unit #p55
272 - 'GPSSpeed' => Exif::RATIONAL, # Speed of GPS receiver
273 - 'GPSTrackRef' => Exif::ASCII, # Reference for direction of movement #p55
274 - 'GPSTrack' => Exif::RATIONAL, # Direction of movement
275 - 'GPSImgDirectionRef' => Exif::ASCII, # Reference for direction of image #p56
276 - 'GPSImgDirection' => Exif::RATIONAL, # Direction of image
277 - 'GPSMapDatum' => Exif::ASCII, # Geodetic survey data used
278 - 'GPSDestLatitudeRef' => Exif::ASCII, # Reference for latitude of destination #p56
279 - 'GPSDestLatitude' => Exif::RATIONAL, # Latitude destination
280 - 'GPSDestLongitudeRef' => Exif::ASCII, # Reference for longitude of destination #p57
281 - 'GPSDestLongitude' => Exif::RATIONAL, # Longitude of destination
282 - 'GPSDestBearingRef' => Exif::ASCII, # Reference for bearing of destination #p57
283 - 'GPSDestBearing' => Exif::RATIONAL, # Bearing of destination
284 - 'GPSDestDistanceRef' => Exif::ASCII, # Reference for distance to destination #p57-58
285 - 'GPSDestDistance' => Exif::RATIONAL, # Distance to destination
286 - 'GPSProcessingMethod' => Exif::UNDEFINED, # Name of GPS processing method
287 - 'GPSAreaInformation' => Exif::UNDEFINED, # Name of GPS area
288 - 'GPSDateStamp' => Exif::ASCII, # GPS date
289 - 'GPSDifferential' => Exif::SHORT, # GPS differential correction
290 - ),
291 - );
292 -
293 - $this->file = $file;
294 - $this->basename = wfBaseName( $this->file );
295 -
296 - $this->makeFlatExifTags();
297 -
298 - $this->debugFile( $this->basename, __FUNCTION__, true );
299 - if( function_exists( 'exif_read_data' ) ) {
300 - wfSuppressWarnings();
301 - $data = exif_read_data( $this->file );
302 - wfRestoreWarnings();
303 - } else {
304 - throw new MWException( "Internal error: exif_read_data not present. \$wgShowEXIF may be incorrectly set or not checked by an extension." );
305 - }
306 - /**
307 - * exif_read_data() will return false on invalid input, such as
308 - * when somebody uploads a file called something.jpeg
309 - * containing random gibberish.
310 - */
311 - $this->mRawExifData = $data ? $data : array();
312 -
313 - $this->makeFilteredData();
314 - $this->makeFormattedData();
315 -
316 - $this->debugFile( __FUNCTION__, false );
317 - }
318 -
319 - /**#@+
320 - * @private
321 - */
322 - /**
323 - * Generate a flat list of the exif tags
324 - */
325 - function makeFlatExifTags() {
326 - $this->extractTags( $this->mExifTags );
327 - }
328 -
329 - /**
330 - * A recursing extractor function used by makeFlatExifTags()
331 - *
332 - * Note: This used to use an array_walk function, but it made PHP5
333 - * segfault, see `cvs diff -u -r 1.4 -r 1.5 Exif.php`
334 - */
335 - function extractTags( &$tagset ) {
336 - foreach( $tagset as $key => $val ) {
337 - if( is_array( $val ) ) {
338 - $this->extractTags( $val );
339 - } else {
340 - $this->mFlatExifTags[$key] = $val;
341 - }
342 - }
343 - }
344 -
345 - /**
346 - * Make $this->mFilteredExifData
347 - */
348 - function makeFilteredData() {
349 - $this->mFilteredExifData = $this->mRawExifData;
350 -
351 - foreach( $this->mFilteredExifData as $k => $v ) {
352 - if ( !in_array( $k, array_keys( $this->mFlatExifTags ) ) ) {
353 - $this->debug( $v, __FUNCTION__, "'$k' is not a valid Exif tag" );
354 - unset( $this->mFilteredExifData[$k] );
355 - }
356 - }
357 -
358 - foreach( $this->mFilteredExifData as $k => $v ) {
359 - if ( !$this->validate($k, $v) ) {
360 - $this->debug( $v, __FUNCTION__, "'$k' contained invalid data" );
361 - unset( $this->mFilteredExifData[$k] );
362 - }
363 - }
364 - }
365 -
366 - /**
367 - * @todo document
368 - */
369 - function makeFormattedData( ) {
370 - $format = new FormatExif( $this->getFilteredData() );
371 - $this->mFormattedExifData = $format->getFormattedData();
372 - }
373 - /**#@-*/
374 -
375 - /**#@+
376 - * @return array
377 - */
378 - /**
379 - * Get $this->mRawExifData
380 - */
381 - function getData() {
382 - return $this->mRawExifData;
383 - }
384 -
385 - /**
386 - * Get $this->mFilteredExifData
387 - */
388 - function getFilteredData() {
389 - return $this->mFilteredExifData;
390 - }
391 -
392 - /**
393 - * Get $this->mFormattedExifData
394 - */
395 - function getFormattedData() {
396 - return $this->mFormattedExifData;
397 - }
398 - /**#@-*/
399 -
400 - /**
401 - * The version of the output format
402 - *
403 - * Before the actual metadata information is saved in the database we
404 - * strip some of it since we don't want to save things like thumbnails
405 - * which usually accompany Exif data. This value gets saved in the
406 - * database along with the actual Exif data, and if the version in the
407 - * database doesn't equal the value returned by this function the Exif
408 - * data is regenerated.
409 - *
410 - * @return int
411 - */
412 - public static function version() {
413 - return 1; // We don't need no bloddy constants!
414 - }
415 -
416 - /**#@+
417 - * Validates if a tag value is of the type it should be according to the Exif spec
418 - *
419 - * @private
420 - *
421 - * @param $in Mixed: the input value to check
422 - * @return bool
423 - */
424 - function isByte( $in ) {
425 - if ( !is_array( $in ) && sprintf('%d', $in) == $in && $in >= 0 && $in <= 255 ) {
426 - $this->debug( $in, __FUNCTION__, true );
427 - return true;
428 - } else {
429 - $this->debug( $in, __FUNCTION__, false );
430 - return false;
431 - }
432 - }
433 -
434 - function isASCII( $in ) {
435 - if ( is_array( $in ) ) {
436 - return false;
437 - }
438 -
439 - if ( preg_match( "/[^\x0a\x20-\x7e]/", $in ) ) {
440 - $this->debug( $in, __FUNCTION__, 'found a character not in our whitelist' );
441 - return false;
442 - }
443 -
444 - if ( preg_match( '/^\s*$/', $in ) ) {
445 - $this->debug( $in, __FUNCTION__, 'input consisted solely of whitespace' );
446 - return false;
447 - }
448 -
449 - return true;
450 - }
451 -
452 - function isShort( $in ) {
453 - if ( !is_array( $in ) && sprintf('%d', $in) == $in && $in >= 0 && $in <= 65536 ) {
454 - $this->debug( $in, __FUNCTION__, true );
455 - return true;
456 - } else {
457 - $this->debug( $in, __FUNCTION__, false );
458 - return false;
459 - }
460 - }
461 -
462 - function isLong( $in ) {
463 - if ( !is_array( $in ) && sprintf('%d', $in) == $in && $in >= 0 && $in <= 4294967296 ) {
464 - $this->debug( $in, __FUNCTION__, true );
465 - return true;
466 - } else {
467 - $this->debug( $in, __FUNCTION__, false );
468 - return false;
469 - }
470 - }
471 -
472 - function isRational( $in ) {
473 - $m = array();
474 - if ( !is_array( $in ) && @preg_match( '/^(\d+)\/(\d+[1-9]|[1-9]\d*)$/', $in, $m ) ) { # Avoid division by zero
475 - return $this->isLong( $m[1] ) && $this->isLong( $m[2] );
476 - } else {
477 - $this->debug( $in, __FUNCTION__, 'fed a non-fraction value' );
478 - return false;
479 - }
480 - }
481 -
482 - function isUndefined( $in ) {
483 - if ( !is_array( $in ) && preg_match( '/^\d{4}$/', $in ) ) { // Allow ExifVersion and FlashpixVersion
484 - $this->debug( $in, __FUNCTION__, true );
485 - return true;
486 - } else {
487 - $this->debug( $in, __FUNCTION__, false );
488 - return false;
489 - }
490 - }
491 -
492 - function isSlong( $in ) {
493 - if ( $this->isLong( abs( $in ) ) ) {
494 - $this->debug( $in, __FUNCTION__, true );
495 - return true;
496 - } else {
497 - $this->debug( $in, __FUNCTION__, false );
498 - return false;
499 - }
500 - }
501 -
502 - function isSrational( $in ) {
503 - $m = array();
504 - if ( !is_array( $in ) && preg_match( '/^(\d+)\/(\d+[1-9]|[1-9]\d*)$/', $in, $m ) ) { # Avoid division by zero
505 - return $this->isSlong( $m[0] ) && $this->isSlong( $m[1] );
506 - } else {
507 - $this->debug( $in, __FUNCTION__, 'fed a non-fraction value' );
508 - return false;
509 - }
510 - }
511 - /**#@-*/
512 -
513 - /**
514 - * Validates if a tag has a legal value according to the Exif spec
515 - *
516 - * @private
517 - *
518 - * @param $tag String: the tag to check.
519 - * @param $val Mixed: the value of the tag.
520 - * @return bool
521 - */
522 - function validate( $tag, $val ) {
523 - $debug = "tag is '$tag'";
524 - // Does not work if not typecast
525 - switch( (string)$this->mFlatExifTags[$tag] ) {
526 - case (string)Exif::BYTE:
527 - $this->debug( $val, __FUNCTION__, $debug );
528 - return $this->isByte( $val );
529 - case (string)Exif::ASCII:
530 - $this->debug( $val, __FUNCTION__, $debug );
531 - return $this->isASCII( $val );
532 - case (string)Exif::SHORT:
533 - $this->debug( $val, __FUNCTION__, $debug );
534 - return $this->isShort( $val );
535 - case (string)Exif::LONG:
536 - $this->debug( $val, __FUNCTION__, $debug );
537 - return $this->isLong( $val );
538 - case (string)Exif::RATIONAL:
539 - $this->debug( $val, __FUNCTION__, $debug );
540 - return $this->isRational( $val );
541 - case (string)Exif::UNDEFINED:
542 - $this->debug( $val, __FUNCTION__, $debug );
543 - return $this->isUndefined( $val );
544 - case (string)Exif::SLONG:
545 - $this->debug( $val, __FUNCTION__, $debug );
546 - return $this->isSlong( $val );
547 - case (string)Exif::SRATIONAL:
548 - $this->debug( $val, __FUNCTION__, $debug );
549 - return $this->isSrational( $val );
550 - case (string)Exif::SHORT.','.Exif::LONG:
551 - $this->debug( $val, __FUNCTION__, $debug );
552 - return $this->isShort( $val ) || $this->isLong( $val );
553 - default:
554 - $this->debug( $val, __FUNCTION__, "The tag '$tag' is unknown" );
555 - return false;
556 - }
557 - }
558 -
559 - /**
560 - * Convenience function for debugging output
561 - *
562 - * @private
563 - *
564 - * @param $in Mixed:
565 - * @param $fname String:
566 - * @param $action Mixed: , default NULL.
567 - */
568 - function debug( $in, $fname, $action = null ) {
569 - if ( !$this->log ) {
570 - return;
571 - }
572 - $type = gettype( $in );
573 - $class = ucfirst( __CLASS__ );
574 - if ( $type === 'array' )
575 - $in = print_r( $in, true );
576 -
577 - if ( $action === true )
578 - wfDebugLog( $this->log, "$class::$fname: accepted: '$in' (type: $type)\n");
579 - elseif ( $action === false )
580 - wfDebugLog( $this->log, "$class::$fname: rejected: '$in' (type: $type)\n");
581 - elseif ( $action === null )
582 - wfDebugLog( $this->log, "$class::$fname: input was: '$in' (type: $type)\n");
583 - else
584 - wfDebugLog( $this->log, "$class::$fname: $action (type: $type; content: '$in')\n");
585 - }
586 -
587 - /**
588 - * Convenience function for debugging output
589 - *
590 - * @private
591 - *
592 - * @param $fname String: the name of the function calling this function
593 - * @param $io Boolean: Specify whether we're beginning or ending
594 - */
595 - function debugFile( $fname, $io ) {
596 - if ( !$this->log ) {
597 - return;
598 - }
599 - $class = ucfirst( __CLASS__ );
600 - if ( $io ) {
601 - wfDebugLog( $this->log, "$class::$fname: begin processing: '{$this->basename}'\n" );
602 - } else {
603 - wfDebugLog( $this->log, "$class::$fname: end processing: '{$this->basename}'\n" );
604 - }
605 - }
606 -
607 -}
608 -
609 -/**
610 - * @todo document (e.g. one-sentence class-overview description)
611 - * @ingroup Media
612 - */
613 -class FormatExif {
614 - /**
615 - * The Exif data to format
616 - *
617 - * @var array
618 - * @private
619 - */
620 - var $mExif;
621 -
622 - /**
623 - * Constructor
624 - *
625 - * @param $exif Array: the Exif data to format ( as returned by
626 - * Exif::getFilteredData() )
627 - */
628 - function __construct( $exif ) {
629 - $this->mExif = $exif;
630 - }
631 -
632 - /**
633 - * Numbers given by Exif user agents are often magical, that is they
634 - * should be replaced by a detailed explanation depending on their
635 - * value which most of the time are plain integers. This function
636 - * formats Exif values into human readable form.
637 - *
638 - * @return array
639 - */
640 - function getFormattedData() {
641 - global $wgLang;
642 -
643 - $tags =& $this->mExif;
644 -
645 - $resolutionunit = !isset( $tags['ResolutionUnit'] ) || $tags['ResolutionUnit'] == 2 ? 2 : 3;
646 - unset( $tags['ResolutionUnit'] );
647 -
648 - foreach( $tags as $tag => $val ) {
649 - switch( $tag ) {
650 - case 'Compression':
651 - switch( $val ) {
652 - case 1: case 6:
653 - $tags[$tag] = $this->msg( $tag, $val );
654 - break;
655 - default:
656 - $tags[$tag] = $val;
657 - break;
658 - }
659 - break;
660 -
661 - case 'PhotometricInterpretation':
662 - switch( $val ) {
663 - case 2: case 6:
664 - $tags[$tag] = $this->msg( $tag, $val );
665 - break;
666 - default:
667 - $tags[$tag] = $val;
668 - break;
669 - }
670 - break;
671 -
672 - case 'Orientation':
673 - switch( $val ) {
674 - case 1: case 2: case 3: case 4: case 5: case 6: case 7: case 8:
675 - $tags[$tag] = $this->msg( $tag, $val );
676 - break;
677 - default:
678 - $tags[$tag] = $val;
679 - break;
680 - }
681 - break;
682 -
683 - case 'PlanarConfiguration':
684 - switch( $val ) {
685 - case 1: case 2:
686 - $tags[$tag] = $this->msg( $tag, $val );
687 - break;
688 - default:
689 - $tags[$tag] = $val;
690 - break;
691 - }
692 - break;
693 -
694 - // TODO: YCbCrSubSampling
695 - // TODO: YCbCrPositioning
696 -
697 - case 'XResolution':
698 - case 'YResolution':
699 - switch( $resolutionunit ) {
700 - case 2:
701 - $tags[$tag] = $this->msg( 'XYResolution', 'i', $this->formatNum( $val ) );
702 - break;
703 - case 3:
704 - $this->msg( 'XYResolution', 'c', $this->formatNum( $val ) );
705 - break;
706 - default:
707 - $tags[$tag] = $val;
708 - break;
709 - }
710 - break;
711 -
712 - // TODO: YCbCrCoefficients #p27 (see annex E)
713 - case 'ExifVersion': case 'FlashpixVersion':
714 - $tags[$tag] = "$val"/100;
715 - break;
716 -
717 - case 'ColorSpace':
718 - switch( $val ) {
719 - case 1: case 'FFFF.H':
720 - $tags[$tag] = $this->msg( $tag, $val );
721 - break;
722 - default:
723 - $tags[$tag] = $val;
724 - break;
725 - }
726 - break;
727 -
728 - case 'ComponentsConfiguration':
729 - switch( $val ) {
730 - case 0: case 1: case 2: case 3: case 4: case 5: case 6:
731 - $tags[$tag] = $this->msg( $tag, $val );
732 - break;
733 - default:
734 - $tags[$tag] = $val;
735 - break;
736 - }
737 - break;
738 -
739 - case 'DateTime':
740 - case 'DateTimeOriginal':
741 - case 'DateTimeDigitized':
742 - if( $val == '0000:00:00 00:00:00' ) {
743 - $tags[$tag] = wfMsg('exif-unknowndate');
744 - } elseif( preg_match( '/^(?:\d{4}):(?:\d\d):(?:\d\d) (?:\d\d):(?:\d\d):(?:\d\d)$/', $val ) ) {
745 - $tags[$tag] = $wgLang->timeanddate( wfTimestamp(TS_MW, $val) );
746 - }
747 - break;
748 -
749 - case 'ExposureProgram':
750 - switch( $val ) {
751 - case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7: case 8:
752 - $tags[$tag] = $this->msg( $tag, $val );
753 - break;
754 - default:
755 - $tags[$tag] = $val;
756 - break;
757 - }
758 - break;
759 -
760 - case 'SubjectDistance':
761 - $tags[$tag] = $this->msg( $tag, '', $this->formatNum( $val ) );
762 - break;
763 -
764 - case 'MeteringMode':
765 - switch( $val ) {
766 - case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7: case 255:
767 - $tags[$tag] = $this->msg( $tag, $val );
768 - break;
769 - default:
770 - $tags[$tag] = $val;
771 - break;
772 - }
773 - break;
774 -
775 - case 'LightSource':
776 - switch( $val ) {
777 - case 0: case 1: case 2: case 3: case 4: case 9: case 10: case 11:
778 - case 12: case 13: case 14: case 15: case 17: case 18: case 19: case 20:
779 - case 21: case 22: case 23: case 24: case 255:
780 - $tags[$tag] = $this->msg( $tag, $val );
781 - break;
782 - default:
783 - $tags[$tag] = $val;
784 - break;
785 - }
786 - break;
787 -
788 - case 'Flash':
789 - $flashDecode = array(
790 - 'fired' => $val & bindec( '00000001' ),
791 - 'return' => ($val & bindec( '00000110' )) >> 1,
792 - 'mode' => ($val & bindec( '00011000' )) >> 3,
793 - 'function' => ($val & bindec( '00100000' )) >> 5,
794 - 'redeye' => ($val & bindec( '01000000' )) >> 6,
795 -// 'reserved' => ($val & bindec( '10000000' )) >> 7,
796 - );
797 -
798 - # We do not need to handle unknown values since all are used.
799 - foreach( $flashDecode as $subTag => $subValue ) {
800 - # We do not need any message for zeroed values.
801 - if( $subTag != 'fired' && $subValue == 0) {
802 - continue;
803 - }
804 - $fullTag = $tag . '-' . $subTag ;
805 - $flashMsgs[] = $this->msg( $fullTag, $subValue );
806 - }
807 - $tags[$tag] = $wgLang->commaList( $flashMsgs );
808 - break;
809 -
810 - case 'FocalPlaneResolutionUnit':
811 - switch( $val ) {
812 - case 2:
813 - $tags[$tag] = $this->msg( $tag, $val );
814 - break;
815 - default:
816 - $tags[$tag] = $val;
817 - break;
818 - }
819 - break;
820 -
821 - case 'SensingMethod':
822 - switch( $val ) {
823 - case 1: case 2: case 3: case 4: case 5: case 7: case 8:
824 - $tags[$tag] = $this->msg( $tag, $val );
825 - break;
826 - default:
827 - $tags[$tag] = $val;
828 - break;
829 - }
830 - break;
831 -
832 - case 'FileSource':
833 - switch( $val ) {
834 - case 3:
835 - $tags[$tag] = $this->msg( $tag, $val );
836 - break;
837 - default:
838 - $tags[$tag] = $val;
839 - break;
840 - }
841 - break;
842 -
843 - case 'SceneType':
844 - switch( $val ) {
845 - case 1:
846 - $tags[$tag] = $this->msg( $tag, $val );
847 - break;
848 - default:
849 - $tags[$tag] = $val;
850 - break;
851 - }
852 - break;
853 -
854 - case 'CustomRendered':
855 - switch( $val ) {
856 - case 0: case 1:
857 - $tags[$tag] = $this->msg( $tag, $val );
858 - break;
859 - default:
860 - $tags[$tag] = $val;
861 - break;
862 - }
863 - break;
864 -
865 - case 'ExposureMode':
866 - switch( $val ) {
867 - case 0: case 1: case 2:
868 - $tags[$tag] = $this->msg( $tag, $val );
869 - break;
870 - default:
871 - $tags[$tag] = $val;
872 - break;
873 - }
874 - break;
875 -
876 - case 'WhiteBalance':
877 - switch( $val ) {
878 - case 0: case 1:
879 - $tags[$tag] = $this->msg( $tag, $val );
880 - break;
881 - default:
882 - $tags[$tag] = $val;
883 - break;
884 - }
885 - break;
886 -
887 - case 'SceneCaptureType':
888 - switch( $val ) {
889 - case 0: case 1: case 2: case 3:
890 - $tags[$tag] = $this->msg( $tag, $val );
891 - break;
892 - default:
893 - $tags[$tag] = $val;
894 - break;
895 - }
896 - break;
897 -
898 - case 'GainControl':
899 - switch( $val ) {
900 - case 0: case 1: case 2: case 3: case 4:
901 - $tags[$tag] = $this->msg( $tag, $val );
902 - break;
903 - default:
904 - $tags[$tag] = $val;
905 - break;
906 - }
907 - break;
908 -
909 - case 'Contrast':
910 - switch( $val ) {
911 - case 0: case 1: case 2:
912 - $tags[$tag] = $this->msg( $tag, $val );
913 - break;
914 - default:
915 - $tags[$tag] = $val;
916 - break;
917 - }
918 - break;
919 -
920 - case 'Saturation':
921 - switch( $val ) {
922 - case 0: case 1: case 2:
923 - $tags[$tag] = $this->msg( $tag, $val );
924 - break;
925 - default:
926 - $tags[$tag] = $val;
927 - break;
928 - }
929 - break;
930 -
931 - case 'Sharpness':
932 - switch( $val ) {
933 - case 0: case 1: case 2:
934 - $tags[$tag] = $this->msg( $tag, $val );
935 - break;
936 - default:
937 - $tags[$tag] = $val;
938 - break;
939 - }
940 - break;
941 -
942 - case 'SubjectDistanceRange':
943 - switch( $val ) {
944 - case 0: case 1: case 2: case 3:
945 - $tags[$tag] = $this->msg( $tag, $val );
946 - break;
947 - default:
948 - $tags[$tag] = $val;
949 - break;
950 - }
951 - break;
952 -
953 - case 'GPSLatitudeRef':
954 - case 'GPSDestLatitudeRef':
955 - switch( $val ) {
956 - case 'N': case 'S':
957 - $tags[$tag] = $this->msg( 'GPSLatitude', $val );
958 - break;
959 - default:
960 - $tags[$tag] = $val;
961 - break;
962 - }
963 - break;
964 -
965 - case 'GPSLongitudeRef':
966 - case 'GPSDestLongitudeRef':
967 - switch( $val ) {
968 - case 'E': case 'W':
969 - $tags[$tag] = $this->msg( 'GPSLongitude', $val );
970 - break;
971 - default:
972 - $tags[$tag] = $val;
973 - break;
974 - }
975 - break;
976 -
977 - case 'GPSStatus':
978 - switch( $val ) {
979 - case 'A': case 'V':
980 - $tags[$tag] = $this->msg( $tag, $val );
981 - break;
982 - default:
983 - $tags[$tag] = $val;
984 - break;
985 - }
986 - break;
987 -
988 - case 'GPSMeasureMode':
989 - switch( $val ) {
990 - case 2: case 3:
991 - $tags[$tag] = $this->msg( $tag, $val );
992 - break;
993 - default:
994 - $tags[$tag] = $val;
995 - break;
996 - }
997 - break;
998 -
999 - case 'GPSSpeedRef':
1000 - case 'GPSDestDistanceRef':
1001 - switch( $val ) {
1002 - case 'K': case 'M': case 'N':
1003 - $tags[$tag] = $this->msg( 'GPSSpeed', $val );
1004 - break;
1005 - default:
1006 - $tags[$tag] = $val;
1007 - break;
1008 - }
1009 - break;
1010 -
1011 - case 'GPSTrackRef':
1012 - case 'GPSImgDirectionRef':
1013 - case 'GPSDestBearingRef':
1014 - switch( $val ) {
1015 - case 'T': case 'M':
1016 - $tags[$tag] = $this->msg( 'GPSDirection', $val );
1017 - break;
1018 - default:
1019 - $tags[$tag] = $val;
1020 - break;
1021 - }
1022 - break;
1023 -
1024 - case 'GPSDateStamp':
1025 - $tags[$tag] = $wgLang->date( substr( $val, 0, 4 ) . substr( $val, 5, 2 ) . substr( $val, 8, 2 ) . '000000' );
1026 - break;
1027 -
1028 - // This is not in the Exif standard, just a special
1029 - // case for our purposes which enables wikis to wikify
1030 - // the make, model and software name to link to their articles.
1031 - case 'Make':
1032 - case 'Model':
1033 - case 'Software':
1034 - $tags[$tag] = $this->msg( $tag, '', $val );
1035 - break;
1036 -
1037 - case 'ExposureTime':
1038 - // Show the pretty fraction as well as decimal version
1039 - $tags[$tag] = wfMsg( 'exif-exposuretime-format',
1040 - $this->formatFraction( $val ), $this->formatNum( $val ) );
1041 - break;
1042 -
1043 - case 'FNumber':
1044 - $tags[$tag] = wfMsg( 'exif-fnumber-format',
1045 - $this->formatNum( $val ) );
1046 - break;
1047 -
1048 - case 'FocalLength':
1049 - $tags[$tag] = wfMsg( 'exif-focallength-format',
1050 - $this->formatNum( $val ) );
1051 - break;
1052 -
1053 - // Do not transform fields with pure text.
1054 - // For some languages the formatNum() conversion results to wrong output like
1055 - // foo,bar@example,com or foo٫bar@example٫com
1056 - case 'ImageDescription':
1057 - case 'Artist':
1058 - case 'Copyright':
1059 - $tags[$tag] = htmlspecialchars( $val );
1060 - break;
1061 - default:
1062 - $tags[$tag] = $this->formatNum( $val );
1063 - break;
1064 - }
1065 - }
1066 -
1067 - return $tags;
1068 - }
1069 -
1070 - /**
1071 - * Convenience function for getFormattedData()
1072 - *
1073 - * @private
1074 - *
1075 - * @param $tag String: the tag name to pass on
1076 - * @param $val String: the value of the tag
1077 - * @param $arg String: an argument to pass ($1)
1078 - * @return string A wfMsg of "exif-$tag-$val" in lower case
1079 - */
1080 - function msg( $tag, $val, $arg = null ) {
1081 - global $wgContLang;
1082 -
1083 - if ($val === '')
1084 - $val = 'value';
1085 - return wfMsg( $wgContLang->lc( "exif-$tag-$val" ), $arg );
1086 - }
1087 -
1088 - /**
1089 - * Format a number, convert numbers from fractions into floating point
1090 - * numbers
1091 - *
1092 - * @private
1093 - *
1094 - * @param $num Mixed: the value to format
1095 - * @return mixed A floating point number or whatever we were fed
1096 - */
1097 - function formatNum( $num ) {
1098 - global $wgLang;
1099 -
1100 - $m = array();
1101 - if ( preg_match( '/^(\d+)\/(\d+)$/', $num, $m ) )
1102 - return $wgLang->formatNum( $m[2] != 0 ? $m[1] / $m[2] : $num );
1103 - else
1104 - return $wgLang->formatNum( $num );
1105 - }
1106 -
1107 - /**
1108 - * Format a rational number, reducing fractions
1109 - *
1110 - * @private
1111 - *
1112 - * @param $num Mixed: the value to format
1113 - * @return mixed A floating point number or whatever we were fed
1114 - */
1115 - function formatFraction( $num ) {
1116 - $m = array();
1117 - if ( preg_match( '/^(\d+)\/(\d+)$/', $num, $m ) ) {
1118 - $numerator = intval( $m[1] );
1119 - $denominator = intval( $m[2] );
1120 - $gcd = $this->gcd( $numerator, $denominator );
1121 - if( $gcd != 0 ) {
1122 - // 0 shouldn't happen! ;)
1123 - return $this->formatNum( $numerator / $gcd ) . '/' . $this->formatNum( $denominator / $gcd );
1124 - }
1125 - }
1126 - return $this->formatNum( $num );
1127 - }
1128 -
1129 - /**
1130 - * Calculate the greatest common divisor of two integers.
1131 - *
1132 - * @param $a Integer: FIXME
1133 - * @param $b Integer: FIXME
1134 - * @return int
1135 - * @private
1136 - */
1137 - function gcd( $a, $b ) {
1138 - /*
1139 - // http://en.wikipedia.org/wiki/Euclidean_algorithm
1140 - // Recursive form would be:
1141 - if( $b == 0 )
1142 - return $a;
1143 - else
1144 - return gcd( $b, $a % $b );
1145 - */
1146 - while( $b != 0 ) {
1147 - $remainder = $a % $b;
1148 -
1149 - // tail recursion...
1150 - $a = $b;
1151 - $b = $remainder;
1152 - }
1153 - return $a;
1154 - }
1155 -}
Index: trunk/phase3/includes/ImagePage.php
@@ -268,7 +268,7 @@
269269 * FIXME: bad interface, see note on MediaHandler::formatMetadata().
270270 *
271271 * @param $metadata Array: the array containing the EXIF data
272 - * @return String
 272+ * @return String The metadata table. This is treated as Wikitext (!)
273273 */
274274 protected function makeMetadataTable( $metadata ) {
275275 $r = "<div class=\"mw-imagepage-section-metadata\">";
Index: trunk/phase3/includes/filerepo/LocalFile.php
@@ -336,6 +336,7 @@
337337 * Upgrade a row if it needs it
338338 */
339339 function maybeUpgradeRow() {
 340+ global $wgUpdateCompatibleMetadata;
340341 if ( wfReadOnly() ) {
341342 return;
342343 }
@@ -347,9 +348,14 @@
348349 $this->upgraded = true;
349350 } else {
350351 $handler = $this->getHandler();
351 - if ( $handler && !$handler->isMetadataValid( $this, $this->metadata ) ) {
352 - $this->upgradeRow();
353 - $this->upgraded = true;
 352+ if ( $handler ) {
 353+ $validity = $handler->isMetadataValid( $this, $this->metadata );
 354+ if ( $validity === MediaHandler::METADATA_BAD
 355+ || ( $validity === MediaHandler::METADATA_COMPATIBLE && $wgUpdateCompatibleMetadata )
 356+ ) {
 357+ $this->upgradeRow();
 358+ $this->upgraded = true;
 359+ }
354360 }
355361 }
356362 }
Index: trunk/phase3/includes/filerepo/File.php
@@ -304,6 +304,26 @@
305305 public function getMetadata() { return false; }
306306
307307 /**
 308+ * get versioned metadata
 309+ *
 310+ * @param $metadata Mixed Array or String of (serialized) metadata
 311+ * @param $version integer version number.
 312+ * @return Array containing metadata, or what was passed to it on fail (unserializing if not array)
 313+ */
 314+ public function convertMetadataVersion($metadata, $version) {
 315+ $handler = $this->getHandler();
 316+ if (!is_array($metadata)) {
 317+ //just to make the return type consistant
 318+ $metadata = unserialize( $metadata );
 319+ }
 320+ if ( $handler ) {
 321+ return $handler->convertMetadataVersion($metadata, $version);
 322+ } else {
 323+ return $metadata;
 324+ }
 325+ }
 326+
 327+ /**
308328 * Return the bit depth of the file
309329 * Overridden by LocalFile
310330 * STUB
Index: trunk/phase3/includes/filerepo/ForeignAPIFile.php
@@ -38,7 +38,9 @@
3939 $data = $repo->fetchImageQuery( array(
4040 'titles' => 'File:' . $title->getDBKey(),
4141 'iiprop' => self::getProps(),
42 - 'prop' => 'imageinfo' ) );
 42+ 'prop' => 'imageinfo',
 43+ 'iimetadataversion' => mediaHandler::getMetadataVersion()
 44+ ) );
4345
4446 $info = $repo->getImageInfo( $data );
4547
Index: trunk/phase3/includes/api/ApiQueryImageInfo.php
@@ -121,7 +121,8 @@
122122 $gotOne = true;
123123
124124 $fit = $this->addPageSubItem( $pageId,
125 - self::getInfo( $img, $prop, $result, $finalThumbParams ) );
 125+ self::getInfo( $img, $prop, $result,
 126+ $finalThumbParams, $params['metadataversion'] ) );
126127 if ( !$fit ) {
127128 if ( count( $pageIds[NS_IMAGE] ) == 1 ) {
128129 // See the 'the user is screwed' comment above
@@ -150,7 +151,8 @@
151152 break;
152153 }
153154 $fit = $this->addPageSubItem( $pageId,
154 - self::getInfo( $oldie, $prop, $result, $finalThumbParams ) );
 155+ self::getInfo( $oldie, $prop, $result,
 156+ $finalThumbParams, $params['metadataversion'] ) );
155157 if ( !$fit ) {
156158 if ( count( $pageIds[NS_IMAGE] ) == 1 ) {
157159 $this->setContinueEnumParameter( 'start',
@@ -265,9 +267,10 @@
266268 * @param $prop Array of properties to get (in the keys)
267269 * @param $result ApiResult object
268270 * @param $thumbParams Array containing 'width' and 'height' items, or null
 271+ * @param $version Version of image metadata (for things like jpeg which have different versions).
269272 * @return Array: result array
270273 */
271 - static function getInfo( $file, $prop, $result, $thumbParams = null ) {
 274+ static function getInfo( $file, $prop, $result, $thumbParams = null, $version = 'latest' ) {
272275 $vals = array();
273276 // Timestamp is shown even if the file is revdelete'd in interface
274277 // so do same here.
@@ -376,8 +379,11 @@
377380 }
378381
379382 if ( $meta ) {
380 - $metadata = $file->getMetadata();
381 - $vals['metadata'] = $metadata ? self::processMetaData( unserialize( $metadata ), $result ) : null;
 383+ $metadata = unserialize( $file->getMetadata() );
 384+ if ( $version !== 'latest' ) {
 385+ $metadata = $file->convertMetadataVersion( $metadata, $version );
 386+ }
 387+ $vals['metadata'] = $metadata ? self::processMetaData( $metadata, $result ) : null;
382388 }
383389
384390 if ( $mime ) {
@@ -463,6 +469,10 @@
464470 ApiBase::PARAM_TYPE => 'integer',
465471 ApiBase::PARAM_DFLT => -1
466472 ),
 473+ 'metadataversion' => array(
 474+ ApiBase::PARAM_TYPE => 'string',
 475+ ApiBase::PARAM_DFLT => '1',
 476+ ),
467477 'urlparam' => array(
468478 ApiBase::PARAM_DFLT => '',
469479 ApiBase::PARAM_TYPE => 'string',
@@ -540,6 +550,8 @@
541551 'limit' => 'How many image revisions to return',
542552 'start' => 'Timestamp to start listing from',
543553 'end' => 'Timestamp to stop listing at',
 554+ 'metadataversion' => array( "Version of metadata to use. if 'latest' is specified, use latest version.",
 555+ "Defaults to '1' for backwards compatibility" ),
544556 'continue' => 'If the query response includes a continue value, use it here to get another page of results'
545557 );
546558 }
Index: trunk/phase3/includes/media/FormatMetadata.php
@@ -0,0 +1,1352 @@
 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, 2010 Brian Wolff
 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 unordered 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+ . ':' . str_pad( intval( $m[0] / $m[1] ), 2, '0', STR_PAD_LEFT )
 104+ . ':' . str_pad( intval( $s[0] / $s[1] ), 2, '0', STR_PAD_LEFT );
 105+
 106+ $time = wfTimestamp( TS_MW, '1971:01:01 ' . $tags[$tag] );
 107+ // the 1971:01:01 is just a placeholder, and not shown to user.
 108+ if ( $time ) {
 109+ $tags[$tag] = $wgLang->time( $time );
 110+ }
 111+ continue;
 112+ }
 113+
 114+ // The contact info is a multi-valued field
 115+ // instead of the other props which are single
 116+ // valued (mostly) so handle as a special case.
 117+ if ( $tag === 'Contact' ) {
 118+ $vals = self::collapseContactInfo( $vals );
 119+ continue;
 120+ }
 121+
 122+ foreach ( $vals as &$val ) {
 123+
 124+ switch( $tag ) {
 125+ case 'Compression':
 126+ switch( $val ) {
 127+ case 1: case 6:
 128+ $val = self::msg( $tag, $val );
 129+ break;
 130+ default:
 131+ $val = $val;
 132+ break;
 133+ }
 134+ break;
 135+
 136+ case 'PhotometricInterpretation':
 137+ switch( $val ) {
 138+ case 2: case 6:
 139+ $val = self::msg( $tag, $val );
 140+ break;
 141+ default:
 142+ $val = $val;
 143+ break;
 144+ }
 145+ break;
 146+
 147+ case 'Orientation':
 148+ switch( $val ) {
 149+ case 1: case 2: case 3: case 4: case 5: case 6: case 7: case 8:
 150+ $val = self::msg( $tag, $val );
 151+ break;
 152+ default:
 153+ $val = $val;
 154+ break;
 155+ }
 156+ break;
 157+
 158+ case 'PlanarConfiguration':
 159+ switch( $val ) {
 160+ case 1: case 2:
 161+ $val = self::msg( $tag, $val );
 162+ break;
 163+ default:
 164+ $val = $val;
 165+ break;
 166+ }
 167+ break;
 168+
 169+ // TODO: YCbCrSubSampling
 170+ case 'YCbCrPositioning':
 171+ switch ( $val ) {
 172+ case 1:
 173+ case 2:
 174+ $val = self::msg( $tag, $val );
 175+ break;
 176+ default:
 177+ $val = $val;
 178+ break;
 179+ }
 180+ break;
 181+
 182+ case 'XResolution':
 183+ case 'YResolution':
 184+ switch( $resolutionunit ) {
 185+ case 2:
 186+ $val = self::msg( 'XYResolution', 'i', self::formatNum( $val ) );
 187+ break;
 188+ case 3:
 189+ $val = self::msg( 'XYResolution', 'c', self::formatNum( $val ) );
 190+ break;
 191+ default:
 192+ $val = $val;
 193+ break;
 194+ }
 195+ break;
 196+
 197+ // TODO: YCbCrCoefficients #p27 (see annex E)
 198+ case 'ExifVersion': case 'FlashpixVersion':
 199+ $val = "$val" / 100;
 200+ break;
 201+
 202+ case 'ColorSpace':
 203+ switch( $val ) {
 204+ case 1: case 65535:
 205+ $val = self::msg( $tag, $val );
 206+ break;
 207+ default:
 208+ $val = $val;
 209+ break;
 210+ }
 211+ break;
 212+
 213+ case 'ComponentsConfiguration':
 214+ switch( $val ) {
 215+ case 0: case 1: case 2: case 3: case 4: case 5: case 6:
 216+ $val = self::msg( $tag, $val );
 217+ break;
 218+ default:
 219+ $val = $val;
 220+ break;
 221+ }
 222+ break;
 223+
 224+ case 'DateTime':
 225+ case 'DateTimeOriginal':
 226+ case 'DateTimeDigitized':
 227+ case 'DateTimeReleased':
 228+ case 'DateTimeExpires':
 229+ case 'GPSDateStamp':
 230+ case 'dc-date':
 231+ case 'DateTimeMetadata':
 232+ if ( $val == '0000:00:00 00:00:00' || $val == ' : : : : ' ) {
 233+ $val = wfMsg( 'exif-unknowndate' );
 234+ } elseif ( preg_match( '/^(?:\d{4}):(?:\d\d):(?:\d\d) (?:\d\d):(?:\d\d):(?:\d\d)$/D', $val ) ) {
 235+ $time = wfTimestamp( TS_MW, $val );
 236+ if ( $time ) {
 237+ $val = $wgLang->timeanddate( $time );
 238+ }
 239+ } elseif ( preg_match( '/^(?:\d{4}):(?:\d\d):(?:\d\d)$/D', $val ) ) {
 240+ // If only the date but not the time is filled in.
 241+ $time = wfTimestamp( TS_MW, substr( $val, 0, 4 )
 242+ . substr( $val, 5, 2 )
 243+ . substr( $val, 8, 2 )
 244+ . '000000' );
 245+ if ( $time ) {
 246+ $val = $wgLang->date( $time );
 247+ }
 248+ }
 249+ // else it will just output $val without formatting it.
 250+ break;
 251+
 252+ case 'ExposureProgram':
 253+ switch( $val ) {
 254+ case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7: case 8:
 255+ $val = self::msg( $tag, $val );
 256+ break;
 257+ default:
 258+ $val = $val;
 259+ break;
 260+ }
 261+ break;
 262+
 263+ case 'SubjectDistance':
 264+ $val = self::msg( $tag, '', self::formatNum( $val ) );
 265+ break;
 266+
 267+ case 'MeteringMode':
 268+ switch( $val ) {
 269+ case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7: case 255:
 270+ $val = self::msg( $tag, $val );
 271+ break;
 272+ default:
 273+ $val = $val;
 274+ break;
 275+ }
 276+ break;
 277+
 278+ case 'LightSource':
 279+ switch( $val ) {
 280+ case 0: case 1: case 2: case 3: case 4: case 9: case 10: case 11:
 281+ case 12: case 13: case 14: case 15: case 17: case 18: case 19: case 20:
 282+ case 21: case 22: case 23: case 24: case 255:
 283+ $val = self::msg( $tag, $val );
 284+ break;
 285+ default:
 286+ $val = $val;
 287+ break;
 288+ }
 289+ break;
 290+
 291+ case 'Flash':
 292+ $flashDecode = array(
 293+ 'fired' => $val & bindec( '00000001' ),
 294+ 'return' => ( $val & bindec( '00000110' ) ) >> 1,
 295+ 'mode' => ( $val & bindec( '00011000' ) ) >> 3,
 296+ 'function' => ( $val & bindec( '00100000' ) ) >> 5,
 297+ 'redeye' => ( $val & bindec( '01000000' ) ) >> 6,
 298+// 'reserved' => ($val & bindec( '10000000' )) >> 7,
 299+ );
 300+
 301+ # We do not need to handle unknown values since all are used.
 302+ foreach ( $flashDecode as $subTag => $subValue ) {
 303+ # We do not need any message for zeroed values.
 304+ if ( $subTag != 'fired' && $subValue == 0 ) {
 305+ continue;
 306+ }
 307+ $fullTag = $tag . '-' . $subTag ;
 308+ $flashMsgs[] = self::msg( $fullTag, $subValue );
 309+ }
 310+ $val = $wgLang->commaList( $flashMsgs );
 311+ break;
 312+
 313+ case 'FocalPlaneResolutionUnit':
 314+ switch( $val ) {
 315+ case 2:
 316+ $val = self::msg( $tag, $val );
 317+ break;
 318+ default:
 319+ $val = $val;
 320+ break;
 321+ }
 322+ break;
 323+
 324+ case 'SensingMethod':
 325+ switch( $val ) {
 326+ case 1: case 2: case 3: case 4: case 5: case 7: case 8:
 327+ $val = self::msg( $tag, $val );
 328+ break;
 329+ default:
 330+ $val = $val;
 331+ break;
 332+ }
 333+ break;
 334+
 335+ case 'FileSource':
 336+ switch( $val ) {
 337+ case 3:
 338+ $val = self::msg( $tag, $val );
 339+ break;
 340+ default:
 341+ $val = $val;
 342+ break;
 343+ }
 344+ break;
 345+
 346+ case 'SceneType':
 347+ switch( $val ) {
 348+ case 1:
 349+ $val = self::msg( $tag, $val );
 350+ break;
 351+ default:
 352+ $val = $val;
 353+ break;
 354+ }
 355+ break;
 356+
 357+ case 'CustomRendered':
 358+ switch( $val ) {
 359+ case 0: case 1:
 360+ $val = self::msg( $tag, $val );
 361+ break;
 362+ default:
 363+ $val = $val;
 364+ break;
 365+ }
 366+ break;
 367+
 368+ case 'ExposureMode':
 369+ switch( $val ) {
 370+ case 0: case 1: case 2:
 371+ $val = self::msg( $tag, $val );
 372+ break;
 373+ default:
 374+ $val = $val;
 375+ break;
 376+ }
 377+ break;
 378+
 379+ case 'WhiteBalance':
 380+ switch( $val ) {
 381+ case 0: case 1:
 382+ $val = self::msg( $tag, $val );
 383+ break;
 384+ default:
 385+ $val = $val;
 386+ break;
 387+ }
 388+ break;
 389+
 390+ case 'SceneCaptureType':
 391+ switch( $val ) {
 392+ case 0: case 1: case 2: case 3:
 393+ $val = self::msg( $tag, $val );
 394+ break;
 395+ default:
 396+ $val = $val;
 397+ break;
 398+ }
 399+ break;
 400+
 401+ case 'GainControl':
 402+ switch( $val ) {
 403+ case 0: case 1: case 2: case 3: case 4:
 404+ $val = self::msg( $tag, $val );
 405+ break;
 406+ default:
 407+ $val = $val;
 408+ break;
 409+ }
 410+ break;
 411+
 412+ case 'Contrast':
 413+ switch( $val ) {
 414+ case 0: case 1: case 2:
 415+ $val = self::msg( $tag, $val );
 416+ break;
 417+ default:
 418+ $val = $val;
 419+ break;
 420+ }
 421+ break;
 422+
 423+ case 'Saturation':
 424+ switch( $val ) {
 425+ case 0: case 1: case 2:
 426+ $val = self::msg( $tag, $val );
 427+ break;
 428+ default:
 429+ $val = $val;
 430+ break;
 431+ }
 432+ break;
 433+
 434+ case 'Sharpness':
 435+ switch( $val ) {
 436+ case 0: case 1: case 2:
 437+ $val = self::msg( $tag, $val );
 438+ break;
 439+ default:
 440+ $val = $val;
 441+ break;
 442+ }
 443+ break;
 444+
 445+ case 'SubjectDistanceRange':
 446+ switch( $val ) {
 447+ case 0: case 1: case 2: case 3:
 448+ $val = self::msg( $tag, $val );
 449+ break;
 450+ default:
 451+ $val = $val;
 452+ break;
 453+ }
 454+ break;
 455+
 456+ //The GPS...Ref values are kept for compatibility, probably won't be reached.
 457+ case 'GPSLatitudeRef':
 458+ case 'GPSDestLatitudeRef':
 459+ switch( $val ) {
 460+ case 'N': case 'S':
 461+ $val = self::msg( 'GPSLatitude', $val );
 462+ break;
 463+ default:
 464+ $val = $val;
 465+ break;
 466+ }
 467+ break;
 468+
 469+ case 'GPSLongitudeRef':
 470+ case 'GPSDestLongitudeRef':
 471+ switch( $val ) {
 472+ case 'E': case 'W':
 473+ $val = self::msg( 'GPSLongitude', $val );
 474+ break;
 475+ default:
 476+ $val = $val;
 477+ break;
 478+ }
 479+ break;
 480+
 481+ case 'GPSAltitude':
 482+ if ( $val < 0 ) {
 483+ $val = self::msg( 'GPSAltitude', 'below-sealevel', self::formatNum( -$val, 3 ) );
 484+ } else {
 485+ $val = self::msg( 'GPSAltitude', 'above-sealevel', self::formatNum( $val, 3 ) );
 486+ }
 487+ break;
 488+
 489+ case 'GPSStatus':
 490+ switch( $val ) {
 491+ case 'A': case 'V':
 492+ $val = self::msg( $tag, $val );
 493+ break;
 494+ default:
 495+ $val = $val;
 496+ break;
 497+ }
 498+ break;
 499+
 500+ case 'GPSMeasureMode':
 501+ switch( $val ) {
 502+ case 2: case 3:
 503+ $val = self::msg( $tag, $val );
 504+ break;
 505+ default:
 506+ $val = $val;
 507+ break;
 508+ }
 509+ break;
 510+
 511+
 512+ case 'GPSTrackRef':
 513+ case 'GPSImgDirectionRef':
 514+ case 'GPSDestBearingRef':
 515+ switch( $val ) {
 516+ case 'T': case 'M':
 517+ $val = self::msg( 'GPSDirection', $val );
 518+ break;
 519+ default:
 520+ $val = $val;
 521+ break;
 522+ }
 523+ break;
 524+
 525+ case 'GPSLatitude':
 526+ case 'GPSDestLatitude':
 527+ $val = self::formatCoords( $val, 'latitude' );
 528+ break;
 529+ case 'GPSLongitude':
 530+ case 'GPSDestLongitude':
 531+ $val = self::formatCoords( $val, 'longitude' );
 532+ break;
 533+
 534+ case 'GPSSpeedRef':
 535+ switch( $val ) {
 536+ case 'K': case 'M': case 'N':
 537+ $val = self::msg( 'GPSSpeed', $val );
 538+ break;
 539+ default:
 540+ $val = $val;
 541+ break;
 542+ }
 543+ break;
 544+
 545+ case 'GPSDestDistanceRef':
 546+ switch( $val ) {
 547+ case 'K': case 'M': case 'N':
 548+ $val = self::msg( 'GPSDestDistance', $val );
 549+ break;
 550+ default:
 551+ $val = $val;
 552+ break;
 553+ }
 554+ break;
 555+
 556+ case 'GPSDOP':
 557+ // See http://en.wikipedia.org/wiki/Dilution_of_precision_(GPS)
 558+ if ( $val <= 2 ) {
 559+ $val = self::msg( $tag, 'excellent', self::formatNum( $val ) );
 560+ } elseif ( $val <= 5 ) {
 561+ $val = self::msg( $tag, 'good', self::formatNum( $val ) );
 562+ } elseif ( $val <= 10 ) {
 563+ $val = self::msg( $tag, 'moderate', self::formatNum( $val ) );
 564+ } elseif ( $val <= 20 ) {
 565+ $val = self::msg( $tag, 'fair', self::formatNum( $val ) );
 566+ } else {
 567+ $val = self::msg( $tag, 'poor', self::formatNum( $val ) );
 568+ }
 569+ break;
 570+
 571+
 572+
 573+ // This is not in the Exif standard, just a special
 574+ // case for our purposes which enables wikis to wikify
 575+ // the make, model and software name to link to their articles.
 576+ case 'Make':
 577+ case 'Model':
 578+ $val = self::msg( $tag, '', $val );
 579+ break;
 580+
 581+ case 'Software':
 582+ if ( is_array( $val ) ) {
 583+ //if its a software, version array.
 584+ $val = wfMsg( 'exif-software-version-value', $val[0], $val[1] );
 585+ } else {
 586+ $val = self::msg( $tag, '', $val );
 587+ }
 588+ break;
 589+
 590+ case 'ExposureTime':
 591+ // Show the pretty fraction as well as decimal version
 592+ $val = wfMsg( 'exif-exposuretime-format',
 593+ self::formatFraction( $val ), self::formatNum( $val ) );
 594+ break;
 595+ case 'ISOSpeedRatings':
 596+ // If its = 65535 that means its at the
 597+ // limit of the size of Exif::short and
 598+ // is really higher.
 599+ if ( $val == '65535' ) {
 600+ $val = self::msg( $tag, 'overflow' );
 601+ } else {
 602+ $val = self::formatNum( $val );
 603+ }
 604+ break;
 605+ case 'FNumber':
 606+ $val = wfMsg( 'exif-fnumber-format',
 607+ self::formatNum( $val ) );
 608+ break;
 609+
 610+ case 'FocalLength': case 'FocalLengthIn35mmFilm':
 611+ $val = wfMsg( 'exif-focallength-format',
 612+ self::formatNum( $val ) );
 613+ break;
 614+
 615+ case 'MaxApertureValue':
 616+ if ( strpos( $val, '/' ) !== false ) {
 617+ // need to expand this earlier to calculate fNumber
 618+ list($n, $d) = explode('/', $val);
 619+ if ( is_numeric( $n ) && is_numeric( $d ) ) {
 620+ $val = $n / $d;
 621+ }
 622+ }
 623+ if ( is_numeric( $val ) ) {
 624+ $fNumber = pow( 2, $val / 2 );
 625+ if ( $fNumber !== false ) {
 626+ $val = wfMsg( 'exif-maxaperturevalue-value',
 627+ self::formatNum( $val ),
 628+ self::formatNum( $fNumber, 2 )
 629+ );
 630+ }
 631+ }
 632+ break;
 633+
 634+ case 'iimCategory':
 635+ switch( strtolower( $val ) ) {
 636+ // See pg 29 of IPTC photo
 637+ // metadata standard.
 638+ case 'ace': case 'clj':
 639+ case 'dis': case 'fin':
 640+ case 'edu': case 'evn':
 641+ case 'hth': case 'hum':
 642+ case 'lab': case 'lif':
 643+ case 'pol': case 'rel':
 644+ case 'sci': case 'soi':
 645+ case 'spo': case 'war':
 646+ case 'wea':
 647+ $val = self::msg(
 648+ 'iimcategory',
 649+ $val
 650+ );
 651+ }
 652+ break;
 653+ case 'SubjectNewsCode':
 654+ // Essentially like iimCategory.
 655+ // 8 (numeric) digit hierarchical
 656+ // classification. We decode the
 657+ // first 2 digits, which provide
 658+ // a broad category.
 659+ $val = self::convertNewsCode( $val );
 660+ break;
 661+ case 'Urgency':
 662+ // 1-8 with 1 being highest, 5 normal
 663+ // 0 is reserved, and 9 is 'user-defined'.
 664+ $urgency = '';
 665+ if ( $val == 0 || $val == 9 ) {
 666+ $urgency = 'other';
 667+ } elseif ( $val < 5 && $val > 1 ) {
 668+ $urgency = 'high';
 669+ } elseif ( $val == 5 ) {
 670+ $urgency = 'normal';
 671+ } elseif ( $val <= 8 && $val > 5) {
 672+ $urgency = 'low';
 673+ }
 674+
 675+ if ( $urgency !== '' ) {
 676+ $val = self::msg( 'urgency',
 677+ $urgency, $val
 678+ );
 679+ }
 680+ break;
 681+
 682+ // Things that have a unit of pixels.
 683+ case 'OriginalImageHeight':
 684+ case 'OriginalImageWidth':
 685+ case 'PixelXDimension':
 686+ case 'PixelYDimension':
 687+ case 'ImageWidth':
 688+ case 'ImageLength':
 689+ $val = self::formatNum( $val ) . ' ' . wfMsg( 'unit-pixel' );
 690+ break;
 691+
 692+ // Do not transform fields with pure text.
 693+ // For some languages the formatNum()
 694+ // conversion results to wrong output like
 695+ // foo,bar@example,com or foo٫bar@example٫com.
 696+ // Also some 'numeric' things like Scene codes
 697+ // are included here as we really don't want
 698+ // commas inserted.
 699+ case 'ImageDescription':
 700+ case 'Artist':
 701+ case 'Copyright':
 702+ case 'RelatedSoundFile':
 703+ case 'ImageUniqueID':
 704+ case 'SpectralSensitivity':
 705+ case 'GPSSatellites':
 706+ case 'GPSVersionID':
 707+ case 'GPSMapDatum':
 708+ case 'Keywords':
 709+ case 'WorldRegionDest':
 710+ case 'CountryDest':
 711+ case 'CountryCodeDest':
 712+ case 'ProvinceOrStateDest':
 713+ case 'CityDest':
 714+ case 'SublocationDest':
 715+ case 'WorldRegionCreated':
 716+ case 'CountryCreated':
 717+ case 'CountryCodeCreated':
 718+ case 'ProvinceOrStateCreated':
 719+ case 'CityCreated':
 720+ case 'SublocationCreated':
 721+ case 'ObjectName':
 722+ case 'SpecialInstructions':
 723+ case 'Headline':
 724+ case 'Credit':
 725+ case 'Source':
 726+ case 'EditStatus':
 727+ case 'FixtureIdentifier':
 728+ case 'LocationDest':
 729+ case 'LocationDestCode':
 730+ case 'Writer':
 731+ case 'JPEGFileComment':
 732+ case 'iimSupplementalCategory':
 733+ case 'OriginalTransmissionRef':
 734+ case 'Identifier':
 735+ case 'dc-contributor':
 736+ case 'dc-coverage':
 737+ case 'dc-publisher':
 738+ case 'dc-relation':
 739+ case 'dc-rights':
 740+ case 'dc-source':
 741+ case 'dc-type':
 742+ case 'Lens':
 743+ case 'SerialNumber':
 744+ case 'CameraOwnerName':
 745+ case 'Label':
 746+ case 'Nickname':
 747+ case 'RightsCertificate':
 748+ case 'CopyrightOwner':
 749+ case 'UsageTerms':
 750+ case 'WebStatement':
 751+ case 'OriginalDocumentID':
 752+ case 'LicenseUrl':
 753+ case 'MorePermissionsUrl':
 754+ case 'AttributionUrl':
 755+ case 'PreferredAttributionName':
 756+ case 'PNGFileComment':
 757+ case 'Disclaimer':
 758+ case 'ContentWarning':
 759+ case 'GIFFileComment':
 760+ case 'SceneCode':
 761+ case 'IntellectualGenre':
 762+ case 'Event':
 763+ case 'OrginisationInImage':
 764+ case 'PersonInImage':
 765+
 766+ $val = htmlspecialchars( $val );
 767+ break;
 768+
 769+ case 'ObjectCycle':
 770+ switch ( $val ) {
 771+ case 'a': case 'p': case 'b':
 772+ $val = self::msg( $tag, $val );
 773+ break;
 774+ default:
 775+ $val = htmlspecialchars( $val );
 776+ break;
 777+ }
 778+ break;
 779+ case 'Copyrighted':
 780+ switch( $val ) {
 781+ case 'True': case 'False':
 782+ $val = self::msg( $tag, $val );
 783+ break;
 784+ }
 785+ break;
 786+ case 'Rating':
 787+ if ( $val == '-1' ) {
 788+ $val = self::msg( $tag, 'rejected' );
 789+ } else {
 790+ $val = self::formatNum( $val );
 791+ }
 792+ break;
 793+
 794+ case 'LanguageCode':
 795+ $lang = $wgLang->getLanguageName( strtolower( $val ) );
 796+ if ($lang) {
 797+ $val = htmlspecialchars( $lang );
 798+ } else {
 799+ $val = htmlspecialchars( $val );
 800+ }
 801+ break;
 802+
 803+ default:
 804+ $val = self::formatNum( $val );
 805+ break;
 806+ }
 807+ }
 808+ // End formatting values, start flattening arrays.
 809+ $vals = self::flattenArray( $vals, $type );
 810+
 811+ }
 812+ return $tags;
 813+ }
 814+
 815+ /**
 816+ * A function to collapse multivalued tags into a single value.
 817+ * This turns an array of (for example) authors into a bulleted list.
 818+ *
 819+ * This is public on the basis it might be useful outside of this class.
 820+ *
 821+ * @param $vals Array array of values
 822+ * @param $type String Type of array (either lang, ul, ol).
 823+ * lang = language assoc array with keys being the lang code
 824+ * ul = unordered list, ol = ordered list
 825+ * type can also come from the '_type' member of $vals.
 826+ * @param $noHtml Boolean If to avoid returning anything resembling
 827+ * html. (Ugly hack for backwards compatibility with old mediawiki).
 828+ * @return String single value (in wiki-syntax).
 829+ */
 830+ public static function flattenArray( $vals, $type = 'ul', $noHtml = false ) {
 831+ if ( isset( $vals['_type'] ) ) {
 832+ $type = $vals['_type'];
 833+ unset( $vals['_type'] );
 834+ }
 835+
 836+ if ( !is_array( $vals ) ) {
 837+ return $vals; // do nothing if not an array;
 838+ }
 839+ elseif ( count( $vals ) === 1 && $type !== 'lang' ) {
 840+ return $vals[0];
 841+ }
 842+ elseif ( count( $vals ) === 0 ) {
 843+ return ""; // paranoia. This should never happen
 844+ wfDebug( __METHOD__ . ' metadata array with 0 elements!' );
 845+ }
 846+ /* Fixme: This should hide some of the list entries if there are
 847+ * say more than four. Especially if a field is translated into 20
 848+ * languages, we don't want to show them all by default
 849+ */
 850+ else {
 851+ switch( $type ) {
 852+ case 'lang':
 853+ global $wgContLang;
 854+ // Display default, followed by ContLang,
 855+ // followed by the rest in no particular
 856+ // order.
 857+
 858+ // Todo: hide some items if really long list.
 859+
 860+ $content = '';
 861+
 862+ $cLang = $wgContLang->getCode();
 863+ $defaultItem = false;
 864+ $defaultLang = false;
 865+
 866+ // If default is set, save it for later,
 867+ // as we don't know if it's equal to
 868+ // one of the lang codes. (In xmp
 869+ // you specify the language for a
 870+ // default property by having both
 871+ // a default prop, and one in the language
 872+ // that are identical)
 873+ if ( isset( $vals['x-default'] ) ) {
 874+ $defaultItem = $vals['x-default'];
 875+ unset( $vals['x-default'] );
 876+ }
 877+ // Do contentLanguage.
 878+ if ( isset( $vals[$cLang] ) ) {
 879+ $isDefault = false;
 880+ if ( $vals[$cLang] === $defaultItem ) {
 881+ $defaultItem = false;
 882+ $isDefault = true;
 883+ }
 884+ $content .= self::langItem(
 885+ $vals[$cLang], $cLang,
 886+ $isDefault, $noHtml );
 887+
 888+ unset( $vals[$cLang] );
 889+ }
 890+
 891+ // Now do the rest.
 892+ foreach ( $vals as $lang => $item ) {
 893+ if ( $item === $defaultItem ) {
 894+ $defaultLang = $lang;
 895+ continue;
 896+ }
 897+ $content .= self::langItem( $item,
 898+ $lang, false, $noHtml );
 899+ }
 900+ if ( $defaultItem !== false ) {
 901+ $content = self::langItem( $defaultItem,
 902+ $defaultLang, true, $noHtml )
 903+ . $content;
 904+ }
 905+ if ( $noHtml ) {
 906+ return $content;
 907+ }
 908+ return '<ul class="metadata-langlist">' .
 909+ $content .
 910+ '</ul>';
 911+ case 'ol':
 912+ if ( $noHtml ) {
 913+ return "\n#" . implode( "\n#", $vals );
 914+ }
 915+ return "<ol><li>" . implode( "</li>\n<li>", $vals ) . '</li></ol>';
 916+ case 'ul':
 917+ default:
 918+ if ( $noHtml ) {
 919+ return "\n*" . implode( "\n*", $vals );
 920+ }
 921+ return "<ul><li>" . implode( "</li>\n<li>", $vals ) . '</li></ul>';
 922+ }
 923+ }
 924+ }
 925+ /** Helper function for creating lists of translations.
 926+ *
 927+ * @param $value String value (this is not escaped)
 928+ * @param $lang String lang code of item or false
 929+ * @param $default Boolean if it is default value.
 930+ * @param $noHtml Boolean If to avoid html (for back-compat)
 931+ * @return language item (Note: despite how this looks,
 932+ * this is treated as wikitext not html).
 933+ */
 934+ private static function langItem( $value, $lang, $default = false, $noHtml = false ) {
 935+ global $wgContLang;
 936+ if ( $lang === false && $default === false) {
 937+ throw new MWException('$lang and $default cannot both '
 938+ . 'be false.');
 939+ }
 940+
 941+ if ( $noHtml ) {
 942+ $wrappedValue = $value;
 943+ } else {
 944+ $wrappedValue = '<span class="mw-metadata-lang-value">'
 945+ . $value . '</span>';
 946+ }
 947+
 948+ if ( $lang === false ) {
 949+ if ( $noHtml ) {
 950+ return wfMsg( 'metadata-langitem-default',
 951+ $wrappedValue ) . "\n\n";
 952+ } /* else */
 953+ return '<li class="mw-metadata-lang-default">'
 954+ . wfMsg( 'metadata-langitem-default',
 955+ $wrappedValue )
 956+ . "</li>\n";
 957+ }
 958+
 959+ $lowLang = strtolower( $lang );
 960+ $langName = $wgContLang->getLanguageName( $lowLang );
 961+ if ( $langName === '' ) {
 962+ //try just the base language name. (aka en-US -> en ).
 963+ list( $langPrefix ) = explode( '-', $lowLang, 2 );
 964+ $langName = $wgContLang->getLanguageName( $langPrefix );
 965+ if ( $langName === '' ) {
 966+ // give up.
 967+ $langName = $lang;
 968+ }
 969+ }
 970+ // else we have a language specified
 971+
 972+ if ( $noHtml ) {
 973+ return '*' . wfMsg( 'metadata-langitem',
 974+ $wrappedValue, $langName, $lang );
 975+ } /* else: */
 976+
 977+ $item = '<li class="mw-metadata-lang-code-'
 978+ . $lang;
 979+ if ( $default ) {
 980+ $item .= ' mw-metadata-lang-default';
 981+ }
 982+ $item .= '" lang="' . $lang . '">';
 983+ $item .= wfMsg( 'metadata-langitem',
 984+ $wrappedValue, $langName, $lang );
 985+ $item .= "</li>\n";
 986+ return $item;
 987+ }
 988+ /**
 989+ * Convenience function for getFormattedData()
 990+ *
 991+ * @private
 992+ *
 993+ * @param $tag String: the tag name to pass on
 994+ * @param $val String: the value of the tag
 995+ * @param $arg String: an argument to pass ($1)
 996+ * @param $arg2 String: a 2nd argument to pass ($2)
 997+ * @return string A wfMsg of "exif-$tag-$val" in lower case
 998+ */
 999+ static function msg( $tag, $val, $arg = null, $arg2 = null ) {
 1000+ global $wgContLang;
 1001+
 1002+ if ($val === '')
 1003+ $val = 'value';
 1004+ return wfMsg( $wgContLang->lc( "exif-$tag-$val" ), $arg, $arg2 );
 1005+ }
 1006+
 1007+ /**
 1008+ * Format a number, convert numbers from fractions into floating point
 1009+ * numbers, joins arrays of numbers with commas.
 1010+ *
 1011+ * @private
 1012+ *
 1013+ * @param $num Mixed: the value to format
 1014+ * @param $round digits to round to or false.
 1015+ * @return mixed A floating point number or whatever we were fed
 1016+ */
 1017+ static function formatNum( $num, $round = false ) {
 1018+ global $wgLang;
 1019+ $m = array();
 1020+ if( is_array($num) ) {
 1021+ $out = array();
 1022+ foreach( $num as $number ) {
 1023+ $out[] = self::formatNum($number);
 1024+ }
 1025+ return $wgLang->commaList( $out );
 1026+ }
 1027+ if ( preg_match( '/^(-?\d+)\/(\d+)$/', $num, $m ) ) {
 1028+ if ( $m[2] != 0 ) {
 1029+ $newNum = $m[1] / $m[2];
 1030+ if ( $round !== false ) {
 1031+ $newNum = round( $newNum, $round );
 1032+ }
 1033+ } else {
 1034+ $newNum = $num;
 1035+ }
 1036+
 1037+ return $wgLang->formatNum( $newNum );
 1038+ } else {
 1039+ if ( is_numeric( $num ) && $round !== false ) {
 1040+ $num = round( $num, $round );
 1041+ }
 1042+ return $wgLang->formatNum( $num );
 1043+ }
 1044+ }
 1045+
 1046+ /**
 1047+ * Format a rational number, reducing fractions
 1048+ *
 1049+ * @private
 1050+ *
 1051+ * @param $num Mixed: the value to format
 1052+ * @return mixed A floating point number or whatever we were fed
 1053+ */
 1054+ static function formatFraction( $num ) {
 1055+ $m = array();
 1056+ if ( preg_match( '/^(-?\d+)\/(\d+)$/', $num, $m ) ) {
 1057+ $numerator = intval( $m[1] );
 1058+ $denominator = intval( $m[2] );
 1059+ $gcd = self::gcd( abs( $numerator ), $denominator );
 1060+ if( $gcd != 0 ) {
 1061+ // 0 shouldn't happen! ;)
 1062+ return self::formatNum( $numerator / $gcd ) . '/' . self::formatNum( $denominator / $gcd );
 1063+ }
 1064+ }
 1065+ return self::formatNum( $num );
 1066+ }
 1067+
 1068+ /**
 1069+ * Calculate the greatest common divisor of two integers.
 1070+ *
 1071+ * @param $a Integer: Numerator
 1072+ * @param $b Integer: Denominator
 1073+ * @return int
 1074+ * @private
 1075+ */
 1076+ static function gcd( $a, $b ) {
 1077+ /*
 1078+ // http://en.wikipedia.org/wiki/Euclidean_algorithm
 1079+ // Recursive form would be:
 1080+ if( $b == 0 )
 1081+ return $a;
 1082+ else
 1083+ return gcd( $b, $a % $b );
 1084+ */
 1085+ while( $b != 0 ) {
 1086+ $remainder = $a % $b;
 1087+
 1088+ // tail recursion...
 1089+ $a = $b;
 1090+ $b = $remainder;
 1091+ }
 1092+ return $a;
 1093+ }
 1094+
 1095+ /** Fetch the human readable version of a news code.
 1096+ * A news code is an 8 digit code. The first two
 1097+ * digits are a general classification, so we just
 1098+ * translate that.
 1099+ *
 1100+ * Note, leading 0's are significant, so this is
 1101+ * a string, not an int.
 1102+ *
 1103+ * @param $val String: The 8 digit news code.
 1104+ * @return The human readable form
 1105+ */
 1106+ static private function convertNewsCode( $val ) {
 1107+ if ( !preg_match( '/^\d{8}$/D', $val ) ) {
 1108+ // Not a valid news code.
 1109+ return $val;
 1110+ }
 1111+ $cat = '';
 1112+ switch( substr( $val , 0, 2 ) ) {
 1113+ case '01':
 1114+ $cat = 'ace';
 1115+ break;
 1116+ case '02':
 1117+ $cat = 'clj';
 1118+ break;
 1119+ case '03':
 1120+ $cat = 'dis';
 1121+ break;
 1122+ case '04':
 1123+ $cat = 'fin';
 1124+ break;
 1125+ case '05':
 1126+ $cat = 'edu';
 1127+ break;
 1128+ case '06':
 1129+ $cat = 'evn';
 1130+ break;
 1131+ case '07':
 1132+ $cat = 'hth';
 1133+ break;
 1134+ case '08':
 1135+ $cat = 'hum';
 1136+ break;
 1137+ case '09':
 1138+ $cat = 'lab';
 1139+ break;
 1140+ case '10':
 1141+ $cat = 'lif';
 1142+ break;
 1143+ case '11':
 1144+ $cat = 'pol';
 1145+ break;
 1146+ case '12':
 1147+ $cat = 'rel';
 1148+ break;
 1149+ case '13':
 1150+ $cat = 'sci';
 1151+ break;
 1152+ case '14':
 1153+ $cat = 'soi';
 1154+ break;
 1155+ case '15':
 1156+ $cat = 'spo';
 1157+ break;
 1158+ case '16':
 1159+ $cat = 'war';
 1160+ break;
 1161+ case '17':
 1162+ $cat = 'wea';
 1163+ break;
 1164+ }
 1165+ if ( $cat !== '' ) {
 1166+ $catMsg = self::msg( 'iimcategory', $cat );
 1167+ $val = self::msg( 'subjectnewscode', '', $val, $catMsg );
 1168+ }
 1169+ return $val;
 1170+ }
 1171+ /**
 1172+ * Format a coordinate value, convert numbers from floating point
 1173+ * into degree minute second representation.
 1174+ *
 1175+ * @private
 1176+ *
 1177+ * @param $coords Array: degrees, minutes and seconds
 1178+ * @param $type String: latitude or longitude (for if its a NWS or E)
 1179+ * @return mixed A floating point number or whatever we were fed
 1180+ */
 1181+ static function formatCoords( $coord, $type ) {
 1182+ $ref = '';
 1183+ if ( $coord < 0 ) {
 1184+ $nCoord = -$coord;
 1185+ if ( $type === 'latitude' ) {
 1186+ $ref = 'S';
 1187+ }
 1188+ elseif ( $type === 'longitude' ) {
 1189+ $ref = 'W';
 1190+ }
 1191+ }
 1192+ else {
 1193+ $nCoord = $coord;
 1194+ if ( $type === 'latitude' ) {
 1195+ $ref = 'N';
 1196+ }
 1197+ elseif ( $type === 'longitude' ) {
 1198+ $ref = 'E';
 1199+ }
 1200+ }
 1201+
 1202+ $deg = floor( $nCoord );
 1203+ $min = floor( ( $nCoord - $deg ) * 60.0 );
 1204+ $sec = round( ( ( $nCoord - $deg ) - $min / 60 ) * 3600, 2 );
 1205+
 1206+ $deg = self::formatNum( $deg );
 1207+ $min = self::formatNum( $min );
 1208+ $sec = self::formatNum( $sec );
 1209+
 1210+ return wfMsg( 'exif-coordinate-format', $deg, $min, $sec, $ref, $coord );
 1211+ }
 1212+ /**
 1213+ * Format the contact info field into a single value.
 1214+ *
 1215+ * @param $vals Array array with fields of the ContactInfo
 1216+ * struct defined in the IPTC4XMP spec. Or potentially
 1217+ * an array with one element that is a free form text
 1218+ * value from the older iptc iim 1:118 prop.
 1219+ *
 1220+ * This function might be called from
 1221+ * JpegHandler::convertMetadataVersion which is why it is
 1222+ * public.
 1223+ *
 1224+ * @return String of html-ish looking wikitext
 1225+ */
 1226+ public function collapseContactInfo( $vals ) {
 1227+ if( ! ( isset( $vals['CiAdrExtadr'] )
 1228+ || isset( $vals['CiAdrCity'] )
 1229+ || isset( $vals['CiAdrCtry'] )
 1230+ || isset( $vals['CiEmailWork'] )
 1231+ || isset( $vals['CiTelWork'] )
 1232+ || isset( $vals['CiAdrPcode'] )
 1233+ || isset( $vals['CiAdrRegion'] )
 1234+ || isset( $vals['CiUrlWork'] )
 1235+ ) ) {
 1236+ // We don't have any sub-properties
 1237+ // This could happen if its using old
 1238+ // iptc that just had this as a free-form
 1239+ // text value.
 1240+ // Note: We run this through htmlspecialchars
 1241+ // partially to be consistent, and partially
 1242+ // because people often insert >, etc into
 1243+ // the metadata which should not be interpreted
 1244+ // but we still want to auto-link urls.
 1245+ foreach( $vals as &$val ) {
 1246+ $val = htmlspecialchars( $val );
 1247+ }
 1248+ return self::flattenArray( $vals );
 1249+ } else {
 1250+ // We have a real ContactInfo field.
 1251+ // Its unclear if all these fields have to be
 1252+ // set, so assume they do not.
 1253+ $url = $tel = $street = $city = $country = '';
 1254+ $email = $postal = $region = '';
 1255+
 1256+ // Also note, some of the class names this uses
 1257+ // are similar to those used by hCard. This is
 1258+ // mostly because they're sensible names. This
 1259+ // does not (and does not attempt to) output
 1260+ // stuff in the hCard microformat. However it
 1261+ // might output in the adr microformat.
 1262+
 1263+ if ( isset( $vals['CiAdrExtadr'] ) ) {
 1264+ // Todo: This can potentially be multi-line.
 1265+ // Need to check how that works in XMP.
 1266+ $street = '<span class="extended-address">'
 1267+ . htmlspecialchars(
 1268+ $vals['CiAdrExtadr'] )
 1269+ . '</span>';
 1270+ }
 1271+ if ( isset( $vals['CiAdrCity'] ) ) {
 1272+ $city = '<span class="locality">'
 1273+ . htmlspecialchars( $vals['CiAdrCity'] )
 1274+ . '</span>';
 1275+ }
 1276+ if ( isset( $vals['CiAdrCtry'] ) ) {
 1277+ $country = '<span class="country-name">'
 1278+ . htmlspecialchars( $vals['CiAdrCtry'] )
 1279+ . '</span>';
 1280+ }
 1281+ if ( isset( $vals['CiEmailWork'] ) ) {
 1282+ $emails = array();
 1283+ // Have to split multiple emails at commas/new lines.
 1284+ $splitEmails = explode( "\n", $vals['CiEmailWork'] );
 1285+ foreach ( $splitEmails as $e1 ) {
 1286+ // Also split on comma
 1287+ foreach ( explode( ',', $e1 ) as $e2 ) {
 1288+ $finalEmail = trim( $e2 );
 1289+ if ( $finalEmail == ',' || $finalEmail == '' ) {
 1290+ continue;
 1291+ }
 1292+ if ( strpos( $finalEmail, '<' ) !== false ) {
 1293+ // Don't do fancy formatting to
 1294+ // "My name" <foo@bar.com> style stuff
 1295+ $emails[] = $finalEmail;
 1296+ } else {
 1297+ $emails[] = '[mailto:'
 1298+ . $finalEmail
 1299+ . ' <span class="email">'
 1300+ . $finalEmail
 1301+ . '</span>]';
 1302+ }
 1303+ }
 1304+ }
 1305+ $email = implode( ', ', $emails );
 1306+ }
 1307+ if ( isset( $vals['CiTelWork'] ) ) {
 1308+ $tel = '<span class="tel">'
 1309+ . htmlspecialchars( $vals['CiTelWork'] )
 1310+ . '</span>';
 1311+ }
 1312+ if ( isset( $vals['CiAdrPcode'] ) ) {
 1313+ $postal = '<span class="postal-code">'
 1314+ . htmlspecialchars(
 1315+ $vals['CiAdrPcode'] )
 1316+ . '</span>';
 1317+ }
 1318+ if ( isset( $vals['CiAdrRegion'] ) ) {
 1319+ // Note this is province/state.
 1320+ $region = '<span class="region">'
 1321+ . htmlspecialchars(
 1322+ $vals['CiAdrRegion'] )
 1323+ . '</span>';
 1324+ }
 1325+ if ( isset( $vals['CiUrlWork'] ) ) {
 1326+ $url = '<span class="url">'
 1327+ . htmlspecialchars( $vals['CiUrlWork'] )
 1328+ . '</span>';
 1329+ }
 1330+ return wfMsg( 'exif-contact-value', $email, $url,
 1331+ $street, $city, $region, $postal, $country,
 1332+ $tel );
 1333+ }
 1334+ }
 1335+}
 1336+
 1337+/** For compatability with old FormatExif class
 1338+ * which some extensions use.
 1339+ *
 1340+ *@deprecated
 1341+ *
 1342+**/
 1343+class FormatExif {
 1344+ var $meta;
 1345+ function FormatExif ( $meta ) {
 1346+ wfDeprecated(__METHOD__);
 1347+ $this->meta = $meta;
 1348+ }
 1349+ function getFormattedData ( ) {
 1350+ return FormatMetadata::getFormattedData( $this->meta );
 1351+ }
 1352+
 1353+}
Property changes on: trunk/phase3/includes/media/FormatMetadata.php
___________________________________________________________________
Added: svn:eol-style
11354 + native
Index: trunk/phase3/includes/media/Exif.php
@@ -0,0 +1,786 @@
 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+ * Class to extract and validate Exif data from jpeg (and possibly tiff) files.
 29+ * @ingroup Media
 30+ */
 31+class Exif {
 32+
 33+ const BYTE = 1; //!< An 8-bit (1-byte) unsigned integer.
 34+ const ASCII = 2; //!< An 8-bit byte containing one 7-bit ASCII code. The final byte is terminated with NULL.
 35+ const SHORT = 3; //!< A 16-bit (2-byte) unsigned integer.
 36+ const LONG = 4; //!< A 32-bit (4-byte) unsigned integer.
 37+ const RATIONAL = 5; //!< Two LONGs. The first LONG is the numerator and the second LONG expresses the denominator
 38+ const UNDEFINED = 7; //!< An 8-bit byte that can take any value depending on the field definition
 39+ const SLONG = 9; //!< A 32-bit (4-byte) signed integer (2's complement notation),
 40+ const SRATIONAL = 10; //!< Two SLONGs. The first SLONG is the numerator and the second SLONG is the denominator.
 41+ const IGNORE = -1; // A fake value for things we don't want or don't support.
 42+
 43+ //@{
 44+ /* @var array
 45+ * @private
 46+ */
 47+
 48+ /**
 49+ * Exif tags grouped by category, the tagname itself is the key and the type
 50+ * is the value, in the case of more than one possible value type they are
 51+ * separated by commas.
 52+ */
 53+ var $mExifTags;
 54+
 55+ /**
 56+ * The raw Exif data returned by exif_read_data()
 57+ */
 58+ var $mRawExifData;
 59+
 60+ /**
 61+ * A Filtered version of $mRawExifData that has been pruned of invalid
 62+ * tags and tags that contain content they shouldn't contain according
 63+ * to the Exif specification
 64+ */
 65+ var $mFilteredExifData;
 66+
 67+ /**
 68+ * Filtered and formatted Exif data, see FormatMetadata::getFormattedData()
 69+ */
 70+ var $mFormattedExifData;
 71+
 72+ //@}
 73+
 74+ //@{
 75+ /* @var string
 76+ * @private
 77+ */
 78+
 79+ /**
 80+ * The file being processed
 81+ */
 82+ var $file;
 83+
 84+ /**
 85+ * The basename of the file being processed
 86+ */
 87+ var $basename;
 88+
 89+ /**
 90+ * The private log to log to, e.g. 'exif'
 91+ */
 92+ var $log = false;
 93+
 94+ //@}
 95+
 96+ /**
 97+ * Constructor
 98+ *
 99+ * @param $file String: filename.
 100+ * @fixme the following are broke:
 101+ * SubjectArea. Need to test the more obscure tags.
 102+ *
 103+ * DigitalZoomRatio = 0/0 is rejected. need to determine if that's valid.
 104+ * possibly should treat 0/0 = 0. need to read exif spec on that.
 105+ */
 106+ function __construct( $file ) {
 107+ /**
 108+ * Page numbers here refer to pages in the EXIF 2.2 standard
 109+ *
 110+ * Note, Exif::UNDEFINED is treated as a string, not as an array of bytes
 111+ * so don't put a count parameter for any UNDEFINED values.
 112+ *
 113+ * @link http://exif.org/Exif2-2.PDF The Exif 2.2 specification
 114+ */
 115+ $this->mExifTags = array(
 116+ # TIFF Rev. 6.0 Attribute Information (p22)
 117+ 'IFD0' => array(
 118+ # Tags relating to image structure
 119+ 'ImageWidth' => Exif::SHORT.','.Exif::LONG, # Image width
 120+ 'ImageLength' => Exif::SHORT.','.Exif::LONG, # Image height
 121+ 'BitsPerSample' => array( Exif::SHORT, 3 ), # Number of bits per component
 122+ # "When a primary image is JPEG compressed, this designation is not"
 123+ # "necessary and is omitted." (p23)
 124+ 'Compression' => Exif::SHORT, # Compression scheme #p23
 125+ 'PhotometricInterpretation' => Exif::SHORT, # Pixel composition #p23
 126+ 'Orientation' => Exif::SHORT, # Orientation of image #p24
 127+ 'SamplesPerPixel' => Exif::SHORT, # Number of components
 128+ 'PlanarConfiguration' => Exif::SHORT, # Image data arrangement #p24
 129+ 'YCbCrSubSampling' => array( Exif::SHORT, 2), # Subsampling ratio of Y to C #p24
 130+ 'YCbCrPositioning' => Exif::SHORT, # Y and C positioning #p24-25
 131+ 'XResolution' => Exif::RATIONAL, # Image resolution in width direction
 132+ 'YResolution' => Exif::RATIONAL, # Image resolution in height direction
 133+ 'ResolutionUnit' => Exif::SHORT, # Unit of X and Y resolution #(p26)
 134+
 135+ # Tags relating to recording offset
 136+ 'StripOffsets' => Exif::SHORT.','.Exif::LONG, # Image data location
 137+ 'RowsPerStrip' => Exif::SHORT.','.Exif::LONG, # Number of rows per strip
 138+ 'StripByteCounts' => Exif::SHORT.','.Exif::LONG, # Bytes per compressed strip
 139+ 'JPEGInterchangeFormat' => Exif::SHORT.','.Exif::LONG, # Offset to JPEG SOI
 140+ 'JPEGInterchangeFormatLength' => Exif::SHORT.','.Exif::LONG, # Bytes of JPEG data
 141+
 142+ # Tags relating to image data characteristics
 143+ 'TransferFunction' => Exif::IGNORE, # Transfer function
 144+ 'WhitePoint' => array( Exif::RATIONAL, 2), # White point chromaticity
 145+ 'PrimaryChromaticities' => array( Exif::RATIONAL, 6), # Chromaticities of primarities
 146+ 'YCbCrCoefficients' => array( Exif::RATIONAL, 3), # Color space transformation matrix coefficients #p27
 147+ 'ReferenceBlackWhite' => array( Exif::RATIONAL, 6), # Pair of black and white reference values
 148+
 149+ # Other tags
 150+ 'DateTime' => Exif::ASCII, # File change date and time
 151+ 'ImageDescription' => Exif::ASCII, # Image title
 152+ 'Make' => Exif::ASCII, # Image input equipment manufacturer
 153+ 'Model' => Exif::ASCII, # Image input equipment model
 154+ 'Software' => Exif::ASCII, # Software used
 155+ 'Artist' => Exif::ASCII, # Person who created the image
 156+ 'Copyright' => Exif::ASCII, # Copyright holder
 157+ ),
 158+
 159+ # Exif IFD Attribute Information (p30-31)
 160+ 'EXIF' => array(
 161+ # TODO: NOTE: Nonexistence of this field is taken to mean nonconformance
 162+ # to the EXIF 2.1 AND 2.2 standards
 163+ 'ExifVersion' => Exif::UNDEFINED, # Exif version
 164+ 'FlashPixVersion' => Exif::UNDEFINED, # Supported Flashpix version #p32
 165+
 166+ # Tags relating to Image Data Characteristics
 167+ 'ColorSpace' => Exif::SHORT, # Color space information #p32
 168+
 169+ # Tags relating to image configuration
 170+ 'ComponentsConfiguration' => Exif::UNDEFINED, # Meaning of each component #p33
 171+ 'CompressedBitsPerPixel' => Exif::RATIONAL, # Image compression mode
 172+ 'PixelYDimension' => Exif::SHORT.','.Exif::LONG, # Valid image width
 173+ 'PixelXDimension' => Exif::SHORT.','.Exif::LONG, # Valid image height
 174+
 175+ # Tags relating to related user information
 176+ 'MakerNote' => Exif::IGNORE, # Manufacturer notes
 177+ 'UserComment' => Exif::UNDEFINED, # User comments #p34
 178+
 179+ # Tags relating to related file information
 180+ 'RelatedSoundFile' => Exif::ASCII, # Related audio file
 181+
 182+ # Tags relating to date and time
 183+ 'DateTimeOriginal' => Exif::ASCII, # Date and time of original data generation #p36
 184+ 'DateTimeDigitized' => Exif::ASCII, # Date and time of original data generation
 185+ 'SubSecTime' => Exif::ASCII, # DateTime subseconds
 186+ 'SubSecTimeOriginal' => Exif::ASCII, # DateTimeOriginal subseconds
 187+ 'SubSecTimeDigitized' => Exif::ASCII, # DateTimeDigitized subseconds
 188+
 189+ # Tags relating to picture-taking conditions (p31)
 190+ 'ExposureTime' => Exif::RATIONAL, # Exposure time
 191+ 'FNumber' => Exif::RATIONAL, # F Number
 192+ 'ExposureProgram' => Exif::SHORT, # Exposure Program #p38
 193+ 'SpectralSensitivity' => Exif::ASCII, # Spectral sensitivity
 194+ 'ISOSpeedRatings' => Exif::SHORT, # ISO speed rating
 195+ 'OECF' => Exif::IGNORE,
 196+ # Optoelectronic conversion factor. Note: We don't have support for this atm.
 197+ 'ShutterSpeedValue' => Exif::SRATIONAL, # Shutter speed
 198+ 'ApertureValue' => Exif::RATIONAL, # Aperture
 199+ 'BrightnessValue' => Exif::SRATIONAL, # Brightness
 200+ 'ExposureBiasValue' => Exif::SRATIONAL, # Exposure bias
 201+ 'MaxApertureValue' => Exif::RATIONAL, # Maximum land aperture
 202+ 'SubjectDistance' => Exif::RATIONAL, # Subject distance
 203+ 'MeteringMode' => Exif::SHORT, # Metering mode #p40
 204+ 'LightSource' => Exif::SHORT, # Light source #p40-41
 205+ 'Flash' => Exif::SHORT, # Flash #p41-42
 206+ 'FocalLength' => Exif::RATIONAL, # Lens focal length
 207+ 'SubjectArea' => array( Exif::SHORT, 4 ), # Subject area
 208+ 'FlashEnergy' => Exif::RATIONAL, # Flash energy
 209+ 'SpatialFrequencyResponse' => Exif::IGNORE, # Spatial frequency response. Not supported atm.
 210+ 'FocalPlaneXResolution' => Exif::RATIONAL, # Focal plane X resolution
 211+ 'FocalPlaneYResolution' => Exif::RATIONAL, # Focal plane Y resolution
 212+ 'FocalPlaneResolutionUnit' => Exif::SHORT, # Focal plane resolution unit #p46
 213+ 'SubjectLocation' => array( Exif::SHORT, 2), # Subject location
 214+ 'ExposureIndex' => Exif::RATIONAL, # Exposure index
 215+ 'SensingMethod' => Exif::SHORT, # Sensing method #p46
 216+ 'FileSource' => Exif::UNDEFINED, # File source #p47
 217+ 'SceneType' => Exif::UNDEFINED, # Scene type #p47
 218+ 'CFAPattern' => Exif::IGNORE, # CFA pattern. not supported atm.
 219+ 'CustomRendered' => Exif::SHORT, # Custom image processing #p48
 220+ 'ExposureMode' => Exif::SHORT, # Exposure mode #p48
 221+ 'WhiteBalance' => Exif::SHORT, # White Balance #p49
 222+ 'DigitalZoomRatio' => Exif::RATIONAL, # Digital zoom ration
 223+ 'FocalLengthIn35mmFilm' => Exif::SHORT, # Focal length in 35 mm film
 224+ 'SceneCaptureType' => Exif::SHORT, # Scene capture type #p49
 225+ 'GainControl' => Exif::SHORT, # Scene control #p49-50
 226+ 'Contrast' => Exif::SHORT, # Contrast #p50
 227+ 'Saturation' => Exif::SHORT, # Saturation #p50
 228+ 'Sharpness' => Exif::SHORT, # Sharpness #p50
 229+ 'DeviceSettingDescription' => Exif::IGNORE,
 230+ # Device settings description. This could maybe be supported. Need to find an
 231+ # example file that uses this to see if it has stuff of interest in it.
 232+ 'SubjectDistanceRange' => Exif::SHORT, # Subject distance range #p51
 233+
 234+ 'ImageUniqueID' => Exif::ASCII, # Unique image ID
 235+ ),
 236+
 237+ # GPS Attribute Information (p52)
 238+ 'GPS' => array(
 239+ 'GPSVersion' => Exif::UNDEFINED,
 240+ # Should be an array of 4 Exif::BYTE's. However php treats it as an undefined
 241+ # Note exif standard calls this GPSVersionID, but php doesn't like the id suffix
 242+ 'GPSLatitudeRef' => Exif::ASCII, # North or South Latitude #p52-53
 243+ 'GPSLatitude' => array( Exif::RATIONAL, 3 ), # Latitude
 244+ 'GPSLongitudeRef' => Exif::ASCII, # East or West Longitude #p53
 245+ 'GPSLongitude' => array( Exif::RATIONAL, 3), # Longitude
 246+ 'GPSAltitudeRef' => Exif::UNDEFINED,
 247+ # Altitude reference. Note, the exif standard says this should be an EXIF::Byte,
 248+ # but php seems to disagree.
 249+ 'GPSAltitude' => Exif::RATIONAL, # Altitude
 250+ 'GPSTimeStamp' => array( Exif::RATIONAL, 3), # GPS time (atomic clock)
 251+ 'GPSSatellites' => Exif::ASCII, # Satellites used for measurement
 252+ 'GPSStatus' => Exif::ASCII, # Receiver status #p54
 253+ 'GPSMeasureMode' => Exif::ASCII, # Measurement mode #p54-55
 254+ 'GPSDOP' => Exif::RATIONAL, # Measurement precision
 255+ 'GPSSpeedRef' => Exif::ASCII, # Speed unit #p55
 256+ 'GPSSpeed' => Exif::RATIONAL, # Speed of GPS receiver
 257+ 'GPSTrackRef' => Exif::ASCII, # Reference for direction of movement #p55
 258+ 'GPSTrack' => Exif::RATIONAL, # Direction of movement
 259+ 'GPSImgDirectionRef' => Exif::ASCII, # Reference for direction of image #p56
 260+ 'GPSImgDirection' => Exif::RATIONAL, # Direction of image
 261+ 'GPSMapDatum' => Exif::ASCII, # Geodetic survey data used
 262+ 'GPSDestLatitudeRef' => Exif::ASCII, # Reference for latitude of destination #p56
 263+ 'GPSDestLatitude' => array( Exif::RATIONAL, 3 ), # Latitude destination
 264+ 'GPSDestLongitudeRef' => Exif::ASCII, # Reference for longitude of destination #p57
 265+ 'GPSDestLongitude' => array( Exif::RATIONAL, 3 ), # Longitude of destination
 266+ 'GPSDestBearingRef' => Exif::ASCII, # Reference for bearing of destination #p57
 267+ 'GPSDestBearing' => Exif::RATIONAL, # Bearing of destination
 268+ 'GPSDestDistanceRef' => Exif::ASCII, # Reference for distance to destination #p57-58
 269+ 'GPSDestDistance' => Exif::RATIONAL, # Distance to destination
 270+ 'GPSProcessingMethod' => Exif::UNDEFINED, # Name of GPS processing method
 271+ 'GPSAreaInformation' => Exif::UNDEFINED, # Name of GPS area
 272+ 'GPSDateStamp' => Exif::ASCII, # GPS date
 273+ 'GPSDifferential' => Exif::SHORT, # GPS differential correction
 274+ ),
 275+ );
 276+
 277+ $this->file = $file;
 278+ $this->basename = wfBaseName( $this->file );
 279+
 280+ $this->debugFile( $this->basename, __FUNCTION__, true );
 281+ if( function_exists( 'exif_read_data' ) ) {
 282+ wfSuppressWarnings();
 283+ $data = exif_read_data( $this->file, 0, true );
 284+ wfRestoreWarnings();
 285+ } else {
 286+ throw new MWException( "Internal error: exif_read_data not present. \$wgShowEXIF may be incorrectly set or not checked by an extension." );
 287+ }
 288+ /**
 289+ * exif_read_data() will return false on invalid input, such as
 290+ * when somebody uploads a file called something.jpeg
 291+ * containing random gibberish.
 292+ */
 293+ $this->mRawExifData = $data ? $data : array();
 294+ $this->makeFilteredData();
 295+ $this->collapseData();
 296+ $this->debugFile( __FUNCTION__, false );
 297+ }
 298+
 299+ /**
 300+ * Make $this->mFilteredExifData
 301+ */
 302+ function makeFilteredData() {
 303+ $this->mFilteredExifData = Array();
 304+
 305+ foreach ( array_keys( $this->mRawExifData ) as $section ) {
 306+ if ( !in_array( $section, array_keys( $this->mExifTags ) ) ) {
 307+ $this->debug( $section , __FUNCTION__, "'$section' is not a valid Exif section" );
 308+ continue;
 309+ }
 310+
 311+ foreach ( array_keys( $this->mRawExifData[$section] ) as $tag ) {
 312+ if ( !in_array( $tag, array_keys( $this->mExifTags[$section] ) ) ) {
 313+ $this->debug( $tag, __FUNCTION__, "'$tag' is not a valid tag in '$section'" );
 314+ continue;
 315+ }
 316+
 317+ $this->mFilteredExifData[$tag] = $this->mRawExifData[$section][$tag];
 318+ // This is ok, as the tags in the different sections do not conflict.
 319+ // except in computed and thumbnail section, which we don't use.
 320+
 321+ $value = $this->mRawExifData[$section][$tag];
 322+ if ( !$this->validate( $section, $tag, $value ) ) {
 323+ $this->debug( $value, __FUNCTION__, "'$tag' contained invalid data" );
 324+ unset( $this->mFilteredExifData[$tag] );
 325+ }
 326+ }
 327+ }
 328+ }
 329+
 330+ /**
 331+ * Collapse some fields together.
 332+ * This converts some fields from exif form, to a more friendly form.
 333+ * For example GPS latitude to a single number.
 334+ *
 335+ * The rationale behind this is that we're storing data, not presenting to the user
 336+ * For example a longitude is a single number describing how far away you are from
 337+ * the prime meridian. Well it might be nice to split it up into minutes and seconds
 338+ * for the user, it doesn't really make sense to split a single number into 4 parts
 339+ * for storage. (degrees, minutes, second, direction vs single floating point number).
 340+ *
 341+ * Other things this might do (not really sure if they make sense or not):
 342+ * Dates -> mediawiki date format.
 343+ * convert values that can be in different units to be in one standardized unit.
 344+ *
 345+ * As an alternative approach, some of this could be done in the validate phase
 346+ * if we make up our own types like Exif::DATE.
 347+ */
 348+ function collapseData( ) {
 349+
 350+ $this->exifGPStoNumber( 'GPSLatitude' );
 351+ $this->exifGPStoNumber( 'GPSDestLatitude' );
 352+ $this->exifGPStoNumber( 'GPSLongitude' );
 353+ $this->exifGPStoNumber( 'GPSDestLongitude' );
 354+
 355+ if ( isset( $this->mFilteredExifData['GPSAltitude'] ) && isset( $this->mFilteredExifData['GPSAltitudeRef'] ) ) {
 356+ if ( $this->mFilteredExifData['GPSAltitudeRef'] === "\1" ) {
 357+ $this->mFilteredExifData['GPSAltitude'] *= - 1;
 358+ }
 359+ unset( $this->mFilteredExifData['GPSAltitudeRef'] );
 360+ }
 361+
 362+ $this->exifPropToOrd( 'FileSource' );
 363+ $this->exifPropToOrd( 'SceneType' );
 364+
 365+ $this->charCodeString( 'UserComment' );
 366+ $this->charCodeString( 'GPSProcessingMethod');
 367+ $this->charCodeString( 'GPSAreaInformation' );
 368+
 369+ //ComponentsConfiguration should really be an array instead of a string...
 370+ //This turns a string of binary numbers into an array of numbers.
 371+
 372+ if ( isset ( $this->mFilteredExifData['ComponentsConfiguration'] ) ) {
 373+ $val = $this->mFilteredExifData['ComponentsConfiguration'];
 374+ $ccVals = array();
 375+ for ($i = 0; $i < strlen($val); $i++) {
 376+ $ccVals[$i] = ord( substr($val, $i, 1) );
 377+ }
 378+ $ccVals['_type'] = 'ol'; //this is for formatting later.
 379+ $this->mFilteredExifData['ComponentsConfiguration'] = $ccVals;
 380+ }
 381+
 382+ //GPSVersion(ID) is treated as the wrong type by php exif support.
 383+ //Go through each byte turning it into a version string.
 384+ //For example: "\x02\x02\x00\x00" -> "2.2.0.0"
 385+
 386+ //Also change exif tag name from GPSVersion (what php exif thinks it is)
 387+ //to GPSVersionID (what the exif standard thinks it is).
 388+
 389+ if ( isset ( $this->mFilteredExifData['GPSVersion'] ) ) {
 390+ $val = $this->mFilteredExifData['GPSVersion'];
 391+ $newVal = '';
 392+ for ($i = 0; $i < strlen($val); $i++) {
 393+ if ( $i !== 0 ) {
 394+ $newVal .= '.';
 395+ }
 396+ $newVal .= ord( substr($val, $i, 1) );
 397+ }
 398+ $this->mFilteredExifData['GPSVersionID'] = $newVal;
 399+ unset( $this->mFilteredExifData['GPSVersion'] );
 400+ }
 401+
 402+ }
 403+ /**
 404+ * Do userComment tags and similar. See pg. 34 of exif standard.
 405+ * basically first 8 bytes is charset, rest is value.
 406+ * This has not been tested on any shift-JIS strings.
 407+ * @param $prop String prop name.
 408+ */
 409+ private function charCodeString ( $prop ) {
 410+ if ( isset( $this->mFilteredExifData[$prop] ) ) {
 411+
 412+ if ( strlen($this->mFilteredExifData[$prop]) <= 8 ) {
 413+ //invalid. Must be at least 9 bytes long.
 414+
 415+ $this->debug( $this->mFilteredExifData[$prop] , __FUNCTION__, false );
 416+ unset($this->mFilteredExifData[$prop]);
 417+ return;
 418+ }
 419+
 420+ $charCode = substr( $this->mFilteredExifData[$prop], 0, 8);
 421+ $val = substr( $this->mFilteredExifData[$prop], 8);
 422+
 423+
 424+ switch ($charCode) {
 425+ case "\x4A\x49\x53\x00\x00\x00\x00\x00":
 426+ //JIS
 427+ $charset = "Shift-JIS";
 428+ break;
 429+ case "UNICODE\x00":
 430+ $charset = "UTF-16";
 431+ break;
 432+ default: //ascii or undefined.
 433+ $charset = "";
 434+ break;
 435+ }
 436+ // This could possibly check to see if iconv is really installed
 437+ // or if we're using the compatibility wrapper in globalFunctions.php
 438+ if ($charset) {
 439+ $val = iconv($charset, 'UTF-8//IGNORE', $val);
 440+ } else {
 441+ // if valid utf-8, assume that, otherwise assume windows-1252
 442+ $valCopy = $val;
 443+ UtfNormal::quickIsNFCVerify( $valCopy ); //validates $valCopy.
 444+ if ( $valCopy !== $val ) {
 445+ $val = iconv('Windows-1252', 'UTF-8//IGNORE', $val);
 446+ }
 447+ }
 448+
 449+ //trim and check to make sure not only whitespace.
 450+ $val = trim($val);
 451+ if ( strlen( $val ) === 0 ) {
 452+ //only whitespace.
 453+ $this->debug( $this->mFilteredExifData[$prop] , __FUNCTION__, "$prop: Is only whitespace" );
 454+ unset($this->mFilteredExifData[$prop]);
 455+ return;
 456+ }
 457+
 458+ //all's good.
 459+ $this->mFilteredExifData[$prop] = $val;
 460+ }
 461+ }
 462+ /**
 463+ * Convert an Exif::UNDEFINED from a raw binary string
 464+ * to its value. This is sometimes needed depending on
 465+ * the type of UNDEFINED field
 466+ * @param $prop String name of property
 467+ */
 468+ private function exifPropToOrd ( $prop ) {
 469+ if ( isset( $this->mFilteredExifData[$prop] ) ) {
 470+ $this->mFilteredExifData[$prop] = ord( $this->mFilteredExifData[$prop] );
 471+ }
 472+ }
 473+ /**
 474+ * Convert gps in exif form to a single floating point number
 475+ * for example 10 degress 20`40`` S -> -10.34444
 476+ * @param String $prop a gps coordinate exif tag name (like GPSLongitude)
 477+ */
 478+ private function exifGPStoNumber ( $prop ) {
 479+ $loc =& $this->mFilteredExifData[$prop];
 480+ $dir =& $this->mFilteredExifData[$prop . 'Ref'];
 481+ $res = false;
 482+
 483+ if ( isset( $loc ) && isset( $dir ) && ( $dir === 'N' || $dir === 'S' || $dir === 'E' || $dir === 'W' ) ) {
 484+ list( $num, $denom ) = explode( '/', $loc[0] );
 485+ $res = $num / $denom;
 486+ list( $num, $denom ) = explode( '/', $loc[1] );
 487+ $res += ( $num / $denom ) * ( 1 / 60 );
 488+ list( $num, $denom ) = explode( '/', $loc[2] );
 489+ $res += ( $num / $denom ) * ( 1 / 3600 );
 490+
 491+ if ( $dir === 'S' || $dir === 'W' ) {
 492+ $res *= - 1; // make negative
 493+ }
 494+ }
 495+
 496+ // update the exif records.
 497+
 498+ if ( $res !== false ) { // using !== as $res could potentially be 0
 499+ $this->mFilteredExifData[$prop] = $res;
 500+ unset( $this->mFilteredExifData[$prop . 'Ref'] );
 501+ } else { // if invalid
 502+ unset( $this->mFilteredExifData[$prop] );
 503+ unset( $this->mFilteredExifData[$prop . 'Ref'] );
 504+ }
 505+ }
 506+
 507+ /**
 508+ * Use FormatMetadata to create formatted values for display to user
 509+ * (is this ever used?)
 510+ */
 511+ private function makeFormattedData( ) {
 512+ $this->mFormattedExifData = FormatMetadata::getFormattedData();
 513+ }
 514+ /**#@-*/
 515+
 516+ /**#@+
 517+ * @return array
 518+ */
 519+ /**
 520+ * Get $this->mRawExifData
 521+ */
 522+ function getData() {
 523+ return $this->mRawExifData;
 524+ }
 525+
 526+ /**
 527+ * Get $this->mFilteredExifData
 528+ */
 529+ function getFilteredData() {
 530+ return $this->mFilteredExifData;
 531+ }
 532+
 533+ /**
 534+ * Get $this->mFormattedExifData
 535+ *
 536+ * This returns the data for display to user.
 537+ * Its unclear if this is ever used.
 538+ */
 539+ function getFormattedData() {
 540+ if (!$this->mFormattedExifData) {
 541+ $this->makeFormattedData();
 542+ }
 543+ return $this->mFormattedExifData;
 544+ }
 545+ /**#@-*/
 546+
 547+ /**
 548+ * The version of the output format
 549+ *
 550+ * Before the actual metadata information is saved in the database we
 551+ * strip some of it since we don't want to save things like thumbnails
 552+ * which usually accompany Exif data. This value gets saved in the
 553+ * database along with the actual Exif data, and if the version in the
 554+ * database doesn't equal the value returned by this function the Exif
 555+ * data is regenerated.
 556+ *
 557+ * @return int
 558+ */
 559+ public static function version() {
 560+ return 2; // We don't need no bloddy constants!
 561+ }
 562+
 563+ /**#@+
 564+ * Validates if a tag value is of the type it should be according to the Exif spec
 565+ *
 566+ * @private
 567+ *
 568+ * @param $in Mixed: the input value to check
 569+ * @return bool
 570+ */
 571+ private function isByte( $in ) {
 572+ if ( !is_array( $in ) && sprintf('%d', $in) == $in && $in >= 0 && $in <= 255 ) {
 573+ $this->debug( $in, __FUNCTION__, true );
 574+ return true;
 575+ } else {
 576+ $this->debug( $in, __FUNCTION__, false );
 577+ return false;
 578+ }
 579+ }
 580+
 581+ private function isASCII( $in ) {
 582+ if ( is_array( $in ) ) {
 583+ return false;
 584+ }
 585+
 586+ if ( preg_match( "/[^\x0a\x20-\x7e]/", $in ) ) {
 587+ $this->debug( $in, __FUNCTION__, 'found a character not in our whitelist' );
 588+ return false;
 589+ }
 590+
 591+ if ( preg_match( '/^\s*$/', $in ) ) {
 592+ $this->debug( $in, __FUNCTION__, 'input consisted solely of whitespace' );
 593+ return false;
 594+ }
 595+
 596+ return true;
 597+ }
 598+
 599+ private function isShort( $in ) {
 600+ if ( !is_array( $in ) && sprintf('%d', $in) == $in && $in >= 0 && $in <= 65536 ) {
 601+ $this->debug( $in, __FUNCTION__, true );
 602+ return true;
 603+ } else {
 604+ $this->debug( $in, __FUNCTION__, false );
 605+ return false;
 606+ }
 607+ }
 608+
 609+ private function isLong( $in ) {
 610+ if ( !is_array( $in ) && sprintf('%d', $in) == $in && $in >= 0 && $in <= 4294967296 ) {
 611+ $this->debug( $in, __FUNCTION__, true );
 612+ return true;
 613+ } else {
 614+ $this->debug( $in, __FUNCTION__, false );
 615+ return false;
 616+ }
 617+ }
 618+
 619+ private function isRational( $in ) {
 620+ $m = array();
 621+ if ( !is_array( $in ) && @preg_match( '/^(\d+)\/(\d+[1-9]|[1-9]\d*)$/', $in, $m ) ) { # Avoid division by zero
 622+ return $this->isLong( $m[1] ) && $this->isLong( $m[2] );
 623+ } else {
 624+ $this->debug( $in, __FUNCTION__, 'fed a non-fraction value' );
 625+ return false;
 626+ }
 627+ }
 628+
 629+ private function isUndefined( $in ) {
 630+
 631+ $this->debug( $in, __FUNCTION__, true );
 632+ return true;
 633+
 634+ /* Exif::UNDEFINED means string of bytes
 635+ so this validation does not make sense.
 636+ comment out for now.
 637+ if ( !is_array( $in ) && preg_match( '/^\d{4}$/', $in ) ) { // Allow ExifVersion and FlashpixVersion
 638+ $this->debug( $in, __FUNCTION__, true );
 639+ return true;
 640+ } else {
 641+ $this->debug( $in, __FUNCTION__, false );
 642+ return false;
 643+ }
 644+ */
 645+ }
 646+
 647+ private function isSlong( $in ) {
 648+ if ( $this->isLong( abs( $in ) ) ) {
 649+ $this->debug( $in, __FUNCTION__, true );
 650+ return true;
 651+ } else {
 652+ $this->debug( $in, __FUNCTION__, false );
 653+ return false;
 654+ }
 655+ }
 656+
 657+ private function isSrational( $in ) {
 658+ $m = array();
 659+ if ( !is_array( $in ) && preg_match( '/^(-?\d+)\/(\d+[1-9]|[1-9]\d*)$/', $in, $m ) ) { # Avoid division by zero
 660+ return $this->isSlong( $m[0] ) && $this->isSlong( $m[1] );
 661+ } else {
 662+ $this->debug( $in, __FUNCTION__, 'fed a non-fraction value' );
 663+ return false;
 664+ }
 665+ }
 666+ /**#@-*/
 667+
 668+ /**
 669+ * Validates if a tag has a legal value according to the Exif spec
 670+ *
 671+ * @private
 672+ * @param $section String: section where tag is located.
 673+ * @param $tag String: the tag to check.
 674+ * @param $val Mixed: the value of the tag.
 675+ * @param $recursive Boolean: true if called recursively for array types.
 676+ * @return bool
 677+ */
 678+ private function validate( $section, $tag, $val, $recursive = false ) {
 679+ $debug = "tag is '$tag'";
 680+ $etype = $this->mExifTags[$section][$tag];
 681+ $ecount = 1;
 682+ if( is_array( $etype ) ) {
 683+ list( $etype, $ecount ) = $etype;
 684+ if ( $recursive )
 685+ $ecount = 1; // checking individual elements
 686+ }
 687+ $count = count( $val );
 688+ if( $ecount != $count ) {
 689+ $this->debug( $val, __FUNCTION__, "Expected $ecount elements for $tag but got $count" );
 690+ return false;
 691+ }
 692+ if( $count > 1 ) {
 693+ foreach( $val as $v ) {
 694+ if( !$this->validate( $section, $tag, $v, true ) ) {
 695+ return false;
 696+ }
 697+ }
 698+ return true;
 699+ }
 700+ // Does not work if not typecast
 701+ switch( (string)$etype ) {
 702+ case (string)Exif::BYTE:
 703+ $this->debug( $val, __FUNCTION__, $debug );
 704+ return $this->isByte( $val );
 705+ case (string)Exif::ASCII:
 706+ $this->debug( $val, __FUNCTION__, $debug );
 707+ return $this->isASCII( $val );
 708+ case (string)Exif::SHORT:
 709+ $this->debug( $val, __FUNCTION__, $debug );
 710+ return $this->isShort( $val );
 711+ case (string)Exif::LONG:
 712+ $this->debug( $val, __FUNCTION__, $debug );
 713+ return $this->isLong( $val );
 714+ case (string)Exif::RATIONAL:
 715+ $this->debug( $val, __FUNCTION__, $debug );
 716+ return $this->isRational( $val );
 717+ case (string)Exif::UNDEFINED:
 718+ $this->debug( $val, __FUNCTION__, $debug );
 719+ return $this->isUndefined( $val );
 720+ case (string)Exif::SLONG:
 721+ $this->debug( $val, __FUNCTION__, $debug );
 722+ return $this->isSlong( $val );
 723+ case (string)Exif::SRATIONAL:
 724+ $this->debug( $val, __FUNCTION__, $debug );
 725+ return $this->isSrational( $val );
 726+ case (string)Exif::SHORT.','.Exif::LONG:
 727+ $this->debug( $val, __FUNCTION__, $debug );
 728+ return $this->isShort( $val ) || $this->isLong( $val );
 729+ case (string)Exif::IGNORE:
 730+ $this->debug( $val, __FUNCTION__, $debug );
 731+ return false;
 732+ default:
 733+ $this->debug( $val, __FUNCTION__, "The tag '$tag' is unknown" );
 734+ return false;
 735+ }
 736+ }
 737+
 738+ /**
 739+ * Convenience function for debugging output
 740+ *
 741+ * @private
 742+ *
 743+ * @param $in Mixed:
 744+ * @param $fname String:
 745+ * @param $action Mixed: , default NULL.
 746+ */
 747+ private function debug( $in, $fname, $action = null ) {
 748+ if ( !$this->log ) {
 749+ return;
 750+ }
 751+ $type = gettype( $in );
 752+ $class = ucfirst( __CLASS__ );
 753+ if ( $type === 'array' )
 754+ $in = print_r( $in, true );
 755+
 756+ if ( $action === true )
 757+ wfDebugLog( $this->log, "$class::$fname: accepted: '$in' (type: $type)\n");
 758+ elseif ( $action === false )
 759+ wfDebugLog( $this->log, "$class::$fname: rejected: '$in' (type: $type)\n");
 760+ elseif ( $action === null )
 761+ wfDebugLog( $this->log, "$class::$fname: input was: '$in' (type: $type)\n");
 762+ else
 763+ wfDebugLog( $this->log, "$class::$fname: $action (type: $type; content: '$in')\n");
 764+ }
 765+
 766+ /**
 767+ * Convenience function for debugging output
 768+ *
 769+ * @private
 770+ *
 771+ * @param $fname String: the name of the function calling this function
 772+ * @param $io Boolean: Specify whether we're beginning or ending
 773+ */
 774+ private function debugFile( $fname, $io ) {
 775+ if ( !$this->log ) {
 776+ return;
 777+ }
 778+ $class = ucfirst( __CLASS__ );
 779+ if ( $io ) {
 780+ wfDebugLog( $this->log, "$class::$fname: begin processing: '{$this->basename}'\n" );
 781+ } else {
 782+ wfDebugLog( $this->log, "$class::$fname: end processing: '{$this->basename}'\n" );
 783+ }
 784+ }
 785+
 786+}
 787+
Property changes on: trunk/phase3/includes/media/Exif.php
___________________________________________________________________
Added: svn:mergeinfo
1788 Merged /branches/new-installer/phase3/includes/Exif.php:r43664-66004
2789 Merged /branches/wmf-deployment/includes/Exif.php:r53381
3790 Merged /branches/REL1_15/phase3/includes/Exif.php:r51646
4791 Merged /branches/sqlite/includes/Exif.php:r58211-58321
Added: svn:eol-style
5792 + native
Added: svn:keywords
6793 + Author Date Id Revision
Index: trunk/phase3/includes/media/IPTC.php
@@ -0,0 +1,570 @@
 2+<?php
 3+/**
 4+*Class for some IPTC functions.
 5+
 6+*/
 7+class IPTC {
 8+
 9+ /**
 10+ * This takes the results of iptcparse() and puts it into a
 11+ * form that can be handled by mediawiki. Generally called from
 12+ * BitmapMetadataHandler::doApp13.
 13+ *
 14+ * @see http://www.iptc.org/std/IIM/4.1/specification/IIMV4.1.pdf
 15+ *
 16+ * @param String $data app13 block from jpeg containing iptc/iim data
 17+ * @return Array iptc metadata array
 18+ */
 19+ static function parse( $rawData ) {
 20+ $parsed = iptcparse( $rawData );
 21+ $data = Array();
 22+ if (!is_array($parsed)) {
 23+ return $data;
 24+ }
 25+
 26+ $c = '';
 27+ //charset info contained in tag 1:90.
 28+ if (isset($parsed['1#090']) && isset($parsed['1#090'][0])) {
 29+ $c = self::getCharset($parsed['1#090'][0]);
 30+ if ($c === false) {
 31+ //Unknown charset. refuse to parse.
 32+ //note: There is a different between
 33+ //unknown and no charset specified.
 34+ return array();
 35+ }
 36+ unset( $parsed['1#090'] );
 37+ }
 38+
 39+ foreach ( $parsed as $tag => $val ) {
 40+ if ( isset( $val[0] ) && trim($val[0]) == '' ) {
 41+ wfDebugLog('iptc', "IPTC tag $tag had only whitespace as its value.");
 42+ continue;
 43+ }
 44+ switch( $tag ) {
 45+ case '2#120': /*IPTC caption. mapped with exif ImageDescription*/
 46+ $data['ImageDescription'] = self::convIPTC( $val, $c );
 47+ break;
 48+ case '2#116': /* copyright. Mapped with exif copyright */
 49+ $data['Copyright'] = self::convIPTC( $val, $c );
 50+ break;
 51+ case '2#080': /* byline. Mapped with exif Artist */
 52+ /* merge with byline title (2:85)
 53+ * like how exif does it with
 54+ * Title, person. Not sure if this is best
 55+ * approach since we no longer have the two fields
 56+ * separate. each byline title entry corresponds to a
 57+ * specific byline. */
 58+
 59+ $bylines = self::convIPTC( $val, $c );
 60+ if ( isset( $parsed['2#085'] ) ) {
 61+ $titles = self::convIPTC( $parsed['2#085'], $c );
 62+ } else {
 63+ $titles = array();
 64+ }
 65+
 66+ for ( $i = 0; $i < count( $titles ); $i++ ) {
 67+ if ( isset( $bylines[$i] ) ) {
 68+ // theoretically this should always be set
 69+ // but doesn't hurt to be careful.
 70+ $bylines[$i] = $titles[$i] . ', ' . $bylines[$i];
 71+ }
 72+ }
 73+ $data['Artist'] = $bylines;
 74+ break;
 75+ case '2#025': /* keywords */
 76+ $data['Keywords'] = self::convIPTC( $val, $c );
 77+ break;
 78+ case '2#101': /* Country (shown)*/
 79+ $data['CountryDest'] = self::convIPTC( $val, $c );
 80+ break;
 81+ case '2#095': /* state/province (shown) */
 82+ $data['ProvinceOrStateDest'] = self::convIPTC( $val, $c );
 83+ break;
 84+ case '2#090': /* city (Shown) */
 85+ $data['CityDest'] = self::convIPTC( $val, $c );
 86+ break;
 87+ case '2#092': /* sublocation (shown) */
 88+ $data['SublocationDest'] = self::convIPTC( $val, $c );
 89+ break;
 90+ case '2#005': /* object name/title */
 91+ $data['ObjectName'] = self::convIPTC( $val, $c );
 92+ break;
 93+ case '2#040': /* special instructions */
 94+ $data['SpecialInstructions'] = self::convIPTC( $val, $c );
 95+ break;
 96+ case '2#105': /* headline*/
 97+ $data['Headline'] = self::convIPTC( $val, $c );
 98+ break;
 99+ case '2#110': /* credit */
 100+ /*"Identifies the provider of the objectdata,
 101+ * not necessarily the owner/creator". */
 102+ $data['Credit'] = self::convIPTC( $val, $c );
 103+ break;
 104+ case '2#115': /* source */
 105+ /* "Identifies the original owner of the intellectual content of the
 106+ *objectdata. This could be an agency, a member of an agency or
 107+ *an individual." */
 108+ $data['Source'] = self::convIPTC( $val, $c );
 109+ break;
 110+
 111+ case '2#007': /* edit status (lead, correction, etc) */
 112+ $data['EditStatus'] = self::convIPTC( $val, $c );
 113+ break;
 114+ case '2#015': /* category. deprecated. max 3 letters in theory, often more */
 115+ $data['iimCategory'] = self::convIPTC( $val, $c );
 116+ break;
 117+ case '2#020': /* category. deprecated. */
 118+ $data['iimSupplementalCategory'] = self::convIPTC( $val, $c );
 119+ break;
 120+ case '2#010': /*urgency (1-8. 1 most, 5 normal, 8 low priority)*/
 121+ $data['Urgency'] = self::convIPTC( $val, $c );
 122+ break;
 123+ case '2#022':
 124+ /* "Identifies objectdata that recurs often and predictably...
 125+ * Example: Euroweather" */
 126+ $data['FixtureIdentifier'] = self::convIPTC( $val, $c );
 127+ break;
 128+ case '2#026':
 129+ /* Content location code (iso 3166 + some custom things)
 130+ * ex: TUR (for turkey), XUN (for UN), XSP (outer space)
 131+ * See wikipedia article on iso 3166 and appendix D of iim std. */
 132+ $data['LocationDestCode'] = self::convIPTC( $val, $c );
 133+ break;
 134+ case '2#027':
 135+ /* Content location name. Full printable name
 136+ * of location of photo. */
 137+ $data['LocationDest'] = self::convIPTC( $val, $c );
 138+ break;
 139+ case '2#065':
 140+ /* Originating Program.
 141+ * Combine with Program version (2:70) if present.
 142+ */
 143+ $software = self::convIPTC( $val, $c );
 144+
 145+ if ( count( $software ) !== 1 ) {
 146+ //according to iim standard this cannot have multiple values
 147+ //so if there is more than one, something weird is happening,
 148+ //and we skip it.
 149+ wfDebugLog( 'iptc', 'IPTC: Wrong count on 2:65 Software field' );
 150+ break;
 151+ }
 152+
 153+ if ( isset( $parsed['2#070'] ) ) {
 154+ //if a version is set for the software.
 155+ $softwareVersion = self::convIPTC( $parsed['2#070'], $c );
 156+ unset($parsed['2#070']);
 157+ $data['Software'] = array( array( $software[0], $softwareVersion[0] ) );
 158+ } else {
 159+ $data['Software'] = $software;
 160+ }
 161+
 162+
 163+ break;
 164+ case '2#075':
 165+ /* Object cycle.
 166+ * a for morning (am), p for evening, b for both */
 167+ $data['ObjectCycle'] = self::convIPTC( $val, $c );
 168+ break;
 169+ case '2#100':
 170+ /* Country/Primary location code.
 171+ * "Indicates the code of the country/primary location where the
 172+ * intellectual property of the objectdata was created"
 173+ * unclear how this differs from 2#026
 174+ */
 175+ $data['CountryCodeDest'] = self::convIPTC( $val, $c );
 176+ break;
 177+ case '2#103':
 178+ /* original transmission ref.
 179+ * "A code representing the location of original transmission ac-
 180+ * cording to practises of the provider."
 181+ */
 182+ $data['OriginalTransmissionRef'] = self::convIPTC( $val, $c );
 183+ break;
 184+ case '2#118': /*contact*/
 185+ $data['Contact'] = self::convIPTC( $val, $c );
 186+ break;
 187+ case '2#122':
 188+ /* Writer/Editor
 189+ * "Identification of the name of the person involved in the writing,
 190+ * editing or correcting the objectdata or caption/abstract."
 191+ */
 192+ $data['Writer'] = self::convIPTC( $val, $c );
 193+ break;
 194+ case '2#135': /* lang code */
 195+ $data['LanguageCode'] = self::convIPTC( $val, $c );
 196+ break;
 197+
 198+ // Start date stuff.
 199+ // It doesn't accept incomplete dates even though they are valid
 200+ // according to spec.
 201+ // Should potentially store timezone as well.
 202+ case '2#055':
 203+ //Date created (not date digitized).
 204+ //Maps to exif DateTimeOriginal
 205+ if ( isset( $parsed['2#060'] ) ) {
 206+ $time = $parsed['2#060'];
 207+ } else {
 208+ $time = Array();
 209+ }
 210+ $timestamp = self::timeHelper( $val, $time, $c );
 211+ if ($timestamp) {
 212+ $data['DateTimeOriginal'] = $timestamp;
 213+ }
 214+ break;
 215+
 216+ case '2#062':
 217+ //Date converted to digital representation.
 218+ //Maps to exif DateTimeDigitized
 219+ if ( isset( $parsed['2#063'] ) ) {
 220+ $time = $parsed['2#063'];
 221+ } else {
 222+ $time = Array();
 223+ }
 224+ $timestamp = self::timeHelper( $val, $time, $c );
 225+ if ($timestamp) {
 226+ $data['DateTimeDigitized'] = $timestamp;
 227+ }
 228+ break;
 229+
 230+ case '2#030':
 231+ //Date released.
 232+ if ( isset( $parsed['2#035'] ) ) {
 233+ $time = $parsed['2#035'];
 234+ } else {
 235+ $time = Array();
 236+ }
 237+ $timestamp = self::timeHelper( $val, $time, $c );
 238+ if ($timestamp) {
 239+ $data['DateTimeReleased'] = $timestamp;
 240+ }
 241+ break;
 242+
 243+ case '2#037':
 244+ //Date expires.
 245+ if ( isset( $parsed['2#038'] ) ) {
 246+ $time = $parsed['2#038'];
 247+ } else {
 248+ $time = Array();
 249+ }
 250+ $timestamp = self::timeHelper( $val, $time, $c );
 251+ if ($timestamp) {
 252+ $data['DateTimeExpires'] = $timestamp;
 253+ }
 254+ break;
 255+
 256+ case '2#000': /* iim version */
 257+ // unlike other tags, this is a 2-byte binary number.
 258+ //technically this is required if there is iptc data
 259+ //but in practise it isn't always there.
 260+ if ( strlen( $val[0] ) == 2 ) {
 261+ //if is just to be paranoid.
 262+ $versionValue = ord( substr( $val[0], 0, 1 ) ) * 256;
 263+ $versionValue += ord( substr( $val[0], 1, 1 ) );
 264+ $data['iimVersion'] = $versionValue;
 265+ }
 266+ break;
 267+
 268+ case '2#004':
 269+ // IntellectualGenere.
 270+ // first 4 characters are an id code
 271+ // That we're not really interested in.
 272+
 273+ // This prop is weird, since it's
 274+ // allowed to have multiple values
 275+ // in iim 4.1, but not in the XMP
 276+ // stuff. We're going to just
 277+ // extract the first value.
 278+ $con = self::ConvIPTC( $val, $c );
 279+ if ( strlen( $con[0] ) < 5 ) {
 280+ wfDebugLog( 'iptc', 'IPTC: '
 281+ . '2:04 too short. '
 282+ . 'Ignoring.' );
 283+ break;
 284+ }
 285+ $extracted = substr( $con[0], 4 );
 286+ $data['IntellectualGenre'] = $extracted;
 287+ break;
 288+
 289+ case '2#012':
 290+ // Subject News code - this is a compound field
 291+ // at the moment we only extract the subject news
 292+ // code, which is an 8 digit (ascii) number
 293+ // describing the subject matter of the content.
 294+ $codes = self::convIPTC( $val, $c );
 295+ foreach ( $codes as $ic ) {
 296+ $fields = explode(':', $ic, 3 );
 297+
 298+ if ( count( $fields ) < 2 ||
 299+ $fields[0] !== 'IPTC' )
 300+ {
 301+ wfDebugLog( 'IPTC', 'IPTC: '
 302+ . 'Invalid 2:12 - ' . $ic );
 303+ break;
 304+ }
 305+ $data['SubjectNewsCode'] = $fields[1];
 306+ }
 307+ break;
 308+
 309+ // purposely does not do 2:125, 2:130, 2:131,
 310+ // 2:47, 2:50, 2:45, 2:42, 2:8, 2:3
 311+ // 2:200, 2:201, 2:202
 312+ // or the audio stuff (2:150 to 2:154)
 313+
 314+ case '2#070':
 315+ case '2#060':
 316+ case '2#063':
 317+ case '2#085':
 318+ case '2#038':
 319+ case '2#035':
 320+ //ignore. Handled elsewhere.
 321+ break;
 322+
 323+ default:
 324+ wfDebugLog( 'iptc', "Unsupported iptc tag: $tag. Value: " . implode( ',', $val ));
 325+ break;
 326+ }
 327+
 328+ }
 329+ return $data;
 330+ }
 331+
 332+ /**
 333+ * Convert an iptc date and time tags into the exif format
 334+ *
 335+ * @todo Potentially this should also capture the timezone offset.
 336+ * @param Array $date The date tag
 337+ * @param Array $time The time tag
 338+ * @return String Date in exif format.
 339+ */
 340+ private static function timeHelper( $date, $time, $c ) {
 341+ if ( count( $date ) === 1 ) {
 342+ //the standard says this should always be 1
 343+ //just double checking.
 344+ list($date) = self::convIPTC( $date, $c );
 345+ } else {
 346+ return null;
 347+ }
 348+
 349+ if ( count( $time ) === 1 ) {
 350+ list($time) = self::convIPTC( $time, $c );
 351+ $dateOnly = false;
 352+ } else {
 353+ $time = '000000+0000'; //placeholder
 354+ $dateOnly = true;
 355+ }
 356+
 357+ if ( ! ( preg_match('/\d\d\d\d\d\d[-+]\d\d\d\d/', $time)
 358+ && preg_match('/\d\d\d\d\d\d\d\d/', $date)
 359+ && substr($date, 0, 4) !== '0000'
 360+ && substr($date, 4, 2) !== '00'
 361+ && substr($date, 6, 2) !== '00'
 362+ ) ) {
 363+ //something wrong.
 364+ // Note, this rejects some valid dates according to iptc spec
 365+ // for example: the date 00000400 means the photo was taken in
 366+ // April, but the year and day is unknown. We don't process these
 367+ // types of incomplete dates atm.
 368+ wfDebugLog( 'iptc', "IPTC: invalid time ( $time ) or date ( $date )");
 369+ return null;
 370+ }
 371+
 372+ $unixTS = wfTimestamp( TS_UNIX, $date . substr( $time, 0, 6 ));
 373+ if ( $unixTS === false ) {
 374+ wfDebugLog( 'iptc', "IPTC: can't convert date to TS_UNIX: $date $time." );
 375+ return null;
 376+ }
 377+
 378+ $tz = ( intval( substr( $time, 7, 2 ) ) *60*60 )
 379+ + ( intval( substr( $time, 9, 2 ) ) * 60 );
 380+
 381+ if ( substr( $time, 6, 1 ) === '-' ) {
 382+ $tz = - $tz;
 383+ }
 384+
 385+ $finalTimestamp = wfTimestamp( TS_EXIF, $unixTS + $tz );
 386+ if ( $finalTimestamp === false ) {
 387+ wfDebugLog( 'iptc', "IPTC: can't make final timestamp. Date: " . ( $unixTS + $tz ) );
 388+ return null;
 389+ }
 390+ if ( $dateOnly ) {
 391+ //return the date only
 392+ return substr( $finalTimestamp, 0, 10 );
 393+ } else {
 394+ return $finalTimestamp;
 395+ }
 396+
 397+ }
 398+
 399+ /**
 400+ * Helper function to convert charset for iptc values.
 401+ * @param $data Mixed String or Array: The iptc string
 402+ * @param $charset String: The charset
 403+ */
 404+ private static function convIPTC ( $data, $charset ) {
 405+ global $wgLang;
 406+ if ( is_array( $data ) ) {
 407+ foreach ($data as &$val) {
 408+ $val = self::convIPTCHelper( $val, $charset );
 409+ }
 410+ } else {
 411+ $data = self::convIPTCHelper ( $data, $charset );
 412+ }
 413+
 414+ return $data;
 415+ }
 416+ /**
 417+ * Helper function of a helper function to convert charset for iptc values.
 418+ * @param $data Mixed String or Array: The iptc string
 419+ * @param $charset String: The charset
 420+ */
 421+ private static function convIPTCHelper ( $data, $charset ) {
 422+ if ( $charset ) {
 423+ $data = iconv($charset, "UTF-8//IGNORE", $data);
 424+ if ($data === false) {
 425+ $data = "";
 426+ wfDebugLog('iptc', __METHOD__ . " Error converting iptc data charset $charset to utf-8");
 427+ }
 428+ } else {
 429+ //treat as utf-8 if is valid utf-8. otherwise pretend its windows-1252
 430+ // most of the time if there is no 1:90 tag, it is either ascii, latin1, or utf-8
 431+ $oldData = $data;
 432+ UtfNormal::quickIsNFCVerify( $data ); //make $data valid utf-8
 433+ if ($data === $oldData) return $data; //if validation didn't change $data
 434+ else return self::convIPTCHelper ( $oldData, 'Windows-1252' );
 435+ }
 436+ return trim( $data );
 437+ }
 438+
 439+ /**
 440+ * take the value of 1:90 tag and returns a charset
 441+ * @param String $tag 1:90 tag.
 442+ * @return charset name or "?"
 443+ * Warning, this function does not (and is not intended to) detect
 444+ * all iso 2022 escape codes. In practise, the code for utf-8 is the
 445+ * only code that seems to have wide use. It does detect that code.
 446+ */
 447+ static function getCharset($tag) {
 448+
 449+ //According to iim standard, charset is defined by the tag 1:90.
 450+ //in which there are iso 2022 escape sequences to specify the character set.
 451+ //the iim standard seems to encourage that all necessary escape sequences are
 452+ //in the 1:90 tag, but says it doesn't have to be.
 453+
 454+ //This is in need of more testing probably. This is definitely not complete.
 455+ //however reading the docs of some other iptc software, it appears that most iptc software
 456+ //only recognizes utf-8. If 1:90 tag is not present content is
 457+ // usually ascii or iso-8859-1 (and sometimes utf-8), but no guarantee.
 458+
 459+ //This also won't work if there are more than one escape sequence in the 1:90 tag
 460+ //or if something is put in the G2, or G3 charsets, etc. It will only reliably recognize utf-8.
 461+
 462+ // This is just going through the charsets mentioned in appendix C of the iim standard.
 463+
 464+ // \x1b = ESC.
 465+ switch ( $tag ) {
 466+ case "\x1b%G": //utf-8
 467+ //Also call things that are compatible with utf-8, utf-8 (e.g. ascii)
 468+ case "\x1b(B": // ascii
 469+ case "\x1b(@": // iso-646-IRV (ascii in latest version, $ different in older version)
 470+ $c = 'UTF-8';
 471+ break;
 472+ case "\x1b(A": //like ascii, but british.
 473+ $c = 'ISO646-GB';
 474+ break;
 475+ case "\x1b(C": //some obscure sweedish/finland encoding
 476+ $c = 'ISO-IR-8-1';
 477+ break;
 478+ case "\x1b(D":
 479+ $c = 'ISO-IR-8-2';
 480+ break;
 481+ case "\x1b(E": //some obscure danish/norway encoding
 482+ $c = 'ISO-IR-9-1';
 483+ break;
 484+ case "\x1b(F":
 485+ $c = 'ISO-IR-9-2';
 486+ break;
 487+ case "\x1b(G":
 488+ $c = 'SEN_850200_B'; // aka iso 646-SE; ascii-like
 489+ break;
 490+ case "\x1b(I":
 491+ $c = "ISO646-IT";
 492+ break;
 493+ case "\x1b(L":
 494+ $c = "ISO646-PT";
 495+ break;
 496+ case "\x1b(Z":
 497+ $c = "ISO646-ES";
 498+ break;
 499+ case "\x1b([":
 500+ $c = "GREEK7-OLD";
 501+ break;
 502+ case "\x1b(K":
 503+ $c = "ISO646-DE";
 504+ break;
 505+ case "\x1b(N": //crylic
 506+ $c = "ISO_5427";
 507+ break;
 508+ case "\x1b(`": //iso646-NO
 509+ $c = "NS_4551-1";
 510+ break;
 511+ case "\x1b(f": //iso646-FR
 512+ $c = "NF_Z_62-010";
 513+ break;
 514+ case "\x1b(g":
 515+ $c = "PT2"; //iso646-PT2
 516+ break;
 517+ case "\x1b(h":
 518+ $c = "ES2";
 519+ break;
 520+ case "\x1b(i": //iso646-HU
 521+ $c = "MSZ_7795.3";
 522+ break;
 523+ case "\x1b(w":
 524+ $c = "CSA_Z243.4-1985-1";
 525+ break;
 526+ case "\x1b(x":
 527+ $c = "CSA_Z243.4-1985-2";
 528+ break;
 529+ case "\x1b$(B":
 530+ case "\x1b$B":
 531+ case "\x1b&@\x1b$B":
 532+ case "\x1b&@\x1b$(B":
 533+ $c = "JIS_C6226-1983";
 534+ break;
 535+ case "\x1b-A": // iso-8859-1. at least for the high code characters.
 536+ case "\x1b(@\x1b-A":
 537+ case "\x1b(B\x1b-A":
 538+ $c = 'ISO-8859-1';
 539+ break;
 540+ case "\x1b-B": // iso-8859-2. at least for the high code characters.
 541+ $c = 'ISO-8859-2';
 542+ break;
 543+ case "\x1b-C": // iso-8859-3. at least for the high code characters.
 544+ $c = 'ISO-8859-3';
 545+ break;
 546+ case "\x1b-D": // iso-8859-4. at least for the high code characters.
 547+ $c = 'ISO-8859-4';
 548+ break;
 549+ case "\x1b-E": // iso-8859-5. at least for the high code characters.
 550+ $c = 'ISO-8859-5';
 551+ break;
 552+ case "\x1b-F": // iso-8859-6. at least for the high code characters.
 553+ $c = 'ISO-8859-6';
 554+ break;
 555+ case "\x1b-G": // iso-8859-7. at least for the high code characters.
 556+ $c = 'ISO-8859-7';
 557+ break;
 558+ case "\x1b-H": // iso-8859-8. at least for the high code characters.
 559+ $c = 'ISO-8859-8';
 560+ break;
 561+ case "\x1b-I": // CSN_369103. at least for the high code characters.
 562+ $c = 'CSN_369103';
 563+ break;
 564+ default:
 565+ wfDebugLog('iptc', __METHOD__ . 'Unknown charset in iptc 1:90: ' . bin2hex( $tag ) );
 566+ //at this point just give up and refuse to parse iptc?
 567+ $c = false;
 568+ }
 569+ return $c;
 570+ }
 571+}
Property changes on: trunk/phase3/includes/media/IPTC.php
___________________________________________________________________
Added: svn:eol-style
1572 + native
Index: trunk/phase3/includes/media/GIFMetadataExtractor.php
@@ -21,6 +21,13 @@
2222 static $gif_extension_sep;
2323 static $gif_term;
2424
 25+ const VERSION = 1;
 26+
 27+ // Each sub-block is less than or equal to 255 bytes.
 28+ // Most of the time its 255 bytes, except for in XMP
 29+ // blocks, where it's usually between 32-127 bytes each.
 30+ const MAX_SUBBLOCKS = 262144; // 5mb divided by 20.
 31+
2532 static function getMetadata( $filename ) {
2633 self::$gif_frame_sep = pack( "C", ord("," ) );
2734 self::$gif_extension_sep = pack( "C", ord("!" ) );
@@ -29,7 +36,9 @@
3037 $frameCount = 0;
3138 $duration = 0.0;
3239 $isLooped = false;
33 -
 40+ $xmp = "";
 41+ $comment = array();
 42+
3443 if ( !$filename ) {
3544 throw new Exception( "No file name specified" );
3645 } elseif ( !file_exists( $filename ) || is_dir( $filename ) ) {
@@ -103,37 +112,94 @@
104113 if ($term != 0 ) {
105114 throw new Exception( "Malformed Graphics Control Extension block" );
106115 }
 116+ } elseif ($extension_code == 0xFE) {
 117+ // Comment block(s).
 118+ $data = '';
 119+
 120+ $data = self::readBlock( $fh );
 121+ if ( $data === "" ) {
 122+ throw new Exception( 'Read error, zero-length comment block' );
 123+ }
 124+
 125+ // The standard says this should be ASCII, however its unclear if
 126+ // thats true in practise. Check to see if its valid utf-8, if so
 127+ // assume its that, otherwise assume its iso-8859-1
 128+ $dataCopy = $data;
 129+ // quickIsNFCVerify has the side effect of replacing any invalid characters
 130+ UtfNormal::quickIsNFCVerify( $dataCopy );
 131+
 132+ if ( $dataCopy !== $data ) {
 133+ wfSuppressWarnings();
 134+ $data = iconv( 'ISO-8859-1', 'UTF-8', $data );
 135+ wfRestoreWarnings();
 136+ }
 137+
 138+ $commentCount = count( $comment );
 139+ if ( $commentCount === 0
 140+ || $comment[$commentCount-1] !== $data )
 141+ {
 142+ // Some applications repeat the same comment on each
 143+ // frame of an animated GIF image, so if this comment
 144+ // is identical to the last, only extract once.
 145+ $comment[] = $data;
 146+ }
107147 } elseif ($extension_code == 0xFF) {
108148 // Application extension (Netscape info about the animated gif)
 149+ // or XMP (or theoretically any other type of extension block)
109150 $blockLength = fread( $fh, 1 );
110151 $blockLength = unpack( 'C', $blockLength );
111152 $blockLength = $blockLength[1];
112153 $data = fread( $fh, $blockLength );
113154
114 - // NETSCAPE2.0 (application name)
115 - if ($blockLength != 11 || $data != 'NETSCAPE2.0') {
 155+ if ($blockLength != 11 ) {
 156+ wfDebug( __METHOD__ . ' GIF application block with wrong length' );
116157 fseek( $fh, -($blockLength + 1), SEEK_CUR );
117158 self::skipBlock( $fh );
118159 continue;
119160 }
120161
121 - $data = fread( $fh, 2 ); // Block length and introduction, should be 03 01
 162+ // NETSCAPE2.0 (application name for animated gif)
 163+ if ( $data == 'NETSCAPE2.0' ) {
 164+
 165+ $data = fread( $fh, 2 ); // Block length and introduction, should be 03 01
122166
123 - if ($data != "\x03\x01") {
124 - throw new Exception( "Expected \x03\x01, got $data" );
125 - }
 167+ if ($data != "\x03\x01") {
 168+ throw new Exception( "Expected \x03\x01, got $data" );
 169+ }
 170+
 171+ // Unsigned little-endian integer, loop count or zero for "forever"
 172+ $loopData = fread( $fh, 2 );
 173+ $loopData = unpack( 'v', $loopData );
 174+ $loopCount = $loopData[1];
 175+
 176+ if ($loopCount != 1) {
 177+ $isLooped = true;
 178+ }
 179+
 180+ // Read out terminator byte
 181+ fread( $fh, 1 );
 182+ } elseif ( $data == 'XMP DataXMP' ) {
 183+ // application name for XMP data.
 184+ // see pg 18 of XMP spec part 3.
126185
127 - // Unsigned little-endian integer, loop count or zero for "forever"
128 - $loopData = fread( $fh, 2 );
129 - $loopData = unpack( 'v', $loopData );
130 - $loopCount = $loopData[1];
 186+ $xmp = self::readBlock( $fh, true );
131187
132 - if ($loopCount != 1) {
133 - $isLooped = true;
 188+ if ( substr( $xmp, -257, 3 ) !== "\x01\xFF\xFE"
 189+ || substr( $xmp, -4 ) !== "\x03\x02\x01\x00" )
 190+ {
 191+ // this is just a sanity check.
 192+ throw new Exception( "XMP does not have magic trailer!" );
 193+ }
 194+
 195+ // strip out trailer.
 196+ $xmp = substr( $xmp, 0, -257 );
 197+
 198+ } else {
 199+ // unrecognized extension block
 200+ fseek( $fh, -($blockLength + 1), SEEK_CUR );
 201+ self::skipBlock( $fh );
 202+ continue;
134203 }
135 -
136 - // Read out terminator byte
137 - fread( $fh, 1 );
138204 } else {
139205 self::skipBlock( $fh );
140206 }
@@ -149,7 +215,9 @@
150216 return array(
151217 'frameCount' => $frameCount,
152218 'looped' => $isLooped,
153 - 'duration' => $duration
 219+ 'duration' => $duration,
 220+ 'xmp' => $xmp,
 221+ 'comment' => $comment,
154222 );
155223 }
156224
@@ -183,4 +251,40 @@
184252 fread( $fh, $block_len );
185253 }
186254 }
 255+ /**
 256+ * Read a block. In the GIF format, a block is made up of
 257+ * several sub-blocks. Each sub block starts with one byte
 258+ * saying how long the sub-block is, followed by the sub-block.
 259+ * The entire block is terminated by a sub-block of length
 260+ * 0.
 261+ * @param $fh FileHandle
 262+ * @param $includeLengths Boolean Include the length bytes of the
 263+ * sub-blocks in the returned value. Normally this is false,
 264+ * except XMP is weird and does a hack where you need to keep
 265+ * these length bytes.
 266+ * @return The data.
 267+ */
 268+ static function readBlock( $fh, $includeLengths = false ) {
 269+ $data = '';
 270+ $subLength = fread( $fh, 1 );
 271+ $blocks = 0;
 272+
 273+ while( $subLength !== "\0" ) {
 274+ $blocks++;
 275+ if ( $blocks > self::MAX_SUBBLOCKS ) {
 276+ throw new Exception( "MAX_SUBBLOCKS exceeded (over $blocks sub-blocks)" );
 277+ }
 278+ if ( feof( $fh ) ) {
 279+ throw new Exception( "Read error: Unexpected EOF." );
 280+ }
 281+ if ( $includeLengths ) {
 282+ $data .= $subLength;
 283+ }
 284+
 285+ $data .= fread( $fh, ord( $subLength ) );
 286+ $subLength = fread( $fh, 1 );
 287+ }
 288+ return $data;
 289+ }
 290+
187291 }
Index: trunk/phase3/includes/media/XMP.php
@@ -0,0 +1,1165 @@
 2+<?php
 3+/**
 4+* Class for reading xmp data containing properties relevant to
 5+* images, and spitting out an array that FormatExif accepts.
 6+*
 7+* Note, this is not meant to recognize every possible thing you can
 8+* encode in XMP. It should recognize all the properties we want.
 9+* For example it doesn't have support for structures with multiple
 10+* nesting levels, as none of the properties we're supporting use that
 11+* feature. If it comes across properties it doesn't recognize, it should
 12+* ignore them.
 13+*
 14+* The public methods one would call in this class are
 15+* - parse( $content )
 16+* Reads in xmp content.
 17+* Can potentially be called multiple times with partial data each time.
 18+* - parseExtended( $content )
 19+* Reads XMPExtended blocks (jpeg files only).
 20+* - getResults
 21+* Outputs a results array.
 22+*
 23+* Note XMP kind of looks like rdf. They are not the same thing - XMP is
 24+* encoded as a specific subset of rdf. This class can read XMP. It cannot
 25+* read rdf.
 26+*
 27+*/
 28+class XMPReader {
 29+
 30+ private $curItem = array(); // array to hold the current element (and previous element, and so on)
 31+ private $ancestorStruct = false; // the structure name when processing nested structures.
 32+ private $charContent = false; // temporary holder for character data that appears in xmp doc.
 33+ private $mode = array(); // stores the state the xmpreader is in (see MODE_FOO constants)
 34+ private $results = array(); // array to hold results
 35+ private $processingArray = false; // if we're doing a seq or bag.
 36+ private $itemLang = false; // used for lang alts only
 37+
 38+ private $xmlParser;
 39+ private $charset = false;
 40+ private $extendedXMPOffset = 0;
 41+
 42+ protected $items;
 43+
 44+ /*
 45+ * These are various mode constants.
 46+ * they are used to figure out what to do
 47+ * with an element when its encountered.
 48+ *
 49+ * For example, MODE_IGNORE is used when processing
 50+ * a property we're not interested in. So if a new
 51+ * element pops up when we're in that mode, we ignore it.
 52+ */
 53+ const MODE_INITIAL = 0;
 54+ const MODE_IGNORE = 1;
 55+ const MODE_LI = 2;
 56+ const MODE_LI_LANG = 3;
 57+ const MODE_QDESC = 4;
 58+
 59+ // The following MODE constants are also used in the
 60+ // $items array to denote what type of property the item is.
 61+ const MODE_SIMPLE = 10;
 62+ const MODE_STRUCT = 11; // structure (associative array)
 63+ const MODE_SEQ = 12; // ordered list
 64+ const MODE_BAG = 13; // unordered list
 65+ const MODE_LANG = 14;
 66+ const MODE_ALT = 15; // non-language alt. Currently not implemented, and not needed atm.
 67+ const MODE_BAGSTRUCT = 16; // A BAG of Structs.
 68+
 69+ const NS_RDF = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#';
 70+ const NS_XML = 'http://www.w3.org/XML/1998/namespace';
 71+
 72+
 73+ /**
 74+ * Constructor.
 75+ *
 76+ * Primary job is to initialize the XMLParser
 77+ */
 78+ function __construct() {
 79+
 80+ if ( !function_exists( 'xml_parser_create_ns' ) ) {
 81+ // this should already be checked by this point
 82+ throw new MWException( 'XMP support requires XML Parser' );
 83+ }
 84+
 85+ $this->items = XMPInfo::getItems();
 86+
 87+ $this->resetXMLParser();
 88+
 89+ }
 90+ /**
 91+ * Main use is if a single item has multiple xmp documents describing it.
 92+ * For example in jpeg's with extendedXMP
 93+ */
 94+ private function resetXMLParser() {
 95+
 96+ if ($this->xmlParser) {
 97+ //is this needed?
 98+ xml_parser_free( $this->xmlParser );
 99+ }
 100+
 101+ $this->xmlParser = xml_parser_create_ns( 'UTF-8', ' ' );
 102+ xml_parser_set_option( $this->xmlParser, XML_OPTION_CASE_FOLDING, 0 );
 103+ xml_parser_set_option( $this->xmlParser, XML_OPTION_SKIP_WHITE, 1 );
 104+
 105+ xml_set_element_handler( $this->xmlParser,
 106+ array( $this, 'startElement' ),
 107+ array( $this, 'endElement' ) );
 108+
 109+ xml_set_character_data_handler( $this->xmlParser, array( $this, 'char' ) );
 110+
 111+
 112+ }
 113+
 114+ /** Destroy the xml parser
 115+ *
 116+ * Not sure if this is actually needed.
 117+ */
 118+ function __destruct() {
 119+ // not sure if this is needed.
 120+ xml_parser_free( $this->xmlParser );
 121+ }
 122+
 123+ /** Get the result array. Do some post-processing before returning
 124+ * the array, and transform any metadata that is special-cased.
 125+ *
 126+ * @return Array array of results as an array of arrays suitable for
 127+ * FormatMetadata::getFormattedData().
 128+ */
 129+ public function getResults() {
 130+ // xmp-special is for metadata that affects how stuff
 131+ // is extracted. For example xmpNote:HasExtendedXMP.
 132+
 133+ // It is also used to handle photoshop:AuthorsPosition
 134+ // which is weird and really part of another property,
 135+ // see 2:85 in IPTC. See also pg 21 of IPTC4XMP standard.
 136+ // The location fields also use it.
 137+
 138+ $data = $this->results;
 139+
 140+ wfRunHooks('XMPGetResults', Array(&$data));
 141+
 142+ if ( isset( $data['xmp-special']['AuthorsPosition'] )
 143+ && is_string( $data['xmp-special']['AuthorsPosition'] )
 144+ && isset( $data['xmp-general']['Artist'][0] )
 145+ ) {
 146+ // Note, if there is more than one creator,
 147+ // this only applies to first. This also will
 148+ // only apply to the dc:Creator prop, not the
 149+ // exif:Artist prop.
 150+
 151+ $data['xmp-general']['Artist'][0] =
 152+ $data['xmp-special']['AuthorsPosition'] . ', '
 153+ . $data['xmp-general']['Artist'][0];
 154+ }
 155+
 156+ // Go through the LocationShown and LocationCreated
 157+ // changing it to the non-hierarchal form used by
 158+ // the other location fields.
 159+
 160+ if ( isset( $data['xmp-special']['LocationShown'][0] )
 161+ && is_array( $data['xmp-special']['LocationShown'][0] )
 162+ ) {
 163+ // the is_array is just paranoia. It should always
 164+ // be an array.
 165+ foreach( $data['xmp-special']['LocationShown'] as $loc ) {
 166+ if ( !is_array( $loc ) ) {
 167+ // To avoid copying over the _type meta-fields.
 168+ continue;
 169+ }
 170+ foreach( $loc as $field => $val ) {
 171+ $data['xmp-general'][$field . 'Dest'][] = $val;
 172+ }
 173+ }
 174+ }
 175+ if ( isset( $data['xmp-special']['LocationCreated'][0] )
 176+ && is_array( $data['xmp-special']['LocationCreated'][0] )
 177+ ) {
 178+ // the is_array is just paranoia. It should always
 179+ // be an array.
 180+ foreach( $data['xmp-special']['LocationCreated'] as $loc ) {
 181+ if ( !is_array( $loc ) ) {
 182+ // To avoid copying over the _type meta-fields.
 183+ continue;
 184+ }
 185+ foreach( $loc as $field => $val ) {
 186+ $data['xmp-general'][$field . 'Created'][] = $val;
 187+ }
 188+ }
 189+ }
 190+
 191+
 192+ // We don't want to return the special values, since they're
 193+ // special and not info to be stored about the file.
 194+ unset( $data['xmp-special'] );
 195+
 196+ // Convert GPSAltitude to negative if below sea level.
 197+ if ( isset( $data['xmp-exif']['GPSAltitudeRef'] ) ) {
 198+ if ( $data['xmp-exif']['GPSAltitudeRef'] == '1'
 199+ && isset( $data['xmp-exif']['GPSAltitude'] )
 200+ ) {
 201+ $data['xmp-exif']['GPSAltitude'] *= -1;
 202+ }
 203+ unset( $data['xmp-exif']['GPSAltitudeRef'] );
 204+ }
 205+
 206+ return $data;
 207+ }
 208+
 209+ /**
 210+ * Main function to call to parse XMP. Use getResults to
 211+ * get results.
 212+ *
 213+ * Also catches any errors during processing, writes them to
 214+ * debug log, blanks result array and returns false.
 215+ *
 216+ * @param String: $content XMP data
 217+ * @param Boolean: $allOfIt If this is all the data (true) or if its split up (false). Default true
 218+ * @param Boolean: $reset - does xml parser need to be reset. Default false
 219+ * @return Boolean success.
 220+ */
 221+ public function parse( $content, $allOfIt = true, $reset = false ) {
 222+ if ( $reset ) {
 223+ $this->resetXMLParser();
 224+ }
 225+ try {
 226+
 227+ // detect encoding by looking for BOM which is supposed to be in processing instruction.
 228+ // see page 12 of http://www.adobe.com/devnet/xmp/pdfs/XMPSpecificationPart3.pdf
 229+ if ( !$this->charset ) {
 230+ $bom = array();
 231+ if ( preg_match( '/\xEF\xBB\xBF|\xFE\xFF|\x00\x00\xFE\xFF|\xFF\xFE\x00\x00|\xFF\xFE/',
 232+ $content, $bom )
 233+ ) {
 234+ switch ( $bom[0] ) {
 235+ case "\xFE\xFF":
 236+ $this->charset = 'UTF-16BE';
 237+ break;
 238+ case "\xFF\xFE":
 239+ $this->charset = 'UTF-16LE';
 240+ break;
 241+ case "\x00\x00\xFE\xFF":
 242+ $this->charset = 'UTF-32BE';
 243+ break;
 244+ case "\xFF\xFE\x00\x00":
 245+ $this->charset = 'UTF-32LE';
 246+ break;
 247+ case "\xEF\xBB\xBF":
 248+ $this->charset = 'UTF-8';
 249+ break;
 250+ default:
 251+ //this should be impossible to get to
 252+ throw new MWException("Invalid BOM");
 253+ break;
 254+
 255+ }
 256+
 257+ } else {
 258+ // standard specifically says, if no bom assume utf-8
 259+ $this->charset = 'UTF-8';
 260+ }
 261+ }
 262+ if ( $this->charset !== 'UTF-8' ) {
 263+ //don't convert if already utf-8
 264+ $content = iconv( $this->charset, 'UTF-8//IGNORE', $content );
 265+ }
 266+
 267+ $ok = xml_parse( $this->xmlParser, $content, $allOfIt );
 268+ if ( !$ok ) {
 269+ $error = xml_error_string( xml_get_error_code( $this->xmlParser ) );
 270+ $where = 'line: ' . xml_get_current_line_number( $this->xmlParser )
 271+ . ' column: ' . xml_get_current_column_number( $this->xmlParser )
 272+ . ' byte offset: ' . xml_get_current_byte_index( $this->xmlParser );
 273+
 274+ wfDebugLog( 'XMP', "XMPReader::parse : Error reading XMP content: $error ($where)" );
 275+ $this->results = array(); // blank if error.
 276+ return false;
 277+ }
 278+ } catch ( MWException $e ) {
 279+ wfDebugLog( 'XMP', 'XMP parse error: ' . $e );
 280+ $this->results = array();
 281+ return false;
 282+ }
 283+ return true;
 284+ }
 285+
 286+ /** Entry point for XMPExtended blocks in jpeg files
 287+ *
 288+ * @todo In serious need of testing
 289+ * @see http://www.adobe.ge/devnet/xmp/pdfs/XMPSpecificationPart3.pdf XMP spec part 3 page 20
 290+ * @param String $content XMPExtended block minus the namespace signature
 291+ * @return Boolean If it succeeded.
 292+ */
 293+ public function parseExtended( $content ) {
 294+ // FIXME: This is untested. Hard to find example files
 295+ // or programs that make such files..
 296+ $guid = substr( $content, 0, 32 );
 297+ if ( !isset( $this->results['xmp-special']['HasExtendedXMP'] )
 298+ || $this->results['xmp-special']['HasExtendedXMP'] !== $guid )
 299+ {
 300+ wfDebugLog('XMP', __METHOD__ . " Ignoring XMPExtended block due to wrong guid (guid= '$guid' )");
 301+ return;
 302+ }
 303+ $len = unpack( 'Nlength/Noffset', substr( $content, 32, 8 ) );
 304+
 305+ if (!$len || $len['length'] < 4 || $len['offset'] < 0 || $len['offset'] > $len['length'] ) {
 306+ wfDebugLog('XMP', __METHOD__ . 'Error reading extended XMP block, invalid length or offset.');
 307+ return false;
 308+ }
 309+
 310+
 311+ // we're not very robust here. we should accept it in the wrong order. To quote
 312+ // the xmp standard:
 313+ // "A JPEG writer should write the ExtendedXMP marker segments in order, immediately following the
 314+ // StandardXMP. However, the JPEG standard does not require preservation of marker segment order. A
 315+ // robust JPEG reader should tolerate the marker segments in any order."
 316+ //
 317+ // otoh the probability that an image will have more than 128k of metadata is rather low...
 318+ // so the probability that it will have > 128k, and be in the wrong order is very low...
 319+
 320+ if ( $len['offset'] !== $this->extendedXMPOffset ) {
 321+ wfDebugLog('XMP', __METHOD__ . 'Ignoring XMPExtended block due to wrong order. (Offset was '
 322+ . $len['offset'] . ' but expected ' . $this->extendedXMPOffset . ')');
 323+ return false;
 324+ }
 325+
 326+ if ( $len['offset'] === 0 ) {
 327+ // if we're starting the extended block, we've probably already
 328+ // done the XMPStandard block, so reset.
 329+ $this->resetXMLParser();
 330+ }
 331+
 332+ $this->extendedXMPOffset += $len['length'];
 333+
 334+ $actualContent = substr( $content, 40 );
 335+
 336+ if ( $this->extendedXMPOffset === strlen( $actualContent ) ) {
 337+ $atEnd = true;
 338+ } else {
 339+ $atEnd = false;
 340+ }
 341+
 342+ wfDebugLog('XMP', __METHOD__ . 'Parsing a XMPExtended block');
 343+ return $this->parse( $actualContent, $atEnd );
 344+ }
 345+
 346+ /**
 347+ * Character data handler
 348+ * Called whenever character data is found in the xmp document.
 349+ *
 350+ * does nothing if we're in MODE_IGNORE or if the data is whitespace
 351+ * throws an error if we're not in MODE_SIMPLE (as we're not allowed to have character
 352+ * data in the other modes).
 353+ *
 354+ * As an example, this happens when we encounter XMP like:
 355+ * <exif:DigitalZoomRatio>0/10</exif:DigitalZoomRatio>
 356+ * and are processing the 0/10 bit.
 357+ *
 358+ * @param $parser XMLParser reference to the xml parser
 359+ * @param $data String Character data
 360+ * @throws MWException on invalid data
 361+ */
 362+ function char( $parser, $data ) {
 363+
 364+ $data = trim( $data );
 365+ if ( trim( $data ) === "" ) {
 366+ return;
 367+ }
 368+
 369+ if ( !isset( $this->mode[0] ) ) {
 370+ throw new MWException( 'Unexpected character data before first rdf:Description element' );
 371+ }
 372+
 373+ if ( $this->mode[0] === self::MODE_IGNORE ) return;
 374+
 375+ if ( $this->mode[0] !== self::MODE_SIMPLE
 376+ && $this->mode[0] !== self::MODE_QDESC
 377+ ) {
 378+ throw new MWException( 'character data where not expected. (mode ' . $this->mode[0] . ')' );
 379+ }
 380+
 381+ // to check, how does this handle w.s.
 382+ if ( $this->charContent === false ) {
 383+ $this->charContent = $data;
 384+ } else {
 385+ $this->charContent .= $data;
 386+ }
 387+
 388+ }
 389+ /** When we hit a closing element in MODE_IGNORE
 390+ * Check to see if this is the element we started to ignore,
 391+ * in which case we get out of MODE_IGNORE
 392+ *
 393+ * @param $elm String Namespace of element followed by a space and then tag name of element.
 394+ */
 395+ private function endElementModeIgnore ( $elm ) {
 396+
 397+ if ( $this->curItem[0] === $elm ) {
 398+ array_shift( $this->curItem );
 399+ array_shift( $this->mode );
 400+ }
 401+ return;
 402+
 403+ }
 404+ /**
 405+ * Hit a closing element when in MODE_SIMPLE.
 406+ * This generally means that we finished processing a
 407+ * property value, and now have to save the result to the
 408+ * results array
 409+ *
 410+ * For example, when processing:
 411+ * <exif:DigitalZoomRatio>0/10</exif:DigitalZoomRatio>
 412+ * this deals with when we hit </exif:DigitalZoomRatio>.
 413+ *
 414+ * Or it could be if we hit the end element of a property
 415+ * of a compound data structure (like a member of an array).
 416+ *
 417+ * @param $elm String namespace, space, and tag name.
 418+ */
 419+ private function endElementModeSimple ( $elm ) {
 420+ if ( $this->charContent !== false ) {
 421+ if ( $this->processingArray ) {
 422+ // if we're processing an array, use the original element
 423+ // name instead of rdf:li.
 424+ list( $ns, $tag ) = explode( ' ', $this->curItem[0], 2 );
 425+ } else {
 426+ list( $ns, $tag ) = explode( ' ', $elm, 2 );
 427+ }
 428+ $this->saveValue( $ns, $tag, $this->charContent );
 429+
 430+ $this->charContent = false; // reset
 431+ }
 432+ array_shift( $this->curItem );
 433+ array_shift( $this->mode );
 434+
 435+ }
 436+ /**
 437+ * Hit a closing element in MODE_STRUCT, MODE_SEQ, MODE_BAG
 438+ * generally means we've finished processing a nested structure.
 439+ * resets some internal variables to indicate that.
 440+ *
 441+ * Note this means we hit the </closing element> not the </rdf:Seq>.
 442+ *
 443+ * For example, when processing:
 444+ * <exif:ISOSpeedRatings> <rdf:Seq> <rdf:li>64</rdf:li>
 445+ * </rdf:Seq> </exif:ISOSpeedRatings>
 446+ *
 447+ * This method is called when we hit the </exif:ISOSpeedRatings> tag.
 448+ *
 449+ * @param $elm String namespace . space . tag name.
 450+ */
 451+ private function endElementNested( $elm ) {
 452+
 453+ /* cur item must be the same as $elm, unless if in MODE_STRUCT
 454+ in which case it could also be rdf:Description */
 455+ if ( $this->curItem[0] !== $elm
 456+ && !( $elm === self::NS_RDF . ' Description'
 457+ && $this->mode[0] === self::MODE_STRUCT )
 458+ ) {
 459+ throw new MWException( "nesting mismatch. got a </$elm> but expected a </" . $this->curItem[0] . '>' );
 460+ }
 461+
 462+ // Validate structures.
 463+ list( $ns, $tag ) = explode( ' ', $elm, 2 );
 464+ if ( isset( $this->items[$ns][$tag]['validate'] ) ) {
 465+
 466+ $info =& $this->items[$ns][$tag];
 467+ $finalName = isset( $info['map_name'] )
 468+ ? $info['map_name'] : $tag;
 469+
 470+ $validate = is_array( $info['validate'] ) ? $info['validate']
 471+ : array( 'XMPValidate', $info['validate'] );
 472+
 473+ if ( !isset( $this->results['xmp-' . $info['map_group']][$finalName] ) ) {
 474+ // This can happen if all the members of the struct failed validation.
 475+ wfDebugLog( 'XMP', __METHOD__ . " <$ns:$tag> has no valid members." );
 476+
 477+ } elseif ( is_callable( $validate ) ) {
 478+ $val =& $this->results['xmp-' . $info['map_group']][$finalName];
 479+ call_user_func_array( $validate, array( $info, &$val, false ) );
 480+ if ( is_null( $val ) ) {
 481+ // the idea being the validation function will unset the variable if
 482+ // its invalid.
 483+ wfDebugLog( 'XMP', __METHOD__ . " <$ns:$tag> failed validation." );
 484+ unset( $this->results['xmp-' . $info['map_group']][$finalName] );
 485+ }
 486+ } else {
 487+ wfDebugLog( 'XMP', __METHOD__ . " Validation function for $finalName ("
 488+ . $validate[0] . '::' . $validate[1] . '()) is not callable.' );
 489+ }
 490+ }
 491+
 492+ array_shift( $this->curItem );
 493+ array_shift( $this->mode );
 494+ $this->ancestorStruct = false;
 495+ $this->processingArray = false;
 496+ $this->itemLang = false;
 497+ }
 498+
 499+ /**
 500+ * Hit a closing element in MODE_LI (either rdf:Seq, or rdf:Bag )
 501+ * Add information about what type of element this is.
 502+ *
 503+ * Note we still have to hit the outer </property>
 504+ *
 505+ * For example, when processing:
 506+ * <exif:ISOSpeedRatings> <rdf:Seq> <rdf:li>64</rdf:li>
 507+ * </rdf:Seq> </exif:ISOSpeedRatings>
 508+ *
 509+ * This method is called when we hit the </rdf:Seq>.
 510+ * (For comparison, we call endElementModeSimple when we
 511+ * hit the </rdf:li>)
 512+ *
 513+ * @param $elm String namespace . ' ' . element name
 514+ */
 515+ private function endElementModeLi( $elm ) {
 516+
 517+ list( $ns, $tag ) = explode( ' ', $this->curItem[0], 2 );
 518+ $info = $this->items[$ns][$tag];
 519+ $finalName = isset( $info['map_name'] )
 520+ ? $info['map_name'] : $tag;
 521+
 522+ array_shift( $this->mode );
 523+
 524+ if ( !isset( $this->results['xmp-' . $info['map_group']][$finalName] ) ) {
 525+ wfDebugLog( 'XMP', __METHOD__ . " Empty compund element $finalName." );
 526+ return;
 527+ }
 528+
 529+ if ( $elm === self::NS_RDF . ' Seq' ) {
 530+ $this->results['xmp-' . $info['map_group']][$finalName]['_type'] = 'ol';
 531+ } elseif ( $elm === self::NS_RDF . ' Bag' ) {
 532+ $this->results['xmp-' . $info['map_group']][$finalName]['_type'] = 'ul';
 533+ } elseif ( $elm === self::NS_RDF . ' Alt' ) {
 534+ // extra if needed as you could theoretically have a non-language alt.
 535+ if ( $info['mode'] === self::MODE_LANG ) {
 536+ $this->results['xmp-' . $info['map_group']][$finalName]['_type'] = 'lang';
 537+ }
 538+
 539+ } else {
 540+ throw new MWException( __METHOD__ . " expected </rdf:seq> or </rdf:bag> but instead got $elm." );
 541+ }
 542+ }
 543+ /**
 544+ * End element while in MODE_QDESC
 545+ * mostly when ending an element when we have a simple value
 546+ * that has qualifiers.
 547+ *
 548+ * Qualifiers aren't all that common, and we don't do anything
 549+ * with them.
 550+ *
 551+ * @param $elm String namespace and element
 552+ */
 553+ private function endElementModeQDesc( $elm ) {
 554+
 555+ if ( $elm === self::NS_RDF . ' value' ) {
 556+ list( $ns, $tag ) = explode( ' ', $this->curItem[0], 2 );
 557+ $this->saveValue( $ns, $tag, $this->charContent );
 558+ return;
 559+ } else {
 560+ array_shift( $this->mode );
 561+ array_shift( $this->curItem );
 562+ }
 563+
 564+
 565+ }
 566+ /**
 567+ * Handler for hitting a closing element.
 568+ *
 569+ * generally just calls a helper function depending on what
 570+ * mode we're in.
 571+ *
 572+ * Ignores the outer wrapping elements that are optional in
 573+ * xmp and have no meaning.
 574+ *
 575+ * @param $parser XMLParser
 576+ * @param $elm String namespace . ' ' . element name
 577+ */
 578+ function endElement( $parser, $elm ) {
 579+ if ( $elm === ( self::NS_RDF . ' RDF' )
 580+ || $elm === 'adobe:ns:meta/ xmpmeta'
 581+ || $elm === 'adobe:ns:meta/ xapmeta' )
 582+ {
 583+ // ignore these.
 584+ return;
 585+ }
 586+
 587+ if ( $elm === self::NS_RDF . ' type' ) {
 588+ // these aren't really supported properly yet.
 589+ // However, it appears they almost never used.
 590+ wfDebugLog( 'XMP', __METHOD__ . ' encountered <rdf:type>' );
 591+ }
 592+
 593+ if ( strpos( $elm, ' ' ) === false ) {
 594+ // This probably shouldn't happen.
 595+ // However, there is a bug in an adobe product
 596+ // that forgets the namespace on some things.
 597+ // (Luckily they are unimportant things).
 598+ wfDebugLog( 'XMP', __METHOD__ . " Encountered </$elm> which has no namespace. Skipping." );
 599+ return;
 600+ }
 601+
 602+ if ( count( $this->mode[0] ) === 0 ) {
 603+ // This should never ever happen and means
 604+ // there is a pretty major bug in this class.
 605+ throw new MWException( 'Encountered end element with no mode' );
 606+ }
 607+
 608+ if ( count( $this->curItem ) == 0 && $this->mode[0] !== self::MODE_INITIAL ) {
 609+ // just to be paranoid. Should always have a curItem, except for initially
 610+ // (aka during MODE_INITAL).
 611+ throw new MWException( "Hit end element </$elm> but no curItem" );
 612+ }
 613+
 614+ switch( $this->mode[0] ) {
 615+ case self::MODE_IGNORE:
 616+ $this->endElementModeIgnore( $elm );
 617+ break;
 618+ case self::MODE_SIMPLE:
 619+ $this->endElementModeSimple( $elm );
 620+ break;
 621+ case self::MODE_STRUCT:
 622+ case self::MODE_SEQ:
 623+ case self::MODE_BAG:
 624+ case self::MODE_LANG:
 625+ case self::MODE_BAGSTRUCT:
 626+ $this->endElementNested( $elm );
 627+ break;
 628+ case self::MODE_INITIAL:
 629+ if ( $elm === self::NS_RDF . ' Description' ) {
 630+ array_shift( $this->mode );
 631+ } else {
 632+ throw new MWException( 'Element ended unexpectedly while in MODE_INITIAL' );
 633+ }
 634+ break;
 635+ case self::MODE_LI:
 636+ case self::MODE_LI_LANG:
 637+ $this->endElementModeLi( $elm );
 638+ break;
 639+ case self::MODE_QDESC:
 640+ $this->endElementModeQDesc( $elm );
 641+ break;
 642+ default:
 643+ wfDebugLog( 'XMP', __METHOD__ . " no mode (elm = $elm)" );
 644+ break;
 645+ }
 646+ }
 647+
 648+
 649+ /**
 650+ * Hit an opening element while in MODE_IGNORE
 651+ *
 652+ * XMP is extensible, so ignore any tag we don't understand.
 653+ *
 654+ * Mostly ignores, unless we encounter the element that we are ignoring.
 655+ * in which case we add it to the item stack, so we can ignore things
 656+ * that are nested, correctly.
 657+ *
 658+ * @param $elm String namespace . ' ' . tag name
 659+ */
 660+ private function startElementModeIgnore( $elm ) {
 661+ if ( $elm === $this->curItem[0] ) {
 662+ array_unshift( $this->curItem, $elm );
 663+ array_unshift( $this->mode, self::MODE_IGNORE );
 664+ }
 665+ }
 666+ /**
 667+ * Start element in MODE_BAG (unordered array)
 668+ * this should always be <rdf:Bag>
 669+ *
 670+ * @param $elm String namespace . ' ' . tag
 671+ * @throws MWException if we have an element that's not <rdf:Bag>
 672+ */
 673+ private function startElementModeBag( $elm ) {
 674+ if ( $elm === self::NS_RDF . ' Bag' ) {
 675+ array_unshift( $this->mode, self::MODE_LI );
 676+ } else {
 677+ throw new MWException( "Expected <rdf:Bag> but got $elm." );
 678+ }
 679+
 680+ }
 681+ /**
 682+ * Start element in MODE_SEQ (ordered array)
 683+ * this should always be <rdf:Seq>
 684+ *
 685+ * @param $elm String namespace . ' ' . tag
 686+ * @throws MWException if we have an element that's not <rdf:Seq>
 687+ */
 688+ private function startElementModeSeq( $elm ) {
 689+ if ( $elm === self::NS_RDF . ' Seq' ) {
 690+ array_unshift( $this->mode, self::MODE_LI );
 691+ } else if ( $elm === self::NS_RDF . ' Bag' ) {
 692+ # bug 27105
 693+ wfDebugLog( 'XMP', __METHOD__ . ' Expected an rdf:Seq, but got an rdf:Bag. Pretending'
 694+ . ' it is a Seq, since some buggy software is known to screw this up.' );
 695+ array_unshift( $this->mode, self::MODE_LI );
 696+ } else {
 697+ throw new MWException( "Expected <rdf:Seq> but got $elm." );
 698+ }
 699+
 700+ }
 701+ /**
 702+ * Start element in MODE_LANG (language alternative)
 703+ * this should always be <rdf:Alt>
 704+ *
 705+ * This tag tends to be used for metadata like describe this
 706+ * picture, which can be translated into multiple languages.
 707+ *
 708+ * XMP supports non-linguistic alternative selections,
 709+ * which are really only used for thumbnails, which
 710+ * we don't care about.
 711+ *
 712+ * @param $elm String namespace . ' ' . tag
 713+ * @throws MWException if we have an element that's not <rdf:Alt>
 714+ */
 715+ private function startElementModeLang( $elm ) {
 716+ if ( $elm === self::NS_RDF . ' Alt' ) {
 717+ array_unshift( $this->mode, self::MODE_LI_LANG );
 718+ } else {
 719+ throw new MWException( "Expected <rdf:Seq> but got $elm." );
 720+ }
 721+
 722+ }
 723+ /**
 724+ * Handle an opening element when in MODE_SIMPLE
 725+ *
 726+ * This should not happen often. This is for if a simple element
 727+ * already opened has a child element. Could happen for a
 728+ * qualified element.
 729+ *
 730+ * For example:
 731+ * <exif:DigitalZoomRatio><rdf:Description><rdf:value>0/10</rdf:value>
 732+ * <foo:someQualifier>Bar</foo:someQualifier> </rdf:Description>
 733+ * </exif:DigitalZoomRatio>
 734+ *
 735+ * This method is called when processing the <rdf:Description> element
 736+ *
 737+ * @param $elm String namespace and tag names separated by space.
 738+ * @param $attribs Array Attributes of the element.
 739+ */
 740+ private function startElementModeSimple( $elm, $attribs ) {
 741+ if ( $elm === self::NS_RDF . ' Description' ) {
 742+ // If this value has qualifiers
 743+ array_unshift( $this->mode, self::MODE_QDESC );
 744+ array_unshift( $this->curItem, $this->curItem[0] );
 745+
 746+ if ( isset( $attribs[self::NS_RDF . ' value'] ) ) {
 747+ list( $ns, $tag ) = explode( ' ', $this->curItem[0], 2 );
 748+ $this->saveValue( $ns, $tag, $attribs[self::NS_RDF . ' value'] );
 749+ }
 750+ } elseif ( $elm === self::NS_RDF . ' value' ) {
 751+ // This should not be here.
 752+ throw new MWException( __METHOD__ . ' Encountered <rdf:value> where it was unexpected.' );
 753+
 754+ } else {
 755+ // something else we don't recognize, like a qualifier maybe.
 756+ wfDebugLog( 'XMP', __METHOD__ . " Encountered element <$elm> where only expecting character data as value of " . $this->curItem[0] );
 757+ array_unshift( $this->mode, self::MODE_IGNORE );
 758+ array_unshift( $this->curItem, $elm );
 759+
 760+ }
 761+
 762+ }
 763+ /**
 764+ * Start an element when in MODE_QDESC.
 765+ * This generally happens when a simple element has an inner
 766+ * rdf:Description to hold qualifier elements.
 767+ *
 768+ * For example in:
 769+ * <exif:DigitalZoomRatio><rdf:Description><rdf:value>0/10</rdf:value>
 770+ * <foo:someQualifier>Bar</foo:someQualifier> </rdf:Description>
 771+ * </exif:DigitalZoomRatio>
 772+ * Called when processing the <rdf:value> or <foo:someQualifier>.
 773+ *
 774+ * @param $elm String namespace and tag name separated by a space.
 775+ *
 776+ */
 777+ private function startElementModeQDesc( $elm ) {
 778+ if ( $elm === self::NS_RDF . ' value' ) {
 779+ return; // do nothing
 780+ } else {
 781+ // otherwise its a qualifier, which we ignore
 782+ array_unshift( $this->mode, self::MODE_IGNORE );
 783+ array_unshift( $this->curItem, $elm );
 784+ }
 785+ }
 786+ /**
 787+ * Starting an element when in MODE_INITIAL
 788+ * This usually happens when we hit an element inside
 789+ * the outer rdf:Description
 790+ *
 791+ * This is generally where most properties start.
 792+ *
 793+ * @param $ns String Namespace
 794+ * @param $tag String tag name (without namespace prefix)
 795+ * @param $attribs Array array of attributes
 796+ */
 797+ private function startElementModeInitial( $ns, $tag, $attribs ) {
 798+ if ( $ns !== self::NS_RDF ) {
 799+
 800+ if ( isset( $this->items[$ns][$tag] ) ) {
 801+ if ( isset( $this->items[$ns][$tag]['structPart'] ) ) {
 802+ // If this element is supposed to appear only as
 803+ // a child of a structure, but appears here (not as
 804+ // a child of a struct), then something weird is
 805+ // happening, so ignore this element and its children.
 806+
 807+ wfDebugLog( 'XMP', "Encountered <$ns:$tag> outside"
 808+ . " of its expected parent. Ignoring." );
 809+
 810+ array_unshift( $this->mode, self::MODE_IGNORE );
 811+ array_unshift( $this->curItem, $ns . ' ' . $tag );
 812+ return;
 813+ }
 814+ $mode = $this->items[$ns][$tag]['mode'];
 815+ array_unshift( $this->mode, $mode );
 816+ array_unshift( $this->curItem, $ns . ' ' . $tag );
 817+ if ( $mode === self::MODE_STRUCT ) {
 818+ $this->ancestorStruct = isset( $this->items[$ns][$tag]['map_name'] )
 819+ ? $this->items[$ns][$tag]['map_name'] : $tag;
 820+ }
 821+ if ( $this->charContent !== false ) {
 822+ // Something weird.
 823+ // Should not happen in valid XMP.
 824+ throw new MWException( 'tag nested in non-whitespace characters.' );
 825+ }
 826+ } else {
 827+ // This element is not on our list of allowed elements so ignore.
 828+ wfDebugLog( 'XMP', __METHOD__ . " Ignoring unrecognized element <$ns:$tag>." );
 829+ array_unshift( $this->mode, self::MODE_IGNORE );
 830+ array_unshift( $this->curItem, $ns . ' ' . $tag );
 831+ return;
 832+ }
 833+
 834+ }
 835+ // process attributes
 836+ $this->doAttribs( $attribs );
 837+ }
 838+ /**
 839+ * Hit an opening element when in a Struct (MODE_STRUCT)
 840+ * This is generally for fields of a compound property.
 841+ *
 842+ * Example of a struct (abbreviated; flash has more properties):
 843+ *
 844+ * <exif:Flash> <rdf:Description> <exif:Fired>True</exif:Fired>
 845+ * <exif:Mode>1</exif:Mode></rdf:Description></exif:Flash>
 846+ *
 847+ * or:
 848+ *
 849+ * <exif:Flash rdf:parseType='Resource'> <exif:Fired>True</exif:Fired>
 850+ * <exif:Mode>1</exif:Mode></exif:Flash>
 851+ *
 852+ * @param $ns String namespace
 853+ * @param $tag String tag name (no ns)
 854+ * @param $attribs Array array of attribs w/ values.
 855+ */
 856+ private function startElementModeStruct( $ns, $tag, $attribs ) {
 857+ if ( $ns !== self::NS_RDF ) {
 858+
 859+ if ( isset( $this->items[$ns][$tag] ) ) {
 860+ if ( isset( $this->items[$ns][$this->ancestorStruct]['children'] )
 861+ && !isset( $this->items[$ns][$this->ancestorStruct]['children'][$tag] ) )
 862+ {
 863+ // This assumes that we don't have inter-namespace nesting
 864+ // which we don't in all the properties we're interested in.
 865+ throw new MWException( " <$tag> appeared nested in <" . $this->ancestorStruct
 866+ . "> where it is not allowed." );
 867+ }
 868+ array_unshift( $this->mode, $this->items[$ns][$tag]['mode'] );
 869+ array_unshift( $this->curItem, $ns . ' ' . $tag );
 870+ if ( $this->charContent !== false ) {
 871+ // Something weird.
 872+ // Should not happen in valid XMP.
 873+ throw new MWException( "tag <$tag> nested in non-whitespace characters (" . $this->charContent . ")." );
 874+ }
 875+ } else {
 876+ array_unshift( $this->mode, self::MODE_IGNORE );
 877+ array_unshift( $this->curItem, $elm );
 878+ return;
 879+ }
 880+
 881+ }
 882+
 883+ if ( $ns === self::NS_RDF && $tag === 'Description' ) {
 884+ $this->doAttribs( $attribs );
 885+ array_unshift( $this->mode, self::MODE_STRUCT );
 886+ array_unshift( $this->curItem, $this->curItem[0] );
 887+ }
 888+ }
 889+ /**
 890+ * opening element in MODE_LI
 891+ * process elements of arrays.
 892+ *
 893+ * Example:
 894+ * <exif:ISOSpeedRatings> <rdf:Seq> <rdf:li>64</rdf:li>
 895+ * </rdf:Seq> </exif:ISOSpeedRatings>
 896+ * This method is called when we hit the <rdf:li> element.
 897+ *
 898+ * @param $elm String: namespace . ' ' . tagname
 899+ * @param $attribs Array: Attributes. (needed for BAGSTRUCTS)
 900+ * @throws MWException if gets a tag other than <rdf:li>
 901+ */
 902+ private function startElementModeLi( $elm, $attribs ) {
 903+ if ( ( $elm ) !== self::NS_RDF . ' li' ) {
 904+ throw new MWException( "<rdf:li> expected but got $elm." );
 905+ }
 906+
 907+ if ( !isset( $this->mode[1] ) ) {
 908+ // This should never ever ever happen. Checking for it
 909+ // to be paranoid.
 910+ throw new MWException( 'In mode Li, but no 2xPrevious mode!' );
 911+ }
 912+
 913+ if ( $this->mode[1] === self::MODE_BAGSTRUCT ) {
 914+ // This list item contains a compound (STRUCT) value.
 915+ array_unshift( $this->mode, self::MODE_STRUCT );
 916+ array_unshift( $this->curItem, $elm );
 917+ $this->processingArray = true;
 918+
 919+ if ( !isset( $this->curItem[1] ) ) {
 920+ // be paranoid.
 921+ throw new MWException( 'Can not find parent of BAGSTRUCT.' );
 922+ }
 923+ list( $curNS, $curTag ) = explode( ' ', $this->curItem[1] );
 924+ $this->ancestorStruct = isset( $this->items[$curNS][$curTag]['map_name'] )
 925+ ? $this->items[$curNS][$curTag]['map_name'] : $curTag;
 926+
 927+ $this->doAttribs( $attribs );
 928+
 929+ } else {
 930+ // Normal BAG or SEQ containing simple values.
 931+ array_unshift( $this->mode, self::MODE_SIMPLE );
 932+ // need to add curItem[0] on again since one is for the specific item
 933+ // and one is for the entire group.
 934+ array_unshift( $this->curItem, $this->curItem[0] );
 935+ $this->processingArray = true;
 936+ }
 937+
 938+ }
 939+ /**
 940+ * Opening element in MODE_LI_LANG.
 941+ * process elements of language alternatives
 942+ *
 943+ * Example:
 944+ * <dc:title> <rdf:Alt> <rdf:li xml:lang="x-default">My house
 945+ * </rdf:li> </rdf:Alt> </dc:title>
 946+ *
 947+ * This method is called when we hit the <rdf:li> element.
 948+ *
 949+ * @param $elm String namespace . ' ' . tag
 950+ * @param $attribs array array of elements (most importantly xml:lang)
 951+ * @throws MWException if gets a tag other than <rdf:li> or if no xml:lang
 952+ */
 953+ private function startElementModeLiLang( $elm, $attribs ) {
 954+ if ( $elm !== self::NS_RDF . ' li' ) {
 955+ throw new MWException( __METHOD__ . " <rdf:li> expected but got $elm." );
 956+ }
 957+ if ( !isset( $attribs[ self::NS_XML . ' lang'] )
 958+ || !preg_match( '/^[-A-Za-z0-9]{2,}$/D', $attribs[ self::NS_XML . ' lang' ] ) )
 959+ {
 960+ throw new MWException( __METHOD__
 961+ . " <rdf:li> did not contain, or has invalid xml:lang attribute in lang alternative" );
 962+ }
 963+
 964+ // Lang is case-insensitive.
 965+ $this->itemLang = strtolower( $attribs[ self::NS_XML . ' lang' ] );
 966+
 967+ // need to add curItem[0] on again since one is for the specific item
 968+ // and one is for the entire group.
 969+ array_unshift( $this->curItem, $this->curItem[0] );
 970+ array_unshift( $this->mode, self::MODE_SIMPLE );
 971+ $this->processingArray = true;
 972+ }
 973+
 974+ /**
 975+ * Hits an opening element.
 976+ * Generally just calls a helper based on what MODE we're in.
 977+ * Also does some initial set up for the wrapper element
 978+ *
 979+ * @param $parser XMLParser
 980+ * @param $elm String namespace <space> element
 981+ * @param $attribs Array attribute name => value
 982+ */
 983+ function startElement( $parser, $elm, $attribs ) {
 984+
 985+ if ( $elm === self::NS_RDF . ' RDF'
 986+ || $elm === 'adobe:ns:meta/ xmpmeta'
 987+ || $elm === 'adobe:ns:meta/ xapmeta')
 988+ {
 989+ /* ignore. */
 990+ return;
 991+ } elseif ( $elm === self::NS_RDF . ' Description' ) {
 992+ if ( count( $this->mode ) === 0 ) {
 993+ // outer rdf:desc
 994+ array_unshift( $this->mode, self::MODE_INITIAL );
 995+ }
 996+ } elseif ( $elm === self::NS_RDF . ' type' ) {
 997+ // This doesn't support rdf:type properly.
 998+ // In practise I have yet to see a file that
 999+ // uses this element, however it is mentioned
 1000+ // on page 25 of part 1 of the xmp standard.
 1001+ //
 1002+ // also it seems as if exiv2 and exiftool do not support
 1003+ // this either (That or I misunderstand the standard)
 1004+ wfDebugLog( 'XMP', __METHOD__ . ' Encountered <rdf:type> which isn\'t currently supported' );
 1005+ }
 1006+
 1007+ if ( strpos( $elm, ' ' ) === false ) {
 1008+ // This probably shouldn't happen.
 1009+ wfDebugLog( 'XMP', __METHOD__ . " Encountered <$elm> which has no namespace. Skipping." );
 1010+ return;
 1011+ }
 1012+
 1013+ list( $ns, $tag ) = explode( ' ', $elm, 2 );
 1014+
 1015+ if ( count( $this->mode ) === 0 ) {
 1016+ // This should not happen.
 1017+ throw new MWException('Error extracting XMP, '
 1018+ . "encountered <$elm> with no mode" );
 1019+ }
 1020+
 1021+ switch( $this->mode[0] ) {
 1022+ case self::MODE_IGNORE:
 1023+ $this->startElementModeIgnore( $elm );
 1024+ break;
 1025+ case self::MODE_SIMPLE:
 1026+ $this->startElementModeSimple( $elm, $attribs );
 1027+ break;
 1028+ case self::MODE_INITIAL:
 1029+ $this->startElementModeInitial( $ns, $tag, $attribs );
 1030+ break;
 1031+ case self::MODE_STRUCT:
 1032+ $this->startElementModeStruct( $ns, $tag, $attribs );
 1033+ break;
 1034+ case self::MODE_BAG:
 1035+ case self::MODE_BAGSTRUCT:
 1036+ $this->startElementModeBag( $elm );
 1037+ break;
 1038+ case self::MODE_SEQ:
 1039+ $this->startElementModeSeq( $elm );
 1040+ break;
 1041+ case self::MODE_LANG:
 1042+ $this->startElementModeLang( $elm );
 1043+ break;
 1044+ case self::MODE_LI_LANG:
 1045+ $this->startElementModeLiLang( $elm, $attribs );
 1046+ break;
 1047+ case self::MODE_LI:
 1048+ $this->startElementModeLi( $elm, $attribs );
 1049+ break;
 1050+ case self::MODE_QDESC:
 1051+ $this->startElementModeQDesc( $elm );
 1052+ break;
 1053+ default:
 1054+ throw new MWException( 'StartElement in unknown mode: ' . $this->mode[0] );
 1055+ break;
 1056+ }
 1057+
 1058+
 1059+
 1060+ }
 1061+ /**
 1062+ * Process attributes.
 1063+ * Simple values can be stored as either a tag or attribute
 1064+ *
 1065+ * Often the initial <rdf:Description> tag just has all the simple
 1066+ * properties as attributes.
 1067+ *
 1068+ * Example:
 1069+ * <rdf:Description rdf:about="" xmlns:exif="http://ns.adobe.com/exif/1.0/" exif:DigitalZoomRatio="0/10">
 1070+ *
 1071+ * @param $attribs Array attribute=>value array.
 1072+ */
 1073+ private function doAttribs( $attribs ) {
 1074+
 1075+ // first check for rdf:parseType attribute, as that can change
 1076+ // how the attributes are interperted.
 1077+
 1078+ if ( isset( $attribs[self::NS_RDF . ' parseType'] )
 1079+ && $attribs[self::NS_RDF . ' parseType'] === 'Resource'
 1080+ && $this->mode[0] === self::MODE_SIMPLE )
 1081+ {
 1082+ // this is equivalent to having an inner rdf:Description
 1083+ $this->mode[0] = self::MODE_QDESC;
 1084+ }
 1085+ foreach ( $attribs as $name => $val ) {
 1086+
 1087+
 1088+ if ( strpos( $name, ' ' ) === false ) {
 1089+ // This shouldn't happen, but so far some old software forgets namespace
 1090+ // on rdf:about.
 1091+ wfDebugLog( 'XMP', __METHOD__ . ' Encountered non-namespaced attribute: '
 1092+ . " $name=\"$val\". Skipping. " );
 1093+ continue;
 1094+ }
 1095+ list( $ns, $tag ) = explode( ' ', $name, 2 );
 1096+ if ( $ns === self::NS_RDF ) {
 1097+ if ( $tag === 'value' || $tag === 'resource' ) {
 1098+ // resource is for url.
 1099+ // value attribute is a weird way of just putting the contents.
 1100+ $this->char( $this->xmlParser, $val );
 1101+ }
 1102+ } elseif ( isset( $this->items[$ns][$tag] ) ) {
 1103+ if ( $this->mode[0] === self::MODE_SIMPLE ) {
 1104+ throw new MWException( __METHOD__
 1105+ . " $ns:$tag found as attribute where not allowed" );
 1106+ }
 1107+ $this->saveValue( $ns, $tag, $val );
 1108+ } else {
 1109+ wfDebugLog( 'XMP', __METHOD__ . " Ignoring unrecognized element <$ns:$tag>." );
 1110+ }
 1111+ }
 1112+ }
 1113+ /**
 1114+ * Given an extracted value, save it to results array
 1115+ *
 1116+ * note also uses $this->ancestorStruct and
 1117+ * $this->processingArray to determine what name to
 1118+ * save the value under. (in addition to $tag).
 1119+ *
 1120+ * @param $ns String namespace of tag this is for
 1121+ * @param $tag String tag name
 1122+ * @param $val String value to save
 1123+ */
 1124+ private function saveValue( $ns, $tag, $val ) {
 1125+
 1126+ $info =& $this->items[$ns][$tag];
 1127+ $finalName = isset( $info['map_name'] )
 1128+ ? $info['map_name'] : $tag;
 1129+ if ( isset( $info['validate'] ) ) {
 1130+ $validate = is_array( $info['validate'] ) ? $info['validate']
 1131+ : array( 'XMPValidate', $info['validate'] );
 1132+
 1133+ if ( is_callable( $validate ) ) {
 1134+ call_user_func_array( $validate, array( $info, &$val, true ) );
 1135+ // the reasoning behind using &$val instead of using the return value
 1136+ // is to be consistent between here and validating structures.
 1137+ if ( is_null( $val ) ) {
 1138+ wfDebugLog( 'XMP', __METHOD__ . " <$ns:$tag> failed validation." );
 1139+ return;
 1140+ }
 1141+ } else {
 1142+ wfDebugLog( 'XMP', __METHOD__ . " Validation function for $finalName ("
 1143+ . $validate[0] . '::' . $validate[1] . '()) is not callable.' );
 1144+ }
 1145+ }
 1146+
 1147+ if ( $this->ancestorStruct && $this->processingArray ) {
 1148+ // Aka both an array and a struct. ( self::MODE_BAGSTRUCT )
 1149+ $this->results['xmp-' . $info['map_group']][$this->ancestorStruct][][$finalName] = $val;
 1150+ } elseif ( $this->ancestorStruct ) {
 1151+ $this->results['xmp-' . $info['map_group']][$this->ancestorStruct][$finalName] = $val;
 1152+ } elseif ( $this->processingArray ) {
 1153+ if ( $this->itemLang === false ) {
 1154+ // normal array
 1155+ $this->results['xmp-' . $info['map_group']][$finalName][] = $val;
 1156+ } else {
 1157+ // lang array.
 1158+ $this->results['xmp-' . $info['map_group']][$finalName][$this->itemLang] = $val;
 1159+ }
 1160+ } else {
 1161+ $this->results['xmp-' . $info['map_group']][$finalName] = $val;
 1162+ }
 1163+ }
 1164+
 1165+
 1166+}
Property changes on: trunk/phase3/includes/media/XMP.php
___________________________________________________________________
Added: svn:eol-style
11167 + native
Index: trunk/phase3/includes/media/GIF.php
@@ -13,22 +13,35 @@
1414 */
1515 class GIFHandler extends BitmapHandler {
1616
 17+ const BROKEN_FILE = '0'; // value to store in img_metadata if error extracting metadata.
 18+
1719 function getMetadata( $image, $filename ) {
18 - if ( !isset( $image->parsedGIFMetadata ) ) {
19 - try {
20 - $image->parsedGIFMetadata = GIFMetadataExtractor::getMetadata( $filename );
21 - } catch( Exception $e ) {
22 - // Broken file?
23 - wfDebug( __METHOD__ . ': ' . $e->getMessage() . "\n" );
24 - return '0';
25 - }
 20+ try {
 21+ $parsedGIFMetadata = BitmapMetadataHandler::GIF( $filename );
 22+ } catch( Exception $e ) {
 23+ // Broken file?
 24+ wfDebug( __METHOD__ . ': ' . $e->getMessage() . "\n" );
 25+ return self::BROKEN_FILE;
2626 }
2727
28 - return serialize( $image->parsedGIFMetadata );
 28+ return serialize($parsedGIFMetadata);
2929 }
3030
3131 function formatMetadata( $image ) {
32 - return false;
 32+ $meta = $image->getMetadata();
 33+
 34+ if ( !$meta ) {
 35+ return false;
 36+ }
 37+ $meta = unserialize( $meta );
 38+ if ( !isset( $meta['metadata'] ) || count( $meta['metadata'] ) <= 1 ) {
 39+ return false;
 40+ }
 41+
 42+ if ( isset( $meta['metadata']['_MW_GIF_VERSION'] ) ) {
 43+ unset( $meta['metadata']['_MW_GIF_VERSION'] );
 44+ }
 45+ return $this->formatMetadataHelper( $meta['metadata'] );
3346 }
3447
3548 /**
@@ -67,10 +80,26 @@
6881 }
6982
7083 function isMetadataValid( $image, $metadata ) {
 84+ if ( $metadata === self::BROKEN_FILE ) {
 85+ // Do not repetitivly regenerate metadata on broken file.
 86+ return self::METADATA_GOOD;
 87+ }
 88+
7189 wfSuppressWarnings();
7290 $data = unserialize( $metadata );
7391 wfRestoreWarnings();
74 - return (boolean) $data;
 92+
 93+ if ( !$data || !is_array( $data ) ) {
 94+ wfDebug(__METHOD__ . ' invalid GIF metadata' );
 95+ return self::METADATA_BAD;
 96+ }
 97+
 98+ if ( !isset( $data['metadata']['_MW_GIF_VERSION'] )
 99+ || $data['metadata']['_MW_GIF_VERSION'] != GIFMetadataExtractor::VERSION ) {
 100+ wfDebug(__METHOD__ . ' old but compatible GIF metadata' );
 101+ return self::METADATA_COMPATIBLE;
 102+ }
 103+ return self::METADATA_GOOD;
75104 }
76105
77106 /**
Index: trunk/phase3/includes/media/BitmapMetadataHandler.php
@@ -0,0 +1,206 @@
 2+<?php
 3+/**
 4+Class to deal with reconciling and extracting metadata from bitmap images.
 5+This is meant to comply with http://www.metadataworkinggroup.org/pdf/mwg_guidance.pdf
 6+
 7+This sort of acts as an intermediary between MediaHandler::getMetadata
 8+and the various metadata extractors.
 9+
 10+@todo other image formats.
 11+*/
 12+class BitmapMetadataHandler {
 13+
 14+ private $metadata = Array();
 15+ private $metaPriority = Array(
 16+ 20 => Array( 'other' ),
 17+ 40 => Array( 'native' ),
 18+ 60 => Array( 'iptc-good-hash', 'iptc-no-hash' ),
 19+ 70 => Array( 'xmp-deprecated' ),
 20+ 80 => Array( 'xmp-general' ),
 21+ 90 => Array( 'xmp-exif' ),
 22+ 100 => Array( 'iptc-bad-hash' ),
 23+ 120 => Array( 'exif' ),
 24+ );
 25+ private $iptcType = 'iptc-no-hash';
 26+
 27+ /**
 28+ * This does the photoshop image resource app13 block
 29+ * of interest, IPTC-IIM metadata is stored here.
 30+ *
 31+ * Mostly just calls doPSIR and doIPTC
 32+ *
 33+ * @param String $app13 String containing app13 block from jpeg file
 34+ */
 35+ private function doApp13 ( $app13 ) {
 36+ $this->iptcType = JpegMetadataExtractor::doPSIR( $app13 );
 37+
 38+ $iptc = IPTC::parse( $app13 );
 39+ $this->addMetadata( $iptc, $this->iptcType );
 40+ }
 41+
 42+
 43+ /** get exif info using exif class.
 44+ * Basically what used to be in BitmapHandler::getMetadata().
 45+ * Just calls stuff in the Exif class.
 46+ */
 47+ function getExif ( $filename ) {
 48+ if ( file_exists( $filename ) ) {
 49+ $exif = new Exif( $filename );
 50+ $data = $exif->getFilteredData();
 51+ if ( $data ) {
 52+ $this->addMetadata( $data, 'exif' );
 53+ }
 54+ }
 55+ }
 56+ /** Add misc metadata. Warning: atm if the metadata category
 57+ * doesn't have a priority, it will be silently discarded.
 58+ *
 59+ * @param Array $metaArray array of metadata values
 60+ * @param string $type type. defaults to other. if two things have the same type they're merged
 61+ */
 62+ function addMetadata ( $metaArray, $type = 'other' ) {
 63+ if ( isset( $this->metadata[$type] ) ) {
 64+ /* merge with old data */
 65+ $metaArray = $metaArray + $this->metadata[$type];
 66+ }
 67+
 68+ $this->metadata[$type] = $metaArray;
 69+ }
 70+
 71+ /**
 72+ * Merge together the various types of metadata
 73+ * the different types have different priorites,
 74+ * and are merged in order.
 75+ *
 76+ * This function is generally called by the media handlers' getMetadata()
 77+ *
 78+ * @return Array metadata array
 79+ */
 80+ function getMetadataArray () {
 81+ // this seems a bit ugly... This is all so its merged in right order
 82+ // based on the MWG recomendation.
 83+ $temp = Array();
 84+ krsort( $this->metaPriority );
 85+ foreach ( $this->metaPriority as $pri ) {
 86+ foreach ( $pri as $type ) {
 87+ if ( isset( $this->metadata[$type] ) ) {
 88+ // Do some special casing for multilingual values.
 89+ // Don't discard translations if also as a simple value.
 90+ foreach ( $this->metadata[$type] as $itemName => $item ) {
 91+ if ( is_array( $item ) && isset( $item['_type'] ) && $item['_type'] === 'lang' ) {
 92+ if ( isset( $temp[$itemName] ) && !is_array( $temp[$itemName] ) ) {
 93+ $default = $temp[$itemName];
 94+ $temp[$itemName] = $item;
 95+ $temp[$itemName]['x-default'] = $default;
 96+ unset( $this->metadata[$type][$itemName] );
 97+ }
 98+ }
 99+ }
 100+
 101+ $temp = $temp + $this->metadata[$type];
 102+ }
 103+ }
 104+ }
 105+ return $temp;
 106+ }
 107+
 108+ /** Main entry point for jpeg's.
 109+ *
 110+ * @param string $file filename (with full path)
 111+ * @return metadata result array.
 112+ * @throws MWException on invalid file.
 113+ */
 114+ static function Jpeg ( $filename ) {
 115+ $showXMP = function_exists( 'xml_parser_create_ns' );
 116+ $meta = new self();
 117+ $meta->getExif( $filename );
 118+ $seg = Array();
 119+ $seg = JpegMetadataExtractor::segmentSplitter( $filename );
 120+ if ( isset( $seg['COM'] ) && isset( $seg['COM'][0] ) ) {
 121+ $meta->addMetadata( Array( 'JPEGFileComment' => $seg['COM'] ), 'native' );
 122+ }
 123+ if ( isset( $seg['PSIR'] ) ) {
 124+ $meta->doApp13( $seg['PSIR'] );
 125+ }
 126+ if ( isset( $seg['XMP'] ) && $showXMP ) {
 127+ $xmp = new XMPReader();
 128+ $xmp->parse( $seg['XMP'] );
 129+ foreach ( $seg['XMP_ext'] as $xmpExt ) {
 130+ /* Support for extended xmp in jpeg files
 131+ * is not well tested and a bit fragile.
 132+ */
 133+ $xmp->parseExtended( $xmpExt );
 134+
 135+ }
 136+ $res = $xmp->getResults();
 137+ foreach ( $res as $type => $array ) {
 138+ $meta->addMetadata( $array, $type );
 139+ }
 140+ }
 141+ return $meta->getMetadataArray();
 142+ }
 143+ /** Entry point for png
 144+ * At some point in the future this might
 145+ * merge the png various tEXt chunks to that
 146+ * are interesting, but for now it only does XMP
 147+ *
 148+ * @param $filename String full path to file
 149+ * @return Array Array for storage in img_metadata.
 150+ */
 151+ static public function PNG ( $filename ) {
 152+ $showXMP = function_exists( 'xml_parser_create_ns' );
 153+
 154+ $meta = new self();
 155+ $array = PNGMetadataExtractor::getMetadata( $filename );
 156+ if ( isset( $array['text']['xmp']['x-default'] ) && $array['text']['xmp']['x-default'] !== '' && $showXMP ) {
 157+ $xmp = new XMPReader();
 158+ $xmp->parse( $array['text']['xmp']['x-default'] );
 159+ $xmpRes = $xmp->getResults();
 160+ foreach ( $xmpRes as $type => $xmpSection ) {
 161+ $meta->addMetadata( $xmpSection, $type );
 162+ }
 163+ }
 164+ unset( $array['text']['xmp'] );
 165+ $meta->addMetadata( $array['text'], 'native' );
 166+ unset( $array['text'] );
 167+ $array['metadata'] = $meta->getMetadataArray();
 168+ $array['metadata']['_MW_PNG_VERSION'] = PNGMetadataExtractor::VERSION;
 169+ return $array;
 170+ }
 171+
 172+ /** function for gif images.
 173+ *
 174+ * They don't really have native metadata, so just merges together
 175+ * XMP and image comment.
 176+ *
 177+ * @param $filename full path to file
 178+ * @return Array metadata array
 179+ */
 180+ static public function GIF ( $filename ) {
 181+
 182+ $meta = new self();
 183+ $baseArray = GIFMetadataExtractor::getMetadata( $filename );
 184+
 185+ if ( count( $baseArray['comment'] ) > 0 ) {
 186+ $meta->addMetadata( array( 'GIFFileComment' => $baseArray['comment'] ), 'native' );
 187+ }
 188+
 189+ if ( $baseArray['xmp'] !== '' && function_exists( 'xml_parser_create_ns' ) ) {
 190+ $xmp = new XMPReader();
 191+ $xmp->parse( $baseArray['xmp'] );
 192+ $xmpRes = $xmp->getResults();
 193+ foreach ( $xmpRes as $type => $xmpSection ) {
 194+ $meta->addMetadata( $xmpSection, $type );
 195+ }
 196+
 197+ }
 198+
 199+ unset( $baseArray['comment'] );
 200+ unset( $baseArray['xmp'] );
 201+
 202+ $baseArray['metadata'] = $meta->getMetadataArray();
 203+ $baseArray['metadata']['_MW_GIF_VERSION'] = GIFMetadataExtractor::VERSION;
 204+ return $baseArray;
 205+ }
 206+
 207+}
Property changes on: trunk/phase3/includes/media/BitmapMetadataHandler.php
___________________________________________________________________
Added: svn:eol-style
1208 + native
Index: trunk/phase3/includes/media/Bitmap.php
@@ -670,8 +670,19 @@
671671 imagejpeg( $dst_image, $thumbPath, 95 );
672672 }
673673
674 -
 674+ /**
 675+ * Its unclear if anything still uses this
 676+ * as jpeg is now in its own subclass.
 677+ *
 678+ * And really each media handler should use a
 679+ * different getMetadata, as the formats aren't
 680+ * all that similar and usually have different
 681+ * metadata needs.
 682+ *
 683+ * @deprecated
 684+ */
675685 function getMetadata( $image, $filename ) {
 686+ wfDeprected( __METHOD__ );
676687 global $wgShowEXIF;
677688 if ( $wgShowEXIF && file_exists( $filename ) ) {
678689 $exif = new Exif( $filename );
Index: trunk/phase3/includes/media/Generic.php
@@ -13,7 +13,9 @@
1414 */
1515 abstract class MediaHandler {
1616 const TRANSFORM_LATER = 1;
17 -
 17+ const METADATA_GOOD = true;
 18+ const METADATA_BAD = false;
 19+ const METADATA_COMPATIBLE = 2; // for old but backwards compatible.
1820 /**
1921 * Instance cache
2022 */
@@ -91,15 +93,62 @@
9294 function getMetadata( $image, $path ) { return ''; }
9395
9496 /**
 97+ * Get metadata version.
 98+ *
 99+ * This is not used for validating metadata, this is used for the api when returning
 100+ * metadata, since api content formats should stay the same over time, and so things
 101+ * using ForiegnApiRepo can keep backwards compatibility
 102+ *
 103+ * All core media handlers share a common version number, and extensions can
 104+ * use the GetMetadataVersion hook to append to the array (they should append a unique
 105+ * string so not to get confusing). If there was a media handler named 'foo' with metadata
 106+ * version 3 it might add to the end of the array the element 'foo=3'. if the core metadata
 107+ * version is 2, the end version string would look like '2;foo=3'.
 108+ *
 109+ * @return string version string
 110+ */
 111+ static function getMetadataVersion () {
 112+ $version = Array( '2' ); // core metadata version
 113+ wfRunHooks('GetMetadataVersion', Array(&$version));
 114+ return implode( ';', $version);
 115+ }
 116+
 117+ /**
 118+ * Convert metadata version.
 119+ *
 120+ * By default just returns $metadata, but can be used to allow
 121+ * media handlers to convert between metadata versions.
 122+ *
 123+ * @param $metadata Mixed String or Array metadata array (serialized if string)
 124+ * @param $version Integer target version
 125+ * @return Array serialized metadata in specified version, or $metadata on fail.
 126+ */
 127+ function convertMetadataVersion( $metadata, $version = 1 ) {
 128+ if ( !is_array( $metadata ) ) {
 129+ //unserialize to keep return parameter consistent.
 130+ wfSuppressWarnings();
 131+ return unserialize( $metadata );
 132+ wfRestoreWarnings();
 133+ }
 134+ return $metadata;
 135+ }
 136+
 137+ /**
95138 * Get a string describing the type of metadata, for display purposes.
96139 */
97140 function getMetadataType( $image ) { return false; }
98141
99142 /**
100143 * Check if the metadata string is valid for this handler.
101 - * If it returns false, Image will reload the metadata from the file and update the database
 144+ * If it returns MediaHandler::METADATA_BAD (or false), Image
 145+ * will reload the metadata from the file and update the database.
 146+ * MediaHandler::METADATA_GOOD for if the metadata is a-ok,
 147+ * MediaHanlder::METADATA_COMPATIBLE if metadata is old but backwards
 148+ * compatible (which may or may not trigger a metadata reload).
102149 */
103 - function isMetadataValid( $image, $metadata ) { return true; }
 150+ function isMetadataValid( $image, $metadata ) {
 151+ return self::METADATA_GOOD;
 152+ }
104153
105154
106155 /**
@@ -239,18 +288,98 @@
240289 return false;
241290 }
242291
 292+ /** sorts the visible/invisible field.
 293+ * Split off from ImageHandler::formatMetadata, as used by more than
 294+ * one type of handler.
 295+ *
 296+ * This is used by the media handlers that use the FormatMetadata class
 297+ *
 298+ * @param $metadataArray Array metadata array
 299+ * @return array for use displaying metadata.
 300+ */
 301+ function formatMetadataHelper( $metadataArray ) {
 302+ $result = array(
 303+ 'visible' => array(),
 304+ 'collapsed' => array()
 305+ );
 306+
 307+ $formatted = FormatMetadata::getFormattedData( $metadataArray );
 308+ // Sort fields into visible and collapsed
 309+ $visibleFields = $this->visibleMetadataFields();
 310+ foreach ( $formatted as $name => $value ) {
 311+ $tag = strtolower( $name );
 312+ self::addMeta( $result,
 313+ in_array( $tag, $visibleFields ) ? 'visible' : 'collapsed',
 314+ 'exif',
 315+ $tag,
 316+ $value
 317+ );
 318+ }
 319+ return $result;
 320+ }
 321+
243322 /**
244 - * @todo Fixme: document this!
245 - * 'value' thingy goes into a wikitext table; it used to be escaped but
246 - * that was incompatible with previous practice of customized display
 323+ * Get a list of metadata items which should be displayed when
 324+ * the metadata table is collapsed.
 325+ *
 326+ * @return array of strings
 327+ * @access private
 328+ */
 329+ function visibleMetadataFields() {
 330+ $fields = array();
 331+ $lines = explode( "\n", wfMsgForContent( 'metadata-fields' ) );
 332+ foreach( $lines as $line ) {
 333+ $matches = array();
 334+ if( preg_match( '/^\\*\s*(.*?)\s*$/', $line, $matches ) ) {
 335+ $fields[] = $matches[1];
 336+ }
 337+ }
 338+ $fields = array_map( 'strtolower', $fields );
 339+ return $fields;
 340+ }
 341+
 342+
 343+ /**
 344+ * This is used to generate an array element for each metadata value
 345+ * That array is then used to generate the table of metadata values
 346+ * on the image page
 347+ *
 348+ * @param &$array Array An array containing elements for each type of visibility
 349+ * and each of those elements being an array of metadata items. This function adds
 350+ * a value to that array.
 351+ * @param $visibility String ('visible' or 'collapsed') if this value is hidden
 352+ * by default.
 353+ * @param $type String type of metadata tag (currently always 'exif')
 354+ * @param $id String the name of the metadata tag (like 'artist' for example).
 355+ * its name in the table displayed is the message "$type-$id" (Ex exif-artist ).
 356+ * @param $value String thingy goes into a wikitext table; it used to be escaped but
 357+ * that was incompatible with previous practise of customized display
247358 * with wikitext formatting via messages such as 'exif-model-value'.
248359 * So the escaping is taken back out, but generally this seems a confusing
249360 * interface.
 361+ * @param $param String value to pass to the message for the name of the field
 362+ * as $1. Currently this parameter doesn't seem to ever be used.
 363+ *
 364+ * @return Array $array but with the new metadata field added.
 365+ *
 366+ * Note, everything here is passed through the parser later on (!)
250367 */
251368 protected static function addMeta( &$array, $visibility, $type, $id, $value, $param = false ) {
 369+ $msgName = "$type-$id";
 370+ if ( wfEmptyMsg( $msgName ) ) {
 371+ // This is for future compatibility when using instant commons.
 372+ // So as to not display as ugly a name if a new metadata
 373+ // property is defined that we don't know about
 374+ // (not a major issue since such a property would be collapsed
 375+ // by default).
 376+ wfDebug( __METHOD__ . ' Unknown metadata name: ' . $id . "\n" );
 377+ $name = wfEscapeWikiText( $id );
 378+ } else {
 379+ $name = wfMsg( $msgName, $param );
 380+ }
252381 $array[$visibility][] = array(
253382 'id' => "$type-$id",
254 - 'name' => wfMsg( "$type-$id", $param ),
 383+ 'name' => $name,
255384 'value' => $value
256385 );
257386 }
Index: trunk/phase3/includes/media/XMPValidate.php
@@ -0,0 +1,317 @@
 2+<?php
 3+/**
 4+* This contains some static methods for
 5+* validating XMP properties. See XMPInfo and XMPReader classes.
 6+*
 7+* Each of these functions take the same parameters
 8+* * an info array which is a subset of the XMPInfo::items array
 9+* * A value (passed as reference) to validate. This can be either a
 10+* simple value or an array
 11+* * A boolean to determine if this is validating a simple or complex values
 12+*
 13+* It should be noted that when an array is being validated, typically the validation
 14+* function is called once for each value, and then once at the end for the entire array.
 15+*
 16+* These validation functions can also be used to modify the data. See the gps and flash one's
 17+* for example.
 18+*
 19+* @see http://www.adobe.com/devnet/xmp/pdfs/XMPSpecificationPart1.pdf starting at pg 28
 20+* @see http://www.adobe.com/devnet/xmp/pdfs/XMPSpecificationPart2.pdf starting at pg 11
 21+*/
 22+class XMPValidate {
 23+ /**
 24+ * function to validate boolean properties ( True or False )
 25+ *
 26+ * @param $info Array information about current property
 27+ * @param &$val Mixed current value to validate
 28+ * @param $standalone Boolean if this is a simple property or array
 29+ */
 30+ public static function validateBoolean( $info, &$val, $standalone ) {
 31+ if ( !$standalone ) {
 32+ // this only validates standalone properties, not arrays, etc
 33+ return;
 34+ }
 35+ if ( $val !== 'True' && $val !== 'False' ) {
 36+ wfDebugLog( 'XMP', __METHOD__ . " Expected True or False but got $val" );
 37+ $val = null;
 38+ }
 39+
 40+ }
 41+ /**
 42+ * function to validate rational properties ( 12/10 )
 43+ *
 44+ * @param $info Array information about current property
 45+ * @param &$val Mixed current value to validate
 46+ * @param $standalone Boolean if this is a simple property or array
 47+ */
 48+ public static function validateRational( $info, &$val, $standalone ) {
 49+ if ( !$standalone ) {
 50+ // this only validates standalone properties, not arrays, etc
 51+ return;
 52+ }
 53+ if ( !preg_match( '/^(?:-?\d+)\/(?:\d+[1-9]|[1-9]\d*)$/D', $val ) ) {
 54+ wfDebugLog( 'XMP', __METHOD__ . " Expected rational but got $val" );
 55+ $val = null;
 56+ }
 57+
 58+ }
 59+ /**
 60+ * function to validate rating properties -1, 0-5
 61+ *
 62+ * if its outside of range put it into range.
 63+ *
 64+ * @see MWG spec
 65+ * @param $info Array information about current property
 66+ * @param &$val Mixed current value to validate
 67+ * @param $standalone Boolean if this is a simple property or array
 68+ */
 69+ public static function validateRating( $info, &$val, $standalone ) {
 70+ if ( !$standalone ) {
 71+ // this only validates standalone properties, not arrays, etc
 72+ return;
 73+ }
 74+ if ( !preg_match( '/^[-+]?\d*(?:\.?\d*)$/D', $val )
 75+ || !is_numeric($val)
 76+ ) {
 77+ wfDebugLog( 'XMP', __METHOD__ . " Expected rating but got $val" );
 78+ $val = null;
 79+ return;
 80+ } else {
 81+ $nVal = (float) $val;
 82+ if ( $nVal < 0 ) {
 83+ // We do < 0 here instead of < -1 here, since
 84+ // the values between 0 and -1 are also illegal
 85+ // as -1 is meant as a special reject rating.
 86+ wfDebugLog( 'XMP', __METHOD__ . " Rating too low, setting to -1 (Rejected)");
 87+ $val = '-1';
 88+ return;
 89+ }
 90+ if ( $nVal > 5 ) {
 91+ wfDebugLog( 'XMP', __METHOD__ . " Rating too high, setting to 5");
 92+ $val = '5';
 93+ return;
 94+ }
 95+ }
 96+ }
 97+ /**
 98+ * function to validate integers
 99+ *
 100+ * @param $info Array information about current property
 101+ * @param &$val Mixed current value to validate
 102+ * @param $standalone Boolean if this is a simple property or array
 103+ */
 104+ public static function validateInteger( $info, &$val, $standalone ) {
 105+ if ( !$standalone ) {
 106+ // this only validates standalone properties, not arrays, etc
 107+ return;
 108+ }
 109+ if ( !preg_match( '/^[-+]?\d+$/D', $val ) ) {
 110+ wfDebugLog( 'XMP', __METHOD__ . " Expected integer but got $val" );
 111+ $val = null;
 112+ }
 113+
 114+ }
 115+ /**
 116+ * function to validate properties with a fixed number of allowed
 117+ * choices. (closed choice)
 118+ *
 119+ * @param $info Array information about current property
 120+ * @param &$val Mixed current value to validate
 121+ * @param $standalone Boolean if this is a simple property or array
 122+ */
 123+ public static function validateClosed( $info, &$val, $standalone ) {
 124+ if ( !$standalone ) {
 125+ // this only validates standalone properties, not arrays, etc
 126+ return;
 127+ }
 128+
 129+ //check if its in a numeric range
 130+ $inRange = false;
 131+ if ( isset( $info['rangeLow'] )
 132+ && isset( $info['rangeHigh'] )
 133+ && is_numeric( $val )
 134+ && ( intval( $val ) <= $info['rangeHigh'] )
 135+ && ( intval( $val ) >= $info['rangeLow'] )
 136+ ) {
 137+ $inRange = true;
 138+ }
 139+
 140+ if ( !isset( $info['choices'][$val] ) && !$inRange ) {
 141+ wfDebugLog( 'XMP', __METHOD__ . " Expected closed choice, but got $val" );
 142+ $val = null;
 143+ }
 144+ }
 145+ /**
 146+ * function to validate and modify flash structure
 147+ *
 148+ * @param $info Array information about current property
 149+ * @param &$val Mixed current value to validate
 150+ * @param $standalone Boolean if this is a simple property or array
 151+ */
 152+ public static function validateFlash( $info, &$val, $standalone ) {
 153+ if ( $standalone ) {
 154+ // this only validates flash structs, not individual properties
 155+ return;
 156+ }
 157+ if ( !( isset( $val['Fired'] )
 158+ && isset( $val['Function'] )
 159+ && isset( $val['Mode'] )
 160+ && isset( $val['RedEyeMode'] )
 161+ && isset( $val['Return'] )
 162+ ) ) {
 163+ wfDebugLog( 'XMP', __METHOD__ . " Flash structure did not have all the required components" );
 164+ $val = null;
 165+ } else {
 166+ $val = ( "\0" | ( $val['Fired'] === 'True' )
 167+ | ( intval( $val['Return'] ) << 1 )
 168+ | ( intval( $val['Mode'] ) << 3 )
 169+ | ( ( $val['Function'] === 'True' ) << 5 )
 170+ | ( ( $val['RedEyeMode'] === 'True' ) << 6 ) );
 171+ }
 172+ }
 173+ /**
 174+ * function to validate LangCode properties ( en-GB, etc )
 175+ *
 176+ * This is just a naive check to make sure it somewhat looks like a lang code.
 177+ *
 178+ * @see rfc 3066
 179+ * @see http://www.adobe.com/devnet/xmp/pdfs/XMPSpecificationPart1.pdf page 30 (section 8.2.2.5)
 180+ *
 181+ * @param $info Array information about current property
 182+ * @param &$val Mixed current value to validate
 183+ * @param $standalone Boolean if this is a simple property or array
 184+ */
 185+ public static function validateLangCode( $info, &$val, $standalone ) {
 186+ if ( !$standalone ) {
 187+ // this only validates standalone properties, not arrays, etc
 188+ return;
 189+ }
 190+ if ( !preg_match( '/^[-A-Za-z0-9]{2,}$/D', $val) ) {
 191+ //this is a rather naive check.
 192+ wfDebugLog( 'XMP', __METHOD__ . " Expected Lang code but got $val" );
 193+ $val = null;
 194+ }
 195+
 196+ }
 197+ /**
 198+ * function to validate date properties, and convert to Exif format.
 199+ *
 200+ * @param $info Array information about current property
 201+ * @param &$val Mixed current value to validate. Converts to TS_EXIF as a side-effect.
 202+ * @param $standalone Boolean if this is a simple property or array
 203+ */
 204+ public static function validateDate( $info, &$val, $standalone ) {
 205+ if ( !$standalone ) {
 206+ // this only validates standalone properties, not arrays, etc
 207+ return;
 208+ }
 209+ $res = array();
 210+ if ( !preg_match(
 211+ /* ahh! scary regex... */
 212+ '/^([0-3]\d{3})(?:-([01]\d)(?:-([0-3]\d)(?:T([0-2]\d):([0-6]\d)(?::([0-6]\d)(?:\.\d+)?)?([-+]\d{2}:\d{2}|Z)?)?)?)?$/D'
 213+ , $val, $res)
 214+ ) {
 215+ wfDebugLog( 'XMP', __METHOD__ . " Expected date but got $val" );
 216+ $val = null;
 217+ } else {
 218+ /*
 219+ * $res is formatted as follows:
 220+ * 0 -> full date.
 221+ * 1 -> year, 2-> month, 3-> day, 4-> hour, 5-> minute, 6->second
 222+ * 7-> Timezone specifier (Z or something like +12:30 )
 223+ * many parts are optional, some aren't. For example if you specify
 224+ * minute, you must specify hour, day, month, and year but not second or TZ.
 225+ */
 226+
 227+ /*
 228+ * First of all, if year = 0000, Something is wrongish,
 229+ * so don't extract. This seems to happen when
 230+ * some programs convert between metadata formats.
 231+ */
 232+ if ( $res[1] === '0000' ) {
 233+ wfDebugLog( 'XMP', __METHOD__ . " Invalid date (year 0): $val" );
 234+ $val = null;
 235+ return;
 236+ }
 237+ //if month, etc unspecified, full out as 01.
 238+ $res[2] = isset( $res[2] ) ? $res[2] : '01'; //month
 239+ $res[3] = isset( $res[3] ) ? $res[3] : '01'; //day
 240+ if ( !isset( $res[4] ) ) { //hour
 241+ //just have the year month day
 242+ $val = $res[1] . ':' . $res[2] . ':' . $res[3];
 243+ return;
 244+ }
 245+ //if hour is set, so is minute or regex above will fail.
 246+ //Extra check for empty string necessary due to TZ but no second case.
 247+ $res[6] = isset( $res[6] ) && $res[6] != '' ? $res[6] : '00';
 248+
 249+ if ( !isset( $res[7] ) || $res[7] === 'Z' ) {
 250+ $val = $res[1] . ':' . $res[2] . ':' . $res[3]
 251+ . ' ' . $res[4] . ':' . $res[5] . ':' . $res[6];
 252+ return;
 253+ }
 254+
 255+ //do timezone processing. We've already done the case that tz = Z.
 256+
 257+ $unix = wfTimestamp( TS_UNIX, $res[1] . $res[2] . $res[3] . $res[4] . $res[5] . $res[6] );
 258+ $offset = intval( substr( $res[7], 1, 2 ) ) * 60 * 60;
 259+ $offset += intval( substr( $res[7], 4, 2 ) ) * 60;
 260+ if ( substr( $res[7], 0, 1 ) === '-' ) {
 261+ $offset = -$offset;
 262+ }
 263+ $val = wfTimestamp( TS_EXIF, $unix + $offset );
 264+ }
 265+
 266+ }
 267+ /** function to validate, and more importantly
 268+ * translate the XMP DMS form of gps coords to
 269+ * the decimal form we use.
 270+ *
 271+ * @see http://www.adobe.com/devnet/xmp/pdfs/XMPSpecificationPart2.pdf
 272+ * section 1.2.7.4 on page 23
 273+ *
 274+ * @param $info Array unused (info about prop)
 275+ * @param &$val String GPS string in either DDD,MM,SSk or
 276+ * or DDD,MM.mmk form
 277+ * @param $standalone Boolean if its a simple prop (should always be true)
 278+ */
 279+ public static function validateGPS ( $info, &$val, $standalone ) {
 280+ if ( !$standalone ) {
 281+ return;
 282+ }
 283+
 284+ $m = array();
 285+ if ( preg_match(
 286+ '/(\d{1,3}),(\d{1,2}),(\d{1,2})([NWSE])/D',
 287+ $val, $m )
 288+ ) {
 289+ $coord = intval( $m[1] );
 290+ $coord += intval( $m[2] ) * (1/60);
 291+ $coord += intval( $m[3] ) * (1/3600);
 292+ if ( $m[4] === 'S' || $m[4] === 'W' ) {
 293+ $coord = -$coord;
 294+ }
 295+ $val = $coord;
 296+ return;
 297+ } elseif ( preg_match(
 298+ '/(\d{1,3}),(\d{1,2}(?:.\d*)?)([NWSE])/D',
 299+ $val, $m )
 300+ ) {
 301+ $coord = intval( $m[1] );
 302+ $coord += floatval( $m[2] ) * (1/60);
 303+ if ( $m[3] === 'S' || $m[3] === 'W' ) {
 304+ $coord = -$coord;
 305+ }
 306+ $val = $coord;
 307+ return;
 308+
 309+ } else {
 310+ wfDebugLog( 'XMP', __METHOD__
 311+ . " Expected GPSCoordinate, but got $val." );
 312+ $val = null;
 313+ return;
 314+ }
 315+
 316+ }
 317+
 318+}
Property changes on: trunk/phase3/includes/media/XMPValidate.php
___________________________________________________________________
Added: svn:eol-style
1319 + native
Index: trunk/phase3/includes/media/XMPInfo.php
@@ -0,0 +1,1136 @@
 2+<?php
 3+/**
 4+* This class is just a container for a big array
 5+* used by XMPReader to determine which XMP items to
 6+* extract.
 7+*/
 8+class XMPInfo {
 9+
 10+ /** get the items array
 11+ * @return Array XMP item configuration array.
 12+ */
 13+ public static function getItems ( ) {
 14+ if( !self::$ranHooks ) {
 15+ // This is for if someone makes a custom metadata extension.
 16+ // For example, a medical wiki might want to decode DICOM xmp properties.
 17+ wfRunHooks('XMPGetInfo', Array(&self::$items));
 18+ self::$ranHooks = true; // Only want to do this once.
 19+ }
 20+ return self::$items;
 21+ }
 22+
 23+ static private $ranHooks = false;
 24+
 25+ /**
 26+ * XMPInfo::$items keeps a list of all the items
 27+ * we are interested to extract, as well as
 28+ * information about the item like what type
 29+ * it is.
 30+ *
 31+ * Format is an array of namespaces,
 32+ * each containing an array of tags
 33+ * each tag is an array of information about the
 34+ * tag, including:
 35+ * * map_group - what group (used for precedence during conflicts)
 36+ * * mode - What type of item (self::MODE_SIMPLE usually, see above for all values)
 37+ * * validate - method to validate input. Could also post-process the input. A string value is assumed to be a static method of XMPValidate. Can also take a array( 'className', 'methodName' ).
 38+ * * choices - array of potential values (format of 'value' => true ). Only used with validateClosed
 39+ * * rangeLow and rangeHigh - alternative to choices for numeric ranges. Again for validateClosed only.
 40+ * * children - for MODE_STRUCT items, allowed children.
 41+ * * structPart - Indicates that this element can only appear as a member of a structure.
 42+ *
 43+ * currently this just has a bunch of exif values as this class is only half-done
 44+ */
 45+
 46+ static private $items = array(
 47+ 'http://ns.adobe.com/exif/1.0/' => array(
 48+ 'ApertureValue' => array(
 49+ 'map_group' => 'exif',
 50+ 'mode' => XMPReader::MODE_SIMPLE,
 51+ 'validate' => 'validateRational'
 52+ ),
 53+ 'BrightnessValue' => array(
 54+ 'map_group' => 'exif',
 55+ 'mode' => XMPReader::MODE_SIMPLE,
 56+ 'validate' => 'validateRational'
 57+ ),
 58+ 'CompressedBitsPerPixel' => array(
 59+ 'map_group' => 'exif',
 60+ 'mode' => XMPReader::MODE_SIMPLE,
 61+ 'validate' => 'validateRational'
 62+ ),
 63+ 'DigitalZoomRatio' => array(
 64+ 'map_group' => 'exif',
 65+ 'mode' => XMPReader::MODE_SIMPLE,
 66+ 'validate' => 'validateRational'
 67+ ),
 68+ 'ExposureBiasValue' => array(
 69+ 'map_group' => 'exif',
 70+ 'mode' => XMPReader::MODE_SIMPLE,
 71+ 'validate' => 'validateRational'
 72+ ),
 73+ 'ExposureIndex' => array(
 74+ 'map_group' => 'exif',
 75+ 'mode' => XMPReader::MODE_SIMPLE,
 76+ 'validate' => 'validateRational'
 77+ ),
 78+ 'ExposureTime' => array(
 79+ 'map_group' => 'exif',
 80+ 'mode' => XMPReader::MODE_SIMPLE,
 81+ 'validate' => 'validateRational'
 82+ ),
 83+ 'FlashEnergy' => array(
 84+ 'map_group' => 'exif',
 85+ 'mode' => XMPReader::MODE_SIMPLE,
 86+ 'validate' => 'validateRational',
 87+ ),
 88+ 'FNumber' => array(
 89+ 'map_group' => 'exif',
 90+ 'mode' => XMPReader::MODE_SIMPLE,
 91+ 'validate' => 'validateRational'
 92+ ),
 93+ 'FocalLength' => array(
 94+ 'map_group' => 'exif',
 95+ 'mode' => XMPReader::MODE_SIMPLE,
 96+ 'validate' => 'validateRational'
 97+ ),
 98+ 'FocalPlaneXResolution' => array(
 99+ 'map_group' => 'exif',
 100+ 'mode' => XMPReader::MODE_SIMPLE,
 101+ 'validate' => 'validateRational'
 102+ ),
 103+ 'FocalPlaneYResolution' => array(
 104+ 'map_group' => 'exif',
 105+ 'mode' => XMPReader::MODE_SIMPLE,
 106+ 'validate' => 'validateRational'
 107+ ),
 108+ 'GPSAltitude' => array(
 109+ 'map_group' => 'exif',
 110+ 'mode' => XMPReader::MODE_SIMPLE,
 111+ 'validate' => 'validateRational',
 112+ ),
 113+ 'GPSDestBearing' => array(
 114+ 'map_group' => 'exif',
 115+ 'mode' => XMPReader::MODE_SIMPLE,
 116+ 'validate' => 'validateRational'
 117+ ),
 118+ 'GPSDestDistance' => array(
 119+ 'map_group' => 'exif',
 120+ 'mode' => XMPReader::MODE_SIMPLE,
 121+ 'validate' => 'validateRational'
 122+ ),
 123+ 'GPSDOP' => array(
 124+ 'map_group' => 'exif',
 125+ 'mode' => XMPReader::MODE_SIMPLE,
 126+ 'validate' => 'validateRational'
 127+ ),
 128+ 'GPSImgDirection' => array(
 129+ 'map_group' => 'exif',
 130+ 'mode' => XMPReader::MODE_SIMPLE,
 131+ 'validate' => 'validateRational'
 132+ ),
 133+ 'GPSSpeed' => array(
 134+ 'map_group' => 'exif',
 135+ 'mode' => XMPReader::MODE_SIMPLE,
 136+ 'validate' => 'validateRational'
 137+ ),
 138+ 'GPSTrack' => array(
 139+ 'map_group' => 'exif',
 140+ 'mode' => XMPReader::MODE_SIMPLE,
 141+ 'validate' => 'validateRational'
 142+ ),
 143+ 'MaxApertureValue' => array(
 144+ 'map_group' => 'exif',
 145+ 'mode' => XMPReader::MODE_SIMPLE,
 146+ 'validate' => 'validateRational'
 147+ ),
 148+ 'ShutterSpeedValue' => array(
 149+ 'map_group' => 'exif',
 150+ 'mode' => XMPReader::MODE_SIMPLE,
 151+ 'validate' => 'validateRational'
 152+ ),
 153+ 'SubjectDistance' => array(
 154+ 'map_group' => 'exif',
 155+ 'mode' => XMPReader::MODE_SIMPLE,
 156+ 'validate' => 'validateRational'
 157+ ),
 158+ /* Flash */
 159+ 'Flash' => array(
 160+ 'mode' => XMPReader::MODE_STRUCT,
 161+ 'children' => array(
 162+ 'Fired' => true,
 163+ 'Function' => true,
 164+ 'Mode' => true,
 165+ 'RedEyeMode' => true,
 166+ 'Return' => true,
 167+ ),
 168+ 'validate' => 'validateFlash',
 169+ 'map_group' => 'exif',
 170+ ),
 171+ 'Fired' => array(
 172+ 'map_group' => 'exif',
 173+ 'validate' => 'validateBoolean',
 174+ 'mode' => XMPReader::MODE_SIMPLE,
 175+ 'structPart'=> true,
 176+ ),
 177+ 'Function' => array(
 178+ 'map_group' => 'exif',
 179+ 'validate' => 'validateBoolean',
 180+ 'mode' => XMPReader::MODE_SIMPLE,
 181+ 'structPart'=> true,
 182+ ),
 183+ 'Mode' => array(
 184+ 'map_group' => 'exif',
 185+ 'validate' => 'validateClosed',
 186+ 'mode' => XMPReader::MODE_SIMPLE,
 187+ 'choices' => array( '0' => true, '1' => true,
 188+ '2' => true, '3' => true ),
 189+ 'structPart'=> true,
 190+ ),
 191+ 'Return' => array(
 192+ 'map_group' => 'exif',
 193+ 'validate' => 'validateClosed',
 194+ 'mode' => XMPReader::MODE_SIMPLE,
 195+ 'choices' => array( '0' => true,
 196+ '2' => true, '3' => true ),
 197+ 'structPart'=> true,
 198+ ),
 199+ 'RedEyeMode' => array(
 200+ 'map_group' => 'exif',
 201+ 'validate' => 'validateBoolean',
 202+ 'mode' => XMPReader::MODE_SIMPLE,
 203+ 'structPart'=> true,
 204+ ),
 205+ /* End Flash */
 206+ 'ISOSpeedRatings' => array(
 207+ 'map_group' => 'exif',
 208+ 'mode' => XMPReader::MODE_SEQ,
 209+ 'validate' => 'validateInteger'
 210+ ),
 211+ /* end rational things */
 212+ 'ColorSpace' => array(
 213+ 'map_group' => 'exif',
 214+ 'mode' => XMPReader::MODE_SIMPLE,
 215+ 'validate' => 'validateClosed',
 216+ 'choices' => array( '1' => true, '65535' => true ),
 217+ ),
 218+ 'ComponentsConfiguration' => array(
 219+ 'map_group' => 'exif',
 220+ 'mode' => XMPReader::MODE_SEQ,
 221+ 'validate' => 'validateClosed',
 222+ 'choices' => array( '1' => true, '2' => true, '3' => true, '4' => true,
 223+ '5' => true, '6' => true )
 224+ ),
 225+ 'Contrast' => array(
 226+ 'map_group' => 'exif',
 227+ 'mode' => XMPReader::MODE_SIMPLE,
 228+ 'validate' => 'validateClosed',
 229+ 'choices' => array( '0' => true, '1' => true, '2' => true )
 230+ ),
 231+ 'CustomRendered' => array(
 232+ 'map_group' => 'exif',
 233+ 'mode' => XMPReader::MODE_SIMPLE,
 234+ 'validate' => 'validateClosed',
 235+ 'choices' => array( '0' => true, '1' => true )
 236+ ),
 237+ 'DateTimeOriginal' => array(
 238+ 'map_group' => 'exif',
 239+ 'mode' => XMPReader::MODE_SIMPLE,
 240+ 'validate' => 'validateDate',
 241+ ),
 242+ 'DateTimeDigitized' => array( /* xmp:CreateDate */
 243+ 'map_group' => 'exif',
 244+ 'mode' => XMPReader::MODE_SIMPLE,
 245+ 'validate' => 'validateDate',
 246+ ),
 247+ /* todo: there might be interesting information in
 248+ * exif:DeviceSettingDescription, but need to find an
 249+ * example
 250+ */
 251+ 'ExifVersion' => array(
 252+ 'map_group' => 'exif',
 253+ 'mode' => XMPReader::MODE_SIMPLE,
 254+ ),
 255+ 'ExposureMode' => array(
 256+ 'map_group' => 'exif',
 257+ 'mode' => XMPReader::MODE_SIMPLE,
 258+ 'validate' => 'validateClosed',
 259+ 'rangeLow' => 0,
 260+ 'rangeHigh' => 2,
 261+ ),
 262+ 'ExposureProgram' => array(
 263+ 'map_group' => 'exif',
 264+ 'mode' => XMPReader::MODE_SIMPLE,
 265+ 'validate' => 'validateClosed',
 266+ 'rangeLow' => 0,
 267+ 'rangeHigh' => 8,
 268+ ),
 269+ 'FileSource' => array(
 270+ 'map_group' => 'exif',
 271+ 'mode' => XMPReader::MODE_SIMPLE,
 272+ 'validate' => 'validateClosed',
 273+ 'choices' => array( '3' => true )
 274+ ),
 275+ 'FlashpixVersion' => array(
 276+ 'map_group' => 'exif',
 277+ 'mode' => XMPReader::MODE_SIMPLE,
 278+ ),
 279+ 'FocalLengthIn35mmFilm' => array(
 280+ 'map_group' => 'exif',
 281+ 'mode' => XMPReader::MODE_SIMPLE,
 282+ 'validate' => 'validateInteger',
 283+ ),
 284+ 'FocalPlaneResolutionUnit' => array(
 285+ 'map_group' => 'exif',
 286+ 'mode' => XMPReader::MODE_SIMPLE,
 287+ 'validate' => 'validateClosed',
 288+ 'choices' => array( '2' => true, '3' => true ),
 289+ ),
 290+ 'GainControl' => array(
 291+ 'map_group' => 'exif',
 292+ 'mode' => XMPReader::MODE_SIMPLE,
 293+ 'validate' => 'validateClosed',
 294+ 'rangeLow' => 0,
 295+ 'rangeHigh' => 4,
 296+ ),
 297+ /* this value is post-processed out later */
 298+ 'GPSAltitudeRef' => array(
 299+ 'map_group' => 'exif',
 300+ 'mode' => XMPReader::MODE_SIMPLE,
 301+ 'validate' => 'validateClosed',
 302+ 'choices' => array( '0' => true, '1' => true ),
 303+ ),
 304+ 'GPSAreaInformation' => array(
 305+ 'map_group' => 'exif',
 306+ 'mode' => XMPReader::MODE_SIMPLE,
 307+ ),
 308+ 'GPSDestBearingRef' => array(
 309+ 'map_group' => 'exif',
 310+ 'mode' => XMPReader::MODE_SIMPLE,
 311+ 'validate' => 'validateClosed',
 312+ 'choices' => array( 'T' => true, 'M' => true ),
 313+ ),
 314+ 'GPSDestDistanceRef' => array(
 315+ 'map_group' => 'exif',
 316+ 'mode' => XMPReader::MODE_SIMPLE,
 317+ 'validate' => 'validateClosed',
 318+ 'choices' => array( 'K' => true, 'M' => true,
 319+ 'N' => true ),
 320+ ),
 321+ 'GPSDestLatitude' => array(
 322+ 'map_group' => 'exif',
 323+ 'mode' => XMPReader::MODE_SIMPLE,
 324+ 'validate' => 'validateGPS',
 325+ ),
 326+ 'GPSDestLongitude' => array(
 327+ 'map_group' => 'exif',
 328+ 'mode' => XMPReader::MODE_SIMPLE,
 329+ 'validate' => 'validateGPS',
 330+ ),
 331+ 'GPSDifferential' => array(
 332+ 'map_group' => 'exif',
 333+ 'mode' => XMPReader::MODE_SIMPLE,
 334+ 'validate' => 'validateClosed',
 335+ 'choices' => array( '0' => true, '1' => true ),
 336+ ),
 337+ 'GPSImgDirectionRef' => array(
 338+ 'map_group' => 'exif',
 339+ 'mode' => XMPReader::MODE_SIMPLE,
 340+ 'validate' => 'validateClosed',
 341+ 'choices' => array( 'T' => true, 'M' => true ),
 342+ ),
 343+ 'GPSLatitude' => array(
 344+ 'map_group' => 'exif',
 345+ 'mode' => XMPReader::MODE_SIMPLE,
 346+ 'validate' => 'validateGPS',
 347+ ),
 348+ 'GPSLongitude' => array(
 349+ 'map_group' => 'exif',
 350+ 'mode' => XMPReader::MODE_SIMPLE,
 351+ 'validate' => 'validateGPS',
 352+ ),
 353+ 'GPSMapDatum' => array(
 354+ 'map_group' => 'exif',
 355+ 'mode' => XMPReader::MODE_SIMPLE,
 356+ ),
 357+ 'GPSMeasureMode' => array(
 358+ 'map_group' => 'exif',
 359+ 'mode' => XMPReader::MODE_SIMPLE,
 360+ 'validate' => 'validateClosed',
 361+ 'choices' => array( '2' => true, '3' => true )
 362+ ),
 363+ 'GPSProcessingMethod' => array(
 364+ 'map_group' => 'exif',
 365+ 'mode' => XMPReader::MODE_SIMPLE,
 366+ ),
 367+ 'GPSSatellites' => array(
 368+ 'map_group' => 'exif',
 369+ 'mode' => XMPReader::MODE_SIMPLE,
 370+ ),
 371+ 'GPSSpeedRef' => array(
 372+ 'map_group' => 'exif',
 373+ 'mode' => XMPReader::MODE_SIMPLE,
 374+ 'validate' => 'validateClosed',
 375+ 'choices' => array( 'K' => true, 'M' => true,
 376+ 'N' => true ),
 377+ ),
 378+ 'GPSStatus' => array(
 379+ 'map_group' => 'exif',
 380+ 'mode' => XMPReader::MODE_SIMPLE,
 381+ 'validate' => 'validateClosed',
 382+ 'choices' => array( 'A' => true, 'V' => true )
 383+ ),
 384+ 'GPSTimeStamp' => array(
 385+ 'map_group' => 'exif',
 386+ // Note: in exif, GPSDateStamp does not include
 387+ // the time, where here it does.
 388+ 'map_name' => 'GPSDateStamp',
 389+ 'mode' => XMPReader::MODE_SIMPLE,
 390+ 'validate' => 'validateDate',
 391+ ),
 392+ 'GPSTrackRef' => array(
 393+ 'map_group' => 'exif',
 394+ 'mode' => XMPReader::MODE_SIMPLE,
 395+ 'validate' => 'validateClosed',
 396+ 'choices' => array( 'T' => true, 'M' => true )
 397+ ),
 398+ 'GPSVersionID' => array(
 399+ 'map_group' => 'exif',
 400+ 'mode' => XMPReader::MODE_SIMPLE,
 401+ ),
 402+ 'ImageUniqueID' => array(
 403+ 'map_group' => 'exif',
 404+ 'mode' => XMPReader::MODE_SIMPLE,
 405+ ),
 406+ 'LightSource' => array(
 407+ 'map_group' => 'exif',
 408+ 'mode' => XMPReader::MODE_SIMPLE,
 409+ 'validate' => 'validateClosed',
 410+ /* can't use a range, as it skips... */
 411+ 'choices' => array( '0' => true, '1' => true,
 412+ '2' => true, '3' => true, '4' => true,
 413+ '9' => true, '10' => true, '11' => true,
 414+ '12' => true, '13' => true,
 415+ '14' => true, '15' => true,
 416+ '17' => true, '18' => true,
 417+ '19' => true, '20' => true,
 418+ '21' => true, '22' => true,
 419+ '23' => true, '24' => true,
 420+ '255' => true,
 421+ ),
 422+ ),
 423+ 'MeteringMode' => array(
 424+ 'map_group' => 'exif',
 425+ 'mode' => XMPReader::MODE_SIMPLE,
 426+ 'validate' => 'validateClosed',
 427+ 'rangeLow' => 0,
 428+ 'rangeHigh' => 6,
 429+ 'choices' => array( '255' => true ),
 430+ ),
 431+ /* Pixel(X|Y)Dimension are rather useless, but for
 432+ * completeness since we do it with exif.
 433+ */
 434+ 'PixelXDimension' => array(
 435+ 'map_group' => 'exif',
 436+ 'mode' => XMPReader::MODE_SIMPLE,
 437+ 'validate' => 'validateInteger',
 438+ ),
 439+ 'PixelYDimension' => array(
 440+ 'map_group' => 'exif',
 441+ 'mode' => XMPReader::MODE_SIMPLE,
 442+ 'validate' => 'validateInteger',
 443+ ),
 444+ 'Saturation' => array(
 445+ 'map_group' => 'exif',
 446+ 'mode' => XMPReader::MODE_SIMPLE,
 447+ 'validate' => 'validateClosed',
 448+ 'rangeLow' => 0,
 449+ 'rangeHigh' => 2,
 450+ ),
 451+ 'SceneCaptureType' => array(
 452+ 'map_group' => 'exif',
 453+ 'mode' => XMPReader::MODE_SIMPLE,
 454+ 'validate' => 'validateClosed',
 455+ 'rangeLow' => 0,
 456+ 'rangeHigh' => 3,
 457+ ),
 458+ 'SceneType' => array(
 459+ 'map_group' => 'exif',
 460+ 'mode' => XMPReader::MODE_SIMPLE,
 461+ 'validate' => 'validateClosed',
 462+ 'choices' => array( '1' => true ),
 463+ ),
 464+ // Note, 6 is not valid SensingMethod.
 465+ 'SensingMethod' => array(
 466+ 'map_group' => 'exif',
 467+ 'mode' => XMPReader::MODE_SIMPLE,
 468+ 'validate' => 'validateClosed',
 469+ 'rangeLow' => 1,
 470+ 'rangeHigh' => 5,
 471+ 'choices' => array( '7' => true, 8 => true ),
 472+ ),
 473+ 'Sharpness' => array(
 474+ 'map_group' => 'exif',
 475+ 'mode' => XMPReader::MODE_SIMPLE,
 476+ 'validate' => 'validateClosed',
 477+ 'rangeLow' => 0,
 478+ 'rangeHigh' => 2,
 479+ ),
 480+ 'SpectralSensitivity' => array(
 481+ 'map_group' => 'exif',
 482+ 'mode' => XMPReader::MODE_SIMPLE,
 483+ ),
 484+ // This tag should perhaps be displayed to user better.
 485+ 'SubjectArea' => array(
 486+ 'map_group' => 'exif',
 487+ 'mode' => XMPReader::MODE_SEQ,
 488+ 'validate' => 'validateInteger',
 489+ ),
 490+ 'SubjectDistanceRange' => array(
 491+ 'map_group' => 'exif',
 492+ 'mode' => XMPReader::MODE_SIMPLE,
 493+ 'validate' => 'validateClosed',
 494+ 'rangeLow' => 0,
 495+ 'rangeHigh' => 3,
 496+ ),
 497+ 'SubjectLocation' => array(
 498+ 'map_group' => 'exif',
 499+ 'mode' => XMPReader::MODE_SEQ,
 500+ 'validate' => 'validateInteger',
 501+ ),
 502+ 'UserComment' => array(
 503+ 'map_group' => 'exif',
 504+ 'mode' => XMPReader::MODE_LANG,
 505+ ),
 506+ 'WhiteBalance' => array(
 507+ 'map_group' => 'exif',
 508+ 'mode' => XMPReader::MODE_SIMPLE,
 509+ 'validate' => 'validateClosed',
 510+ 'choices' => array( '0' => true, '1' => true )
 511+ ),
 512+ ),
 513+ 'http://ns.adobe.com/tiff/1.0/' => array(
 514+ 'Artist' => array(
 515+ 'map_group' => 'exif',
 516+ 'mode' => XMPReader::MODE_SIMPLE,
 517+ ),
 518+ 'BitsPerSample' => array(
 519+ 'map_group' => 'exif',
 520+ 'mode' => XMPReader::MODE_SEQ,
 521+ 'validate' => 'validateInteger',
 522+ ),
 523+ 'Compression' => array(
 524+ 'map_group' => 'exif',
 525+ 'mode' => XMPReader::MODE_SIMPLE,
 526+ 'validate' => 'validateClosed',
 527+ 'choices' => array( '1' => true, '6' => true ),
 528+ ),
 529+ /* this prop should not be used in XMP. dc:rights is the correct prop */
 530+ 'Copyright' => array(
 531+ 'map_group' => 'exif',
 532+ 'mode' => XMPReader::MODE_LANG,
 533+ ),
 534+ 'DateTime' => array( /* proper prop is xmp:ModifyDate */
 535+ 'map_group' => 'exif',
 536+ 'mode' => XMPReader::MODE_SIMPLE,
 537+ 'validate' => 'validateDate',
 538+ ),
 539+ 'ImageDescription' => array( /* proper one is dc:description */
 540+ 'map_group' => 'exif',
 541+ 'mode' => XMPReader::MODE_LANG,
 542+ ),
 543+ 'ImageLength' => array(
 544+ 'map_group' => 'exif',
 545+ 'mode' => XMPReader::MODE_SIMPLE,
 546+ 'validate' => 'validateInteger',
 547+ ),
 548+ 'ImageWidth' => array(
 549+ 'map_group' => 'exif',
 550+ 'mode' => XMPReader::MODE_SIMPLE,
 551+ 'validate' => 'validateInteger',
 552+ ),
 553+ 'Make' => array(
 554+ 'map_group' => 'exif',
 555+ 'mode' => XMPReader::MODE_SIMPLE,
 556+ ),
 557+ 'Model' => array(
 558+ 'map_group' => 'exif',
 559+ 'mode' => XMPReader::MODE_SIMPLE,
 560+ ),
 561+ 'Orientation' => array(
 562+ 'map_group' => 'exif',
 563+ 'mode' => XMPReader::MODE_SIMPLE,
 564+ 'validate' => 'validateClosed',
 565+ 'choices' => array( '1' => true, '2' => true, '3' => true, '4' => true, 5 => true,
 566+ '6' => true, '7' => true, '8' => true ),
 567+ ),
 568+ 'PhotometricInterpretation' => array(
 569+ 'map_group' => 'exif',
 570+ 'mode' => XMPReader::MODE_SIMPLE,
 571+ 'validate' => 'validateClosed',
 572+ 'choices' => array( '2' => true, '6' => true ),
 573+ ),
 574+ 'PlanerConfiguration' => array(
 575+ 'map_group' => 'exif',
 576+ 'mode' => XMPReader::MODE_SIMPLE,
 577+ 'validate' => 'validateClosed',
 578+ 'choices' => array( '1' => true, '2' => true ),
 579+ ),
 580+ 'PrimaryChromaticities' => array(
 581+ 'map_group' => 'exif',
 582+ 'mode' => XMPReader::MODE_SEQ,
 583+ 'validate' => 'validateRational',
 584+ ),
 585+ 'ReferenceBlackWhite' => array(
 586+ 'map_group' => 'exif',
 587+ 'mode' => XMPReader::MODE_SEQ,
 588+ 'validate' => 'validateRational',
 589+ ),
 590+ 'ResolutionUnit' => array(
 591+ 'map_group' => 'exif',
 592+ 'mode' => XMPReader::MODE_SIMPLE,
 593+ 'validate' => 'validateClosed',
 594+ 'choices' => array( '2' => true, '3' => true ),
 595+ ),
 596+ 'SamplesPerPixel' => array(
 597+ 'map_group' => 'exif',
 598+ 'mode' => XMPReader::MODE_SIMPLE,
 599+ 'validate' => 'validateInteger',
 600+ ),
 601+ 'Software' => array( /* see xmp:CreatorTool */
 602+ 'map_group' => 'exif',
 603+ 'mode' => XMPReader::MODE_SIMPLE,
 604+ ),
 605+ /* ignore TransferFunction */
 606+ 'WhitePoint' => array(
 607+ 'map_group' => 'exif',
 608+ 'mode' => XMPReader::MODE_SEQ,
 609+ 'validate' => 'validateRational',
 610+ ),
 611+ 'XResolution' => array(
 612+ 'map_group' => 'exif',
 613+ 'mode' => XMPReader::MODE_SIMPLE,
 614+ 'validate' => 'validateRational',
 615+ ),
 616+ 'YResolution' => array(
 617+ 'map_group' => 'exif',
 618+ 'mode' => XMPReader::MODE_SIMPLE,
 619+ 'validate' => 'validateRational',
 620+ ),
 621+ 'YCbCrCoefficients' => array(
 622+ 'map_group' => 'exif',
 623+ 'mode' => XMPReader::MODE_SEQ,
 624+ 'validate' => 'validateRational',
 625+ ),
 626+ 'YCbCrPositioning' => array(
 627+ 'map_group' => 'exif',
 628+ 'mode' => XMPReader::MODE_SIMPLE,
 629+ 'validate' => 'validateClosed',
 630+ 'choices' => array( '1' => true, '2' => true ),
 631+ ),
 632+ 'YCbCrSubSampling' => array(
 633+ 'map_group' => 'exif',
 634+ 'mode' => XMPReader::MODE_SEQ,
 635+ 'validate' => 'validateClosed',
 636+ 'choices' => array( '1' => true, '2' => true ),
 637+ ),
 638+ ),
 639+ 'http://ns.adobe.com/exif/1.0/aux/' => array(
 640+ 'Lens' => array(
 641+ 'map_group' => 'exif',
 642+ 'mode' => XMPReader::MODE_SIMPLE,
 643+ ),
 644+ 'SerialNumber' => array(
 645+ 'map_group' => 'exif',
 646+ 'mode' => XMPReader::MODE_SIMPLE,
 647+ ),
 648+ 'OwnerName' => array(
 649+ 'map_group' => 'exif',
 650+ 'map_name' => 'CameraOwnerName',
 651+ 'mode' => XMPReader::MODE_SIMPLE,
 652+ ),
 653+ ),
 654+ 'http://purl.org/dc/elements/1.1/' => array(
 655+ 'title' => array(
 656+ 'map_group' => 'general',
 657+ 'map_name' => 'ObjectName',
 658+ 'mode' => XMPReader::MODE_LANG
 659+ ),
 660+ 'description' => array(
 661+ 'map_group' => 'general',
 662+ 'map_name' => 'ImageDescription',
 663+ 'mode' => XMPReader::MODE_LANG
 664+ ),
 665+ 'contributor' => array(
 666+ 'map_group' => 'general',
 667+ 'map_name' => 'dc-contributor',
 668+ 'mode' => XMPReader::MODE_BAG
 669+ ),
 670+ 'coverage' => array(
 671+ 'map_group' => 'general',
 672+ 'map_name' => 'dc-coverage',
 673+ 'mode' => XMPReader::MODE_SIMPLE,
 674+ ),
 675+ 'creator' => array(
 676+ 'map_group' => 'general',
 677+ 'map_name' => 'Artist', //map with exif Artist, iptc byline (2:80)
 678+ 'mode' => XMPReader::MODE_SEQ,
 679+ ),
 680+ 'date' => array(
 681+ 'map_group' => 'general',
 682+ // Note, not mapped with other date properties, as this type of date is
 683+ // non-specific: "A point or period of time associated with an event in
 684+ // the lifecycle of the resource"
 685+ 'map_name' => 'dc-date',
 686+ 'mode' => XMPReader::MODE_SEQ,
 687+ 'validate' => 'validateDate',
 688+ ),
 689+ /* Do not extract dc:format, as we've got better ways to determine mimetype */
 690+ 'identifier' => array(
 691+ 'map_group' => 'deprecated',
 692+ 'map_name' => 'Identifier',
 693+ 'mode' => XMPReader::MODE_SIMPLE,
 694+ ),
 695+ 'language' => array(
 696+ 'map_group' => 'general',
 697+ 'map_name' => 'LanguageCode', /* mapped with iptc 2:135 */
 698+ 'mode' => XMPReader::MODE_BAG,
 699+ 'validate' => 'validateLangCode',
 700+ ),
 701+ 'publisher' => array(
 702+ 'map_group' => 'general',
 703+ 'map_name' => 'dc-publisher',
 704+ 'mode' => XMPReader::MODE_BAG,
 705+ ),
 706+ // for related images/resources
 707+ 'relation' => array(
 708+ 'map_group' => 'general',
 709+ 'map_name' => 'dc-relation',
 710+ 'mode' => XMPReader::MODE_BAG,
 711+ ),
 712+ 'rights' => array(
 713+ 'map_group' => 'general',
 714+ 'map_name' => 'Copyright',
 715+ 'mode' => XMPReader::MODE_LANG,
 716+ ),
 717+ // Note: source is not mapped with iptc source, since iptc
 718+ // source describes the source of the image in terms of a person
 719+ // who provided the image, where this is to describe an image that the
 720+ // current one is based on.
 721+ 'source' => array(
 722+ 'map_group' => 'general',
 723+ 'map_name' => 'dc-source',
 724+ 'mode' => XMPReader::MODE_SIMPLE,
 725+ ),
 726+ 'subject' => array(
 727+ 'map_group' => 'general',
 728+ 'map_name' => 'Keywords', /* maps to iptc 2:25 */
 729+ 'mode' => XMPReader::MODE_BAG,
 730+ ),
 731+ 'type' => array(
 732+ 'map_group' => 'general',
 733+ 'map_name' => 'dc-type',
 734+ 'mode' => XMPReader::MODE_BAG,
 735+ ),
 736+ ),
 737+ 'http://ns.adobe.com/xap/1.0/' => array(
 738+ 'CreateDate' => array(
 739+ 'map_group' => 'general',
 740+ 'map_name' => 'DateTimeDigitized',
 741+ 'mode' => XMPReader::MODE_SIMPLE,
 742+ 'validate' => 'validateDate',
 743+ ),
 744+ 'CreatorTool' => array(
 745+ 'map_group' => 'general',
 746+ 'map_name' => 'Software',
 747+ 'mode' => XMPReader::MODE_SIMPLE
 748+ ),
 749+ 'Identifier' => array(
 750+ 'map_group' => 'general',
 751+ 'mode' => XMPReader::MODE_BAG,
 752+ ),
 753+ 'Label' => array(
 754+ 'map_group' => 'general',
 755+ 'mode' => XMPReader::MODE_SIMPLE,
 756+ ),
 757+ 'ModifyDate' => array(
 758+ 'map_group' => 'general',
 759+ 'mode' => XMPReader::MODE_SIMPLE,
 760+ 'map_name' => 'DateTime',
 761+ 'validate' => 'validateDate',
 762+ ),
 763+ 'MetadataDate' => array(
 764+ 'map_group' => 'general',
 765+ 'mode' => XMPReader::MODE_SIMPLE,
 766+ // map_name to be consistent with other date names.
 767+ 'map_name' => 'DateTimeMetadata',
 768+ 'validate' => 'validateDate',
 769+ ),
 770+ 'Nickname' => array(
 771+ 'map_group' => 'general',
 772+ 'mode' => XMPReader::MODE_SIMPLE,
 773+ ),
 774+ 'Rating' => array(
 775+ 'map_group' => 'general',
 776+ 'mode' => XMPReader::MODE_SIMPLE,
 777+ 'validate' => 'validateRating',
 778+ ),
 779+ ),
 780+ 'http://ns.adobe.com/xap/1.0/rights/' => array(
 781+ 'Certificate' => array(
 782+ 'map_group' => 'general',
 783+ 'map_name' => 'RightsCertificate',
 784+ 'mode' => XMPReader::MODE_SIMPLE,
 785+ ),
 786+ 'Marked' => array(
 787+ 'map_group' => 'general',
 788+ 'map_name' => 'Copyrighted',
 789+ 'mode' => XMPReader::MODE_SIMPLE,
 790+ 'validate' => 'validateBoolean',
 791+ ),
 792+ 'Owner' => array(
 793+ 'map_group' => 'general',
 794+ 'map_name' => 'CopyrightOwner',
 795+ 'mode' => XMPReader::MODE_BAG,
 796+ ),
 797+ // this seems similar to dc:rights.
 798+ 'UsageTerms' => array(
 799+ 'map_group' => 'general',
 800+ 'mode' => XMPReader::MODE_LANG,
 801+ ),
 802+ 'WebStatement' => array(
 803+ 'map_group' => 'general',
 804+ 'mode' => XMPReader::MODE_SIMPLE,
 805+ ),
 806+ ),
 807+ // XMP media management.
 808+ 'http://ns.adobe.com/xap/1.0/mm/' => array(
 809+ // if we extract the exif UniqueImageID, might
 810+ // as well do this too.
 811+ 'OriginalDocumentID' => array(
 812+ 'map_group' => 'general',
 813+ 'mode' => XMPReader::MODE_SIMPLE,
 814+ ),
 815+ // It might also be useful to do xmpMM:LastURL
 816+ // and xmpMM:DerivedFrom as you can potentially,
 817+ // get the url of this document/source for this
 818+ // document. However whats more likely is you'd
 819+ // get a file:// url for the path of the doc,
 820+ // which is somewhat of a privacy issue.
 821+ ),
 822+ 'http://creativecommons.org/ns#' => array(
 823+ 'license' => array(
 824+ 'map_name' => 'LicenseUrl',
 825+ 'map_group' => 'general',
 826+ 'mode' => XMPReader::MODE_SIMPLE,
 827+ ),
 828+ 'morePermissions' => array(
 829+ 'map_name' => 'MorePermissionsUrl',
 830+ 'map_group' => 'general',
 831+ 'mode' => XMPReader::MODE_SIMPLE,
 832+ ),
 833+ 'attributionURL' => array(
 834+ 'map_group' => 'general',
 835+ 'map_name' => 'AttributionUrl',
 836+ 'mode' => XMPReader::MODE_SIMPLE,
 837+ ),
 838+ 'attributionName' => array(
 839+ 'map_group' => 'general',
 840+ 'map_name' => 'PreferredAttributionName',
 841+ 'mode' => XMPReader::MODE_SIMPLE,
 842+ ),
 843+ ),
 844+ //Note, this property affects how jpeg metadata is extracted.
 845+ 'http://ns.adobe.com/xmp/note/' => array(
 846+ 'HasExtendedXMP' => array(
 847+ 'map_group' => 'special',
 848+ 'mode' => XMPReader::MODE_SIMPLE,
 849+ ),
 850+ ),
 851+ /* Note, in iptc schemas, the legacy properties are denoted
 852+ * as deprecated, since other properties should used instead,
 853+ * and properties marked as deprecated in the standard are
 854+ * are marked as general here as they don't have replacements
 855+ */
 856+ 'http://ns.adobe.com/photoshop/1.0/' => array(
 857+ 'City' => array(
 858+ 'map_group' => 'deprecated',
 859+ 'mode' => XMPReader::MODE_SIMPLE,
 860+ 'map_name' => 'CityDest',
 861+ ),
 862+ 'Country' => array(
 863+ 'map_group' => 'deprecated',
 864+ 'mode' => XMPReader::MODE_SIMPLE,
 865+ 'map_name' => 'CountryDest',
 866+ ),
 867+ 'State' => array(
 868+ 'map_group' => 'deprecated',
 869+ 'mode' => XMPReader::MODE_SIMPLE,
 870+ 'map_name' => 'ProvinceOrStateDest',
 871+ ),
 872+ 'DateCreated' => array(
 873+ 'map_group' => 'deprecated',
 874+ // marking as deprecated as the xmp prop preferred
 875+ 'mode' => XMPReader::MODE_SIMPLE,
 876+ 'map_name' => 'DateTimeOriginal',
 877+ 'validate' => 'validateDate',
 878+ // note this prop is an XMP, not IPTC date
 879+ ),
 880+ 'CaptionWriter' => array(
 881+ 'map_group' => 'general',
 882+ 'mode' => XMPReader::MODE_SIMPLE,
 883+ 'map_name' => 'Writer',
 884+ ),
 885+ 'Instructions' => array(
 886+ 'map_group' => 'general',
 887+ 'mode' => XMPReader::MODE_SIMPLE,
 888+ 'map_name' => 'SpecialInstructions',
 889+ ),
 890+ 'TransmissionReference' => array(
 891+ 'map_group' => 'general',
 892+ 'mode' => XMPReader::MODE_SIMPLE,
 893+ 'map_name' => 'OriginalTransmissionRef',
 894+ ),
 895+ 'AuthorsPosition' => array(
 896+ /* This corresponds with 2:85
 897+ * By-line Title, which needs to be
 898+ * handled weirdly to correspond
 899+ * with iptc/exif. */
 900+ 'map_group' => 'special',
 901+ 'mode' => XMPReader::MODE_SIMPLE
 902+ ),
 903+ 'Credit' => array(
 904+ 'map_group' => 'general',
 905+ 'mode' => XMPReader::MODE_SIMPLE,
 906+ ),
 907+ 'Source' => array(
 908+ 'map_group' => 'general',
 909+ 'mode' => XMPReader::MODE_SIMPLE,
 910+ ),
 911+ 'Urgency' => array(
 912+ 'map_group' => 'general',
 913+ 'mode' => XMPReader::MODE_SIMPLE,
 914+ ),
 915+ 'Category' => array(
 916+ // Note, this prop is deprecated, but in general
 917+ // group since it doesn't have a replacement.
 918+ 'map_group' => 'general',
 919+ 'mode' => XMPReader::MODE_SIMPLE,
 920+ 'map_name' => 'iimCategory',
 921+ ),
 922+ 'SupplementalCategories' => array(
 923+ 'map_group' => 'general',
 924+ 'mode' => XMPReader::MODE_BAG,
 925+ 'map_name' => 'iimSupplementalCategory',
 926+ ),
 927+ 'Headline' => array(
 928+ 'map_group' => 'general',
 929+ 'mode' => XMPReader::MODE_SIMPLE
 930+ ),
 931+ ),
 932+ 'http://iptc.org/std/Iptc4xmpCore/1.0/xmlns/' => array(
 933+ 'CountryCode' => array(
 934+ 'map_group' => 'deprecated',
 935+ 'mode' => XMPReader::MODE_SIMPLE,
 936+ 'map_name' => 'CountryCodeDest',
 937+ ),
 938+ 'IntellectualGenre' => array(
 939+ 'map_group' => 'general',
 940+ 'mode' => XMPReader::MODE_SIMPLE,
 941+ ),
 942+ // Note, this is a six digit code.
 943+ // See: http://cv.iptc.org/newscodes/scene/
 944+ // Since these aren't really all that common,
 945+ // we just show the number.
 946+ 'Scene' => array(
 947+ 'map_group' => 'general',
 948+ 'mode' => XMPReader::MODE_BAG,
 949+ 'validate' => 'validateInteger',
 950+ 'map_name' => 'SceneCode',
 951+ ),
 952+ /* Note: SubjectCode should be an 8 ascii digits.
 953+ * it is not really an integer (has leading 0's,
 954+ * cannot have a +/- sign), but validateInteger
 955+ * will let it through.
 956+ */
 957+ 'SubjectCode' => array(
 958+ 'map_group' => 'general',
 959+ 'mode' => XMPReader::MODE_BAG,
 960+ 'map_name' => 'SubjectNewsCode',
 961+ 'validate' => 'validateInteger'
 962+ ),
 963+ 'Location' => array(
 964+ 'map_group' => 'deprecated',
 965+ 'mode' => XMPReader::MODE_SIMPLE,
 966+ 'map_name' => 'SublocationDest',
 967+ ),
 968+ 'CreatorContactInfo' => array(
 969+ /* Note this maps to 2:118 in iim
 970+ * (Contact) field. However those field
 971+ * types are slightly different - 2:118
 972+ * is free form text field, where this
 973+ * is more structured.
 974+ */
 975+ 'map_group' => 'general',
 976+ 'mode' => XMPReader::MODE_STRUCT,
 977+ 'map_name' => 'Contact',
 978+ 'children' => array(
 979+ 'CiAdrExtadr' => true,
 980+ 'CiAdrCity' => true,
 981+ 'CiAdrCtry' => true,
 982+ 'CiEmailWork' => true,
 983+ 'CiTelWork' => true,
 984+ 'CiAdrPcode' => true,
 985+ 'CiAdrRegion' => true,
 986+ 'CiUrlWork' => true,
 987+ ),
 988+ ),
 989+ 'CiAdrExtadr' => array( /* address */
 990+ 'map_group' => 'general',
 991+ 'mode' => XMPReader::MODE_SIMPLE,
 992+ 'structPart'=> true,
 993+ ),
 994+ 'CiAdrCity' => array( /* city */
 995+ 'map_group' => 'general',
 996+ 'mode' => XMPReader::MODE_SIMPLE,
 997+ 'structPart'=> true,
 998+ ),
 999+ 'CiAdrCtry' => array( /* country */
 1000+ 'map_group' => 'general',
 1001+ 'mode' => XMPReader::MODE_SIMPLE,
 1002+ 'structPart'=> true,
 1003+ ),
 1004+ 'CiEmailWork' => array( /* email (possibly separated by ',') */
 1005+ 'map_group' => 'general',
 1006+ 'mode' => XMPReader::MODE_SIMPLE,
 1007+ 'structPart'=> true,
 1008+ ),
 1009+ 'CiTelWork' => array( /* telephone */
 1010+ 'map_group' => 'general',
 1011+ 'mode' => XMPReader::MODE_SIMPLE,
 1012+ 'structPart'=> true,
 1013+ ),
 1014+ 'CiAdrPcode' => array( /* postal code */
 1015+ 'map_group' => 'general',
 1016+ 'mode' => XMPReader::MODE_SIMPLE,
 1017+ 'structPart'=> true,
 1018+ ),
 1019+ 'CiAdrRegion' => array( /* province/state */
 1020+ 'map_group' => 'general',
 1021+ 'mode' => XMPReader::MODE_SIMPLE,
 1022+ 'structPart'=> true,
 1023+ ),
 1024+ 'CiUrlWork' => array( /* url. Multiple may be separated by comma. */
 1025+ 'map_group' => 'general',
 1026+ 'mode' => XMPReader::MODE_SIMPLE,
 1027+ 'structPart'=> true,
 1028+ ),
 1029+ /* End contact info struct properties */
 1030+ ),
 1031+ 'http://iptc.org/std/Iptc4xmpExt/2008-02-29/' => array(
 1032+ 'Event' => array(
 1033+ 'map_group' => 'general',
 1034+ 'mode' => XMPReader::MODE_SIMPLE,
 1035+ ),
 1036+ 'OrganisationInImageName' => array(
 1037+ 'map_group' => 'general',
 1038+ 'mode' => XMPReader::MODE_BAG,
 1039+ 'map_name' => 'OrganisationInImage'
 1040+ ),
 1041+ 'PersonInImage' => array(
 1042+ 'map_group' => 'general',
 1043+ 'mode' => XMPReader::MODE_BAG,
 1044+ ),
 1045+ 'MaxAvailHeight' => array(
 1046+ 'map_group' => 'general',
 1047+ 'mode' => XMPReader::MODE_SIMPLE,
 1048+ 'validate' => 'validateInteger',
 1049+ 'map_name' => 'OriginalImageHeight',
 1050+ ),
 1051+ 'MaxAvailWidth' => array(
 1052+ 'map_group' => 'general',
 1053+ 'mode' => XMPReader::MODE_SIMPLE,
 1054+ 'validate' => 'validateInteger',
 1055+ 'map_name' => 'OriginalImageWidth',
 1056+ ),
 1057+ // LocationShown and LocationCreated are handled
 1058+ // specially because they are hierarchical, but we
 1059+ // also want to merge with the old non-hierarchical.
 1060+ 'LocationShown' => array(
 1061+ 'map_group' => 'special',
 1062+ 'mode' => XMPReader::MODE_BAGSTRUCT,
 1063+ 'children' => array(
 1064+ 'WorldRegion' => true,
 1065+ 'CountryCode' => true, /* iso code */
 1066+ 'CountryName' => true,
 1067+ 'ProvinceState' => true,
 1068+ 'City' => true,
 1069+ 'Sublocation' => true,
 1070+ ),
 1071+ ),
 1072+ 'LocationCreated' => array(
 1073+ 'map_group' => 'special',
 1074+ 'mode' => XMPReader::MODE_BAGSTRUCT,
 1075+ 'children' => array(
 1076+ 'WorldRegion' => true,
 1077+ 'CountryCode' => true, /* iso code */
 1078+ 'CountryName' => true,
 1079+ 'ProvinceState' => true,
 1080+ 'City' => true,
 1081+ 'Sublocation' => true,
 1082+ ),
 1083+ ),
 1084+ 'WorldRegion' => array(
 1085+ 'map_group' => 'special',
 1086+ 'mode' => XMPReader::MODE_SIMPLE,
 1087+ 'structPart'=> true,
 1088+ ),
 1089+ 'CountryCode' => array(
 1090+ 'map_group' => 'special',
 1091+ 'mode' => XMPReader::MODE_SIMPLE,
 1092+ 'structPart'=> true,
 1093+ ),
 1094+ 'CountryName' => array(
 1095+ 'map_group' => 'special',
 1096+ 'mode' => XMPReader::MODE_SIMPLE,
 1097+ 'structPart'=> true,
 1098+ 'map_name' => 'Country',
 1099+ ),
 1100+ 'ProvinceState' => array(
 1101+ 'map_group' => 'special',
 1102+ 'mode' => XMPReader::MODE_SIMPLE,
 1103+ 'structPart'=> true,
 1104+ 'map_name' => 'ProvinceOrState',
 1105+ ),
 1106+ 'City' => array(
 1107+ 'map_group' => 'special',
 1108+ 'mode' => XMPReader::MODE_SIMPLE,
 1109+ 'structPart'=> true,
 1110+ ),
 1111+ 'Sublocation' => array(
 1112+ 'map_group' => 'special',
 1113+ 'mode' => XMPReader::MODE_SIMPLE,
 1114+ 'structPart'=> true,
 1115+ ),
 1116+
 1117+ /* Other props that might be interesting but
 1118+ * Not currently extracted:
 1119+ * ArtworkOrObject, (info about objects in picture)
 1120+ * DigitalSourceType
 1121+ * RegistryId
 1122+ */
 1123+ ),
 1124+
 1125+ /* Plus props we might want to consider:
 1126+ * (Note: some of these have unclear/incomplete definitions
 1127+ * from the iptc4xmp standard).
 1128+ * ImageSupplier (kind of like iptc source field)
 1129+ * ImageSupplierId (id code for image from supplier)
 1130+ * CopyrightOwner
 1131+ * ImageCreator
 1132+ * Licensor
 1133+ * Various model release fields
 1134+ * Property release fields.
 1135+ */
 1136+ );
 1137+}
Property changes on: trunk/phase3/includes/media/XMPInfo.php
___________________________________________________________________
Added: svn:eol-style
11138 + native
Index: trunk/phase3/includes/media/PNGMetadataExtractor.php
@@ -1,6 +1,6 @@
22 <?php
33 /**
4 - * PNG frame counter.
 4+ * PNG frame counter and metadata extractor.
55 * Slightly derived from GIFMetadataExtractor.php
66 * Deliberately not using MWExceptions to avoid external dependencies, encouraging
77 * redistribution.
@@ -17,28 +17,62 @@
1818 class PNGMetadataExtractor {
1919 static $png_sig;
2020 static $CRC_size;
 21+ static $text_chunks;
2122
 23+ const VERSION = 1;
 24+ const MAX_CHUNK_SIZE = 3145728; // 3 megabytes
 25+
2226 static function getMetadata( $filename ) {
2327 self::$png_sig = pack( "C8", 137, 80, 78, 71, 13, 10, 26, 10 );
2428 self::$CRC_size = 4;
25 -
 29+ /* based on list at http://owl.phy.queensu.ca/~phil/exiftool/TagNames/PNG.html#TextualData
 30+ * and http://www.w3.org/TR/PNG/#11keywords
 31+ */
 32+ self::$text_chunks = array(
 33+ 'xml:com.adobe.xmp' => 'xmp',
 34+ # Artist is unofficial. Author is the recommended
 35+ # keyword in the PNG spec. However some people output
 36+ # Artist so support both.
 37+ 'artist' => 'Artist',
 38+ 'model' => 'Model',
 39+ 'make' => 'Make',
 40+ 'author' => 'Artist',
 41+ 'comment' => 'PNGFileComment',
 42+ 'description' => 'ImageDescription',
 43+ 'title' => 'ObjectName',
 44+ 'copyright' => 'Copyright',
 45+ # Source as in original device used to make image
 46+ # not as in who gave you the image
 47+ 'source' => 'Model',
 48+ 'software' => 'Software',
 49+ 'disclaimer' => 'Disclaimer',
 50+ 'warning' => 'ContentWarning',
 51+ 'url' => 'Identifier', # Not sure if this is best mapping. Maybe WebStatement.
 52+ 'label' => 'Label',
 53+ 'creation time' => 'DateTimeDigitized',
 54+ /* Other potentially useful things - Document */
 55+ );
 56+
 57+ $showXMP = function_exists( 'xml_parser_create_ns' );
 58+
2659 $frameCount = 0;
2760 $loopCount = 1;
28 - $duration = 0.0;
 61+ $text = array();
2962 $bitDepth = 0;
3063 $colorType = 'unknown';
3164
32 - if (!$filename)
 65+ if ( !$filename ) {
3366 throw new Exception( __METHOD__ . ": No file name specified" );
34 - elseif ( !file_exists($filename) || is_dir($filename) )
 67+ } elseif ( !file_exists( $filename ) || is_dir( $filename ) ) {
3568 throw new Exception( __METHOD__ . ": File $filename does not exist" );
36 -
 69+ }
 70+
3771 $fh = fopen( $filename, 'r' );
38 -
39 - if (!$fh) {
 72+
 73+ if ( !$fh ) {
4074 throw new Exception( __METHOD__ . ": Unable to open file $filename" );
4175 }
42 -
 76+
4377 // Check for the PNG header
4478 $buf = fread( $fh, 8 );
4579 if ( $buf != self::$png_sig ) {
@@ -46,22 +80,22 @@
4781 }
4882
4983 // Read chunks
50 - while( !feof( $fh ) ) {
 84+ while ( !feof( $fh ) ) {
5185 $buf = fread( $fh, 4 );
52 - if( !$buf ) {
 86+ if ( !$buf ) {
5387 throw new Exception( __METHOD__ . ": Read error" );
5488 }
55 - $chunk_size = unpack( "N", $buf);
 89+ $chunk_size = unpack( "N", $buf );
5690 $chunk_size = $chunk_size[1];
5791
5892 $chunk_type = fread( $fh, 4 );
59 - if( !$chunk_type ) {
 93+ if ( !$chunk_type ) {
6094 throw new Exception( __METHOD__ . ": Read error" );
6195 }
6296
6397 if ( $chunk_type == "IHDR" ) {
64 - $buf = fread( $fh, $chunk_size );
65 - if( !$buf ) {
 98+ $buf = self::read( $fh, $chunk_size );
 99+ if ( !$buf ) {
66100 throw new Exception( __METHOD__ . ": Read error" );
67101 }
68102 $bitDepth = ord( substr( $buf, 8, 1 ) );
@@ -97,20 +131,203 @@
98132 $frameCount = $actl['frames'];
99133 $loopCount = $actl['plays'];
100134 } elseif ( $chunk_type == "fcTL" ) {
101 - $buf = fread( $fh, $chunk_size );
102 - if( !$buf ) {
 135+ $buf = self::read( $fh, $chunk_size );
 136+ if ( !$buf ) {
103137 throw new Exception( __METHOD__ . ": Read error" );
104138 }
105 - $buf = substr( $buf, 20 );
 139+ $buf = substr( $buf, 20 );
106140
107141 $fctldur = unpack( "ndelay_num/ndelay_den", $buf );
108 - if( $fctldur['delay_den'] == 0 ) $fctldur['delay_den'] = 100;
109 - if( $fctldur['delay_num'] ) {
 142+ if ( $fctldur['delay_den'] == 0 ) {
 143+ $fctldur['delay_den'] = 100;
 144+ }
 145+ if ( $fctldur['delay_num'] ) {
110146 $duration += $fctldur['delay_num'] / $fctldur['delay_den'];
111147 }
112 - } elseif ( ( $chunk_type == "IDAT" || $chunk_type == "IEND" ) && $frameCount == 0 ) {
113 - // Not a valid animated image. No point in continuing.
114 - break;
 148+ } elseif ( $chunk_type == "iTXt" ) {
 149+ // Extracts iTXt chunks, uncompressing if necessary.
 150+ $buf = self::read( $fh, $chunk_size );
 151+ $items = array();
 152+ if ( preg_match(
 153+ '/^([^\x00]{1,79})\x00(\x00|\x01)\x00([^\x00]*)(.)[^\x00]*\x00(.*)$/Ds',
 154+ $buf, $items )
 155+ ) {
 156+ /* $items[1] = text chunk name, $items[2] = compressed flag,
 157+ * $items[3] = lang code (or ""), $items[4]= compression type.
 158+ * $items[5] = content
 159+ */
 160+
 161+ // Theoretically should be case-sensitive, but in practise...
 162+ $items[1] = strtolower( $items[1] );
 163+ if ( !isset( self::$text_chunks[$items[1]] ) ) {
 164+ // Only extract textual chunks on our list.
 165+ fseek( $fh, self::$CRC_size, SEEK_CUR );
 166+ continue;
 167+ }
 168+
 169+ $items[3] = strtolower( $items[3] );
 170+ if ( $items[3] == '' ) {
 171+ // if no lang specified use x-default like in xmp.
 172+ $items[3] = 'x-default';
 173+ }
 174+
 175+ // if compressed
 176+ if ( $items[2] == "\x01" ) {
 177+ if ( function_exists( 'gzuncompress' ) && $items[4] === "\x00" ) {
 178+ wfSuppressWarnings();
 179+ $items[5] = gzuncompress( $items[5] );
 180+ wfRestoreWarnings();
 181+
 182+ if ( $items[5] === false ) {
 183+ // decompression failed
 184+ wfDebug( __METHOD__ . ' Error decompressing iTxt chunk - ' . $items[1] );
 185+ fseek( $fh, self::$CRC_size, SEEK_CUR );
 186+ continue;
 187+ }
 188+
 189+ } else {
 190+ wfDebug( __METHOD__ . ' Skipping compressed png iTXt chunk due to lack of zlib,'
 191+ . ' or potentially invalid compression method' );
 192+ fseek( $fh, self::$CRC_size, SEEK_CUR );
 193+ continue;
 194+ }
 195+ }
 196+ $finalKeyword = self::$text_chunks[ $items[1] ];
 197+ $text[ $finalKeyword ][ $items[3] ] = $items[5];
 198+ $text[ $finalKeyword ]['_type'] = 'lang';
 199+
 200+ } else {
 201+ // Error reading iTXt chunk
 202+ throw new Exception( __METHOD__ . ": Read error on iTXt chunk" );
 203+ }
 204+
 205+ } elseif ( $chunk_type == 'tEXt' ) {
 206+ $buf = self::read( $fh, $chunk_size );
 207+ $keyword = '';
 208+ $content = '';
 209+
 210+ list( $keyword, $content ) = explode( "\x00", $buf, 2 );
 211+ if ( $keyword === '' || $content === '' ) {
 212+ throw new Exception( __METHOD__ . ": Read error on tEXt chunk" );
 213+ }
 214+
 215+ // Theoretically should be case-sensitive, but in practise...
 216+ $keyword = strtolower( $keyword );
 217+ if ( !isset( self::$text_chunks[ $keyword ] ) ) {
 218+ // Don't recognize chunk, so skip.
 219+ fseek( $fh, self::$CRC_size, SEEK_CUR );
 220+ continue;
 221+ }
 222+ wfSuppressWarnings();
 223+ $content = iconv( 'ISO-8859-1', 'UTF-8', $content );
 224+ wfRestoreWarnings();
 225+
 226+ if ( $content === false ) {
 227+ throw new Exception( __METHOD__ . ": Read error (error with iconv)" );
 228+ }
 229+
 230+ $finalKeyword = self::$text_chunks[ $keyword ];
 231+ $text[ $finalKeyword ][ 'x-default' ] = $content;
 232+ $text[ $finalKeyword ]['_type'] = 'lang';
 233+
 234+ } elseif ( $chunk_type == 'zTXt' ) {
 235+ if ( function_exists( 'gzuncompress' ) ) {
 236+ $buf = self::read( $fh, $chunk_size );
 237+ $keyword = '';
 238+ $postKeyword = '';
 239+
 240+ list( $keyword, $postKeyword ) = explode( "\x00", $buf, 2 );
 241+ if ( $keyword === '' || $postKeyword === '' ) {
 242+ throw new Exception( __METHOD__ . ": Read error on zTXt chunk" );
 243+ }
 244+ // Theoretically should be case-sensitive, but in practise...
 245+ $keyword = strtolower( $keyword );
 246+
 247+ if ( !isset( self::$text_chunks[ $keyword ] ) ) {
 248+ // Don't recognize chunk, so skip.
 249+ fseek( $fh, self::$CRC_size, SEEK_CUR );
 250+ continue;
 251+ }
 252+ $compression = substr( $postKeyword, 0, 1 );
 253+ $content = substr( $postKeyword, 1 );
 254+ if ( $compression !== "\x00" ) {
 255+ wfDebug( __METHOD__ . " Unrecognized compression method in zTXt ($keyword). Skipping." );
 256+ fseek( $fh, self::$CRC_size, SEEK_CUR );
 257+ continue;
 258+ }
 259+
 260+ wfSuppressWarnings();
 261+ $content = gzuncompress( $content );
 262+ wfRestoreWarnings();
 263+
 264+ if ( $content === false ) {
 265+ // decompression failed
 266+ wfDebug( __METHOD__ . ' Error decompressing zTXt chunk - ' . $keyword );
 267+ fseek( $fh, self::$CRC_size, SEEK_CUR );
 268+ continue;
 269+ }
 270+
 271+ wfSuppressWarnings();
 272+ $content = iconv( 'ISO-8859-1', 'UTF-8', $content );
 273+ wfRestoreWarnings();
 274+
 275+ if ( $content === false ) {
 276+ throw new Exception( __METHOD__ . ": Read error (error with iconv)" );
 277+ }
 278+
 279+ $finalKeyword = self::$text_chunks[ $keyword ];
 280+ $text[ $finalKeyword ][ 'x-default' ] = $content;
 281+ $text[ $finalKeyword ]['_type'] = 'lang';
 282+
 283+ } else {
 284+ wfDebug( __METHOD__ . " Cannot decompress zTXt chunk due to lack of zlib. Skipping." );
 285+ fseek( $fh, $chunk_size, SEEK_CUR );
 286+ }
 287+ } elseif ( $chunk_type == 'tIME' ) {
 288+ // last mod timestamp.
 289+ if ( $chunk_size !== 7 ) {
 290+ throw new Exception( __METHOD__ . ": tIME wrong size" );
 291+ }
 292+ $buf = self::read( $fh, $chunk_size );
 293+ if ( !$buf ) {
 294+ throw new Exception( __METHOD__ . ": Read error" );
 295+ }
 296+
 297+ // Note: spec says this should be UTC.
 298+ $t = unpack( "ny/Cm/Cd/Ch/Cmin/Cs", $buf );
 299+ $strTime = sprintf( "%04d%02d%02d%02d%02d%02d",
 300+ $t['y'], $t['m'], $t['d'], $t['h'],
 301+ $t['min'], $t['s'] );
 302+
 303+ $exifTime = wfTimestamp( TS_EXIF, $strTime );
 304+
 305+ if ( $exifTime ) {
 306+ $text['DateTime'] = $exifTime;
 307+ }
 308+
 309+ } elseif ( $chunk_type == 'pHYs' ) {
 310+ // how big pixels are (dots per meter).
 311+ if ( $chunk_size !== 9 ) {
 312+ throw new Exception( __METHOD__ . ": pHYs wrong size" );
 313+ }
 314+
 315+ $buf = self::read( $fh, $chunk_size );
 316+ if ( !$buf ) {
 317+ throw new Exception( __METHOD__ . ": Read error" );
 318+ }
 319+
 320+ $dim = unpack( "Nwidth/Nheight/Cunit", $buf );
 321+ if ( $dim['unit'] == 1 ) {
 322+ // unit is meters
 323+ // (as opposed to 0 = undefined )
 324+ $text['XResolution'] = $dim['width']
 325+ . '/100';
 326+ $text['YResolution'] = $dim['height']
 327+ . '/100';
 328+ $text['ResolutionUnit'] = 3;
 329+ // 3 = dots per cm (from Exif).
 330+ }
 331+
115332 } elseif ( $chunk_type == "IEND" ) {
116333 break;
117334 } else {
@@ -120,17 +337,60 @@
121338 }
122339 fclose( $fh );
123340
124 - if( $loopCount > 1 ) {
 341+ if ( $loopCount > 1 ) {
125342 $duration *= $loopCount;
126343 }
127344
 345+ if ( isset( $text['DateTimeDigitized'] ) ) {
 346+ // Convert date format from rfc2822 to exif.
 347+ foreach ( $text['DateTimeDigitized'] as $name => &$value ) {
 348+ if ( $name === '_type' ) {
 349+ continue;
 350+ }
 351+
 352+ // fixme: currently timezones are ignored.
 353+ // possibly should be wfTimestamp's
 354+ // responsibility. (at least for numeric TZ)
 355+ $formatted = wfTimestamp( TS_EXIF, $value );
 356+ if ( $formatted ) {
 357+ // Only change if we could convert the
 358+ // date.
 359+ // The png standard says it should be
 360+ // in rfc2822 format, but not required.
 361+ // In general for the exif stuff we
 362+ // prettify the date if we can, but we
 363+ // display as-is if we cannot or if
 364+ // it is invalid.
 365+ // So do the same here.
 366+
 367+ $value = $formatted;
 368+ }
 369+ }
 370+ }
128371 return array(
129372 'frameCount' => $frameCount,
130373 'loopCount' => $loopCount,
131374 'duration' => $duration,
 375+ 'text' => $text,
 376+ 'duration' => $duration,
132377 'bitDepth' => $bitDepth,
133378 'colorType' => $colorType,
134379 );
135 -
 380+
136381 }
 382+ /**
 383+ * Read a chunk, checking to make sure its not too big.
 384+ *
 385+ * @param $fh resource The file handle
 386+ * @param $size Integer size in bytes.
 387+ * @throws Exception if too big.
 388+ * @return String The chunk.
 389+ */
 390+ static private function read( $fh, $size ) {
 391+ if ( $size > self::MAX_CHUNK_SIZE ) {
 392+ throw new Exception( __METHOD__ . ': Chunk size of ' . $size .
 393+ ' too big. Max size is: ' . self::MAX_CHUNK_SIZE );
 394+ }
 395+ return fread( $fh, $size );
 396+ }
137397 }
Index: trunk/phase3/includes/media/JpegMetadataExtractor.php
@@ -0,0 +1,212 @@
 2+<?php
 3+/**
 4+* Class for reading jpegs and extracting metadata.
 5+* see also BitmapMetadataHandler.
 6+*
 7+* Based somewhat on GIFMetadataExtrator.
 8+*/
 9+class JpegMetadataExtractor {
 10+ const MAX_JPEG_SEGMENTS = 200;
 11+ // the max segment is a sanity check.
 12+ // A jpeg file should never even remotely have
 13+ // that many segments. Your average file has about 10.
 14+
 15+ /** Function to extract metadata segments of interest from jpeg files
 16+ * based on GIFMetadataExtractor.
 17+ *
 18+ * we can almost use getimagesize to do this
 19+ * but gis doesn't support having multiple app1 segments
 20+ * and those can't extract xmp on files containing both exif and xmp data
 21+ *
 22+ * @param String $filename name of jpeg file
 23+ * @return Array of interesting segments.
 24+ * @throws MWException if given invalid file.
 25+ */
 26+ static function segmentSplitter ( $filename ) {
 27+ $showXMP = function_exists( 'xml_parser_create_ns' );
 28+
 29+ $segmentCount = 0;
 30+
 31+ $segments = Array( 'XMP_ext' => array(), 'COM' => array() );
 32+
 33+ if ( !$filename ) throw new MWException( "No filename specified for " . __METHOD__ );
 34+ if ( !file_exists( $filename ) || is_dir( $filename ) ) throw new MWException( "Invalid file $filename passed to " . __METHOD__ );
 35+
 36+ $fh = fopen( $filename, "rb" );
 37+
 38+ if ( !$fh ) throw new MWException( "Could not open file $filename" );
 39+
 40+ $buffer = fread( $fh, 2 );
 41+ if ( $buffer !== "\xFF\xD8" ) throw new MWException( "Not a jpeg, no SOI" );
 42+ while ( !feof( $fh ) ) {
 43+ $buffer = fread( $fh, 1 );
 44+ $segmentCount++;
 45+ if ( $segmentCount > self::MAX_JPEG_SEGMENTS ) {
 46+ // this is just a sanity check
 47+ throw new MWException( 'Too many jpeg segments. Aborting' );
 48+ }
 49+ if ( $buffer !== "\xFF" ) {
 50+ throw new MWException( "Error reading jpeg file marker" );
 51+ }
 52+
 53+ $buffer = fread( $fh, 1 );
 54+ if ( $buffer === "\xFE" ) {
 55+
 56+ // COM section -- file comment
 57+ // First see if valid utf-8,
 58+ // if not try to convert it to windows-1252.
 59+ $com = $oldCom = trim( self::jpegExtractMarker( $fh ) );
 60+
 61+ UtfNormal::quickIsNFCVerify( $com );
 62+ // turns $com to valid utf-8.
 63+ // thus if no change, its utf-8, otherwise its something else.
 64+ if ( $com !== $oldCom ) {
 65+ $oldCom = iconv( 'windows-1252', 'UTF-8//IGNORE', $oldCom );
 66+ }
 67+ $segments["COM"][] = $oldCom;
 68+
 69+ } elseif ( $buffer === "\xE1" && $showXMP ) {
 70+ // APP1 section (Exif, XMP, and XMP extended)
 71+ // only extract if XMP is enabled.
 72+ $temp = self::jpegExtractMarker( $fh );
 73+
 74+ // check what type of app segment this is.
 75+ if ( substr( $temp, 0, 29 ) === "http://ns.adobe.com/xap/1.0/\x00" ) {
 76+ $segments["XMP"] = substr( $temp, 29 );
 77+ } elseif ( substr( $temp, 0, 35 ) === "http://ns.adobe.com/xmp/extension/\x00" ) {
 78+ $segments["XMP_ext"][] = substr( $temp, 35 );
 79+ } elseif ( substr( $temp, 0, 29 ) === "XMP\x00://ns.adobe.com/xap/1.0/\x00" ) {
 80+ // Some images (especially flickr images) seem to have this.
 81+ // I really have no idea what the deal is with them, but
 82+ // whatever...
 83+ $segments["XMP"] = substr( $temp, 29 );
 84+ wfDebug( __METHOD__ . ' Found XMP section with wrong app identifier '
 85+ . "Using anyways.\n" );
 86+ }
 87+ } elseif ( $buffer === "\xED" ) {
 88+ // APP13 - PSIR. IPTC and some photoshop stuff
 89+ $temp = self::jpegExtractMarker( $fh );
 90+ if ( substr( $temp, 0, 14 ) === "Photoshop 3.0\x00" ) {
 91+ $segments["PSIR"] = $temp;
 92+ }
 93+ } elseif ( $buffer === "\xD9" || $buffer === "\xDA" ) {
 94+ // EOI - end of image or SOS - start of scan. either way we're past any interesting segments
 95+ return $segments;
 96+ } else {
 97+ // segment we don't care about, so skip
 98+ $size = unpack( "nint", fread( $fh, 2 ) );
 99+ if ( $size['int'] <= 2 ) throw new MWException( "invalid marker size in jpeg" );
 100+ fseek( $fh, $size['int'] - 2, SEEK_CUR );
 101+ }
 102+
 103+ }
 104+ // shouldn't get here.
 105+ throw new MWException( "Reached end of jpeg file unexpectedly" );
 106+
 107+ }
 108+ /**
 109+ * Helper function for jpegSegmentSplitter
 110+ * @param &$fh FileHandle for jpeg file
 111+ * @return data content of segment.
 112+ */
 113+ private static function jpegExtractMarker( &$fh ) {
 114+ $size = unpack( "nint", fread( $fh, 2 ) );
 115+ if ( $size['int'] <= 2 ) throw new MWException( "invalid marker size in jpeg" );
 116+ return fread( $fh, $size['int'] - 2 );
 117+ }
 118+
 119+ /**
 120+ * This reads the photoshop image resource.
 121+ * Currently it only compares the iptc/iim hash
 122+ * with the stored hash, which is used to determine the precedence
 123+ * of the iptc data. In future it may extract some other info, like
 124+ * url of copyright license.
 125+ *
 126+ * This should generally be called by BitmapMetadataHandler::doApp13()
 127+ *
 128+ * @param String $app13 photoshop psir app13 block from jpg.
 129+ * @return String if the iptc hash is good or not.
 130+ */
 131+ public static function doPSIR ( $app13 ) {
 132+ if ( !$app13 ) return;
 133+ // First compare hash with real thing
 134+ // 0x404 contains IPTC, 0x425 has hash
 135+ // This is used to determine if the iptc is newer than
 136+ // the xmp data, as xmp programs update the hash,
 137+ // where non-xmp programs don't.
 138+
 139+ $offset = 14; // skip past PHOTOSHOP 3.0 identifier. should already be checked.
 140+ $appLen = strlen( $app13 );
 141+ $realHash = "";
 142+ $recordedHash = "";
 143+
 144+ // the +12 is the length of an empty item.
 145+ while ( $offset + 12 <= $appLen ) {
 146+ $valid = true;
 147+ $id = false;
 148+ $lenName = false;
 149+ $lenData = false;
 150+
 151+ if ( substr( $app13, $offset, 4 ) !== '8BIM' ) {
 152+ // its supposed to be 8BIM
 153+ // but apparently sometimes isn't esp. in
 154+ // really old jpg's
 155+ $valid = false;
 156+ }
 157+ $offset += 4;
 158+ $id = substr( $app13, $offset, 2 );
 159+ // id is a 2 byte id number which identifies
 160+ // the piece of info this record contains.
 161+
 162+ $offset += 2;
 163+
 164+ // some record types can contain a name, which
 165+ // is a pascal string 0-padded to be an even
 166+ // number of bytes. Most times (and any time
 167+ // we care) this is empty, making it two null bytes.
 168+
 169+ $lenName = ord( substr( $app13, $offset, 1 ) ) + 1;
 170+ // we never use the name so skip it. +1 for length byte
 171+ if ( $lenName % 2 == 1 ) $lenName++; // pad to even.
 172+ $offset += $lenName;
 173+
 174+ // now length of data (unsigned long big endian)
 175+ $lenData = unpack( 'Nlen', substr( $app13, $offset, 4 ) );
 176+ $offset += 4; // 4bytes length field;
 177+
 178+ // this should not happen, but check.
 179+ if ( $lenData['len'] + $offset > $appLen ) {
 180+ wfDebug( __METHOD__ . " PSIR data too long.\n" );
 181+ return 'iptc-no-hash';
 182+ }
 183+
 184+ if ( $valid ) {
 185+ switch ( $id ) {
 186+ case "\x04\x04":
 187+ // IPTC block
 188+ $realHash = md5( substr( $app13, $offset, $lenData['len'] ), true );
 189+ break;
 190+ case "\x04\x25":
 191+ $recordedHash = substr( $app13, $offset, $lenData['len'] );
 192+ break;
 193+ }
 194+ }
 195+
 196+ // if odd, add 1 to length to account for
 197+ // null pad byte.
 198+ if ( $lenData['len'] % 2 == 1 ) $lenData['len']++;
 199+ $offset += $lenData['len'];
 200+
 201+ }
 202+
 203+ if ( !$realHash || !$recordedHash ) {
 204+ return 'iptc-no-hash';
 205+ } elseif ( $realHash === $recordedHash ) {
 206+ return 'iptc-good-hash';
 207+ } else { /*$realHash !== $recordedHash */
 208+ return 'iptc-bad-hash';
 209+ }
 210+
 211+ }
 212+
 213+}
Property changes on: trunk/phase3/includes/media/JpegMetadataExtractor.php
___________________________________________________________________
Added: svn:eol-style
1214 + native
Index: trunk/phase3/includes/media/PNG.php
@@ -13,28 +13,41 @@
1414 */
1515 class PNGHandler extends BitmapHandler {
1616
 17+ const BROKEN_FILE = '0';
 18+
 19+
1720 /**
1821 * @param File $image
1922 * @param string $filename
2023 * @return string
2124 */
2225 function getMetadata( $image, $filename ) {
23 - if ( !isset($image->parsedPNGMetadata) ) {
24 - try {
25 - $image->parsedPNGMetadata = PNGMetadataExtractor::getMetadata( $filename );
26 - } catch( Exception $e ) {
27 - // Broken file?
28 - wfDebug( __METHOD__ . ': ' . $e->getMessage() . "\n" );
29 - return '0';
30 - }
 26+ try {
 27+ $metadata = BitmapMetadataHandler::PNG( $filename );
 28+ } catch( Exception $e ) {
 29+ // Broken file?
 30+ wfDebug( __METHOD__ . ': ' . $e->getMessage() . "\n" );
 31+ return self::BROKEN_FILE;
3132 }
3233
33 - return serialize($image->parsedPNGMetadata);
34 -
 34+ return serialize($metadata);
3535 }
3636
3737 function formatMetadata( $image ) {
38 - return false;
 38+ $meta = $image->getMetadata();
 39+
 40+ if ( !$meta ) {
 41+ return false;
 42+ }
 43+ $meta = unserialize( $meta );
 44+ if ( !isset( $meta['metadata'] ) || count( $meta['metadata'] ) <= 1 ) {
 45+ return false;
 46+ }
 47+
 48+ if ( isset( $meta['metadata']['_MW_PNG_VERSION'] ) ) {
 49+ unset( $meta['metadata']['_MW_PNG_VERSION'] );
 50+ }
 51+ return $this->formatMetadataHelper( $meta['metadata'] );
3952 }
4053
4154 /**
@@ -55,10 +68,27 @@
5669 }
5770
5871 function isMetadataValid( $image, $metadata ) {
 72+
 73+ if ( $metadata === self::BROKEN_FILE ) {
 74+ // Do not repetitivly regenerate metadata on broken file.
 75+ return self::METADATA_GOOD;
 76+ }
 77+
5978 wfSuppressWarnings();
6079 $data = unserialize( $metadata );
6180 wfRestoreWarnings();
62 - return (boolean) $data;
 81+
 82+ if ( !$data || !is_array( $data ) ) {
 83+ wfDebug(__METHOD__ . ' invalid png metadata' );
 84+ return self::METADATA_BAD;
 85+ }
 86+
 87+ if ( !isset( $data['metadata']['_MW_PNG_VERSION'] )
 88+ || $data['metadata']['_MW_PNG_VERSION'] != PNGMetadataExtractor::VERSION ) {
 89+ wfDebug(__METHOD__ . ' old but compatible png metadata' );
 90+ return self::METADATA_COMPATIBLE;
 91+ }
 92+ return self::METADATA_GOOD;
6393 }
6494
6595 /**
Index: trunk/phase3/includes/media/Jpeg.php
@@ -0,0 +1,140 @@
 2+<?php
 3+/**
 4+ * @file
 5+ * @ingroup Media
 6+ */
 7+
 8+/** JPEG specific handler.
 9+ * Inherits most stuff from BitmapHandler, just here to do the metadata handler differently
 10+ * @ingroup Media
 11+ */
 12+class JpegHandler extends BitmapHandler {
 13+
 14+ const BROKEN_FILE = '-1'; // error extracting metadata
 15+ const OLD_BROKEN_FILE = '0'; // outdated error extracting metadata.
 16+
 17+ function getMetadata ( $image, $filename ) {
 18+ try {
 19+ $meta = BitmapMetadataHandler::Jpeg( $filename );
 20+ if ( !is_array( $meta ) ) {
 21+ // This should never happen, but doesn't hurt to be paranoid.
 22+ throw new MWException('Metadata array is not an array');
 23+ }
 24+ $meta['MEDIAWIKI_EXIF_VERSION'] = Exif::version();
 25+ return serialize( $meta );
 26+ }
 27+ catch ( MWException $e ) {
 28+ // BitmapMetadataHandler throws an exception in certain exceptional cases like if file does not exist.
 29+ wfDebug( __METHOD__ . ': ' . $e->getMessage() . "\n" );
 30+
 31+ /* This used to use 0 (self::OLD_BROKEN_FILE) for the cases
 32+ * * No metadata in the file
 33+ * * Something is broken in the file.
 34+ * However, if the metadata support gets expanded then you can't tell if the 0 is from
 35+ * a broken file, or just no props found. A broken file is likely to stay broken, but
 36+ * a file which had no props could have props once the metadata support is improved.
 37+ * Thus switch to using -1 to denote only a broken file, and use an array with only
 38+ * MEDIAWIKI_EXIF_VERSION to denote no props.
 39+ */
 40+ return self::BROKEN_FILE;
 41+ }
 42+ }
 43+
 44+ function convertMetadataVersion( $metadata, $version = 1 ) {
 45+ // basically flattens arrays.
 46+ $version = explode(';', $version, 2);
 47+ $version = intval($version[0]);
 48+ if ( $version < 1 || $version >= 2 ) {
 49+ return $metadata;
 50+ }
 51+
 52+ $avoidHtml = true;
 53+
 54+ if ( !is_array( $metadata ) ) {
 55+ $metadata = unserialize( $metadata );
 56+ }
 57+ if ( !isset( $metadata['MEDIAWIKI_EXIF_VERSION'] ) || $metadata['MEDIAWIKI_EXIF_VERSION'] != 2 ) {
 58+ return $metadata;
 59+ }
 60+
 61+ // Treat Software as a special case because in can contain
 62+ // an array of (SoftwareName, Version).
 63+ if (isset( $metadata['Software'] )
 64+ && is_array( $metadata['Software'] )
 65+ && is_array( $metadata['Software'][0])
 66+ && isset( $metadata['Software'][0][0] )
 67+ && isset( $metadata['Software'][0][1])
 68+ ) {
 69+ $metadata['Software'] = $metadata['Software'][0][0] . ' (Version '
 70+ . $metadata['Software'][0][1] . ')';
 71+ }
 72+
 73+ // ContactInfo also has to be dealt with specially
 74+ if ( isset( $metadata['Contact'] ) ) {
 75+ $metadata['Contact'] =
 76+ FormatMetadata::collapseContactInfo(
 77+ $metadata['Contact'] );
 78+ }
 79+
 80+ foreach ( $metadata as &$val ) {
 81+ if ( is_array( $val ) ) {
 82+ $val = FormatMetadata::flattenArray( $val, 'ul', $avoidHtml );
 83+ }
 84+ }
 85+ $metadata['MEDIAWIKI_EXIF_VERSION'] = 1;
 86+ return $metadata;
 87+ }
 88+
 89+ function isMetadataValid( $image, $metadata ) {
 90+ global $wgShowEXIF;
 91+ if ( !$wgShowEXIF ) {
 92+ # Metadata disabled and so an empty field is expected
 93+ return self::METADATA_GOOD;
 94+ }
 95+ if ( $metadata === self::OLD_BROKEN_FILE ) {
 96+ # Old special value indicating that there is no EXIF data in the file.
 97+ # or that there was an error well extracting the metadata.
 98+ wfDebug( __METHOD__ . ": back-compat version\n");
 99+ return self::METADATA_COMPATIBLE;
 100+ }
 101+ if ( $metadata === self::BROKEN_FILE ) {
 102+ return self::METADATA_GOOD;
 103+ }
 104+ wfSuppressWarnings();
 105+ $exif = unserialize( $metadata );
 106+ wfRestoreWarnings();
 107+ if ( !isset( $exif['MEDIAWIKI_EXIF_VERSION'] ) ||
 108+ $exif['MEDIAWIKI_EXIF_VERSION'] != Exif::version() )
 109+ {
 110+ if ( isset( $exif['MEDIAWIKI_EXIF_VERSION'] ) &&
 111+ $exif['MEDIAWIKI_EXIF_VERSION'] == 1 )
 112+ {
 113+ //back-compatible but old
 114+ wfDebug( __METHOD__.": back-compat version\n" );
 115+ return self::METADATA_COMPATIBLE;
 116+ }
 117+ # Wrong (non-compatible) version
 118+ wfDebug( __METHOD__.": wrong version\n" );
 119+ return self::METADATA_BAD;
 120+ }
 121+ return self::METADATA_GOOD;
 122+ }
 123+
 124+ function formatMetadata( $image ) {
 125+ $metadata = $image->getMetadata();
 126+ if ( !$metadata || $metadata == self::BROKEN_FILE ) {
 127+ return false;
 128+ }
 129+ $exif = unserialize( $metadata );
 130+ if ( !$exif ) {
 131+ return false;
 132+ }
 133+ unset( $exif['MEDIAWIKI_EXIF_VERSION'] );
 134+ if ( count( $exif ) == 0 ) {
 135+ return false;
 136+ }
 137+ return $this->formatMetadataHelper( $exif );
 138+ }
 139+
 140+
 141+}
Property changes on: trunk/phase3/includes/media/Jpeg.php
___________________________________________________________________
Added: svn:eol-style
1142 + native
Index: trunk/phase3/includes/AutoLoader.php
@@ -75,7 +75,7 @@
7676 'EmailNotification' => 'includes/UserMailer.php',
7777 'EnhancedChangesList' => 'includes/ChangesList.php',
7878 'ErrorPageError' => 'includes/Exception.php',
79 - 'Exif' => 'includes/Exif.php',
 79+ 'Exif' => 'includes/media/Exif.php',
8080 'ExplodeIterator' => 'includes/StringUtils.php',
8181 'ExternalEdit' => 'includes/ExternalEdit.php',
8282 'ExternalStoreDB' => 'includes/ExternalStoreDB.php',
@@ -93,7 +93,7 @@
9494 'FileDependency' => 'includes/CacheDependency.php',
9595 'FileRevertForm' => 'includes/FileRevertForm.php',
9696 'ForkController' => 'includes/ForkController.php',
97 - 'FormatExif' => 'includes/Exif.php',
 97+ 'FormatExif' => 'includes/media/FormatMetadata.php',
9898 'FormOptions' => 'includes/FormOptions.php',
9999 'GenderCache' => 'includes/GenderCache.php',
100100 'GlobalDependency' => 'includes/CacheDependency.php',
@@ -516,6 +516,7 @@
517517 'BitmapHandler_ClientOnly' => 'includes/media/Bitmap_ClientOnly.php',
518518 'BmpHandler' => 'includes/media/BMP.php',
519519 'DjVuHandler' => 'includes/media/DjVu.php',
 520+ 'FormatMetadata' => 'includes/media/FormatMetadata.php',
520521 'GIFHandler' => 'includes/media/GIF.php',
521522 'GIFMetadataExtractor' => 'includes/media/GIFMetadataExtractor.php',
522523 'ImageHandler' => 'includes/media/Generic.php',
@@ -526,9 +527,16 @@
527528 'PNGMetadataExtractor' => 'includes/media/PNGMetadataExtractor.php',
528529 'SvgHandler' => 'includes/media/SVG.php',
529530 'SVGMetadataExtractor' => 'includes/media/SVGMetadataExtractor.php',
 531+ 'JpegHandler' => 'includes/media/Jpeg.php',
 532+ 'JpegMetadataExtractor' => 'includes/media/JpegMetadataExtractor.php',
 533+ 'BitmapMetadataHandler' => 'includes/media/BitmapMetadataHandler.php',
 534+ 'IPTC' => 'includes/media/IPTC.php',
530535 'ThumbnailImage' => 'includes/media/MediaTransformOutput.php',
531536 'TiffHandler' => 'includes/media/Tiff.php',
532537 'TransformParameterError' => 'includes/media/MediaTransformOutput.php',
 538+ 'XMPReader' => 'includes/media/XMP.php',
 539+ 'XMPInfo' => 'includes/media/XMPInfo.php',
 540+ 'XMPValidate' => 'includes/media/XMPValidate.php',
533541
534542 # includes/normal
535543 'UtfNormal' => 'includes/normal/UtfNormal.php',
Index: trunk/phase3/includes/DefaultSettings.php
@@ -395,6 +395,13 @@
396396 $wgShowEXIF = function_exists( 'exif_read_data' );
397397
398398 /**
 399+ * If to automatically update the img_metadata field
 400+ * if the metadata field is outdated but compatible with the current version.
 401+ * Defaults to false.
 402+ */
 403+$wgUpdateCompatibleMetadata = false;
 404+
 405+/**
399406 * If you operate multiple wikis, you can define a shared upload path here.
400407 * Uploads to this wiki will NOT be put there - they will be put into
401408 * $wgUploadDirectory.
@@ -592,7 +599,7 @@
593600 * Each entry in the array maps a MIME type to a class name
594601 */
595602 $wgMediaHandlers = array(
596 - 'image/jpeg' => 'BitmapHandler',
 603+ 'image/jpeg' => 'JpegHandler',
597604 'image/png' => 'PNGHandler',
598605 'image/gif' => 'GIFHandler',
599606 'image/tiff' => 'TiffHandler',
Index: trunk/phase3/languages/messages/MessagesFrp.php
@@ -3242,7 +3242,7 @@
32433243 'exif-planarconfiguration-1' => 'Balyês ategnentes',
32443244 'exif-planarconfiguration-2' => 'Balyês sèparâs',
32453245
3246 -'exif-colorspace-ffff.h' => 'Pas calibrâ',
 3246+'exif-colorspace-65535' => 'Pas calibrâ',
32473247
32483248 'exif-componentsconfiguration-0' => 'Ègziste pas',
32493249 'exif-componentsconfiguration-5' => 'V',
Index: trunk/phase3/languages/messages/MessagesQqq.php
@@ -3215,6 +3215,10 @@
32163216 {{Identical|Metadata}}',
32173217 'metadata-expand' => 'On an image description page, there is mostly a table containing data (metadata) about the image. The most important data are shown, but if you click on this link, you can see more data and information. For the link to hide back the less important data, see "[[MediaWiki:Metadata-collapse/{{SUBPAGENAME}}|{{int:metadata-collapse}}]]".',
32183218 'metadata-collapse' => 'On an image description page, there is mostly a table containing data (metadata) about the image. The most important data are shown, but if you click on the link "[[MediaWiki:Metadata-expand/{{SUBPAGENAME}}|{{int:metadata-expand}}]]", you can see more data and information. This message is for the link to hide back the less important data.',
 3219+'metadata-langitem' => 'This is used for constructing the list of translations when a metadata property is translated into multiple languages.
 3220+
 3221+$1 is the value of the property (in one language), $2 is the language name that this translation is for (or language code if language name cannot be determined), $3 is the language code.',
 3222+'metadata-langitem-default' => 'Similar to "metadata-langitem" but for the case where a multilingual property has a default specified that does not specify what language the default is in. $1 is the value of the property. ',
32193223 'metadata-fields' => "'''Warning:''' Do not translate list items, only translate the text! So leave \"<tt>* make</tt>\" and the other items exactly as they are.
32203224
32213225 The sentences are for explanation only and are not shown to the user.",
@@ -3234,15 +3238,20 @@
32353239 'exif-planarconfiguration' => 'Exif is a format for storing metadata in image files. See this [http://en.wikipedia.org/wiki/Exchangeable_image_file_format Wikipedia article] and the example at the bottom of [http://commons.wikimedia.org/wiki/File:Phalacrocorax-auritus-020.jpg this page on Commons]. The tags are explained [http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html briefly] and [http://www.kodak.com/global/plugins/acrobat/en/service/digCam/exifStandard2.pdf in further detail].',
32363240 'exif-ycbcrsubsampling' => 'Exif is a format for storing metadata in image files. See this [http://en.wikipedia.org/wiki/Exchangeable_image_file_format Wikipedia article] and the example at the bottom of [http://commons.wikimedia.org/wiki/File:Phalacrocorax-auritus-020.jpg this page on Commons]. The tags are explained [http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html briefly] and [http://www.kodak.com/global/plugins/acrobat/en/service/digCam/exifStandard2.pdf in further detail].',
32373241 'exif-ycbcrpositioning' => 'Exif is a format for storing metadata in image files. See this [http://en.wikipedia.org/wiki/Exchangeable_image_file_format Wikipedia article] and the example at the bottom of [http://commons.wikimedia.org/wiki/File:Phalacrocorax-auritus-020.jpg this page on Commons]. The tags are explained [http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html briefly] and [http://www.kodak.com/global/plugins/acrobat/en/service/digCam/exifStandard2.pdf in further detail].',
3238 -'exif-xresolution' => 'Exif is a format for storing metadata in image files. See this [http://en.wikipedia.org/wiki/Exchangeable_image_file_format Wikipedia article] and the example at the bottom of [http://commons.wikimedia.org/wiki/File:Phalacrocorax-auritus-020.jpg this page on Commons]. The tags are explained [http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html briefly] and [http://www.kodak.com/global/plugins/acrobat/en/service/digCam/exifStandard2.pdf in further detail].',
3239 -'exif-yresolution' => 'Exif is a format for storing metadata in image files. See this [http://en.wikipedia.org/wiki/Exchangeable_image_file_format Wikipedia article] and the example at the bottom of [http://commons.wikimedia.org/wiki/File:Phalacrocorax-auritus-020.jpg this page on Commons]. The tags are explained [http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html briefly] and [http://www.kodak.com/global/plugins/acrobat/en/service/digCam/exifStandard2.pdf in further detail].',
 3242+'exif-xresolution' => 'Exif is a format for storing metadata in image files. See this [http://en.wikipedia.org/wiki/Exchangeable_image_file_format Wikipedia article] and the example at the bottom of [http://commons.wikimedia.org/wiki/File:Phalacrocorax-auritus-020.jpg this page on Commons]. The tags are explained [http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html briefly] and [http://www.kodak.com/global/plugins/acrobat/en/service/digCam/exifStandard2.pdf in further detail].
 3243+
 3244+This is the horizontal resolution in either dots/inch or dots/cm.',
 3245+'exif-yresolution' => 'Exif is a format for storing metadata in image files. See this [http://en.wikipedia.org/wiki/Exchangeable_image_file_format Wikipedia article] and the example at the bottom of [http://commons.wikimedia.org/wiki/File:Phalacrocorax-auritus-020.jpg this page on Commons]. The tags are explained [http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html briefly] and [http://www.kodak.com/global/plugins/acrobat/en/service/digCam/exifStandard2.pdf in further detail].
 3246+
 3247+This is the vertical resolution in either dots/inch or dots/cm.',
 3248+'exif-xyresolution-i' => '{{Optional}} Used to format {{msg-mw|exif-xresolution}} and {{msg-mw|exif-yresolution}} if the unit is dots per inch. $1 is the number of dots/in.',
 3249+'exif-xyresolution-c' => '{{Optional}} Used to format {{msg-mw|exif-xresolution}} and {{msg-mw|exif-yresolution}} if the unit is dots per centimetre. $1 is the number of dots/cm.',
32403250 'exif-resolutionunit' => 'Exif is a format for storing metadata in image files. See this [http://en.wikipedia.org/wiki/Exchangeable_image_file_format Wikipedia article] and the example at the bottom of [http://commons.wikimedia.org/wiki/File:Phalacrocorax-auritus-020.jpg this page on Commons]. The tags are explained [http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html briefly] and [http://www.kodak.com/global/plugins/acrobat/en/service/digCam/exifStandard2.pdf in further detail].',
32413251 'exif-stripoffsets' => 'Exif is a format for storing metadata in image files. See this [http://en.wikipedia.org/wiki/Exchangeable_image_file_format Wikipedia article] and the example at the bottom of [http://commons.wikimedia.org/wiki/File:Phalacrocorax-auritus-020.jpg this page on Commons]. The tags are explained [http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html briefly] and [http://www.kodak.com/global/plugins/acrobat/en/service/digCam/exifStandard2.pdf in further detail].',
32423252 'exif-rowsperstrip' => 'Exif is a format for storing metadata in image files. See this [http://en.wikipedia.org/wiki/Exchangeable_image_file_format Wikipedia article] and the example at the bottom of [http://commons.wikimedia.org/wiki/File:Phalacrocorax-auritus-020.jpg this page on Commons]. The tags are explained [http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html briefly] and [http://www.kodak.com/global/plugins/acrobat/en/service/digCam/exifStandard2.pdf in further detail].',
32433253 'exif-stripbytecounts' => 'Exif is a format for storing metadata in image files. See this [http://en.wikipedia.org/wiki/Exchangeable_image_file_format Wikipedia article] and the example at the bottom of [http://commons.wikimedia.org/wiki/File:Phalacrocorax-auritus-020.jpg this page on Commons]. The tags are explained [http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html briefly] and [http://www.kodak.com/global/plugins/acrobat/en/service/digCam/exifStandard2.pdf in further detail].',
32443254 'exif-jpeginterchangeformat' => 'Exif is a format for storing metadata in image files. See this [http://en.wikipedia.org/wiki/Exchangeable_image_file_format Wikipedia article] and the example at the bottom of [http://commons.wikimedia.org/wiki/File:Phalacrocorax-auritus-020.jpg this page on Commons]. The tags are explained [http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html briefly] and [http://www.kodak.com/global/plugins/acrobat/en/service/digCam/exifStandard2.pdf in further detail].',
32453255 'exif-jpeginterchangeformatlength' => 'Exif is a format for storing metadata in image files. See this [http://en.wikipedia.org/wiki/Exchangeable_image_file_format Wikipedia article] and the example at the bottom of [http://commons.wikimedia.org/wiki/File:Phalacrocorax-auritus-020.jpg this page on Commons]. The tags are explained [http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html briefly] and [http://www.kodak.com/global/plugins/acrobat/en/service/digCam/exifStandard2.pdf in further detail].',
3246 -'exif-transferfunction' => 'Exif is a format for storing metadata in image files. See this [http://en.wikipedia.org/wiki/Exchangeable_image_file_format Wikipedia article] and the example at the bottom of [http://commons.wikimedia.org/wiki/File:Phalacrocorax-auritus-020.jpg this page on Commons]. The tags are explained [http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html briefly] and [http://www.kodak.com/global/plugins/acrobat/en/service/digCam/exifStandard2.pdf in further detail].',
32473256 'exif-whitepoint' => 'Exif is a format for storing metadata in image files. See this [http://en.wikipedia.org/wiki/Exchangeable_image_file_format Wikipedia article] and the example at the bottom of [http://commons.wikimedia.org/wiki/File:Phalacrocorax-auritus-020.jpg this page on Commons]. The tags are explained [http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html briefly] and [http://www.kodak.com/global/plugins/acrobat/en/service/digCam/exifStandard2.pdf in further detail].',
32483257 'exif-primarychromaticities' => 'The chromaticity of the three primary colours of the image. Normally this tag is not necessary, since colour space is specified in the colour space information tag. This should probably be translated it as "Chromaticity of primary colours".
32493258
@@ -3251,30 +3260,65 @@
32523261 'exif-referenceblackwhite' => 'Exif is a format for storing metadata in image files. See this [http://en.wikipedia.org/wiki/Exchangeable_image_file_format Wikipedia article] and the example at the bottom of [http://commons.wikimedia.org/wiki/File:Phalacrocorax-auritus-020.jpg this page on Commons]. The tags are explained [http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html briefly] and [http://www.kodak.com/global/plugins/acrobat/en/service/digCam/exifStandard2.pdf in further detail].',
32533262 'exif-datetime' => 'Exif is a format for storing metadata in image files. See this [http://en.wikipedia.org/wiki/Exchangeable_image_file_format Wikipedia article] and the example at the bottom of [http://commons.wikimedia.org/wiki/File:Phalacrocorax-auritus-020.jpg this page on Commons]. The tags are explained [http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html briefly] and [http://www.kodak.com/global/plugins/acrobat/en/service/digCam/exifStandard2.pdf in further detail].
32543263
 3264+Note, this message is also used for the XMP:ModifyDate property in XMP metadata. See page 35 of http://www.adobe.com/content/dam/Adobe/en/devnet/xmp/pdfs/XMPSpecificationPart1.pdf
 3265+
32553266 Datetime is the time that the digital file was last changed.',
3256 -'exif-imagedescription' => 'Exif is a format for storing metadata in image files. See this [http://en.wikipedia.org/wiki/Exchangeable_image_file_format Wikipedia article] and the example at the bottom of [http://commons.wikimedia.org/wiki/File:Phalacrocorax-auritus-020.jpg this page on Commons]. The tags are explained [http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html briefly] and [http://www.kodak.com/global/plugins/acrobat/en/service/digCam/exifStandard2.pdf in further detail].',
3257 -'exif-make' => 'Exif is a format for storing metadata in image files. See this [http://en.wikipedia.org/wiki/Exchangeable_image_file_format Wikipedia article] and the example at the bottom of [http://commons.wikimedia.org/wiki/File:Phalacrocorax-auritus-020.jpg this page on Commons]. The tags are explained [http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html briefly] and [http://www.kodak.com/global/plugins/acrobat/en/service/digCam/exifStandard2.pdf in further detail].',
3258 -'exif-model' => 'Exif is a format for storing metadata in image files. See this [http://en.wikipedia.org/wiki/Exchangeable_image_file_format Wikipedia article] and the example at the bottom of [http://commons.wikimedia.org/wiki/File:Phalacrocorax-auritus-020.jpg this page on Commons]. The tags are explained [http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html briefly] and [http://www.kodak.com/global/plugins/acrobat/en/service/digCam/exifStandard2.pdf in further detail].',
3259 -'exif-software' => 'Short for "The software which was used to create this image".
 3267+'exif-imagedescription' => 'Exif is a format for storing metadata in image files. See this [http://en.wikipedia.org/wiki/Exchangeable_image_file_format Wikipedia article] and the example at the bottom of [http://commons.wikimedia.org/wiki/File:Phalacrocorax-auritus-020.jpg this page on Commons]. The tags are explained [http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html briefly] and [http://www.kodak.com/global/plugins/acrobat/en/service/digCam/exifStandard2.pdf in further detail].
32603268
 3269+This property is the description or caption of the image. It is used for the exif ImageDescription property, the dc:description property in XMP (see http://www.adobe.com/content/dam/Adobe/en/devnet/xmp/pdfs/XMPSpecificationPart1.pdf ), and the iptc-iim 2:120 caption/abstract property ( http://www.iptc.org/std/IIM/4.1/specification/IIMV4.1.pdf ).
 3270+
 3271+When an image has multiple differing descriptions, mediawiki follows the MWG guidelines when deciding which to show (Which typically means Exif takes precedence).',
 3272+'exif-make' => 'Exif is a format for storing metadata in image files. See this [http://en.wikipedia.org/wiki/Exchangeable_image_file_format Wikipedia article] and the example at the bottom of [http://commons.wikimedia.org/wiki/File:Phalacrocorax-auritus-020.jpg this page on Commons]. The tags are explained [http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html briefly] and [http://www.kodak.com/global/plugins/acrobat/en/service/digCam/exifStandard2.pdf in further detail].
 3273+
 3274+The Manufacturer of the digital camera (or scanner) that took the photo.',
 3275+'exif-model' => 'Exif is a format for storing metadata in image files. See this [http://en.wikipedia.org/wiki/Exchangeable_image_file_format Wikipedia article] and the example at the bottom of [http://commons.wikimedia.org/wiki/File:Phalacrocorax-auritus-020.jpg this page on Commons]. The tags are explained [http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html briefly] and [http://www.kodak.com/global/plugins/acrobat/en/service/digCam/exifStandard2.pdf in further detail].
 3276+
 3277+The model of camera (or scanner) used to take the picture.',
 3278+'exif-software' => 'Short for "The software which was used to create or modify this image".
 3279+
 3280+The property can come from the Exif Software tag, PNG software chunk, iptc-iim 2:65 Software field, or XMP\'s xmp:CreatorTool field.
 3281+
32613282 Exif is a format for storing metadata in image files. See this [http://en.wikipedia.org/wiki/Exchangeable_image_file_format Wikipedia article] and the example at the bottom of [http://commons.wikimedia.org/wiki/File:Phalacrocorax-auritus-020.jpg this page on Commons]. The tags are explained [http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html briefly] and [http://www.kodak.com/global/plugins/acrobat/en/service/digCam/exifStandard2.pdf in further detail].',
32623283 'exif-artist' => 'Exif is a format for storing metadata in image files. See this [http://en.wikipedia.org/wiki/Exchangeable_image_file_format Wikipedia article] and the example at the bottom of [http://commons.wikimedia.org/wiki/File:Phalacrocorax-auritus-020.jpg this page on Commons]. The tags are explained [http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html briefly] and [http://www.kodak.com/global/plugins/acrobat/en/service/digCam/exifStandard2.pdf in further detail].
32633284
 3285+This message labels the author or artist of the work. Usually this means who took the photograph, or who drew the picture. The corresponding value field most commonly contains a single author, however it can contain an ordered (or unordered depending on which metadata standard is used to store the information) list of authors. Sometimes the persons position is prefixed before their name such as "Photographer, John Smith". The exif standard recommends multiple authors be specified by "position, Author 1; position for author 2, Author 2\'s name" however this doesn\'t seem to happen in practise very often. If multiple authors are specified using a non-exif standard, then a billeted (or numbered) list is used.
 3286+
 3287+This property can be specified by exif Artist tag, XMP\'s tiff:Artist, XMP\'s dc:creator, iptc-iim\'s 2:80 byline, PNG\'s author textual chunk, PNG\'s (unofficial) artist textual chunk. XMP\'s photoshop:AuthorsPosition and iptc 2:85 byline-title can also affect display of this property.
 3288+
32643289 {{Identical|Author}}',
3265 -'exif-copyright' => 'Exif is a format for storing metadata in image files. See this [http://en.wikipedia.org/wiki/Exchangeable_image_file_format Wikipedia article] and the example at the bottom of [http://commons.wikimedia.org/wiki/File:Phalacrocorax-auritus-020.jpg this page on Commons]. The tags are explained [http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html briefly] and [http://www.kodak.com/global/plugins/acrobat/en/service/digCam/exifStandard2.pdf in further detail].',
3266 -'exif-exifversion' => 'Exif is a format for storing metadata in image files. See this [http://en.wikipedia.org/wiki/Exchangeable_image_file_format Wikipedia article] and the example at the bottom of [http://commons.wikimedia.org/wiki/File:Phalacrocorax-auritus-020.jpg this page on Commons]. The tags are explained [http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html briefly] and [http://www.kodak.com/global/plugins/acrobat/en/service/digCam/exifStandard2.pdf in further detail].',
3267 -'exif-flashpixversion' => 'Exif is a format for storing metadata in image files. See this [http://en.wikipedia.org/wiki/Exchangeable_image_file_format Wikipedia article] and the example at the bottom of [http://commons.wikimedia.org/wiki/File:Phalacrocorax-auritus-020.jpg this page on Commons]. The tags are explained [http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html briefly] and [http://www.kodak.com/global/plugins/acrobat/en/service/digCam/exifStandard2.pdf in further detail].',
3268 -'exif-colorspace' => 'Exif is a format for storing metadata in image files. See this [http://en.wikipedia.org/wiki/Exchangeable_image_file_format Wikipedia article] and the example at the bottom of [http://commons.wikimedia.org/wiki/File:Phalacrocorax-auritus-020.jpg this page on Commons]. The tags are explained [http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html briefly] and [http://www.kodak.com/global/plugins/acrobat/en/service/digCam/exifStandard2.pdf in further detail].',
3269 -'exif-componentsconfiguration' => 'Exif is a format for storing metadata in image files. See this [http://en.wikipedia.org/wiki/Exchangeable_image_file_format Wikipedia article] and the example at the bottom of [http://commons.wikimedia.org/wiki/File:Phalacrocorax-auritus-020.jpg this page on Commons]. The tags are explained [http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html briefly] and [http://www.kodak.com/global/plugins/acrobat/en/service/digCam/exifStandard2.pdf in further detail].',
 3290+'exif-copyright' => 'Exif is a format for storing metadata in image files. See this [http://en.wikipedia.org/wiki/Exchangeable_image_file_format Wikipedia article] and the example at the bottom of [http://commons.wikimedia.org/wiki/File:Phalacrocorax-auritus-020.jpg this page on Commons]. The tags are explained [http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html briefly] and [http://www.kodak.com/global/plugins/acrobat/en/service/digCam/exifStandard2.pdf in further detail].
 3291+
 3292+Label for information contained in exif Copyright tag, XMP dc:rights, IPTC-iim 2:116, or PNG copyright textual chunk.
 3293+
 3294+Typically the copyright statement for the photograph/drawing/video (such as \'\'(c) 2010 John Smith. Released under GFDL\'\'). Sometimes contains license information. See also {{msg-mw|exif-copyrightowner}}',
 3295+'exif-exifversion' => 'Exif is a format for storing metadata in image files. See this [http://en.wikipedia.org/wiki/Exchangeable_image_file_format Wikipedia article] and the example at the bottom of [http://commons.wikimedia.org/wiki/File:Phalacrocorax-auritus-020.jpg this page on Commons]. The tags are explained [http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html briefly] and [http://www.kodak.com/global/plugins/acrobat/en/service/digCam/exifStandard2.pdf in further detail].
 3296+
 3297+Version of exif standard photo uses. Typically this is 2.22',
 3298+'exif-flashpixversion' => 'Exif is a format for storing metadata in image files. See this [http://en.wikipedia.org/wiki/Exchangeable_image_file_format Wikipedia article] and the example at the bottom of [http://commons.wikimedia.org/wiki/File:Phalacrocorax-auritus-020.jpg this page on Commons]. The tags are explained [http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html briefly] and [http://www.kodak.com/global/plugins/acrobat/en/service/digCam/exifStandard2.pdf in further detail].
 3299+
 3300+Version of flashpix used. Flashpix is a format used for storing some types of metadata in image. It is not as commonly used as EXIF, and mediawiki currently cannot read Flashpix data.',
 3301+'exif-colorspace' => 'Exif is a format for storing metadata in image files. See this [http://en.wikipedia.org/wiki/Exchangeable_image_file_format Wikipedia article] and the example at the bottom of [http://commons.wikimedia.org/wiki/File:Phalacrocorax-auritus-020.jpg this page on Commons]. The tags are explained [http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html briefly] and [http://www.kodak.com/global/plugins/acrobat/en/service/digCam/exifStandard2.pdf in further detail].
 3302+
 3303+The colorspace of the photo. This tells the computer how to make the colours in the photo be more true to the original photo. Typical values for this are sRGB or uncalibrated. This only gives information on colour information given in the exif-colorspace property. However, colour information is often stored elsewhere in the photo.',
 3304+'exif-componentsconfiguration' => 'Exif is a format for storing metadata in image files. See this [http://en.wikipedia.org/wiki/Exchangeable_image_file_format Wikipedia article] and the example at the bottom of [http://commons.wikimedia.org/wiki/File:Phalacrocorax-auritus-020.jpg this page on Commons]. The tags are explained [http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html briefly] and [http://www.kodak.com/global/plugins/acrobat/en/service/digCam/exifStandard2.pdf in further detail].
 3305+
 3306+This contains how the information in the picture is stored. This is most commonly Y, Cr, Cb to specify luma, red, blue. RGB is also possible to specify Red, Green, Blue.',
32703307 'exif-compressedbitsperpixel' => 'Exif is a format for storing metadata in image files. See this [http://en.wikipedia.org/wiki/Exchangeable_image_file_format Wikipedia article] and the example at the bottom of [http://commons.wikimedia.org/wiki/File:Phalacrocorax-auritus-020.jpg this page on Commons]. The tags are explained [http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html briefly] and [http://www.kodak.com/global/plugins/acrobat/en/service/digCam/exifStandard2.pdf in further detail].',
3271 -'exif-pixelydimension' => 'Exif is a format for storing metadata in image files. See this [http://en.wikipedia.org/wiki/Exchangeable_image_file_format Wikipedia article] and the example at the bottom of [http://commons.wikimedia.org/wiki/File:Phalacrocorax-auritus-020.jpg this page on Commons]. The tags are explained [http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html briefly] and [http://www.kodak.com/global/plugins/acrobat/en/service/digCam/exifStandard2.pdf in further detail].',
3272 -'exif-pixelxdimension' => 'Exif is a format for storing metadata in image files. See this [http://en.wikipedia.org/wiki/Exchangeable_image_file_format Wikipedia article] and the example at the bottom of [http://commons.wikimedia.org/wiki/File:Phalacrocorax-auritus-020.jpg this page on Commons]. The tags are explained [http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html briefly] and [http://www.kodak.com/global/plugins/acrobat/en/service/digCam/exifStandard2.pdf in further detail].',
3273 -'exif-makernote' => 'Exif is a format for storing metadata in image files. See this [http://en.wikipedia.org/wiki/Exchangeable_image_file_format Wikipedia article] and the example at the bottom of [http://commons.wikimedia.org/wiki/File:Phalacrocorax-auritus-020.jpg this page on Commons]. The tags are explained [http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html briefly] and [http://www.kodak.com/global/plugins/acrobat/en/service/digCam/exifStandard2.pdf in further detail].',
3274 -'exif-usercomment' => 'Exif is a format for storing metadata in image files. See this [http://en.wikipedia.org/wiki/Exchangeable_image_file_format Wikipedia article] and the example at the bottom of [http://commons.wikimedia.org/wiki/File:Phalacrocorax-auritus-020.jpg this page on Commons]. The tags are explained [http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html briefly] and [http://www.kodak.com/global/plugins/acrobat/en/service/digCam/exifStandard2.pdf in further detail].',
3275 -'exif-relatedsoundfile' => 'Exif is a format for storing metadata in image files. See this [http://en.wikipedia.org/wiki/Exchangeable_image_file_format Wikipedia article] and the example at the bottom of [http://commons.wikimedia.org/wiki/File:Phalacrocorax-auritus-020.jpg this page on Commons]. The tags are explained [http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html briefly] and [http://www.kodak.com/global/plugins/acrobat/en/service/digCam/exifStandard2.pdf in further detail].',
 3308+'exif-pixelydimension' => 'Exif is a format for storing metadata in image files. See this [http://en.wikipedia.org/wiki/Exchangeable_image_file_format Wikipedia article] and the example at the bottom of [http://commons.wikimedia.org/wiki/File:Phalacrocorax-auritus-020.jpg this page on Commons]. The tags are explained [http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html briefly] and [http://www.kodak.com/global/plugins/acrobat/en/service/digCam/exifStandard2.pdf in further detail].
 3309+
 3310+{{Identical|Height}}',
 3311+'exif-pixelxdimension' => 'Exif is a format for storing metadata in image files. See this [http://en.wikipedia.org/wiki/Exchangeable_image_file_format Wikipedia article] and the example at the bottom of [http://commons.wikimedia.org/wiki/File:Phalacrocorax-auritus-020.jpg this page on Commons]. The tags are explained [http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html briefly] and [http://www.kodak.com/global/plugins/acrobat/en/service/digCam/exifStandard2.pdf in further detail].
 3312+
 3313+{{Identical|Width}}',
 3314+'exif-usercomment' => 'Exif is a format for storing metadata in image files. See this [http://en.wikipedia.org/wiki/Exchangeable_image_file_format Wikipedia article] and the example at the bottom of [http://commons.wikimedia.org/wiki/File:Phalacrocorax-auritus-020.jpg this page on Commons]. The tags are explained [http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html briefly] and [http://www.kodak.com/global/plugins/acrobat/en/service/digCam/exifStandard2.pdf in further detail].
 3315+
 3316+Comments by user. Sometimes used like ImageDescription when the ImageDescription contained non-ascii characters. (Technically ImageDescription is supposed to contain ascii characters. In practise utf-8 is used in ImageDescription, so this field isn\'t used too much.)',
 3317+'exif-relatedsoundfile' => 'Exif is a format for storing metadata in image files. See this [http://en.wikipedia.org/wiki/Exchangeable_image_file_format Wikipedia article] and the example at the bottom of [http://commons.wikimedia.org/wiki/File:Phalacrocorax-auritus-020.jpg this page on Commons]. The tags are explained [http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html briefly] and [http://www.kodak.com/global/plugins/acrobat/en/service/digCam/exifStandard2.pdf in further detail].
 3318+
 3319+Some cameras offer the option to record an audio "memo" for the photo they just took. If the user did that, the name of the file is labelled with this message.',
32763320 'exif-datetimeoriginal' => 'Exif is a format for storing metadata in image files. See this [http://en.wikipedia.org/wiki/Exchangeable_image_file_format Wikipedia article] and the example at the bottom of [http://commons.wikimedia.org/wiki/File:Phalacrocorax-auritus-020.jpg this page on Commons]. The tags are explained [http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html briefly] and [http://www.kodak.com/global/plugins/acrobat/en/service/digCam/exifStandard2.pdf in further detail].
32773321
3278 -The date and time when the original image data was generated.',
 3322+The date and time when the original image data was generated. For example if it was a painting from 1773, scanned in to a computer in 2007, the datetimeoriginal would be 1773 and {{msg-mw|exif-datetimedigitized}} would have the 2007 date.',
32793323 'exif-datetimedigitized' => 'Exif is a format for storing metadata in image files. See this [http://en.wikipedia.org/wiki/Exchangeable_image_file_format Wikipedia article] and the example at the bottom of [http://commons.wikimedia.org/wiki/File:Phalacrocorax-auritus-020.jpg this page on Commons]. The tags are explained [http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html briefly] and [http://www.kodak.com/global/plugins/acrobat/en/service/digCam/exifStandard2.pdf in further detail].
32803324
32813325 The date and time when the image was stored as digital data.',
@@ -3287,7 +3331,9 @@
32883332 'exif-subsectimedigitized' => 'Exif is a format for storing metadata in image files. See this [http://en.wikipedia.org/wiki/Exchangeable_image_file_format Wikipedia article] and the example at the bottom of [http://commons.wikimedia.org/wiki/File:Phalacrocorax-auritus-020.jpg this page on Commons]. The tags are explained [http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html briefly] and [http://www.kodak.com/global/plugins/acrobat/en/service/digCam/exifStandard2.pdf in further detail].
32893333
32903334 This tag shows the detail of the fraction of a second (1/100s) at which the file was stored as digital data, when the tag {{msg-mw|Exif-datetimedigitized}} is recorded to the whole second.',
3291 -'exif-exposuretime' => 'Exif is a format for storing metadata in image files. See this [http://en.wikipedia.org/wiki/Exchangeable_image_file_format Wikipedia article] and the example at the bottom of [http://commons.wikimedia.org/wiki/File:Phalacrocorax-auritus-020.jpg this page on Commons]. The tags are explained [http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html briefly] and [http://www.kodak.com/global/plugins/acrobat/en/service/digCam/exifStandard2.pdf in further detail].',
 3335+'exif-exposuretime' => 'Exif is a format for storing metadata in image files. See this [http://en.wikipedia.org/wiki/Exchangeable_image_file_format Wikipedia article] and the example at the bottom of [http://commons.wikimedia.org/wiki/File:Phalacrocorax-auritus-020.jpg this page on Commons]. The tags are explained [http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html briefly] and [http://www.kodak.com/global/plugins/acrobat/en/service/digCam/exifStandard2.pdf in further detail].
 3336+
 3337+The exposure time. Number of (or fraction of) seconds the film was exposed to light. The value for this property is formatted using {{msg-mw|exif-exposuretime-format}}',
32923338 'exif-exposuretime-format' => "Exif is a format for storing metadata in image files. See this [http://en.wikipedia.org/wiki/Exchangeable_image_file_format Wikipedia article] and the example at the bottom of [http://commons.wikimedia.org/wiki/File:Phalacrocorax-auritus-020.jpg this page on Commons]. The tags are explained [http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html briefly] and [http://www.kodak.com/global/plugins/acrobat/en/service/digCam/exifStandard2.pdf in further detail].
32933339
32943340 *$1 is the exposure time written as a fraction of a second, for example 1/640 of a second.
@@ -3299,19 +3345,30 @@
33003346 'exif-fnumber-format' => "{{optional}}
33013347 Exif is a format for storing metadata in image files. See this [http://en.wikipedia.org/wiki/Exchangeable_image_file_format Wikipedia article] and the example at the bottom of [http://commons.wikimedia.org/wiki/File:Phalacrocorax-auritus-020.jpg this page on Commons]. The tags are explained [http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html briefly] and [http://www.kodak.com/global/plugins/acrobat/en/service/digCam/exifStandard2.pdf in further detail].
33023348
 3349+{{optional}}
 3350+
33033351 *$1 is a number
33043352 *f is the abbreviation used in English for 'f-number'.",
3305 -'exif-exposureprogram' => 'Exif is a format for storing metadata in image files. See this [http://en.wikipedia.org/wiki/Exchangeable_image_file_format Wikipedia article] and the example at the bottom of [http://commons.wikimedia.org/wiki/File:Phalacrocorax-auritus-020.jpg this page on Commons]. The tags are explained [http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html briefly] and [http://www.kodak.com/global/plugins/acrobat/en/service/digCam/exifStandard2.pdf in further detail].',
3306 -'exif-spectralsensitivity' => 'Exif is a format for storing metadata in image files. See this [http://en.wikipedia.org/wiki/Exchangeable_image_file_format Wikipedia article] and the example at the bottom of [http://commons.wikimedia.org/wiki/File:Phalacrocorax-auritus-020.jpg this page on Commons]. The tags are explained [http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html briefly] and [http://www.kodak.com/global/plugins/acrobat/en/service/digCam/exifStandard2.pdf in further detail].',
3307 -'exif-isospeedratings' => 'Exif is a format for storing metadata in image files. See this [http://en.wikipedia.org/wiki/Exchangeable_image_file_format Wikipedia article] and the example at the bottom of [http://commons.wikimedia.org/wiki/File:Phalacrocorax-auritus-020.jpg this page on Commons]. The tags are explained [http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html briefly] and [http://www.kodak.com/global/plugins/acrobat/en/service/digCam/exifStandard2.pdf in further detail].',
3308 -'exif-oecf' => 'Exif is a format for storing metadata in image files. See this [http://en.wikipedia.org/wiki/Exchangeable_image_file_format Wikipedia article] and the example at the bottom of [http://commons.wikimedia.org/wiki/File:Phalacrocorax-auritus-020.jpg this page on Commons]. The tags are explained [http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html briefly] and [http://www.kodak.com/global/plugins/acrobat/en/service/digCam/exifStandard2.pdf in further detail].',
 3353+'exif-exposureprogram' => 'Exif is a format for storing metadata in image files. See this [http://en.wikipedia.org/wiki/Exchangeable_image_file_format Wikipedia article] and the example at the bottom of [http://commons.wikimedia.org/wiki/File:Phalacrocorax-auritus-020.jpg this page on Commons]. The tags are explained [http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html briefly] and [http://www.kodak.com/global/plugins/acrobat/en/service/digCam/exifStandard2.pdf in further detail].
 3354+
 3355+How the camera figured out what exposure to use. (If it was manually set, if its optimizing for fast shutter speed, etc).',
 3356+'exif-spectralsensitivity' => 'Exif is a format for storing metadata in image files. See this [http://en.wikipedia.org/wiki/Exchangeable_image_file_format Wikipedia article] and the example at the bottom of [http://commons.wikimedia.org/wiki/File:Phalacrocorax-auritus-020.jpg this page on Commons]. The tags are explained [http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html briefly] and [http://www.kodak.com/global/plugins/acrobat/en/service/digCam/exifStandard2.pdf in further detail].
 3357+
 3358+How sensitive each channel (colour) of the photo is to light. This tag is almost never used.',
 3359+'exif-isospeedratings' => 'Exif is a format for storing metadata in image files. See this [http://en.wikipedia.org/wiki/Exchangeable_image_file_format Wikipedia article] and the example at the bottom of [http://commons.wikimedia.org/wiki/File:Phalacrocorax-auritus-020.jpg this page on Commons]. The tags are explained [http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html briefly] and [http://www.kodak.com/global/plugins/acrobat/en/service/digCam/exifStandard2.pdf in further detail].
 3360+
 3361+The iso speed of the film used in the camera. This is basically a measure of how sensitive the film in the camera is to light.',
33093362 'exif-shutterspeedvalue' => 'Exif is a format for storing metadata in image files. See this [http://en.wikipedia.org/wiki/Exchangeable_image_file_format Wikipedia article] and the example at the bottom of [http://commons.wikimedia.org/wiki/File:Phalacrocorax-auritus-020.jpg this page on Commons]. The tags are explained [http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html briefly] and [http://www.kodak.com/global/plugins/acrobat/en/service/digCam/exifStandard2.pdf in further detail].
33103363
3311 -[http://en.wikipedia.org/wiki/Shutter_speed Shutter speed] is the time that the camera shutter is open.',
 3364+[http://en.wikipedia.org/wiki/Shutter_speed Shutter speed] is the time that the camera shutter is open.
 3365+
 3366+This is the shutter speed measured in APEX units (negative base 2 log of shutter speed in seconds). See {{msg-mw|exif-exposuretime}} for this property in more traditional units of seconds.',
33123367 'exif-aperturevalue' => "Exif is a format for storing metadata in image files. See this [http://en.wikipedia.org/wiki/Exchangeable_image_file_format Wikipedia article] and the example at the bottom of [http://commons.wikimedia.org/wiki/File:Phalacrocorax-auritus-020.jpg this page on Commons]. The tags are explained [http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html briefly] and [http://www.kodak.com/global/plugins/acrobat/en/service/digCam/exifStandard2.pdf in further detail].
33133368
3314 -The [http://en.wikipedia.org/wiki/Aperture aperture] of a camera is the hole through which light shines. This message can be translated 'Aperture width'.",
3315 -'exif-brightnessvalue' => 'Exif is a format for storing metadata in image files. See this [http://en.wikipedia.org/wiki/Exchangeable_image_file_format Wikipedia article] and the example at the bottom of [http://commons.wikimedia.org/wiki/File:Phalacrocorax-auritus-020.jpg this page on Commons]. The tags are explained [http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html briefly] and [http://www.kodak.com/global/plugins/acrobat/en/service/digCam/exifStandard2.pdf in further detail].',
 3369+The [http://en.wikipedia.org/wiki/Aperture aperture] of a camera is the hole through which light shines. This message can be translated 'Aperture width'. Note, this is measured in APEX units which is 2*log<sub>2</sub>(f-number) . See {{msg-mw|exif-fnumber}} for this value in more traditional units.",
 3370+'exif-brightnessvalue' => 'Exif is a format for storing metadata in image files. See this [http://en.wikipedia.org/wiki/Exchangeable_image_file_format Wikipedia article] and the example at the bottom of [http://commons.wikimedia.org/wiki/File:Phalacrocorax-auritus-020.jpg this page on Commons]. The tags are explained [http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html briefly] and [http://www.kodak.com/global/plugins/acrobat/en/service/digCam/exifStandard2.pdf in further detail].
 3371+
 3372+How intense the illumination of the scene photographed is. Measured in APEX brightness units. See Annex C of Exif standard for details on the measurement system in use.',
33163373 'exif-exposurebiasvalue' => "Exif is a format for storing metadata in image files. See this [http://en.wikipedia.org/wiki/Exchangeable_image_file_format Wikipedia article] and the example at the bottom of [http://commons.wikimedia.org/wiki/File:Phalacrocorax-auritus-020.jpg this page on Commons]. The tags are explained [http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html briefly] and [http://www.kodak.com/global/plugins/acrobat/en/service/digCam/exifStandard2.pdf in further detail].
33173374
33183375 Another term for [http://en.wikipedia.org/wiki/Exposure_bias 'exposure bias'] is 'exposure compensation'.",
@@ -3336,12 +3393,16 @@
33373394 'exif-focallength-format' => "{{optional}}
33383395 Exif is a format for storing metadata in image files. See this [http://en.wikipedia.org/wiki/Exchangeable_image_file_format Wikipedia article] and the example at the bottom of [http://commons.wikimedia.org/wiki/File:Phalacrocorax-auritus-020.jpg this page on Commons]. The tags are explained [http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html briefly] and [http://www.kodak.com/global/plugins/acrobat/en/service/digCam/exifStandard2.pdf in further detail].
33393396
 3397+{{optional}}
 3398+
33403399 *$1 is a number
33413400 *mm is the abbreviation used in English for the unit of measurement of length 'millimetre'.",
33423401 'exif-subjectarea' => 'Exif is a format for storing metadata in image files. See this [http://en.wikipedia.org/wiki/Exchangeable_image_file_format Wikipedia article] and the example at the bottom of [http://commons.wikimedia.org/wiki/File:Phalacrocorax-auritus-020.jpg this page on Commons]. The tags are explained [http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html briefly] and [http://www.kodak.com/global/plugins/acrobat/en/service/digCam/exifStandard2.pdf in further detail].
33433402
3344 -This exif property contains the position of the main subject of the picture in pixels from the upper left corner and additionally its width and height in pixels.',
3345 -'exif-flashenergy' => 'Exif is a format for storing metadata in image files. See this [http://en.wikipedia.org/wiki/Exchangeable_image_file_format Wikipedia article] and the example at the bottom of [http://commons.wikimedia.org/wiki/File:Phalacrocorax-auritus-020.jpg this page on Commons]. The tags are explained [http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html briefly] and [http://www.kodak.com/global/plugins/acrobat/en/service/digCam/exifStandard2.pdf in further detail].',
 3403+This exif property contains the position of the main subject. The first two numbers is the position of the subject in the picture in pixels from the upper left corner. If a third number is specified, it is a circle centred at the first two numbers. If four numbers are specified, the first two are coordinates of the centre of the subject as before, the third is the width of the rectangle, and the fourth is the height of the rectangle. It is rare for a photo to use this tag.',
 3404+'exif-flashenergy' => 'Exif is a format for storing metadata in image files. See this [http://en.wikipedia.org/wiki/Exchangeable_image_file_format Wikipedia article] and the example at the bottom of [http://commons.wikimedia.org/wiki/File:Phalacrocorax-auritus-020.jpg this page on Commons]. The tags are explained [http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html briefly] and [http://www.kodak.com/global/plugins/acrobat/en/service/digCam/exifStandard2.pdf in further detail].
 3405+
 3406+How bright the flash is in beam candle power seconds.',
33463407 'exif-spatialfrequencyresponse' => '[http://en.wikipedia.org/wiki/Spatial_frequency Spatial frequency] is the number of edges per degree of the visual angle. The human eye scans the viewed scenary for edges and uses these edges to detect what it sees. Few edges make it hard to recognize the seen objects, but many edges do so too. A rate of about 4 to 6 edges per degree of the viewing range is seen as optimal for the recognition of objects.
33473408
33483409 Spatial frequency response is a measure for the capability of camera lenses to depict spatial frequencies.',
@@ -3350,11 +3411,17 @@
33513412 Indicates the number of pixels in the image width (X) direction per FocalPlaneResolutionUnit on the camera focal plane.',
33523413 'exif-focalplaneyresolution' => 'Exif is a format for storing metadata in image files. See this [http://en.wikipedia.org/wiki/Exchangeable_image_file_format Wikipedia article] and the example at the bottom of [http://commons.wikimedia.org/wiki/File:Phalacrocorax-auritus-020.jpg this page on Commons]. The tags are explained [http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html briefly] and [http://www.kodak.com/global/plugins/acrobat/en/service/digCam/exifStandard2.pdf in further detail].',
33533414 'exif-focalplaneresolutionunit' => 'Exif is a format for storing metadata in image files. See this [http://en.wikipedia.org/wiki/Exchangeable_image_file_format Wikipedia article] and the example at the bottom of [http://commons.wikimedia.org/wiki/File:Phalacrocorax-auritus-020.jpg this page on Commons]. The tags are explained [http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html briefly] and [http://www.kodak.com/global/plugins/acrobat/en/service/digCam/exifStandard2.pdf in further detail].',
3354 -'exif-subjectlocation' => 'Exif is a format for storing metadata in image files. See this [http://en.wikipedia.org/wiki/Exchangeable_image_file_format Wikipedia article] and the example at the bottom of [http://commons.wikimedia.org/wiki/File:Phalacrocorax-auritus-020.jpg this page on Commons]. The tags are explained [http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html briefly] and [http://www.kodak.com/global/plugins/acrobat/en/service/digCam/exifStandard2.pdf in further detail].',
 3415+'exif-subjectlocation' => 'Exif is a format for storing metadata in image files. See this [http://en.wikipedia.org/wiki/Exchangeable_image_file_format Wikipedia article] and the example at the bottom of [http://commons.wikimedia.org/wiki/File:Phalacrocorax-auritus-020.jpg this page on Commons]. The tags are explained [http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html briefly] and [http://www.kodak.com/global/plugins/acrobat/en/service/digCam/exifStandard2.pdf in further detail].
 3416+
 3417+Same as {{msg-mw|exif-subjectarea}} but only ever has two numbers as a value.',
33553418 'exif-exposureindex' => 'Exif is a format for storing metadata in image files. See this [http://en.wikipedia.org/wiki/Exchangeable_image_file_format Wikipedia article] and the example at the bottom of [http://commons.wikimedia.org/wiki/File:Phalacrocorax-auritus-020.jpg this page on Commons]. The tags are explained [http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html briefly] and [http://www.kodak.com/global/plugins/acrobat/en/service/digCam/exifStandard2.pdf in further detail].',
33563419 'exif-sensingmethod' => 'Exif is a format for storing metadata in image files. See this [http://en.wikipedia.org/wiki/Exchangeable_image_file_format Wikipedia article] and the example at the bottom of [http://commons.wikimedia.org/wiki/File:Phalacrocorax-auritus-020.jpg this page on Commons]. The tags are explained [http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html briefly] and [http://www.kodak.com/global/plugins/acrobat/en/service/digCam/exifStandard2.pdf in further detail].',
3357 -'exif-filesource' => 'Exif is a format for storing metadata in image files. See this [http://en.wikipedia.org/wiki/Exchangeable_image_file_format Wikipedia article] and the example at the bottom of [http://commons.wikimedia.org/wiki/File:Phalacrocorax-auritus-020.jpg this page on Commons]. The tags are explained [http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html briefly] and [http://www.kodak.com/global/plugins/acrobat/en/service/digCam/exifStandard2.pdf in further detail].',
3358 -'exif-scenetype' => 'Exif is a format for storing metadata in image files. See this [http://en.wikipedia.org/wiki/Exchangeable_image_file_format Wikipedia article] and the example at the bottom of [http://commons.wikimedia.org/wiki/File:Phalacrocorax-auritus-020.jpg this page on Commons]. The tags are explained [http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html briefly] and [http://www.kodak.com/global/plugins/acrobat/en/service/digCam/exifStandard2.pdf in further detail].',
 3420+'exif-filesource' => 'Exif is a format for storing metadata in image files. See this [http://en.wikipedia.org/wiki/Exchangeable_image_file_format Wikipedia article] and the example at the bottom of [http://commons.wikimedia.org/wiki/File:Phalacrocorax-auritus-020.jpg this page on Commons]. The tags are explained [http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html briefly] and [http://www.kodak.com/global/plugins/acrobat/en/service/digCam/exifStandard2.pdf in further detail].
 3421+
 3422+Determines if the image was recorded by a digital camera adhering to DSC standard (which is almost all digital cameras).',
 3423+'exif-scenetype' => 'Exif is a format for storing metadata in image files. See this [http://en.wikipedia.org/wiki/Exchangeable_image_file_format Wikipedia article] and the example at the bottom of [http://commons.wikimedia.org/wiki/File:Phalacrocorax-auritus-020.jpg this page on Commons]. The tags are explained [http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html briefly] and [http://www.kodak.com/global/plugins/acrobat/en/service/digCam/exifStandard2.pdf in further detail].
 3424+
 3425+If the image is directly photographed (taken by a digital camera).',
33593426 'exif-cfapattern' => 'Exif is a format for storing metadata in image files. See this [http://en.wikipedia.org/wiki/Exchangeable_image_file_format Wikipedia article] and the example at the bottom of [http://commons.wikimedia.org/wiki/File:Phalacrocorax-auritus-020.jpg this page on Commons]. The tags are explained [http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html briefly] and [http://www.kodak.com/global/plugins/acrobat/en/service/digCam/exifStandard2.pdf in further detail].
33603427
33613428 CFA stands for [http://en.wikipedia.org/wiki/Color_filter_array color filter array].',
@@ -3363,7 +3430,7 @@
33643431 See also Wikipedia on [http://en.wikipedia.org/wiki/Image_processing image processing].',
33653432 'exif-exposuremode' => 'Exif is a format for storing metadata in image files. See this [http://en.wikipedia.org/wiki/Exchangeable_image_file_format Wikipedia article] and the example at the bottom of [http://commons.wikimedia.org/wiki/File:Phalacrocorax-auritus-020.jpg this page on Commons]. The tags are explained [http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html briefly] and [http://www.kodak.com/global/plugins/acrobat/en/service/digCam/exifStandard2.pdf in further detail].
33663433
3367 -See also Wikipedia on [http://en.wikipedia.org/wiki/Exposure_(photography) exposure in photography].',
 3434+See also Wikipedia on [http://en.wikipedia.org/wiki/Exposure_(photography) exposure in photography]. This tag shows if the photo\'s exposure was manually set or automatically determined.',
33683435 'exif-whitebalance' => 'Exif is a format for storing metadata in image files. See this [http://en.wikipedia.org/wiki/Exchangeable_image_file_format Wikipedia article] and the example at the bottom of [http://commons.wikimedia.org/wiki/File:Phalacrocorax-auritus-020.jpg this page on Commons]. The tags are explained [http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html briefly] and [http://www.kodak.com/global/plugins/acrobat/en/service/digCam/exifStandard2.pdf in further detail].
33693436
33703437 See also Wikipedia on [http://en.wikipedia.org/wiki/Color_balance color balance].',
@@ -3373,10 +3440,124 @@
33743441 'exif-focallengthin35mmfilm' => 'Exif is a format for storing metadata in image files. See this [http://en.wikipedia.org/wiki/Exchangeable_image_file_format Wikipedia article] and the example at the bottom of [http://commons.wikimedia.org/wiki/File:Phalacrocorax-auritus-020.jpg this page on Commons]. The tags are explained [http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html briefly] and [http://www.kodak.com/global/plugins/acrobat/en/service/digCam/exifStandard2.pdf in further detail].
33753442
33763443 See also Wikipedia on [http://en.wikipedia.org/wiki/Focal_length#In_photography focal length].',
3377 -'exif-gpslatitude' => '{{Identical|Latitude}}',
3378 -'exif-gpslongitude' => '{{Identical|Longitude}}',
 3444+'exif-imageuniqueid' => 'A unique identifier for the image in the form of a 128-bit hexadecimal string. See http://www.exif.org/Exif2-2.PDF for details on exif properties.',
 3445+'exif-gpsversionid' => 'Version of the GPS IFD used to store location information. This is usually 2.2.0.0',
 3446+'exif-gpslatituderef' => 'In older versions of mediawiki this referred to if the latitude was North or South. This is no longer used in modern versions of mediawiki except for when using a foreign image repository that is using an older version of mediawiki since the information is now contained in {{msg-mw|exif-gpslatitude}}.',
 3447+'exif-gpslongituderef' => 'Same as {{msg-mw|exif-gpslatituderef}} but for longitude.',
 3448+'exif-gpsaltituderef' => 'No longer used except for when using foreign image repository with old version of mediawiki. 0 for above sea level, 1 for below sea level.',
 3449+'exif-gpsaltitude' => 'Altitude in meters that the image was taken at.',
 3450+'exif-gpstimestamp' => 'Time (does not include date) that GPS measurement was taken, in UTC. Since often this is at the same time as photo was taken, this is sometimes more reliable than {{msg-mw|exif-datetimeoriginal}}.',
 3451+'exif-gpsdatestamp' => 'Date (does not generally include time unless recorded in XMP) that GPS measurement was taken, in UTC. Since often this is at the same date as photo was taken, this is sometimes more reliable than {{msg-mw|exif-datetimeoriginal}}.',
 3452+'exif-gpsmeasuremode' => 'Is the measurement 2D (latitude and longitude) or 3D (latitude, longitude, and altitude).',
 3453+'exif-gpsdop' => 'How accurate the GPS information is. See http://en.wikipedia.org/wiki/Dilution_of_precision_(GPS)',
 3454+'exif-gpslatitude' => 'The latitude of the location from where the picture was taken from.
33793455 'exif-objectname' => 'This message labels a field in the image metadata table that is a short name or title for the image. (As compared to {{msg-mw|exif-imagedescription}} which is a long description of the image).',
33803456
 3457+{{Identical|Latitude}}',
 3458+'exif-gpslongitude' => 'The longitude of the location from where the picture was taken from.
 3459+
 3460+{{Identical|Longitude}}',
 3461+'exif-gpsdestlatitude' => 'The latitude of the location shown in the picture, if it is different from latitude of the camera location. See {{msg-mw|exif-gpslatitude}}.
 3462+
 3463+{{Identical|Latitude}}',
 3464+'exif-gpsdestlongitude' => 'The longitude of the location shown in the picture, if it is different from longitude of the camera location. See {{msg-mw|exif-gpslongitude}}.
 3465+
 3466+{{Identical|Longitude}}',
 3467+'exif-coordinate-format' => '{{optional}} 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.',
 3468+'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. This is similar to {{msg-mw|exif-usercomment}}, {{msg-mw|exif-pngfilecomment}}, and {{msg-mw|exif-giffilecomment}}.',
 3469+'exif-keywords' => 'List of keywords for the photograph (or other media).
 3470+
 3471+This can come from IPTC-iim 2:25 keyword field, or XMP\'s dc:subject field.',
 3472+'exif-worldregioncreated' => 'The world region (generally that means continent, but could also include \'World\' as a whole) where the media was created.',
 3473+'exif-countrycreated' => 'Country that the picture was taken in. Note this is where it was taken, not what country is depicted in the picture.',
 3474+'exif-countrycodecreated' => 'ISO Code for the country that the picture was taken in. Note this is where it was taken, not what country is depicted in the picture.',
 3475+'exif-provinceorstatecreated' => 'Province, state, territory, or other secondary political division (bigger than a city, smaller then a country) where that the picture was taken in. Note this is where it was taken, not what province/state is depicted in the picture.',
 3476+'exif-citycreated' => 'City that the picture was taken in. Note this is where it was taken, not what city is depicted in the picture. This is generally only used if different from the city depicted in photo.',
 3477+'exif-sublocationcreated' => 'Sub-location of the city that the picture was taken in. This might be a street, a part of town, etc. Note this is where it was taken, not what sub-location is depicted in the picture.',
 3478+'exif-worldregiondest' => 'World region shown. This generally means the continent, but could have the value of world as well.',
 3479+'exif-countrydest' => 'Country shown. See also {{msg-mw|exif-worldregioncreated}}.',
 3480+'exif-countrycodedest' => 'ISO Code for country shown',
 3481+'exif-provinceorstatedest' => 'Province, state, territory, or other secondary political division shown.',
 3482+'exif-citydest' => 'City shown',
 3483+'exif-sublocationdest' => 'Sub-location of city shown. This could be an address, a street, an area of town, etc.',
 3484+'exif-objectname' => 'This is a short name for the image or other media. (As compared to {{msg-mw|exif-imagedescription}} which is a long description of the image). This is sometimes an id number used to identify the photo, or a (short) title of the photo.
 3485+
 3486+This property is extracted based on XMP\'s dc:title property ( http://www.adobe.com/content/dam/Adobe/en/devnet/xmp/pdfs/XMPSpecificationPart1.pdf ), PNG\'s title keyword ( http://www.w3.org/TR/PNG/#11keywords ), or IPTC-iim 2:05 Object name property ( http://www.iptc.org/std/IIM/4.1/specification/IIMV4.1.pdf ).',
 3487+'exif-specialinstructions' => 'Special instructions for how to use the image/media. This might include embargo notices, or other warnings.
 3488+
 3489+This is IPTC-iim property 2:40. See http://www.iptc.org/std/IIM/4.1/specification/IIMV4.1.pdf for details.',
 3490+'exif-headline' => 'A short version of the image caption. The IPTC4XMP standard is clear that "this is not the same thing as title [ {{msg-mw|exif-objectname}} ]".
 3491+
 3492+This is extracted from XMP\'s photoshop:headline ( http://www.iptc.org/std/photometadata/specification/IPTC-PhotoMetadata-201007_1.pdf ) and IPTC-iim: 2:105 Headline tag ( http://www.iptc.org/std/IIM/4.1/specification/IIMV4.1.pdf ).',
 3493+'exif-credit' => 'Provider/credit.
 3494+
 3495+Who gave us the image. This might be different from the creator of the image. This is IPTC-iim property 2:110',
 3496+'exif-source' => 'See IPTC-iim standard 2:115 - http://www.iptc.org/std/IIM/4.1/specification/IIMV4.1.pdf.
 3497+
 3498+This is who originally owned the image (a person, stock photo agency, etc). This does not refer to the image this image is based on.',
 3499+'exif-editstatus' => 'Editorial status of image. This is more intended for use with people making news papers. This denotes weather the image is on the main page, is part of a correction, etc. See 2:07 of http://www.iptc.org/std/IIM/4.1/specification/IIMV4.1.pdf',
 3500+'exif-urgency' => 'Urgency. How urgent this image is. 1 is very urgent, 5 is normal, 8 is very low priority.',
 3501+'exif-fixtureidentifier' => 'Fixture name. If this image is part of a regular column in a news paper, name of column goes here.',
 3502+'exif-locationdest' => 'Full printable name of location.',
 3503+'exif-locationdestcode' => 'Code of location depicted. Typically this is an ISO country code, but the IPTC-iim standard also defines other codes like XSP for outer space. See appendix D (and tag 2:100) of http://www.iptc.org/std/IIM/4.1/specification/IIMV4.1.pdf',
 3504+'exif-objectcycle' => 'Time of day that media is intended for. Either morning only, evening only, or all day. Typically only used for news related things that might only be broadcast at a specific time of day. See {{msg-mw|exif-objectcycle-a}}, {{msg-mw|exif-objectcycle-p}} and {{msg-mw|exif-objectcycle-b}} for the values that this message labels.',
 3505+'exif-contact' => 'Contact information of the person responsible for the image.',
 3506+'exif-writer' => 'The person who wrote the caption of the image. See Description Writer on page 18 of http://www.iptc.org/std/photometadata/specification/IPTC-PhotoMetadata-201007_1.pdf',
 3507+'exif-languagecode' => 'Language of image/media.
 3508+
 3509+This is taken from IPTC-iim 2:135 and XMP\'s dc:language.',
 3510+'exif-iimversion' => 'IIM version number. Version of information interchange 2:xx records. 4 is current version. 2 is often seen as well. This is the value stored 2:00 field (Note, iptc-iim also stores a model version in 1:00. This version field displays the 2:00 record only)',
 3511+'exif-iimcategory' => 'Primary Category of image (or other media). Technically supposed to be limited to 3 characters, however that is not always followed. Some common 3 letter category abbreviations are expanded by mediawiki. Similar to {{msg-mw|exif-keywords}}.',
 3512+'exif-iimsupplementalcategory' => 'Supplemental categories. Like {{msg-mw|exif-iimcategory}} but for categories beyond the main one.',
 3513+'exif-datetimeexpires' => 'Date after which not to use the image (media). This is often used in news situations were certain things (like forecasts) should not be used after a specified date.',
 3514+'exif-datetimereleased' => 'Earliest date the image (media) can be used. See 2:30 of http://www.iptc.org/std/IIM/4.1/specification/IIMV4.1.pdf',
 3515+'exif-originaltransmissionref' => 'This is basically a job id. This could help an individual keep track of for what reason the image was created. See Job Id on page 19 of http://www.iptc.org/std/photometadata/specification/IPTC-PhotoMetadata-201007_1.pdf ',
 3516+'exif-lens' => 'Description of lens used. This is taken from aux:Lens XMP property. See http://www.adobe.com/content/dam/Adobe/en/devnet/xmp/pdfs/XMPSpecificationPart2.pdf',
 3517+'exif-serialnumber' => 'Serial number of camera. See aux:SerialNumber in http://www.adobe.com/content/dam/Adobe/en/devnet/xmp/pdfs/XMPSpecificationPart2.pdf',
 3518+'exif-cameraownername' => 'Who owns the camera.',
 3519+'exif-label' => 'Label given to the image for organizational purposes. This is very similar to {{msg-mw|exif-keywords}}. Label is more used by a person to organize their media, where keywords are used to describe the photo contents itself.
 3520+
 3521+This property can come from xmp:Label in XMP ( http://www.adobe.com/content/dam/Adobe/en/devnet/xmp/pdfs/XMPSpecificationPart1.pdf ) or the label textual chunk in PNG.',
 3522+'exif-datetimemetadata' => 'Date metadata was last modified. Typically this refers to XMP metadata.',
 3523+'exif-nickname' => 'Short informal name of image. See http://www.adobe.com/content/dam/Adobe/en/devnet/xmp/pdfs/XMPSpecificationPart2.pdf',
 3524+'exif-rating' => 'This is a rating for how good the image is. The range is between 1 to 5 (5 highest), with an additional option of "reject".',
 3525+'exif-rightscertificate' => 'URL of Rights management certificate. This comes from XMPRights:Certificate property. See http://www.adobe.com/content/dam/Adobe/en/devnet/xmp/pdfs/XMPSpecificationPart1.pdf',
 3526+'exif-copyrighted' => 'Copyright status. This is a true or false field showing either Copyrighted or Public Domain. It should be noted that Copyrighted includes freely-licensed works.',
 3527+'exif-copyrightowner' => 'Copyright owner. Can have more than one person or entity.',
 3528+'exif-usageterms' => 'Terms under which you\'re allowed to use the image/media.',
 3529+'exif-webstatement' => 'URL detailing the copyright status of the image, and how you\'re allowed to use the image. Often this is a link to a creative commons license, however the creative commons people recommend using a page that generally contains specific information about the image, and recommend using {{msg-mw|exif-licenseurl}} for linking to the license. See http://wiki.creativecommons.org/XMP',
 3530+'exif-originaldocumentid' => 'A unique id of the original document (image) that this document (image) is based on.',
 3531+'exif-licenseurl' => 'URL for copyright license. This is almost always a creative commons license since this information comes from the creative commons namespace of XMP (but could be a link to any type of license). See also {{msg-mw|exif-webstatement}}',
 3532+'exif-morepermissionsurl' => 'A url where you can "buy" (or otherwise negotiate) to get more rights for the image.',
 3533+'exif-attributionurl' => 'A url that you\'re supposed to use when re-using the image.',
 3534+'exif-preferredattributionname' => 'The preferred name to give credit to when re-using this image.',
 3535+'exif-disclaimer' => 'Disclaimer for the image.',
 3536+'exif-contentwarning' => 'Content warning for the image. For example if the image/media contains violent, sexual or otherwise offensive content.
 3537+
 3538+This comes from the png warning textual chunk. See http://www.w3.org/TR/PNG/#11keywords',
 3539+'exif-intellectualgenre' => 'The "intellectual genre" of the image/media item. This typically means the type of item it is, ignoring the actual content of the item. See http://cv.iptc.org/newscodes/genre/ for some examples of the types of values this field might have.',
 3540+'exif-subjectnewscode' => 'A (or multiple) codes describing the content of the image/media. The code is an 8 digit number representing some sort of category. The code is hierarchical , with the first two digits being a broad category (this broad category is shown to the user. See {{msg-mw|exif-subjectnewscode-value}} for how the value this field labels is shown to user). See http://cv.iptc.org/newscodes/subjectcode for the full list of codes.',
 3541+'exif-scenecode' => 'IPTC (numeric) scene code. Contains information on what type of scene it is (like panoramic scene, close-up, etc). See http://cv.iptc.org/newscodes/scene/',
 3542+'exif-event' => 'The event depicted',
 3543+'exif-organisationinimage' => 'Name of organisations shown in image',
 3544+'exif-personinimage' => 'Name of person depicted in image',
 3545+'exif-originalimageheight' => 'Height of image before it was cropped in pixels
 3546+
 3547+{{identical|Height}}',
 3548+'exif-originalimagewidth' => 'Width of image before it was cropped in pixels
 3549+
 3550+{{identical|Width}}',
 3551+'exif-identifier' => 'A formal identifier for the image. Often this is a URL.',
 3552+'exif-dc-contributor' => 'People who helped make the resource, but are secondary in contribution to the author.',
 3553+'exif-dc-coverage' => '"The extent or scope of the resource" see dc:coverage in http://www.adobe.com/content/dam/Adobe/en/devnet/xmp/pdfs/XMPSpecificationPart2.pdf',
 3554+'exif-dc-date' => 'One or more dates associated with the image. How they are associated is not really defined. From the dc:date XMP property.',
 3555+'exif-dc-publisher' => 'One or more publisher of resource',
 3556+'exif-dc-relation' => 'Something related to this image. Often a list of url\'s to related images.',
 3557+'exif-dc-rights' => 'Copyright information about the image/media given in informal language.',
 3558+'exif-dc-source' => 'Source of the image. This is another image that this image is based on. This does not refer to the person who provided the image.',
 3559+'exif-dc-type' => 'Type or genre of image/media. This might be something like painting or photograph.',
 3560+
 3561+
33813562 # EXIF attributes
33823563 'exif-compression-6' => '{{optional}}',
33833564
@@ -3401,8 +3582,8 @@
34023583
34033584 CCW is an abbreviation for counter-clockwise.',
34043585
3405 -'exif-colorspace-1' => '{{optional}}',
3406 -'exif-colorspace-ffff.h' => '{{optional}}',
 3586+'exif-colorspace-1' => '{{Optional}} If it uses the standard sRGB colour space.',
 3587+'exif-colorspace-65535' => 'The photograph is not colour calibrated.',
34073588
34083589 'exif-componentsconfiguration-1' => '{{optional}}',
34093590 'exif-componentsconfiguration-2' => '{{optional}}',
@@ -3449,8 +3630,6 @@
34503631 'exif-sensingmethod-5' => "''Color sequential'' means, that the three base colors are measured one after another (i.e. the sensor is first measuring red, than green, than blue).",
34513632 'exif-sensingmethod-8' => "''Color sequential'' means, that the three base colors are measured one after another (i.e. the sensor is first measuring red, than green, than blue).",
34523633
3453 -'exif-filesource-3' => '{{optional}}',
3454 -
34553634 'exif-exposuremode-2' => "A type of exposure mode shown as part of the metadata on image description pages. The Wikipedia article on [http://en.wikipedia.org/wiki/Bracketing#Exposure_bracketing bracketing] says that 'auto bracket' is a camera exposure setting which automatically takes a series of pictures at slightly different light exposures.",
34563635
34573636 'exif-scenecapturetype-0' => '{{Identical|Standard}}',
@@ -3490,7 +3669,82 @@
34913670
34923671 # Pseudotags used for GPSSpeedRef
34933672 'exif-gpsspeed-n' => "Knots: ''Knot'' is a unit of speed on water used for ships, etc., equal to one nautical mile per hour.",
 3673+# Pseudotags used for GPSLatitudeRef and GPSDestLatitudeRef
 3674+'exif-gpslatitude-n' => 'Very rarely used. Only used when using an old version of Mediawiki as a foreign image repo.',
 3675+'exif-gpslatitude-s' => 'Very rarely used. Only used when using an old version of Mediawiki as a foreign image repo.',
34943676
 3677+# Pseudotags used for GPSLongitudeRef and GPSDestLongitudeRef
 3678+'exif-gpslongitude-e' => 'Very rarely used. Only used when using an old version of Mediawiki as a foreign image repo.',
 3679+'exif-gpslongitude-w' => 'Very rarely used. Only used when using an old version of Mediawiki as a foreign image repo.',
 3680+
 3681+'exif-gpsmeasuremode-2' => 'Only latitude and longitude recorded, no altitude.',
 3682+'exif-gpsmeasuremode-3' => 'Latitude, longitude, and altitude recorded.',
 3683+
 3684+'exif-gpsdop-excellent' => '$1 is the actual HDOP/PDOP value (less than or equal to 2 for excellent). See http://en.wikipedia.org/wiki/Dilution_of_precision_(GPS)',
 3685+'exif-gpsdop-good' => '$1 is the actual HDOP/PDOP value (2-5 for good). See http://en.wikipedia.org/wiki/Dilution_of_precision_(GPS)',
 3686+'exif-gpsdop-moderate' => '$1 is the actual HDOP/PDOP value (5-10 for moderate). See http://en.wikipedia.org/wiki/Dilution_of_precision_(GPS)',
 3687+'exif-gpsdop-fair' => '$1 is the actual HDOP/PDOP value (10-20 for fair). See http://en.wikipedia.org/wiki/Dilution_of_precision_(GPS)',
 3688+'exif-gpsdop-poor' => '$1 is the actual HDOP/PDOP value (greater than 20 for poor). See http://en.wikipedia.org/wiki/Dilution_of_precision_(GPS)',
 3689+
 3690+'exif-objectcycle-a' => 'Morning only (a is for AM)',
 3691+'exif-objectcycle-p' => 'Evening only (p is for PM)',
 3692+'exif-objectcycle-b' => 'Both morning and evening (b is for both)',
 3693+
 3694+'exif-ycbcrpositioning-1' => 'If the Chrominance samples are centered with respect to the Luminance samples.',
 3695+'exif-ycbcrpositioning-2' => 'If the Chrominance samples are on top of to the Luminance samples.',
 3696+
 3697+'exif-software-version-value' => 'This is very rarely used, and only with iptc-iim 2:70 property. $1 is the Software name, $2 is its version.',
 3698+
 3699+'exif-copyrighted-true' => 'The image is under copyright (including if its copyrighted but freely licensed)',
 3700+'exif-copyrighted-false' => 'The image is Public domain',
 3701+
 3702+'exif-rating-rejected' => 'If the rating field has a rating of -1 to mean that the file was totally "rejected"',
 3703+
 3704+'exif-isospeedratings-overflow' => 'Exif can\'t store iso speed ratings beyond 65535. This message is shown if the iso speed is too big to be stored.',
 3705+
 3706+'exif-maxaperturevalue-value' => '{{Optional}}
 3707+$1 is maxaperture in APEX units (APEX aperture units = 2log<sub>2</sub>(f-number) ). $2 is the value in the more traditional f-number units.',
 3708+
 3709+'exif-contact-value' => '{{optional}}
 3710+*$1 is email
 3711+*$2 is URL of website
 3712+*$3 is street address.
 3713+*$4 is city
 3714+*$5 is region
 3715+*$6 is postal code
 3716+*$7 is country
 3717+*$8 is telephone number
 3718+Note, not all fields are guaranteed to be present, some may be empty strings.',
 3719+
 3720+'exif-iimcategory-ace' => 'Displayed as part of the iimcategory field if the 3 letter code is recognized, or as part {{msg-mw|exif-subjectnewscode-value}}',
 3721+'exif-iimcategory-clj' => 'Displayed as part of the iimcategory field if the 3 letter code is recognized, or as part {{msg-mw|exif-subjectnewscode-value}}',
 3722+'exif-iimcategory-dis' => 'Displayed as part of the iimcategory field if the 3 letter code is recognized, or as part {{msg-mw|exif-subjectnewscode-value}}',
 3723+'exif-iimcategory-fin' => 'Displayed as part of the iimcategory field if the 3 letter code is recognized, or as part {{msg-mw|exif-subjectnewscode-value}}',
 3724+'exif-iimcategory-edu' => 'Displayed as part of the iimcategory field if the 3 letter code is recognized, or as part {{msg-mw|exif-subjectnewscode-value}}',
 3725+'exif-iimcategory-evn' => 'Displayed as part of the iimcategory field if the 3 letter code is recognized, or as part {{msg-mw|exif-subjectnewscode-value}}',
 3726+'exif-iimcategory-hth' => 'Displayed as part of the iimcategory field if the 3 letter code is recognized, or as part {{msg-mw|exif-subjectnewscode-value}}',
 3727+'exif-iimcategory-hum' => 'Displayed as part of the iimcategory field if the 3 letter code is recognized, or as part {{msg-mw|exif-subjectnewscode-value}}',
 3728+'exif-iimcategory-lab' => 'Displayed as part of the iimcategory field if the 3 letter code is recognized, or as part {{msg-mw|exif-subjectnewscode-value}}',
 3729+'exif-iimcategory-lif' => 'Displayed as part of the iimcategory field if the 3 letter code is recognized, or as part {{msg-mw|exif-subjectnewscode-value}}',
 3730+'exif-iimcategory-pol' => 'Displayed as part of the iimcategory field if the 3 letter code is recognized, or as part {{msg-mw|exif-subjectnewscode-value}}',
 3731+'exif-iimcategory-rel' => 'Displayed as part of the iimcategory field if the 3 letter code is recognized, or as part {{msg-mw|exif-subjectnewscode-value}}',
 3732+'exif-iimcategory-sci' => 'Displayed as part of the iimcategory field if the 3 letter code is recognized, or as part {{msg-mw|exif-subjectnewscode-value}}',
 3733+'exif-iimcategory-soi' => 'Displayed as part of the iimcategory field if the 3 letter code is recognized, or as part {{msg-mw|exif-subjectnewscode-value}}',
 3734+'exif-iimcategory-spo' => 'Displayed as part of the iimcategory field if the 3 letter code is recognized, or as part {{msg-mw|exif-subjectnewscode-value}}',
 3735+'exif-iimcategory-war' => 'Displayed as part of the iimcategory field if the 3 letter code is recognized, or as part {{msg-mw|exif-subjectnewscode-value}}',
 3736+'exif-iimcategory-wea' => 'Displayed as part of the iimcategory field if the 3 letter code is recognized, or as part {{msg-mw|exif-subjectnewscode-value}}',
 3737+
 3738+'exif-subjectnewscode-value' => '{{Optional}}
 3739+
 3740+*$1 is numeric IPTC subject news code (one of http://cv.iptc.org/newscodes/subjectcode )
 3741+*$2 is one of 17 broad categories that the code falls into. For example any code starting with 15 has the contents of {{msg-mw|exif-iimcategory-spo}} for $2.',
 3742+
 3743+'exif-urgency-normal' => '$1 is numeric priority (aka 5 for normal)',
 3744+'exif-urgency-low' => '$1 is numeric priority (6-8 for low)',
 3745+'exif-urgency-high' => '$1 is numeric priority (1-4 for high)',
 3746+'exif-urgency-other' => '$1 is numeric priority. Most specs define 0 and 9 to either be reserved or not allowed. However the exiftool documentation defines 0 to be reserved and 9 to be user-defined priority.',
 3747+
 3748+
34953749 # External editor support
34963750 'edit-externally' => 'Displayed on image description pages. See for example [[:Image:Yes.png#filehistory]].',
34973751 'edit-externally-help' => 'Displayed on image description pages. See for example [[:Image:Yes.png#filehistory]].
Index: trunk/phase3/languages/messages/MessagesIt.php
@@ -3035,7 +3035,7 @@
30363036 'exif-xyresolution-i' => '$1 punti per pollice (dpi)',
30373037 'exif-xyresolution-c' => '$1 punti per centimetro (dpc)',
30383038
3039 -'exif-colorspace-ffff.h' => 'Non calibrato',
 3039+'exif-colorspace-65535' => 'Non calibrato',
30403040
30413041 'exif-componentsconfiguration-0' => 'assente',
30423042
Index: trunk/phase3/languages/messages/MessagesEn.php
@@ -3668,12 +3668,14 @@
36693669 'variantname-tg' => 'tg', # only translate this message to other languages if you have to change it
36703670
36713671 # Metadata
3672 -'metadata' => 'Metadata',
3673 -'metadata-help' => 'This file contains additional information, probably added from the digital camera or scanner used to create or digitize it.
 3672+'metadata' => 'Metadata',
 3673+'metadata-help' => 'This file contains additional information, probably added from the digital camera or scanner used to create or digitize it.
36743674 If the file has been modified from its original state, some details may not fully reflect the modified file.',
3675 -'metadata-expand' => 'Show extended details',
3676 -'metadata-collapse' => 'Hide extended details',
3677 -'metadata-fields' => 'EXIF metadata fields listed in this message will be included on image page display when the metadata table is collapsed.
 3675+'metadata-expand' => 'Show extended details',
 3676+'metadata-collapse' => 'Hide extended details',
 3677+'metadata-langitem' => '\'\'\'$2:\'\'\' $1',
 3678+'metadata-langitem-default' => '$1',
 3679+'metadata-fields' => 'Image metadata fields listed in this message will be included on image page display when the metadata table is collapsed.
36783680 Others will be hidden by default.
36793681 * make
36803682 * model
@@ -3681,7 +3683,13 @@
36823684 * exposuretime
36833685 * fnumber
36843686 * isospeedratings
3685 -* focallength',
 3687+* focallength
 3688+* artist
 3689+* copyright
 3690+* imagedescription
 3691+* gpslatitude
 3692+* gpslongitude
 3693+* gpsaltitude',
36863694
36873695 # EXIF tags
36883696 'exif-imagewidth' => 'Width',
@@ -3696,13 +3704,11 @@
36973705 'exif-ycbcrpositioning' => 'Y and C positioning',
36983706 'exif-xresolution' => 'Horizontal resolution',
36993707 'exif-yresolution' => 'Vertical resolution',
3700 -'exif-resolutionunit' => 'Unit of X and Y resolution',
37013708 'exif-stripoffsets' => 'Image data location',
37023709 'exif-rowsperstrip' => 'Number of rows per strip',
37033710 'exif-stripbytecounts' => 'Bytes per compressed strip',
37043711 'exif-jpeginterchangeformat' => 'Offset to JPEG SOI',
37053712 'exif-jpeginterchangeformatlength' => 'Bytes of JPEG data',
3706 -'exif-transferfunction' => 'Transfer function',
37073713 'exif-whitepoint' => 'White point chromaticity',
37083714 'exif-primarychromaticities' => 'Chromaticities of primarities',
37093715 'exif-ycbcrcoefficients' => 'Color space transformation matrix coefficients',
@@ -3719,9 +3725,8 @@
37203726 'exif-colorspace' => 'Color space',
37213727 'exif-componentsconfiguration' => 'Meaning of each component',
37223728 'exif-compressedbitsperpixel' => 'Image compression mode',
3723 -'exif-pixelydimension' => 'Valid image width',
3724 -'exif-pixelxdimension' => 'Valid image height',
3725 -'exif-makernote' => 'Manufacturer notes',
 3729+'exif-pixelydimension' => 'Image width',
 3730+'exif-pixelxdimension' => 'Image height',
37263731 'exif-usercomment' => 'User comments',
37273732 'exif-relatedsoundfile' => 'Related audio file',
37283733 'exif-datetimeoriginal' => 'Date and time of data generation',
@@ -3736,11 +3741,10 @@
37373742 'exif-exposureprogram' => 'Exposure Program',
37383743 'exif-spectralsensitivity' => 'Spectral sensitivity',
37393744 'exif-isospeedratings' => 'ISO speed rating',
3740 -'exif-oecf' => 'Optoelectronic conversion factor',
3741 -'exif-shutterspeedvalue' => 'Shutter speed',
3742 -'exif-aperturevalue' => 'Aperture',
3743 -'exif-brightnessvalue' => 'Brightness',
3744 -'exif-exposurebiasvalue' => 'Exposure bias',
 3745+'exif-shutterspeedvalue' => 'APEX shutter speed',
 3746+'exif-aperturevalue' => 'APEX aperture',
 3747+'exif-brightnessvalue' => 'APEX brightness',
 3748+'exif-exposurebiasvalue' => 'APEX exposure bias',
37453749 'exif-maxaperturevalue' => 'Maximum land aperture',
37463750 'exif-subjectdistance' => 'Subject distance',
37473751 'exif-meteringmode' => 'Metering mode',
@@ -3750,7 +3754,6 @@
37513755 'exif-focallength-format' => '$1 mm', # only translate this message to other languages if you have to change it
37523756 'exif-subjectarea' => 'Subject area',
37533757 'exif-flashenergy' => 'Flash energy',
3754 -'exif-spatialfrequencyresponse' => 'Spatial frequency response',
37553758 'exif-focalplanexresolution' => 'Focal plane X resolution',
37563759 'exif-focalplaneyresolution' => 'Focal plane Y resolution',
37573760 'exif-focalplaneresolutionunit' => 'Focal plane resolution unit',
@@ -3759,7 +3762,6 @@
37603763 'exif-sensingmethod' => 'Sensing method',
37613764 'exif-filesource' => 'File source',
37623765 'exif-scenetype' => 'Scene type',
3763 -'exif-cfapattern' => 'CFA pattern',
37643766 'exif-customrendered' => 'Custom image processing',
37653767 'exif-exposuremode' => 'Exposure mode',
37663768 'exif-whitebalance' => 'White balance',
@@ -3804,12 +3806,77 @@
38053807 'exif-gpsareainformation' => 'Name of GPS area',
38063808 'exif-gpsdatestamp' => 'GPS date',
38073809 'exif-gpsdifferential' => 'GPS differential correction',
 3810+'exif-coordinate-format' => '$1° $2′ $3″ $4',
 3811+'exif-jpegfilecomment' => 'JPEG file comment',
 3812+'exif-keywords' => 'Keywords',
 3813+'exif-worldregioncreated' => 'World region that the picture was taken in',
 3814+'exif-countrycreated' => 'Country that the picture was taken in',
 3815+'exif-countrycodecreated' => 'Code for the country that the picture was taken in',
 3816+'exif-provinceorstatecreated' => 'Province or state that the picture was taken in',
 3817+'exif-citycreated' => 'City that the picture was taken in',
 3818+'exif-sublocationcreated' => 'Sublocation of the city that the picture was taken in',
 3819+'exif-worldregiondest' => 'World region shown',
 3820+'exif-countrydest' => 'Country shown',
 3821+'exif-countrycodedest' => 'Code for country shown',
 3822+'exif-provinceorstatedest' => 'Province or state shown',
 3823+'exif-citydest' => 'City shown',
 3824+'exif-sublocationdest' => 'Sublocation of city shown',
38083825 'exif-objectname' => 'Short title',
 3826+'exif-specialinstructions' => 'Special instructions',
 3827+'exif-headline' => 'Headline',
 3828+'exif-credit' => 'Credit/Provider',
 3829+'exif-source' => 'Source',
 3830+'exif-editstatus' => 'Editorial status of image',
 3831+'exif-urgency' => 'Urgency',
 3832+'exif-fixtureidentifier' => 'Fixture name',
 3833+'exif-locationdest' => 'Location depicted',
 3834+'exif-locationdestcode' => 'Code of location depicted',
 3835+'exif-objectcycle' => 'Time of day that media is intended for',
 3836+'exif-contact' => 'Contact information',
 3837+'exif-writer' => 'Writer',
 3838+'exif-languagecode' => 'Language',
 3839+'exif-iimversion' => 'IIM version',
 3840+'exif-iimcategory' => 'Category',
 3841+'exif-iimsupplementalcategory' => 'Supplemental categories',
 3842+'exif-datetimeexpires' => 'Do not use after',
 3843+'exif-datetimereleased' => 'Released on',
 3844+'exif-originaltransmissionref' => 'Original transmission location code',
 3845+'exif-lens' => 'Lens used',
 3846+'exif-serialnumber' => 'Serial number of camera',
 3847+'exif-cameraownername' => 'Owner of camera',
 3848+'exif-label' => 'Label',
 3849+'exif-datetimemetadata' => 'Date metadata was last modified',
 3850+'exif-nickname' => 'Informal name of image',
 3851+'exif-rating' => 'Rating (out of 5)',
 3852+'exif-rightscertificate' => 'Rights management certificate',
 3853+'exif-copyrighted' => 'Copyright status',
 3854+'exif-copyrightowner' => 'Copyright owner',
 3855+'exif-usageterms' => 'Usage terms',
 3856+'exif-webstatement' => 'Online copyright statement',
 3857+'exif-originaldocumentid' => 'Unique ID of original document',
 3858+'exif-licenseurl' => 'URL for copyright license',
 3859+'exif-morepermissionsurl' => 'Alternative licensing information',
 3860+'exif-attributionurl' => 'When re-using this work, please link to',
 3861+'exif-preferredattributionname' => 'When re-using this work, please credit',
 3862+'exif-pngfilecomment' => 'PNG file comment',
 3863+'exif-disclaimer' => 'Disclaimer',
 3864+'exif-contentwarning' => 'Content warning',
 3865+'exif-giffilecomment' => 'GIF file comment',
 3866+'exif-intellectualgenre' => 'Type of item',
 3867+'exif-subjectnewscode' => 'Subject code',
 3868+'exif-scenecode' => 'IPTC scene code',
 3869+'exif-event' => 'Event depicted',
 3870+'exif-organisationinimage' => 'Organization depicted',
 3871+'exif-personinimage' => 'Person depicted',
 3872+'exif-originalimageheight' => 'Height of image before it was cropped',
 3873+'exif-originalimagewidth' => 'Width of image before it was cropped',
38093874
 3875+
38103876 # Make & model, can be wikified in order to link to the camera and model name
3811 -'exif-make-value' => '$1', # do not translate or duplicate this message to other languages
3812 -'exif-model-value' => '$1', # do not translate or duplicate this message to other languages
3813 -'exif-software-value' => '$1', # do not translate or duplicate this message to other languages
 3877+'exif-make-value' => '$1', # do not translate or duplicate this message to other languages
 3878+'exif-model-value' => '$1', # do not translate or duplicate this message to other languages
 3879+'exif-software-value' => '$1', # do not translate or duplicate this message to other languages
 3880+'exif-software-version-value' => '$1 (Version $2)',
38143881
38153882 # EXIF attributes
38163883 'exif-compression-1' => 'Uncompressed',
@@ -3836,7 +3903,7 @@
38373904 'exif-xyresolution-c' => '$1 dpc', # only translate this message to other languages if you have to change it
38383905
38393906 'exif-colorspace-1' => 'sRGB', # only translate this message to other languages if you have to change it
3840 -'exif-colorspace-ffff.h' => 'FFFF.H', # only translate this message to other languages if you have to change it
 3907+'exif-colorspace-65535' => 'Uncalibrated', # only translate this message to other languages if you have to change it
38413908
38423909 'exif-componentsconfiguration-0' => 'does not exist',
38433910 'exif-componentsconfiguration-1' => 'Y', # only translate this message to other languages if you have to change it
@@ -3860,9 +3927,9 @@
38613928
38623929 'exif-meteringmode-0' => 'Unknown',
38633930 'exif-meteringmode-1' => 'Average',
3864 -'exif-meteringmode-2' => 'CenterWeightedAverage',
 3931+'exif-meteringmode-2' => 'Center weighted average',
38653932 'exif-meteringmode-3' => 'Spot',
3866 -'exif-meteringmode-4' => 'MultiSpot',
 3933+'exif-meteringmode-4' => 'Multi-Spot',
38673934 'exif-meteringmode-5' => 'Pattern',
38683935 'exif-meteringmode-6' => 'Partial',
38693936 'exif-meteringmode-255' => 'Other',
@@ -3911,7 +3978,7 @@
39123979 'exif-sensingmethod-7' => 'Trilinear sensor',
39133980 'exif-sensingmethod-8' => 'Color sequential linear sensor',
39143981
3915 -'exif-filesource-3' => 'DSC', # only translate this message to other languages if you have to change it
 3982+'exif-filesource-3' => 'Digital still camera',
39163983
39173984 'exif-scenetype-1' => 'A directly photographed image',
39183985
@@ -3961,6 +4028,10 @@
39624029 'exif-gpslongitude-e' => 'East longitude',
39634030 'exif-gpslongitude-w' => 'West longitude',
39644031
 4032+# Pseudotags used for GPSAltitude
 4033+'exif-gpsaltitude-above-sealevel' => '$1 {{plural:$1|meters|meter}} above sea level',
 4034+'exif-gpsaltitude-below-sealevel' => '$1 {{plural:$1|meters|meter}} below sea level',
 4035+
39654036 'exif-gpsstatus-a' => 'Measurement in progress',
39664037 'exif-gpsstatus-v' => 'Measurement interoperability',
39674038
@@ -3972,10 +4043,85 @@
39734044 'exif-gpsspeed-m' => 'Miles per hour',
39744045 'exif-gpsspeed-n' => 'Knots',
39754046
 4047+# Pseudotags used for GPSDestDistanceRef
 4048+'exif-gpsdestdistance-k' => 'Kilometres',
 4049+'exif-gpsdestdistance-m' => 'Miles',
 4050+'exif-gpsdestdistance-n' => 'Nautical miles',
 4051+
39764052 # Pseudotags used for GPSTrackRef, GPSImgDirectionRef and GPSDestBearingRef
39774053 'exif-gpsdirection-t' => 'True direction',
39784054 'exif-gpsdirection-m' => 'Magnetic direction',
39794055
 4056+'exif-gpsdop-excellent' => 'Excellent ($1)',
 4057+'exif-gpsdop-good' => 'Good ($1)',
 4058+'exif-gpsdop-moderate' => 'Moderate ($1)',
 4059+'exif-gpsdop-fair' => 'Fair ($1)',
 4060+'exif-gpsdop-poor' => 'Poor ($1)',
 4061+
 4062+'exif-objectcycle-a' => 'Morning only',
 4063+'exif-objectcycle-p' => 'Evening only',
 4064+'exif-objectcycle-b' => 'Both morning and evening',
 4065+
 4066+'exif-ycbcrpositioning-1' => 'Centered',
 4067+'exif-ycbcrpositioning-2' => 'Co-sited',
 4068+
 4069+'exif-identifier' => 'Identifier',
 4070+# dc stuff
 4071+
 4072+'exif-dc-contributor' => 'Contributors',
 4073+'exif-dc-coverage' => 'Spatial or temporal scope of media',
 4074+'exif-dc-date' => 'Date(s)',
 4075+'exif-dc-publisher' => 'Publisher',
 4076+'exif-dc-relation' => 'Related media',
 4077+'exif-dc-rights' => 'Rights',
 4078+'exif-dc-source' => 'Source media',
 4079+'exif-dc-type' => 'Type of media',
 4080+
 4081+'exif-copyrighted-true' => 'Copyrighted',
 4082+'exif-copyrighted-false' => 'Public domain',
 4083+
 4084+'exif-rating-rejected' => 'Rejected',
 4085+
 4086+'exif-isospeedratings-overflow' => 'Greater than 65535',
 4087+
 4088+'exif-maxaperturevalue-value' => '$1 APEX (f/$2)',
 4089+
 4090+'exif-contact-value' => '$1
 4091+
 4092+$2
 4093+<div class="adr">
 4094+$3
 4095+
 4096+$4, $5, $6 $7
 4097+</div>
 4098+$8',
 4099+
 4100+'exif-iimcategory-ace' => 'Arts, culture and enterntainment',
 4101+'exif-iimcategory-clj' => 'Crime and law',
 4102+'exif-iimcategory-dis' => 'Disasters and accidents',
 4103+'exif-iimcategory-fin' => 'Economy and business',
 4104+'exif-iimcategory-edu' => 'Education',
 4105+'exif-iimcategory-evn' => 'Environment',
 4106+'exif-iimcategory-hth' => 'Health',
 4107+'exif-iimcategory-hum' => 'Human interest',
 4108+'exif-iimcategory-lab' => 'Labour',
 4109+'exif-iimcategory-lif' => 'Lifestyle and leisure',
 4110+'exif-iimcategory-pol' => 'Politics',
 4111+'exif-iimcategory-rel' => 'Religion and belief',
 4112+'exif-iimcategory-sci' => 'Science and technology',
 4113+'exif-iimcategory-soi' => 'Social issues',
 4114+'exif-iimcategory-spo' => 'Sports',
 4115+'exif-iimcategory-war' => 'War, conflict and unrest',
 4116+'exif-iimcategory-wea' => 'Weather',
 4117+
 4118+'exif-subjectnewscode-value' => '$2 ($1)',
 4119+
 4120+'exif-urgency-normal' => 'Normal ($1)',
 4121+'exif-urgency-low' => 'Low ($1)',
 4122+'exif-urgency-high' => 'High ($1)',
 4123+'exif-urgency-other' => 'User-defined priority ($1)',
 4124+
 4125+
39804126 # External editor support
39814127 'edit-externally' => 'Edit this file using an external application',
39824128 'edit-externally-help' => '(See the [http://www.mediawiki.org/wiki/Manual:External_editors setup instructions] for more information)',
Index: trunk/phase3/languages/messages/MessagesScn.php
@@ -2652,7 +2652,7 @@
26532653 'exif-xyresolution-i' => '$1 punti pi puseri (dpi)',
26542654 'exif-xyresolution-c' => '$1 punti pi cintìmitru (dpc)',
26552655
2656 -'exif-colorspace-ffff.h' => 'Nun calibbratu',
 2656+'exif-colorspace-65535' => 'Nun calibbratu',
26572657
26582658 'exif-componentsconfiguration-0' => 'assenti',
26592659
Index: trunk/phase3/languages/messages/MessagesJa.php
@@ -3315,7 +3315,7 @@
33163316 'exif-planarconfiguration-1' => '点順次フォーマット',
33173317 'exif-planarconfiguration-2' => '面順次フォーマット',
33183318
3319 -'exif-colorspace-ffff.h' => 'その他',
 3319+'exif-colorspace-65535' => 'その他',
33203320
33213321 'exif-componentsconfiguration-0' => 'なし',
33223322
Index: trunk/phase3/languages/messages/MessagesFr.php
@@ -3225,7 +3225,7 @@
32263226 'exif-planarconfiguration-1' => 'Données contiguës',
32273227 'exif-planarconfiguration-2' => 'Données séparées',
32283228
3229 -'exif-colorspace-ffff.h' => 'Non calibré',
 3229+'exif-colorspace-65535' => 'Non calibré',
32303230
32313231 'exif-componentsconfiguration-0' => 'N’existe pas',
32323232 'exif-componentsconfiguration-5' => 'V',
Index: trunk/phase3/languages/messages/MessagesNl.php
@@ -3326,7 +3326,7 @@
33273327 'exif-planarconfiguration-1' => 'chunky gegevensformaat',
33283328 'exif-planarconfiguration-2' => 'planar gegevensformaat',
33293329
3330 -'exif-colorspace-ffff.h' => 'Ongekalibreerd',
 3330+'exif-colorspace-65535' => 'Ongekalibreerd',
33313331
33323332 'exif-componentsconfiguration-0' => 'bestaat niet',
33333333
Index: trunk/phase3/languages/messages/MessagesOc.php
@@ -3022,7 +3022,7 @@
30233023 'exif-planarconfiguration-1' => 'Donadas atenentas',
30243024 'exif-planarconfiguration-2' => 'Donadas separadas',
30253025
3026 -'exif-colorspace-ffff.h' => 'Pas calibrat',
 3026+'exif-colorspace-65535' => 'Pas calibrat',
30273027
30283028 'exif-componentsconfiguration-0' => 'existís pas',
30293029 'exif-componentsconfiguration-5' => 'V',
Index: trunk/phase3/languages/messages/MessagesPms.php
@@ -2912,7 +2912,7 @@
29132913 'exif-xyresolution-i' => '$1 pont për pòles (dpi)',
29142914 'exif-xyresolution-c' => '$1 pont për centim (dpc)',
29152915
2916 -'exif-colorspace-ffff.h' => 'Nen calibrà',
 2916+'exif-colorspace-65535' => 'Nen calibrà',
29172917
29182918 'exif-componentsconfiguration-0' => 'a esist pa',
29192919

Follow-up revisions

RevisionCommit summaryAuthorDate
r86171(Follow-up r86169) Add release notes for img metadata changes.bawolff01:31, 16 April 2011
r86174(Follow-up r86169) Fix three minor issues Reedy found....bawolff02:19, 16 April 2011
r86191Fix for r86169: added missing items in messages.inc and rebuilt MessagesEn.phpialex09:03, 16 April 2011
r86194Followup r86169, move unreachable debug statementreedy11:11, 16 April 2011
r86199Rebuild all language files....siebrand11:27, 16 April 2011
r86201Update $wgEXIFMessages for r86169.siebrand11:36, 16 April 2011
r86212(Follow-up r86169) Needed supress warnings around iconv...bawolff15:53, 16 April 2011
r86213follow-up r86195 - That $valid really was reachable :P...bawolff16:23, 16 April 2011
r86219(follow-up r86169) I had {{plural}} screwed up in Exif-gpsaltitude-below-seal...bawolff16:59, 16 April 2011
r86221(follow-up r86169) per Reedy, the $val = $val thing is weird....bawolff17:25, 16 April 2011
r86307Few style/whitespace/comment issues from r86169reedy13:08, 18 April 2011
r86310Few more comment/whitespace issues from r86169reedy13:18, 18 April 2011
r86567Remove the JPEG/TIFF specific metadata code from BitmapHandler and put it in ...bawolff23:15, 20 April 2011
r90682Per CR on r86169, start adding unit tests for metadata extraction....bawolff23:25, 23 June 2011
r94867follow-up r86169: unit tests for extraction of JPEG comment (COM) segments.bawolff05:45, 18 August 2011
r95155follow-up r86169 - 2 minor issues found while writing unit tests...bawolff17:16, 21 August 2011
r95163(follow-up r86169) Moar unit-tests!! (sorry for uttetly huge commit)...bawolff18:05, 21 August 2011

Comments

#Comment by Siebrand (talk | contribs)   11:07, 16 April 2011

Thank you for documenting all/most of the newly added messages. This makes you one of the most i18n/L10n aware developers around, in my opinion. On behalf of hundreds MediaWiki translators, I thank you for allowing them to be able to more easily translate the added messages.

Have a look at r86191. Whenever you add/remove a key to MessagesEn.php, you need to update maintenance/languages/messages.inc, too.

#Comment by Bawolff (talk | contribs)   17:35, 16 April 2011

Thanks. Much of the message documentation was done as a result of Nikerabbit's gentle prodding.

As for messages.inc - I thought I was doing that, but apparently I forgot for a whole bunch of them

#Comment by Reedy (talk | contribs)   11:20, 16 April 2011

Can we do something about the $val = $val;

I think they look daft, even if becomes

					default:
						// no change
						break;
#Comment by Reedy (talk | contribs)   16:30, 20 April 2011

Any reason you added the Deprecated to the Bitmap file?

But not the other ones?

PHP Notice: Use of BitmapHandler::getMetadata is deprecated. [Called from File::getPropsFromPath in /www/w/includes/filerepo/File.php at line 1267] for /wiki/Special:ListFiles

    #Comment by Bawolff (talk | contribs)   19:25, 20 April 2011

    It was because the method was JPEG specific, and I essentially moved it to JPEG.php. It probably won't work for any other file type (except maybe tiff), so really should be replaced with just a return false method for those handlers that don't override getMetadata.

    #Comment by Bawolff (talk | contribs)   23:16, 20 April 2011

    Ok, took it out of the base class in r86567 (It really was very JPEG/TIFF specific).

    #Comment by Reedy (talk | contribs)   10:24, 21 April 2011

    Cheers :)

    #Comment by Brion VIBBER (talk | contribs)   21:29, 21 June 2011

    The problems noted above look solved by now, but I notice there's little or no test coverage -- for a refactor like this, tests to confirm that behavior has not changed are essential and should be a precondition for committing... I would be reverting for this if it were a new commit, but since there's a billion follow-ups we'll wait. :)

    Needs phpunit tests for at least:

    • PNG metadata extraction
    • GIF metadata extraction, including newly-added XMP support
    • JPEG metadata extraction
    • TIFF metadata extraction

    Probably should have tests for the metadata version converty functions -- if nothing's added new versions it should at least be able to get an identity transformation without error. I do see at least one thing claiming 'version 2' metadata though -- what's the output look like?

    #Comment by Bawolff (talk | contribs)   18:13, 21 August 2011

    As of r95163 this is pretty well covered by unit tests, so I'm going to reset to new and rm the need unit tests keyword.


    >I do see at least one thing claiming 'version 2' metadata though -- what's the output look like?

    The metadata converty thing is mostly to make older versions of instant commons happy with the new type of metadata that jpeg's output. With this revision jpeg's can have metadata properties that are multi-valued - for example you can have a file with multiple authors. Older MediaWiki will throw a fit if any of the jpeg metadata keys have non-string values, so the convert function converts everything back to a string.

    Status & tagging log