103 $resolutionunit = !isset( $tags[
'ResolutionUnit'] ) || $tags[
'ResolutionUnit'] == 2 ? 2 : 3;
104 unset( $tags[
'ResolutionUnit'] );
107 unset( $tags[
'HasExtendedXMP'] );
108 unset( $tags[
'AuthorsPosition'] );
109 unset( $tags[
'LocationCreated'] );
110 unset( $tags[
'LocationShown'] );
111 unset( $tags[
'GPSAltitudeRef'] );
113 foreach ( $tags as $tag => &$vals ) {
116 if ( !is_array( $vals ) ) {
121 if ( isset( $vals[
'_type'] ) ) {
122 $type = $vals[
'_type'];
123 unset( $vals[
'_type'] );
130 if ( isset( $tags[$tag][
'_formatted'] ) ) {
132 $tags[$tag][
'_formatted'], $type
138 if ( $tag ===
'GPSTimeStamp' && count( $vals ) === 3 ) {
141 $h = explode(
'/', $vals[0], 2 );
142 $m = explode(
'/', $vals[1], 2 );
143 $s = explode(
'/', $vals[2], 2 );
158 $vals = str_pad( (
string)( (
int)$h[0] / (
int)$h[1] ), 2,
'0', STR_PAD_LEFT )
159 .
':' . str_pad( (
string)( (
int)$m[0] / (
int)$m[1] ), 2,
'0', STR_PAD_LEFT )
160 .
':' . str_pad( (
string)( (
int)$s[0] / (
int)$s[1] ), 2,
'0', STR_PAD_LEFT );
162 $time =
wfTimestamp( TS::MW,
'1971:01:01 ' . $vals );
164 if ( $time && (
int)$time > 0 ) {
173 if ( $tag ===
'Contact' || $tag ===
'CreatorContactInfo' ) {
178 foreach ( $vals as &$val ) {
181 $val = match ( $val ) {
182 1, 2, 3, 4, 5, 6, 7, 8,
185 34712 => $this->exifMsg( $tag, $val ),
187 default => $this->
literal( $val )
191 case 'PhotometricInterpretation':
192 $val = match ( $val ) {
193 0, 1, 2, 3, 4, 5, 6, 8, 9, 10,
195 34892 => $this->exifMsg( $tag, $val ),
197 default => $this->
literal( $val )
202 $val = match ( $val ) {
203 1, 2, 3, 4, 5, 6, 7, 8 => $this->exifMsg( $tag, $val ),
205 default => $this->
literal( $val )
209 case 'PlanarConfiguration':
211 case 'YCbCrPositioning':
212 $val = match ( $val ) {
213 1, 2 => $this->exifMsg( $tag, $val ),
215 default => $this->
literal( $val )
221 $val = match ( $resolutionunit ) {
222 2 => $this->exifMsg(
'XYResolution',
'i', $this->formatNum( $val ) ),
223 3 => $this->exifMsg(
'XYResolution',
'c', $this->formatNum( $val ) ),
225 default => $this->
literal( $val )
236 case 'FlashPixVersion':
239 case 'FlashpixVersion':
240 $val = $this->
literal( (
int)$val / 100 );
244 $val = match ( $val ) {
245 1, 65535 => $this->exifMsg( $tag, $val ),
247 default => $this->
literal( $val )
251 case 'ComponentsConfiguration':
252 $val = match ( $val ) {
253 0, 1, 2, 3, 4, 5, 6 => $this->exifMsg( $tag, $val ),
255 default => $this->
literal( $val )
260 case 'DateTimeOriginal':
261 case 'DateTimeDigitized':
262 case 'DateTimeReleased':
263 case 'DateTimeExpires':
266 case 'DateTimeMetadata':
267 case 'FirstPhotoDate':
268 case 'LastPhotoDate':
269 if ( $val ===
null ) {
276 if ( $val ===
'0000:00:00 00:00:00' || $val ===
' : : : : ' ) {
277 $val = $this->
msg(
'exif-unknowndate' )->text();
281 '/^(?:\d{4}):(?:\d\d):(?:\d\d) (?:\d\d):(?:\d\d):(?:\d\d)$/D',
286 if ( $time && (
int)$time > 0 ) {
290 } elseif ( preg_match(
'/^(?:\d{4}):(?:\d\d):(?:\d\d) (?:\d\d):(?:\d\d)$/D', $val ) ) {
295 if ( $time && (
int)$time > 0 ) {
299 } elseif ( preg_match(
'/^(?:\d{4}):(?:\d\d):(?:\d\d)$/D', $val ) ) {
302 . substr( $val, 5, 2 )
303 . substr( $val, 8, 2 )
305 if ( $time && (
int)$time > 0 ) {
314 case 'ExposureProgram':
315 $val = match ( $val ) {
316 0, 1, 2, 3, 4, 5, 6, 7, 8 => $this->exifMsg( $tag, $val ),
318 default => $this->
literal( $val )
322 case 'SubjectDistance':
323 $val = $this->exifMsg( $tag,
'', $this->formatNum( $val ) );
327 $val = match ( $val ) {
328 0, 1, 2, 3, 4, 5, 6, 7, 255 => $this->exifMsg( $tag, $val ),
330 default => $this->
literal( $val )
335 $val = match ( $val ) {
336 0, 1, 2, 3, 4, 9, 10, 11, 12, 13, 14, 15, 17, 18, 19, 20, 21, 22, 23, 24,
337 255 => $this->exifMsg( $tag, $val ),
339 default => $this->
literal( $val )
344 if ( !is_int( $val ) ) {
348 'fired' => $val & 0b00000001,
349 'return' => ( $val & 0b00000110 ) >> 1,
350 'mode' => ( $val & 0b00011000 ) >> 3,
351 'function' => ( $val & 0b00100000 ) >> 5,
352 'redeye' => ( $val & 0b01000000 ) >> 6,
356 # We do not need to handle unknown values since all are used.
357 foreach ( $flashDecode as $subTag => $subValue ) {
358 # We do not need any message for zeroed values.
359 if ( $subTag !==
'fired' && $subValue === 0 ) {
362 $fullTag = $tag .
'-' . $subTag;
363 $flashMsgs[] = $this->exifMsg( $fullTag, $subValue );
365 $val = $this->
getLanguage()->commaList( $flashMsgs );
368 case 'FocalPlaneResolutionUnit':
369 $val = match ( $val ) {
370 2 => $this->exifMsg( $tag, $val ),
372 default => $this->
literal( $val )
376 case 'SensingMethod':
377 $val = match ( $val ) {
378 1, 2, 3, 4, 5, 7, 8 => $this->exifMsg( $tag, $val ),
380 default => $this->
literal( $val )
385 $val = match ( $val ) {
386 3 => $this->exifMsg( $tag, $val ),
388 default => $this->
literal( $val )
393 $val = match ( $val ) {
394 1 => $this->exifMsg( $tag, $val ),
396 default => $this->
literal( $val )
400 case 'CustomRendered':
401 $val = match ( $val ) {
410 8 => $this->exifMsg( $tag, $val ),
412 default => $this->
literal( $val )
420 $val = match ( $val ) {
421 0, 1, 2 => $this->exifMsg( $tag, $val ),
423 default => $this->
literal( $val )
428 $val = match ( $val ) {
429 0, 1 => $this->exifMsg( $tag, $val ),
431 default => $this->
literal( $val )
435 case 'SceneCaptureType':
436 case 'SubjectDistanceRange':
437 $val = match ( $val ) {
438 0, 1, 2, 3 => $this->exifMsg( $tag, $val ),
440 default => $this->
literal( $val )
445 $val = match ( $val ) {
446 0, 1, 2, 3, 4 => $this->exifMsg( $tag, $val ),
448 default => $this->
literal( $val )
453 case 'GPSLatitudeRef':
454 case 'GPSDestLatitudeRef':
455 $val = match ( $val ) {
456 'N',
'S' => $this->exifMsg(
'GPSLatitude', $val ),
458 default => $this->
literal( $val )
462 case 'GPSLongitudeRef':
463 case 'GPSDestLongitudeRef':
464 $val = match ( $val ) {
465 'E',
'W' => $this->exifMsg(
'GPSLongitude', $val ),
467 default => $this->
literal( $val )
473 $val = $this->exifMsg(
'GPSAltitude',
'below-sealevel', $this->formatNum( -$val, 3 ) );
475 $val = $this->exifMsg(
'GPSAltitude',
'above-sealevel', $this->formatNum( $val, 3 ) );
480 $val = match ( $val ) {
481 'A',
'V' => $this->exifMsg( $tag, $val ),
483 default => $this->
literal( $val )
487 case 'GPSMeasureMode':
488 $val = match ( $val ) {
489 2, 3 => $this->exifMsg( $tag, $val ),
491 default => $this->
literal( $val )
496 case 'GPSImgDirectionRef':
497 case 'GPSDestBearingRef':
498 $val = match ( $val ) {
499 'T',
'M' => $this->exifMsg(
'GPSDirection', $val ),
501 default => $this->
literal( $val )
506 case 'GPSDestLatitude':
507 $val = $this->formatCoords( $val,
'latitude' );
510 case 'GPSDestLongitude':
511 $val = $this->formatCoords( $val,
'longitude' );
515 $val = match ( $val ) {
516 'K',
'M',
'N' => $this->exifMsg(
'GPSSpeed', $val ),
518 default => $this->
literal( $val )
522 case 'GPSDestDistanceRef':
523 $val = match ( $val ) {
524 'K',
'M',
'N' => $this->exifMsg(
'GPSDestDistance', $val ),
526 default => $this->
literal( $val )
533 $val = $this->exifMsg( $tag,
'excellent', $this->formatNum( $val ) );
534 } elseif ( $val <= 5 ) {
535 $val = $this->exifMsg( $tag,
'good', $this->formatNum( $val ) );
536 } elseif ( $val <= 10 ) {
537 $val = $this->exifMsg( $tag,
'moderate', $this->formatNum( $val ) );
538 } elseif ( $val <= 20 ) {
539 $val = $this->exifMsg( $tag,
'fair', $this->formatNum( $val ) );
541 $val = $this->exifMsg( $tag,
'poor', $this->formatNum( $val ) );
550 $val = $this->exifMsg( $tag,
'', $this->
literal( $val ) );
554 if ( is_array( $val ) ) {
555 if ( count( $val ) > 1 ) {
558 'exif-software-version-value',
564 $val = $this->exifMsg( $tag,
'', $this->
literal( $val[0] ) );
567 $val = $this->exifMsg( $tag,
'', $this->
literal( $val ) );
573 $val = $this->
msg(
'exif-exposuretime-format',
574 $this->formatFraction( $val ), $this->formatNum( $val ) )->text();
576 case 'ISOSpeedRatings':
580 if ( $val ===
'65535' ) {
581 $val = $this->exifMsg( $tag,
'overflow' );
583 $val = $this->formatNum( $val );
587 $val = $this->
msg(
'exif-fnumber-format',
588 $this->formatNum( $val ) )->text();
592 case 'FocalLengthIn35mmFilm':
593 $val = $this->
msg(
'exif-focallength-format',
594 $this->formatNum( $val ) )->text();
597 case 'MaxApertureValue':
598 if ( str_contains( $val,
'/' ) ) {
600 [ $n, $d ] = explode(
'/', $val, 2 );
601 if ( is_numeric( $n ) && is_numeric( $d ) ) {
602 $val = (int)$n / (
int)$d;
605 if ( is_numeric( $val ) ) {
606 $fNumber = 2 ** ( $val / 2 );
607 if ( is_finite( $fNumber ) ) {
608 $val = $this->
msg(
'exif-maxaperturevalue-value',
609 $this->formatNum( $val ),
610 $this->formatNum( $fNumber, 2 )
619 $val = match ( strtolower( $val ) ) {
622 'ace',
'clj',
'dis',
'fin',
'edu',
'evn',
'hth',
'hum',
'lab',
'lif',
623 'pol',
'rel',
'sci',
'soi',
'spo',
'war',
624 'wea' => $this->exifMsg(
'iimcategory', $val ),
625 default => $this->
literal( $val )
628 case 'SubjectNewsCode':
634 $val = $this->convertNewsCode( $val );
640 if ( $val === 0 || $val === 9 ) {
642 } elseif ( $val < 5 && $val > 1 ) {
644 } elseif ( $val === 5 ) {
646 } elseif ( $val <= 8 && $val > 5 ) {
650 if ( $urgency !==
'' ) {
651 $val = $this->exifMsg(
'urgency',
652 $urgency, $this->
literal( $val )
658 case 'DigitalSourceType':
661 if ( str_starts_with( $val,
'http://cv.iptc.org/newscodes/digitalsourcetype/' ) ) {
662 $code = substr( $val, 47 );
663 $msg = $this->
msg(
'exif-digitalsourcetype-' . strtolower( $code ) );
664 if ( !$msg->isDisabled() ) {
672 case 'OriginalImageHeight':
673 case 'OriginalImageWidth':
674 case 'PixelXDimension':
675 case 'PixelYDimension':
678 $val = $this->formatNum( $val ) .
' ' . $this->
msg(
'unit-pixel' )->text();
688 case 'ImageDescription':
692 case 'RelatedSoundFile':
693 case 'ImageUniqueID':
694 case 'SpectralSensitivity':
695 case 'GPSSatellites':
699 case 'WorldRegionDest':
701 case 'CountryCodeDest':
702 case 'ProvinceOrStateDest':
704 case 'SublocationDest':
705 case 'WorldRegionCreated':
706 case 'CountryCreated':
707 case 'CountryCodeCreated':
708 case 'ProvinceOrStateCreated':
710 case 'SublocationCreated':
712 case 'SpecialInstructions':
717 case 'FixtureIdentifier':
719 case 'LocationDestCode':
721 case 'JPEGFileComment':
722 case 'iimSupplementalCategory':
723 case 'OriginalTransmissionRef':
725 case 'dc-contributor':
734 case 'CameraOwnerName':
737 case 'RightsCertificate':
738 case 'CopyrightOwner':
741 case 'OriginalDocumentID':
743 case 'MorePermissionsUrl':
744 case 'AttributionUrl':
745 case 'PreferredAttributionName':
746 case 'PNGFileComment':
748 case 'ContentWarning':
749 case 'GIFFileComment':
751 case 'IntellectualGenre':
753 case 'OrganisationInImage':
754 case 'PersonInImage':
755 case 'CaptureSoftware':
756 case 'GPSAreaInformation':
757 case 'GPSProcessingMethod':
758 case 'StitchingSoftware':
760 case 'SubSecTimeOriginal':
761 case 'SubSecTimeDigitized':
765 case 'ProjectionType':
766 $val = match ( $val ) {
767 'equirectangular' => $this->exifMsg( $tag, $val ),
768 default => $this->
literal( $val )
772 $val = match ( $val ) {
773 'a',
'p',
'b' => $this->exifMsg( $tag, $val ),
774 default => $this->
literal( $val )
778 case 'UsePanoramaViewer':
779 case 'ExposureLockUsed':
780 $val = match ( $val ) {
781 'True',
'False' => $this->exifMsg( $tag, $val ),
782 default => $this->
literal( $val )
786 if ( $val ===
'-1' ) {
787 $val = $this->exifMsg( $tag,
'rejected' );
789 $val = $this->formatNum( $val );
795 ->getLanguageNameUtils()
796 ->getLanguageName( strtolower( $val ), $this->
getLanguage()->getCode() );
797 $val = $this->
literal( $lang ?: $val );
801 $val = $this->formatNum( $val,
false, $tag );