120 # TIFF Rev. 6.0 Attribute Information (p22)
122 # Tags relating to image structure
125 'BitsPerSample' => [
Exif::SHORT, 3 ], # Number
of bits per component
126 # "When a primary image is JPEG compressed, this designation is not"
127 # "necessary and is omitted." (p23)
128 'Compression' =>
Exif::SHORT, # Compression scheme #p23
129 'PhotometricInterpretation' =>
Exif::SHORT, # Pixel composition #p23
133 'YCbCrSubSampling' => [
Exif::SHORT, 2 ], # Subsampling ratio
of Y to C #p24
139 # Tags relating to recording offset
146 # Tags relating to image
data characteristics
149 'PrimaryChromaticities' => [
Exif::RATIONAL, 6 ], # Chromaticities
of primarities
150 # Color space transformation matrix coefficients #p27
160 'Artist' =>
Exif::ASCII, # Person who created the image
164 #
Exif IFD Attribute Information (p30-31)
166 # @todo NOTE: Nonexistence of this field is taken to mean nonconformance
167 # to the Exif 2.1 AND 2.2 standards
171 # Tags relating to Image Data Characteristics
172 'ColorSpace' =>
Exif::SHORT, # Color space information #p32
174 # Tags relating to image configuration
180 # Tags relating to related user information
184 # Tags relating to related file information
187 # Tags relating to date and time
191 'SubSecTimeOriginal' =>
Exif::ASCII, # DateTimeOriginal subseconds
192 'SubSecTimeDigitized' =>
Exif::ASCII, # DateTimeDigitized subseconds
194 # Tags relating to picture-taking conditions (p31)
198 'SpectralSensitivity' =>
Exif::ASCII, # Spectral sensitivity
199 'ISOSpeedRatings' =>
Exif::SHORT, # ISO speed rating
201 # Optoelectronic conversion factor. Note: We don't have support for this atm.
212 'SubjectArea' => [
Exif::SHORT, 4 ], # Subject area
214 'SpatialFrequencyResponse' =>
Exif::IGNORE, # Spatial frequency response. Not supported atm.
215 'FocalPlaneXResolution' =>
Exif::RATIONAL, # Focal plane X resolution
216 'FocalPlaneYResolution' =>
Exif::RATIONAL, # Focal plane Y resolution
217 'FocalPlaneResolutionUnit' =>
Exif::SHORT, # Focal plane resolution unit #p46
218 'SubjectLocation' => [
Exif::SHORT, 2 ], # Subject location
220 'SensingMethod' =>
Exif::SHORT, # Sensing method #p46
226 'WhiteBalance' =>
Exif::SHORT, # White Balance #p49
228 'FocalLengthIn35mmFilm' =>
Exif::SHORT, # Focal length
in 35 mm film
230 'GainControl' =>
Exif::SHORT, # Scene control #p49-50
235 # Device settings description. This could maybe be supported. Need to find an
236 # example file that uses this to see if it has stuff of interest in it.
237 'SubjectDistanceRange' =>
Exif::SHORT, # Subject distance range #p51
242 # GPS Attribute Information (p52)
245 # Should be an array of 4 Exif::BYTE's. However php treats it as an undefined
246 # Note exif standard calls this GPSVersionID, but php doesn't like the id suffix
247 'GPSLatitudeRef' =>
Exif::ASCII, # North
or South Latitude #p52-53
249 'GPSLongitudeRef' =>
Exif::ASCII, # East
or West Longitude #p53
252 # Altitude reference. Note, the exif standard says this should be an EXIF::Byte,
253 # but php seems to disagree.
255 'GPSTimeStamp' => [
Exif::RATIONAL, 3 ], # GPS time (atomic clock)
262 'GPSTrackRef' =>
Exif::ASCII, # Reference
for direction
of movement #p55
264 'GPSImgDirectionRef' =>
Exif::ASCII, # Reference
for direction
of image #p56
267 'GPSDestLatitudeRef' =>
Exif::ASCII, # Reference
for latitude
of destination #p56
268 'GPSDestLatitude' => [
Exif::RATIONAL, 3 ], # Latitude destination
269 'GPSDestLongitudeRef' =>
Exif::ASCII, # Reference
for longitude
of destination #p57
271 'GPSDestBearingRef' =>
Exif::ASCII, # Reference
for bearing
of destination #p57
273 'GPSDestDistanceRef' =>
Exif::ASCII, # Reference
for distance to destination #p57-58
278 'GPSDifferential' =>
Exif::SHORT, # GPS differential correction
290 wfWarn(
'Exif class did not have byte order specified. ' .
291 'Some properties may be decoded incorrectly.' );
292 $this->byteOrder =
'BE';
295 $this->
debugFile( $this->basename, __FUNCTION__,
true );
296 if ( function_exists(
'exif_read_data' ) ) {
297 MediaWiki\suppressWarnings();
298 $data = exif_read_data( $this->file, 0,
true );
299 MediaWiki\restoreWarnings();
301 throw new MWException(
"Internal error: exif_read_data not present. " .
302 "\$wgShowEXIF may be incorrectly set or not checked by an extension." );
309 $this->mRawExifData = $data ?: [];
319 $this->mFilteredExifData = [];
321 foreach ( array_keys( $this->mRawExifData )
as $section ) {
322 if ( !array_key_exists(
$section, $this->mExifTags ) ) {
323 $this->
debug( $section, __FUNCTION__,
"'$section' is not a valid Exif section" );
328 if ( !array_key_exists(
$tag, $this->mExifTags[
$section] ) ) {
329 $this->
debug( $tag, __FUNCTION__,
"'$tag' is not a valid tag in '$section'" );
339 $this->
debug(
$value, __FUNCTION__,
"'$tag' contained invalid data" );
340 unset( $this->mFilteredExifData[
$tag] );
371 if ( isset( $this->mFilteredExifData[
'GPSAltitude'] )
372 && isset( $this->mFilteredExifData[
'GPSAltitudeRef'] )
377 list( $num, $denom ) = explode(
'/', $this->mFilteredExifData[
'GPSAltitude'] );
378 $this->mFilteredExifData[
'GPSAltitude'] = $num / $denom;
380 if ( $this->mFilteredExifData[
'GPSAltitudeRef'] ===
"\1" ) {
381 $this->mFilteredExifData[
'GPSAltitude'] *= -1;
383 unset( $this->mFilteredExifData[
'GPSAltitudeRef'] );
396 if ( isset( $this->mFilteredExifData[
'ComponentsConfiguration'] ) ) {
397 $val = $this->mFilteredExifData[
'ComponentsConfiguration'];
400 $strLen = strlen( $val );
401 for ( $i = 0; $i < $strLen; $i++ ) {
402 $ccVals[$i] = ord( substr( $val, $i, 1 ) );
404 $ccVals[
'_type'] =
'ol';
405 $this->mFilteredExifData[
'ComponentsConfiguration'] = $ccVals;
415 if ( isset( $this->mFilteredExifData[
'GPSVersion'] ) ) {
416 $val = $this->mFilteredExifData[
'GPSVersion'];
419 $strLen = strlen( $val );
420 for ( $i = 0; $i < $strLen; $i++ ) {
424 $newVal .= ord( substr( $val, $i, 1 ) );
427 if ( $this->byteOrder ===
'LE' ) {
430 for ( $i = strlen( $newVal ) - 1; $i >= 0; $i-- ) {
431 $newVal2 .= substr( $newVal, $i, 1 );
433 $this->mFilteredExifData[
'GPSVersionID'] = $newVal2;
435 $this->mFilteredExifData[
'GPSVersionID'] = $newVal;
437 unset( $this->mFilteredExifData[
'GPSVersion'] );
448 if ( isset( $this->mFilteredExifData[$prop] ) ) {
450 if ( strlen( $this->mFilteredExifData[$prop] ) <= 8 ) {
453 $this->
debug( $this->mFilteredExifData[$prop], __FUNCTION__,
false );
454 unset( $this->mFilteredExifData[$prop] );
458 $charCode = substr( $this->mFilteredExifData[$prop], 0, 8 );
459 $val = substr( $this->mFilteredExifData[$prop], 8 );
461 switch ( $charCode ) {
462 case "\x4A\x49\x53\x00\x00\x00\x00\x00":
464 $charset =
"Shift-JIS";
474 MediaWiki\suppressWarnings();
475 $val = iconv( $charset,
'UTF-8//IGNORE', $val );
476 MediaWiki\restoreWarnings();
480 UtfNormal\Validator::quickIsNFCVerify( $valCopy );
481 if ( $valCopy !== $val ) {
482 MediaWiki\suppressWarnings();
483 $val = iconv(
'Windows-1252',
'UTF-8//IGNORE', $val );
484 MediaWiki\restoreWarnings();
490 if ( strlen( $val ) === 0 ) {
492 $this->
debug( $this->mFilteredExifData[$prop], __FUNCTION__,
"$prop: Is only whitespace" );
493 unset( $this->mFilteredExifData[$prop] );
499 $this->mFilteredExifData[$prop] = $val;
510 if ( isset( $this->mFilteredExifData[$prop] ) ) {
511 $this->mFilteredExifData[$prop] = ord( $this->mFilteredExifData[$prop] );
521 $loc =& $this->mFilteredExifData[$prop];
522 $dir =& $this->mFilteredExifData[$prop .
'Ref'];
525 if ( isset( $loc ) && isset(
$dir )
528 list( $num, $denom ) = explode(
'/', $loc[0] );
529 $res = $num / $denom;
530 list( $num, $denom ) = explode(
'/', $loc[1] );
531 $res += ( $num / $denom ) * ( 1 / 60 );
532 list( $num, $denom ) = explode(
'/', $loc[2] );
533 $res += ( $num / $denom ) * ( 1 / 3600 );
535 if (
$dir ===
'S' ||
$dir ===
'W' ) {
542 if (
$res !==
false ) {
543 $this->mFilteredExifData[$prop] =
$res;
544 unset( $this->mFilteredExifData[$prop .
'Ref'] );
546 unset( $this->mFilteredExifData[$prop] );
547 unset( $this->mFilteredExifData[$prop .
'Ref'] );
597 if ( !is_array( $in ) && sprintf(
'%d', $in ) == $in && $in >= 0 && $in <= 255 ) {
598 $this->
debug( $in, __FUNCTION__,
true );
602 $this->
debug( $in, __FUNCTION__,
false );
613 if ( is_array( $in ) ) {
617 if ( preg_match(
"/[^\x0a\x20-\x7e]/", $in ) ) {
618 $this->
debug( $in, __FUNCTION__,
'found a character not in our whitelist' );
623 if ( preg_match(
'/^\s*$/', $in ) ) {
624 $this->
debug( $in, __FUNCTION__,
'input consisted solely of whitespace' );
637 if ( !is_array( $in ) && sprintf(
'%d', $in ) == $in && $in >= 0 && $in <= 65536 ) {
638 $this->
debug( $in, __FUNCTION__,
true );
642 $this->
debug( $in, __FUNCTION__,
false );
653 if ( !is_array( $in ) && sprintf(
'%d', $in ) == $in && $in >= 0 && $in <= 4294967296 ) {
654 $this->
debug( $in, __FUNCTION__,
true );
658 $this->
debug( $in, __FUNCTION__,
false );
671 # Avoid division by zero
672 if ( !is_array( $in )
673 && preg_match(
'/^(\d+)\/(\d+[1-9]|[1-9]\d*)$/', $in, $m )
675 return $this->
isLong( $m[1] ) && $this->
isLong( $m[2] );
677 $this->
debug( $in, __FUNCTION__,
'fed a non-fraction value' );
688 $this->
debug( $in, __FUNCTION__,
true );
698 if ( $this->
isLong( abs( $in ) ) ) {
699 $this->
debug( $in, __FUNCTION__,
true );
703 $this->
debug( $in, __FUNCTION__,
false );
716 # Avoid division by zero
717 if ( !is_array( $in ) &&
718 preg_match(
'/^(-?\d+)\/(\d+[1-9]|[1-9]\d*)$/', $in, $m )
722 $this->
debug( $in, __FUNCTION__,
'fed a non-fraction value' );
743 if ( is_array( $etype ) ) {
744 list( $etype, $ecount ) = $etype;
749 $count =
count( $val );
750 if ( $ecount != $count ) {
751 $this->
debug( $val, __FUNCTION__,
"Expected $ecount elements for $tag but got $count" );
756 foreach ( $val
as $v ) {
765 switch ( (
string)$etype ) {
769 return $this->
isByte( $val );
781 return $this->
isLong( $val );
807 $this->
debug( $val, __FUNCTION__,
"The tag '$tag' is unknown" );
824 $type = gettype( $in );
825 $class = ucfirst( __CLASS__ );
826 if ( is_array( $in ) ) {
827 $in = print_r( $in,
true );
830 if ( $action ===
true ) {
831 wfDebugLog( $this->log,
"$class::$fname: accepted: '$in' (type: $type)" );
832 } elseif ( $action ===
false ) {
833 wfDebugLog( $this->log,
"$class::$fname: rejected: '$in' (type: $type)" );
834 } elseif ( $action ===
null ) {
835 wfDebugLog( $this->log,
"$class::$fname: input was: '$in' (type: $type)" );
837 wfDebugLog( $this->log,
"$class::$fname: $action (type: $type; content: '$in')" );
851 $class = ucfirst( __CLASS__ );
853 wfDebugLog( $this->log,
"$class::$fname: begin processing: '{$this->basename}'" );
855 wfDebugLog( $this->log,
"$class::$fname: end processing: '{$this->basename}'" );