88f9a78365bfc750ddb18778264d09f9625c738b
3 * This program is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation; either version 2 of the License, or
6 * (at your option) any later version.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
13 * You should have received a copy of the GNU General Public License along
14 * with this program; if not, write to the Free Software Foundation, Inc.,
15 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 * http://www.gnu.org/copyleft/gpl.html
19 * @author Ævar Arnfjörð Bjarmason <avarab@gmail.com>
20 * @copyright Copyright © 2005, Ævar Arnfjörð Bjarmason, 2009 Brent Garber
21 * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License
22 * @see http://exif.org/Exif2-2.PDF The Exif 2.2 specification
27 * @todo document (e.g. one-sentence class-overview description)
32 const BYTE
= 1; //!< An 8-bit (1-byte) unsigned integer.
33 const ASCII
= 2; //!< An 8-bit byte containing one 7-bit ASCII code. The final byte is terminated with NULL.
34 const SHORT
= 3; //!< A 16-bit (2-byte) unsigned integer.
35 const LONG
= 4; //!< A 32-bit (4-byte) unsigned integer.
36 const RATIONAL
= 5; //!< Two LONGs. The first LONG is the numerator and the second LONG expresses the denominator
37 const UNDEFINED
= 7; //!< An 8-bit byte that can take any value depending on the field definition
38 const SLONG
= 9; //!< A 32-bit (4-byte) signed integer (2's complement notation),
39 const SRATIONAL
= 10; //!< Two SLONGs. The first SLONG is the numerator and the second SLONG is the denominator.
47 * Exif tags grouped by category, the tagname itself is the key and the type
48 * is the value, in the case of more than one possible value type they are
49 * separated by commas.
54 * The raw Exif data returned by exif_read_data()
59 * A Filtered version of $mRawExifData that has been pruned of invalid
60 * tags and tags that contain content they shouldn't contain according
61 * to the Exif specification
63 var $mFilteredExifData;
66 * Filtered and formatted Exif data, see FormatExif::getFormattedData()
68 var $mFormattedExifData;
78 * The file being processed
83 * The basename of the file being processed
88 * The private log to log to, e.g. 'exif'
97 * @param $file String: filename.
99 function __construct( $file ) {
101 * Page numbers here refer to pages in the EXIF 2.2 standard
103 * @link http://exif.org/Exif2-2.PDF The Exif 2.2 specification
105 $this->mExifTags
= array(
106 # TIFF Rev. 6.0 Attribute Information (p22)
108 # Tags relating to image structure
109 'ImageWidth' => Exif
::SHORT
.','.Exif
::LONG
, # Image width
110 'ImageLength' => Exif
::SHORT
.','.Exif
::LONG
, # Image height
111 'BitsPerSample' => array( Exif
::SHORT
, 3 ), # Number of bits per component
112 # "When a primary image is JPEG compressed, this designation is not"
113 # "necessary and is omitted." (p23)
114 'Compression' => Exif
::SHORT
, # Compression scheme #p23
115 'PhotometricInterpretation' => Exif
::SHORT
, # Pixel composition #p23
116 'Orientation' => Exif
::SHORT
, # Orientation of image #p24
117 'SamplesPerPixel' => Exif
::SHORT
, # Number of components
118 'PlanarConfiguration' => Exif
::SHORT
, # Image data arrangement #p24
119 'YCbCrSubSampling' => array( Exif
::SHORT
, 2), # Subsampling ratio of Y to C #p24
120 'YCbCrPositioning' => Exif
::SHORT
, # Y and C positioning #p24-25
121 'XResolution' => Exif
::RATIONAL
, # Image resolution in width direction
122 'YResolution' => Exif
::RATIONAL
, # Image resolution in height direction
123 'ResolutionUnit' => Exif
::SHORT
, # Unit of X and Y resolution #(p26)
125 # Tags relating to recording offset
126 'StripOffsets' => Exif
::SHORT
.','.Exif
::LONG
, # Image data location
127 'RowsPerStrip' => Exif
::SHORT
.','.Exif
::LONG
, # Number of rows per strip
128 'StripByteCounts' => Exif
::SHORT
.','.Exif
::LONG
, # Bytes per compressed strip
129 'JPEGInterchangeFormat' => Exif
::SHORT
.','.Exif
::LONG
, # Offset to JPEG SOI
130 'JPEGInterchangeFormatLength' => Exif
::SHORT
.','.Exif
::LONG
, # Bytes of JPEG data
132 # Tags relating to image data characteristics
133 'TransferFunction' => Exif
::SHORT
, # Transfer function
134 'WhitePoint' => array( Exif
::RATIONAL
, 2), # White point chromaticity
135 'PrimaryChromaticities' => array( Exif
::RATIONAL
, 6), # Chromaticities of primarities
136 'YCbCrCoefficients' => array( Exif
::RATIONAL
, 3), # Color space transformation matrix coefficients #p27
137 'ReferenceBlackWhite' => array( Exif
::RATIONAL
, 6), # Pair of black and white reference values
140 'DateTime' => Exif
::ASCII
, # File change date and time
141 'ImageDescription' => Exif
::ASCII
, # Image title
142 'Make' => Exif
::ASCII
, # Image input equipment manufacturer
143 'Model' => Exif
::ASCII
, # Image input equipment model
144 'Software' => Exif
::ASCII
, # Software used
145 'Artist' => Exif
::ASCII
, # Person who created the image
146 'Copyright' => Exif
::ASCII
, # Copyright holder
149 # Exif IFD Attribute Information (p30-31)
151 # TODO: NOTE: Nonexistence of this field is taken to mean nonconformance
152 # to the EXIF 2.1 AND 2.2 standards
153 'ExifVersion' => array( Exif
::UNDEFINED
, 4 ), # Exif version
154 'FlashPixVersion' => array( Exif
::UNDEFINED
, 4 ), # Supported Flashpix version #p32
156 # Tags relating to Image Data Characteristics
157 'ColorSpace' => Exif
::SHORT
, # Color space information #p32
159 # Tags relating to image configuration
160 'ComponentsConfiguration' => array( Exif
::UNDEFINED
, 1), # Meaning of each component #p33
161 'CompressedBitsPerPixel' => Exif
::RATIONAL
, # Image compression mode
162 'PixelYDimension' => Exif
::SHORT
.','.Exif
::LONG
, # Valid image width
163 'PixelXDimension' => Exif
::SHORT
.','.Exif
::LONG
, # Valid image height
165 # Tags relating to related user information
166 'MakerNote' => Exif
::UNDEFINED
, # Manufacturer notes
167 'UserComment' => Exif
::UNDEFINED
, # User comments #p34
169 # Tags relating to related file information
170 'RelatedSoundFile' => Exif
::ASCII
, # Related audio file
172 # Tags relating to date and time
173 'DateTimeOriginal' => Exif
::ASCII
, # Date and time of original data generation #p36
174 'DateTimeDigitized' => Exif
::ASCII
, # Date and time of original data generation
175 'SubSecTime' => Exif
::ASCII
, # DateTime subseconds
176 'SubSecTimeOriginal' => Exif
::ASCII
, # DateTimeOriginal subseconds
177 'SubSecTimeDigitized' => Exif
::ASCII
, # DateTimeDigitized subseconds
179 # Tags relating to picture-taking conditions (p31)
180 'ExposureTime' => Exif
::RATIONAL
, # Exposure time
181 'FNumber' => Exif
::RATIONAL
, # F Number
182 'ExposureProgram' => Exif
::SHORT
, # Exposure Program #p38
183 'SpectralSensitivity' => Exif
::ASCII
, # Spectral sensitivity
184 'ISOSpeedRatings' => Exif
::SHORT
, # ISO speed rating
185 'OECF' => Exif
::UNDEFINED
, # Optoelectronic conversion factor
186 'ShutterSpeedValue' => Exif
::SRATIONAL
, # Shutter speed
187 'ApertureValue' => Exif
::RATIONAL
, # Aperture
188 'BrightnessValue' => Exif
::SRATIONAL
, # Brightness
189 'ExposureBiasValue' => Exif
::SRATIONAL
, # Exposure bias
190 'MaxApertureValue' => Exif
::RATIONAL
, # Maximum land aperture
191 'SubjectDistance' => Exif
::RATIONAL
, # Subject distance
192 'MeteringMode' => Exif
::SHORT
, # Metering mode #p40
193 'LightSource' => Exif
::SHORT
, # Light source #p40-41
194 'Flash' => Exif
::SHORT
, # Flash #p41-42
195 'FocalLength' => Exif
::RATIONAL
, # Lens focal length
196 'SubjectArea' => array( Exif
::SHORT
, 4 ), # Subject area
197 'FlashEnergy' => Exif
::RATIONAL
, # Flash energy
198 'SpatialFrequencyResponse' => Exif
::UNDEFINED
, # Spatial frequency response
199 'FocalPlaneXResolution' => Exif
::RATIONAL
, # Focal plane X resolution
200 'FocalPlaneYResolution' => Exif
::RATIONAL
, # Focal plane Y resolution
201 'FocalPlaneResolutionUnit' => Exif
::SHORT
, # Focal plane resolution unit #p46
202 'SubjectLocation' => array( Exif
::SHORT
, 2), # Subject location
203 'ExposureIndex' => Exif
::RATIONAL
, # Exposure index
204 'SensingMethod' => Exif
::SHORT
, # Sensing method #p46
205 'FileSource' => Exif
::UNDEFINED
, # File source #p47
206 'SceneType' => Exif
::UNDEFINED
, # Scene type #p47
207 'CFAPattern' => Exif
::UNDEFINED
, # CFA pattern
208 'CustomRendered' => Exif
::SHORT
, # Custom image processing #p48
209 'ExposureMode' => Exif
::SHORT
, # Exposure mode #p48
210 'WhiteBalance' => Exif
::SHORT
, # White Balance #p49
211 'DigitalZoomRatio' => Exif
::RATIONAL
, # Digital zoom ration
212 'FocalLengthIn35mmFilm' => Exif
::SHORT
, # Focal length in 35 mm film
213 'SceneCaptureType' => Exif
::SHORT
, # Scene capture type #p49
214 'GainControl' => Exif
::RATIONAL
, # Scene control #p49-50
215 'Contrast' => Exif
::SHORT
, # Contrast #p50
216 'Saturation' => Exif
::SHORT
, # Saturation #p50
217 'Sharpness' => Exif
::SHORT
, # Sharpness #p50
218 'DeviceSettingDescription' => Exif
::UNDEFINED
, # Desice settings description
219 'SubjectDistanceRange' => Exif
::SHORT
, # Subject distance range #p51
221 'ImageUniqueID' => Exif
::ASCII
, # Unique image ID
224 # GPS Attribute Information (p52)
226 'GPSVersionID' => array( Exif
::BYTE
, 4 ), # GPS tag version
227 'GPSLatitudeRef' => Exif
::ASCII
, # North or South Latitude #p52-53
228 'GPSLatitude' => array( Exif
::RATIONAL
, 3 ), # Latitude
229 'GPSLongitudeRef' => Exif
::ASCII
, # East or West Longitude #p53
230 'GPSLongitude' => array( Exif
::RATIONAL
, 3), # Longitude
231 'GPSAltitudeRef' => Exif
::BYTE
, # Altitude reference
232 'GPSAltitude' => Exif
::RATIONAL
, # Altitude
233 'GPSTimeStamp' => array( Exif
::RATIONAL
, 3), # GPS time (atomic clock)
234 'GPSSatellites' => Exif
::ASCII
, # Satellites used for measurement
235 'GPSStatus' => Exif
::ASCII
, # Receiver status #p54
236 'GPSMeasureMode' => Exif
::ASCII
, # Measurement mode #p54-55
237 'GPSDOP' => Exif
::RATIONAL
, # Measurement precision
238 'GPSSpeedRef' => Exif
::ASCII
, # Speed unit #p55
239 'GPSSpeed' => Exif
::RATIONAL
, # Speed of GPS receiver
240 'GPSTrackRef' => Exif
::ASCII
, # Reference for direction of movement #p55
241 'GPSTrack' => Exif
::RATIONAL
, # Direction of movement
242 'GPSImgDirectionRef' => Exif
::ASCII
, # Reference for direction of image #p56
243 'GPSImgDirection' => Exif
::RATIONAL
, # Direction of image
244 'GPSMapDatum' => Exif
::ASCII
, # Geodetic survey data used
245 'GPSDestLatitudeRef' => Exif
::ASCII
, # Reference for latitude of destination #p56
246 'GPSDestLatitude' => array( Exif
::RATIONAL
, 3 ), # Latitude destination
247 'GPSDestLongitudeRef' => Exif
::ASCII
, # Reference for longitude of destination #p57
248 'GPSDestLongitude' => array( Exif
::RATIONAL
, 3 ), # Longitude of destination
249 'GPSDestBearingRef' => Exif
::ASCII
, # Reference for bearing of destination #p57
250 'GPSDestBearing' => Exif
::RATIONAL
, # Bearing of destination
251 'GPSDestDistanceRef' => Exif
::ASCII
, # Reference for distance to destination #p57-58
252 'GPSDestDistance' => Exif
::RATIONAL
, # Distance to destination
253 'GPSProcessingMethod' => Exif
::UNDEFINED
, # Name of GPS processing method
254 'GPSAreaInformation' => Exif
::UNDEFINED
, # Name of GPS area
255 'GPSDateStamp' => Exif
::ASCII
, # GPS date
256 'GPSDifferential' => Exif
::SHORT
, # GPS differential correction
261 $this->basename
= wfBaseName( $this->file
);
263 $this->debugFile( $this->basename
, __FUNCTION__
, true );
264 wfSuppressWarnings();
265 $data = exif_read_data( $this->file
, 0, true );
268 * exif_read_data() will return false on invalid input, such as
269 * when somebody uploads a file called something.jpeg
270 * containing random gibberish.
272 $this->mRawExifData
= $data ?
$data : array();
273 $this->makeFilteredData();
274 $this->makeFormattedData();
275 $this->debugFile( __FUNCTION__
, false );
279 * Make $this->mFilteredExifData
281 function makeFilteredData() {
282 $this->mFilteredExifData
= $this->mRawExifData
;
284 foreach( array_keys( $this->mFilteredExifData
) as $section ) {
285 if ( !in_array( $section, array_keys( $this->mExifTags
) ) ) {
286 $this->debug( $section , __FUNCTION__
, "'$section' is not a valid Exif section" );
287 unset( $this->mFilteredExifData
[$section] );
291 foreach( array_keys( $this->mFilteredExifData
[$section] ) as $tag ) {
292 if ( !in_array( $tag, array_keys( $this->mExifTags
[$section] ) ) ) {
293 $this->debug( $tag, __FUNCTION__
, "'$tag' is not a valid tag in '$section'" );
294 unset( $this->mFilteredExifData
[$section][$tag] );
297 $value = $this->mFilteredExifData
[$section][$tag];
298 if( !$this->validate( $section, $tag, $value ) ) {
299 $this->debug( $value, __FUNCTION__
, "'$tag' contained invalid data" );
300 unset( $this->mFilteredExifData
[$section][$tag] );
309 function makeFormattedData( ) {
310 $format = new FormatExif( $this->getFilteredData() );
311 $this->mFormattedExifData
= $format->getFormattedData();
319 * Get $this->mRawExifData
322 return $this->mRawExifData
;
326 * Get $this->mFilteredExifData
328 function getFilteredData() {
329 return $this->mFilteredExifData
;
333 * Get $this->mFormattedExifData
335 function getFormattedData() {
336 return $this->mFormattedExifData
;
341 * The version of the output format
343 * Before the actual metadata information is saved in the database we
344 * strip some of it since we don't want to save things like thumbnails
345 * which usually accompany Exif data. This value gets saved in the
346 * database along with the actual Exif data, and if the version in the
347 * database doesn't equal the value returned by this function the Exif
348 * data is regenerated.
352 public static function version() {
353 return 2; // We don't need no bloddy constants!
357 * Validates if a tag value is of the type it should be according to the Exif spec
361 * @param $in Mixed: the input value to check
364 function isByte( $in ) {
365 if ( !is_array( $in ) && sprintf('%d', $in) == $in && $in >= 0 && $in <= 255 ) {
366 $this->debug( $in, __FUNCTION__
, true );
369 $this->debug( $in, __FUNCTION__
, false );
374 function isASCII( $in ) {
375 if ( is_array( $in ) ) {
379 if ( preg_match( "/[^\x0a\x20-\x7e]/", $in ) ) {
380 $this->debug( $in, __FUNCTION__
, 'found a character not in our whitelist' );
384 if ( preg_match( '/^\s*$/', $in ) ) {
385 $this->debug( $in, __FUNCTION__
, 'input consisted solely of whitespace' );
392 function isShort( $in ) {
393 if ( !is_array( $in ) && sprintf('%d', $in) == $in && $in >= 0 && $in <= 65536 ) {
394 $this->debug( $in, __FUNCTION__
, true );
397 $this->debug( $in, __FUNCTION__
, false );
402 function isLong( $in ) {
403 if ( !is_array( $in ) && sprintf('%d', $in) == $in && $in >= 0 && $in <= 4294967296 ) {
404 $this->debug( $in, __FUNCTION__
, true );
407 $this->debug( $in, __FUNCTION__
, false );
412 function isRational( $in ) {
414 if ( !is_array( $in ) && @preg_match
( '/^(\d+)\/(\d+[1-9]|[1-9]\d*)$/', $in, $m ) ) { # Avoid division by zero
415 return $this->isLong( $m[1] ) && $this->isLong( $m[2] );
417 $this->debug( $in, __FUNCTION__
, 'fed a non-fraction value' );
422 function isUndefined( $in ) {
423 if ( !is_array( $in ) && preg_match( '/^\d{4}$/', $in ) ) { // Allow ExifVersion and FlashpixVersion
424 $this->debug( $in, __FUNCTION__
, true );
427 $this->debug( $in, __FUNCTION__
, false );
432 function isSlong( $in ) {
433 if ( $this->isLong( abs( $in ) ) ) {
434 $this->debug( $in, __FUNCTION__
, true );
437 $this->debug( $in, __FUNCTION__
, false );
442 function isSrational( $in ) {
444 if ( !is_array( $in ) && preg_match( '/^(\d+)\/(\d+[1-9]|[1-9]\d*)$/', $in, $m ) ) { # Avoid division by zero
445 return $this->isSlong( $m[0] ) && $this->isSlong( $m[1] );
447 $this->debug( $in, __FUNCTION__
, 'fed a non-fraction value' );
454 * Validates if a tag has a legal value according to the Exif spec
457 * @param $section String: section where tag is located.
458 * @param $tag String: the tag to check.
459 * @param $val Mixed: the value of the tag.
460 * @param $recursive Boolean: true if called recursively for array types.
463 function validate( $section, $tag, $val, $recursive = false ) {
464 $debug = "tag is '$tag'";
465 $etype = $this->mExifTags
[$section][$tag];
467 if( is_array( $etype ) ) {
468 list( $etype, $ecount ) = $etype;
470 $ecount = 1; // checking individual elements
472 $count = count( $val );
473 if( $ecount != $count ) {
474 $this->debug( $val, __FUNCTION__
, "Expected $ecount elements for $tag but got $count" );
478 foreach( $val as $v ) {
479 if( !$this->validate( $section, $tag, $v, true ) ) {
485 // Does not work if not typecast
486 switch( (string)$etype ) {
487 case (string)Exif
::BYTE
:
488 $this->debug( $val, __FUNCTION__
, $debug );
489 return $this->isByte( $val );
490 case (string)Exif
::ASCII
:
491 $this->debug( $val, __FUNCTION__
, $debug );
492 return $this->isASCII( $val );
493 case (string)Exif
::SHORT
:
494 $this->debug( $val, __FUNCTION__
, $debug );
495 return $this->isShort( $val );
496 case (string)Exif
::LONG
:
497 $this->debug( $val, __FUNCTION__
, $debug );
498 return $this->isLong( $val );
499 case (string)Exif
::RATIONAL
:
500 $this->debug( $val, __FUNCTION__
, $debug );
501 return $this->isRational( $val );
502 case (string)Exif
::UNDEFINED
:
503 $this->debug( $val, __FUNCTION__
, $debug );
504 return $this->isUndefined( $val );
505 case (string)Exif
::SLONG
:
506 $this->debug( $val, __FUNCTION__
, $debug );
507 return $this->isSlong( $val );
508 case (string)Exif
::SRATIONAL
:
509 $this->debug( $val, __FUNCTION__
, $debug );
510 return $this->isSrational( $val );
511 case (string)Exif
::SHORT
.','.Exif
::LONG
:
512 $this->debug( $val, __FUNCTION__
, $debug );
513 return $this->isShort( $val ) ||
$this->isLong( $val );
515 $this->debug( $val, __FUNCTION__
, "The tag '$tag' is unknown" );
521 * Convenience function for debugging output
526 * @param $fname String:
527 * @param $action Mixed: , default NULL.
529 function debug( $in, $fname, $action = NULL ) {
533 $type = gettype( $in );
534 $class = ucfirst( __CLASS__
);
535 if ( $type === 'array' )
536 $in = print_r( $in, true );
538 if ( $action === true )
539 wfDebugLog( $this->log
, "$class::$fname: accepted: '$in' (type: $type)\n");
540 elseif ( $action === false )
541 wfDebugLog( $this->log
, "$class::$fname: rejected: '$in' (type: $type)\n");
542 elseif ( $action === null )
543 wfDebugLog( $this->log
, "$class::$fname: input was: '$in' (type: $type)\n");
545 wfDebugLog( $this->log
, "$class::$fname: $action (type: $type; content: '$in')\n");
549 * Convenience function for debugging output
553 * @param $fname String: the name of the function calling this function
554 * @param $io Boolean: Specify whether we're beginning or ending
556 function debugFile( $fname, $io ) {
560 $class = ucfirst( __CLASS__
);
562 wfDebugLog( $this->log
, "$class::$fname: begin processing: '{$this->basename}'\n" );
564 wfDebugLog( $this->log
, "$class::$fname: end processing: '{$this->basename}'\n" );
571 * @todo document (e.g. one-sentence class-overview description)
576 * The Exif data to format
586 * @param $exif Array: the Exif data to format ( as returned by
587 * Exif::getFilteredData() )
589 function FormatExif( $exif ) {
590 $this->mExif
= $exif;
594 * Numbers given by Exif user agents are often magical, that is they
595 * should be replaced by a detailed explanation depending on their
596 * value which most of the time are plain integers. This function
597 * formats Exif values into human readable form.
601 function getFormattedData() {
604 $sections =& $this->mExif
;
606 $resolutionunit = !isset( $sections['IFD0']['ResolutionUnit'] ) ||
$sections['IFD0']['ResolutionUnit'] == 2 ?
2 : 3;
607 unset( $sections['IFD0']['ResolutionUnit'] );
609 foreach( $sections as $section => &$tags ) {
610 foreach( $tags as $tag => $val ) {
615 $tags[$tag] = $this->msg( $tag, $val );
623 case 'PhotometricInterpretation':
626 $tags[$tag] = $this->msg( $tag, $val );
636 case 1: case 2: case 3: case 4: case 5: case 6: case 7: case 8:
637 $tags[$tag] = $this->msg( $tag, $val );
645 case 'PlanarConfiguration':
648 $tags[$tag] = $this->msg( $tag, $val );
656 // TODO: YCbCrSubSampling
657 // TODO: YCbCrPositioning
661 switch( $resolutionunit ) {
663 $tags[$tag] = $this->msg( 'XYResolution', 'i', $this->formatNum( $val ) );
666 $this->msg( 'XYResolution', 'c', $this->formatNum( $val ) );
674 // TODO: YCbCrCoefficients #p27 (see annex E)
675 case 'ExifVersion': case 'FlashPixVersion':
676 $tags[$tag] = "$val"/100;
681 case 1: case 'FFFF.H':
682 $tags[$tag] = $this->msg( $tag, $val );
690 case 'ComponentsConfiguration':
692 case 0: case 1: case 2: case 3: case 4: case 5: case 6:
693 $tags[$tag] = $this->msg( $tag, $val );
702 case 'DateTimeOriginal':
703 case 'DateTimeDigitized':
704 if( $val == '0000:00:00 00:00:00' ) {
705 $tags[$tag] = wfMsg('exif-unknowndate');
706 } elseif( preg_match( '/^(?:\d{4}):(?:\d\d):(?:\d\d) (?:\d\d):(?:\d\d):(?:\d\d)$/', $val ) ) {
707 $tags[$tag] = $wgLang->timeanddate( wfTimestamp(TS_MW
, $val) );
711 case 'ExposureProgram':
713 case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7: case 8:
714 $tags[$tag] = $this->msg( $tag, $val );
722 case 'SubjectDistance':
723 $tags[$tag] = $this->msg( $tag, '', $this->formatNum( $val ) );
728 case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7: case 255:
729 $tags[$tag] = $this->msg( $tag, $val );
739 case 0: case 1: case 2: case 3: case 4: case 9: case 10: case 11:
740 case 12: case 13: case 14: case 15: case 17: case 18: case 19: case 20:
741 case 21: case 22: case 23: case 24: case 255:
742 $tags[$tag] = $this->msg( $tag, $val );
751 $flashDecode = array(
752 'fired' => $val & bindec( '00000001' ),
753 'return' => ($val & bindec( '00000110' )) >> 1,
754 'mode' => ($val & bindec( '00011000' )) >> 3,
755 'function' => ($val & bindec( '00100000' )) >> 5,
756 'redeye' => ($val & bindec( '01000000' )) >> 6,
757 // 'reserved' => ($val & bindec( '10000000' )) >> 7,
760 # We do not need to handle unknown values since all are used.
761 foreach( $flashDecode as $subTag => $subValue ) {
762 # We do not need any message for zeroed values.
763 if( $subTag != 'fired' && $subValue == 0) {
766 $fullTag = $tag . '-' . $subTag ;
767 $flashMsgs[] = $this->msg( $fullTag, $subValue );
769 $tags[$tag] = $wgLang->commaList( $flashMsgs );
772 case 'FocalPlaneResolutionUnit':
775 $tags[$tag] = $this->msg( $tag, $val );
783 case 'SensingMethod':
785 case 1: case 2: case 3: case 4: case 5: case 7: case 8:
786 $tags[$tag] = $this->msg( $tag, $val );
797 $tags[$tag] = $this->msg( $tag, $val );
808 $tags[$tag] = $this->msg( $tag, $val );
816 case 'CustomRendered':
819 $tags[$tag] = $this->msg( $tag, $val );
829 case 0: case 1: case 2:
830 $tags[$tag] = $this->msg( $tag, $val );
841 $tags[$tag] = $this->msg( $tag, $val );
849 case 'SceneCaptureType':
851 case 0: case 1: case 2: case 3:
852 $tags[$tag] = $this->msg( $tag, $val );
862 case 0: case 1: case 2: case 3: case 4:
863 $tags[$tag] = $this->msg( $tag, $val );
873 case 0: case 1: case 2:
874 $tags[$tag] = $this->msg( $tag, $val );
884 case 0: case 1: case 2:
885 $tags[$tag] = $this->msg( $tag, $val );
895 case 0: case 1: case 2:
896 $tags[$tag] = $this->msg( $tag, $val );
904 case 'SubjectDistanceRange':
906 case 0: case 1: case 2: case 3:
907 $tags[$tag] = $this->msg( $tag, $val );
915 case 'GPSLatitudeRef':
916 case 'GPSDestLatitudeRef':
919 $tags[$tag] = $this->msg( 'GPSLatitude', $val );
927 case 'GPSLongitudeRef':
928 case 'GPSDestLongitudeRef':
931 $tags[$tag] = $this->msg( 'GPSLongitude', $val );
939 case 'GPSAltitudeRef':
942 $tags[$tag] = $this->msg( 'GPSAltitude', $val );
951 case 'GPSDestLatitude':
953 case 'GPSDestLongitude':
954 $tags[$tag] = $this->formatCoords( $val );
960 $tags[$tag] = $this->msg( $tag, $val );
968 case 'GPSMeasureMode':
971 $tags[$tag] = $this->msg( $tag, $val );
981 case 'K': case 'M': case 'N':
982 $tags[$tag] = $this->msg( 'GPSSpeed', $val );
990 case 'GPSDestDistanceRef':
992 case 'K': case 'M': case 'N':
993 $tags[$tag] = $this->msg( 'GPSDestDistance', $val );
1002 case 'GPSImgDirectionRef':
1003 case 'GPSDestBearingRef':
1006 $tags[$tag] = $this->msg( 'GPSDirection', $val );
1014 case 'GPSDateStamp':
1015 $tags[$tag] = $wgLang->date( substr( $val, 0, 4 ) . substr( $val, 5, 2 ) . substr( $val, 8, 2 ) . '000000' );
1018 // This is not in the Exif standard, just a special
1019 // case for our purposes which enables wikis to wikify
1020 // the make, model and software name to link to their articles.
1024 $tags[$tag] = $this->msg( $tag, '', $val );
1027 case 'ExposureTime':
1028 // Show the pretty fraction as well as decimal version
1029 $tags[$tag] = wfMsg( 'exif-exposuretime-format',
1030 $this->formatFraction( $val ), $this->formatNum( $val ) );
1034 $tags[$tag] = wfMsg( 'exif-fnumber-format',
1035 $this->formatNum( $val ) );
1039 $tags[$tag] = wfMsg( 'exif-focallength-format',
1040 $this->formatNum( $val ) );
1044 $tags[$tag] = $this->formatNum( $val );
1050 return $this->mExif
;
1054 * Convenience function for getFormattedData()
1058 * @param $tag String: the tag name to pass on
1059 * @param $val String: the value of the tag
1060 * @param $arg String: an argument to pass ($1)
1061 * @return string A wfMsg of "exif-$tag-$val" in lower case
1063 function msg( $tag, $val, $arg = null ) {
1068 return wfMsg( $wgContLang->lc( "exif-$tag-$val" ), $arg );
1072 * Format a number, convert numbers from fractions into floating point
1073 * numbers, joins arrays of numbers with commas.
1077 * @param $num Mixed: the value to format
1078 * @return mixed A floating point number or whatever we were fed
1080 function formatNum( $num ) {
1083 if( is_array($num) ) {
1085 foreach( $num as $number ) {
1086 $out[] = $this->formatNum($number);
1088 return $wgLang->commaList( $out );
1090 if ( preg_match( '/^(\d+)\/(\d+)$/', $num, $m ) )
1091 return $m[2] != 0 ?
$m[1] / $m[2] : $num;
1097 * Format a rational number, reducing fractions
1101 * @param $num Mixed: the value to format
1102 * @return mixed A floating point number or whatever we were fed
1104 function formatFraction( $num ) {
1106 if ( preg_match( '/^(\d+)\/(\d+)$/', $num, $m ) ) {
1107 $numerator = intval( $m[1] );
1108 $denominator = intval( $m[2] );
1109 $gcd = $this->gcd( $numerator, $denominator );
1111 // 0 shouldn't happen! ;)
1112 return $numerator / $gcd . '/' . $denominator / $gcd;
1115 return $this->formatNum( $num );
1119 * Calculate the greatest common divisor of two integers.
1121 * @param $a Integer: FIXME
1122 * @param $b Integer: FIXME
1126 function gcd( $a, $b ) {
1128 // http://en.wikipedia.org/wiki/Euclidean_algorithm
1129 // Recursive form would be:
1133 return gcd( $b, $a % $b );
1136 $remainder = $a %
$b;
1138 // tail recursion...
1146 * Format a coordinate value, convert numbers from fractions
1147 * into floating point numbers, .
1151 * @param $coords Array: degrees, minutes and seconds
1152 * @param $ref String: reference direction (N/S/E/W), optional
1153 * @return mixed A floating point number or whatever we were fed
1155 function formatCoords( $coords, $ref = null ) {
1156 list($deg, $min, $sec) = $coords;
1157 $deg = $this->formatNum($deg);
1158 $min = $this->formatNum($min);
1159 $sec = $this->formatNum($sec);
1161 if ($min) $out .= " " . $min . "'";
1162 if ($sec) $out .= " " . $sec . '"';
1163 if ($ref) $out .= " " . $ref;
1170 * MW 1.6 compatibility
1172 define( 'MW_EXIF_BYTE', Exif
::BYTE
);
1173 define( 'MW_EXIF_ASCII', Exif
::ASCII
);
1174 define( 'MW_EXIF_SHORT', Exif
::SHORT
);
1175 define( 'MW_EXIF_LONG', Exif
::LONG
);
1176 define( 'MW_EXIF_RATIONAL', Exif
::RATIONAL
);
1177 define( 'MW_EXIF_UNDEFINED', Exif
::UNDEFINED
);
1178 define( 'MW_EXIF_SLONG', Exif
::SLONG
);
1179 define( 'MW_EXIF_SRATIONAL', Exif
::SRATIONAL
);