From c7dbaa0f580cbd6fc8c5e3664e55d6bee1c29136 Mon Sep 17 00:00:00 2001 From: Siebrand Mazeland Date: Thu, 5 Dec 2013 11:05:05 +0100 Subject: [PATCH] Update formatting for media related classes Change-Id: Iaa81af5b65a650222fa65bf8c368e4f1ec3ce9c0 --- includes/media/BMP.php | 2 +- includes/media/Bitmap.php | 74 +- includes/media/BitmapMetadataHandler.php | 12 +- includes/media/Bitmap_ClientOnly.php | 2 +- includes/media/DjVu.php | 15 +- includes/media/DjVuImage.php | 23 +- includes/media/Exif.php | 268 ++-- includes/media/ExifBitmap.php | 9 +- includes/media/FormatMetadata.php | 1523 ++++++++++++---------- includes/media/GIF.php | 12 +- includes/media/GIFMetadataExtractor.php | 5 +- includes/media/IPTC.php | 18 +- includes/media/ImageHandler.php | 16 +- includes/media/Jpeg.php | 11 +- includes/media/JpegMetadataExtractor.php | 6 +- includes/media/MediaHandler.php | 17 +- includes/media/MediaTransformOutput.php | 4 +- includes/media/PNG.php | 12 +- includes/media/PNGMetadataExtractor.php | 43 +- includes/media/SVG.php | 19 +- includes/media/SVGMetadataExtractor.php | 10 +- includes/media/Tiff.php | 8 +- includes/media/XCF.php | 12 +- includes/media/XMP.php | 64 +- includes/media/XMPInfo.php | 966 +++++++------- includes/media/XMPValidate.php | 21 +- 26 files changed, 1732 insertions(+), 1440 deletions(-) diff --git a/includes/media/BMP.php b/includes/media/BMP.php index 99b7741a59..7271d993fd 100644 --- a/includes/media/BMP.php +++ b/includes/media/BMP.php @@ -28,7 +28,6 @@ * @ingroup Media */ class BmpHandler extends BitmapHandler { - /** * @param $file * @return bool @@ -75,6 +74,7 @@ class BmpHandler extends BitmapHandler { } catch ( MWException $e ) { return false; } + return array( $w[1], $h[1] ); } } diff --git a/includes/media/Bitmap.php b/includes/media/Bitmap.php index 4959687415..f50c3e5962 100644 --- a/includes/media/Bitmap.php +++ b/includes/media/Bitmap.php @@ -61,9 +61,10 @@ class BitmapHandler extends ImageHandler { if ( is_null( $checkImageAreaHookResult ) ) { global $wgMaxImageArea; - if ( $srcWidth * $srcHeight > $wgMaxImageArea && - !( $image->getMimeType() == 'image/jpeg' && - self::getScalerType( false, false ) == 'im' ) ) { + if ( $srcWidth * $srcHeight > $wgMaxImageArea + && !( $image->getMimeType() == 'image/jpeg' + && self::getScalerType( false, false ) == 'im' ) + ) { # Only ImageMagick can efficiently downsize jpg images without loading # the entire file in memory return false; @@ -96,6 +97,7 @@ class BitmapHandler extends ImageHandler { $width = $params['physicalWidth']; $height = $params['physicalHeight']; } + return array( $width, $height ); } @@ -121,8 +123,9 @@ class BitmapHandler extends ImageHandler { 'clientWidth' => $params['width'], 'clientHeight' => $params['height'], # Comment as will be added to the Exif of the thumbnail - 'comment' => isset( $params['descriptionUrl'] ) ? - "File source: {$params['descriptionUrl']}" : '', + 'comment' => isset( $params['descriptionUrl'] ) + ? "File source: {$params['descriptionUrl']}" + : '', # Properties of the original image 'srcWidth' => $image->getWidth(), 'srcHeight' => $image->getHeight(), @@ -137,11 +140,13 @@ class BitmapHandler extends ImageHandler { wfDebug( __METHOD__ . ": creating {$scalerParams['physicalDimensions']} thumbnail at $dstPath using scaler $scaler\n" ); if ( !$image->mustRender() && - $scalerParams['physicalWidth'] == $scalerParams['srcWidth'] - && $scalerParams['physicalHeight'] == $scalerParams['srcHeight'] ) { + $scalerParams['physicalWidth'] == $scalerParams['srcWidth'] + && $scalerParams['physicalHeight'] == $scalerParams['srcHeight'] + ) { # normaliseParams (or the user) wants us to return the unscaled image wfDebug( __METHOD__ . ": returning unscaled image\n" ); + return $this->getClientScalingThumbnailImage( $image, $scalerParams ); } @@ -157,12 +162,14 @@ class BitmapHandler extends ImageHandler { 'width' => $scalerParams['clientWidth'], 'height' => $scalerParams['clientHeight'] ); + return new ThumbnailImage( $image, $dstUrl, false, $params ); } # Try to make a target path for the thumbnail if ( !wfMkdirParents( dirname( $dstPath ), null, __METHOD__ ) ) { wfDebug( __METHOD__ . ": Unable to create thumbnail destination directory, falling back to client scaling\n" ); + return $this->getClientScalingThumbnailImage( $image, $scalerParams ); } @@ -172,6 +179,7 @@ class BitmapHandler extends ImageHandler { wfDebugLog( 'thumbnail', sprintf( 'Thumbnail failed on %s: could not get local copy of "%s"', wfHostname(), $image->getName() ) ); + return new MediaTransformError( 'thumbnail_error', $scalerParams['clientWidth'], $scalerParams['clientHeight'] ); } @@ -220,6 +228,7 @@ class BitmapHandler extends ImageHandler { 'width' => $scalerParams['clientWidth'], 'height' => $scalerParams['clientHeight'] ); + return new ThumbnailImage( $image, $dstUrl, $dstPath, $params ); } } @@ -249,6 +258,7 @@ class BitmapHandler extends ImageHandler { } else { $scaler = 'client'; } + return $scaler; } @@ -267,6 +277,7 @@ class BitmapHandler extends ImageHandler { 'width' => $scalerParams['clientWidth'], 'height' => $scalerParams['clientHeight'] ); + return new ThumbnailImage( $image, $image->getURL(), null, $params ); } @@ -280,8 +291,7 @@ class BitmapHandler extends ImageHandler { */ protected function transformImageMagick( $image, $params ) { # use ImageMagick - global $wgSharpenReductionThreshold, $wgSharpenParameter, - $wgMaxAnimatedGifArea, + global $wgSharpenReductionThreshold, $wgSharpenParameter, $wgMaxAnimatedGifArea, $wgImageMagickTempDir, $wgImageMagickConvertCommand; $quality = ''; @@ -294,15 +304,15 @@ class BitmapHandler extends ImageHandler { $quality = "-quality 80"; // 80% # Sharpening, see bug 6193 if ( ( $params['physicalWidth'] + $params['physicalHeight'] ) - / ( $params['srcWidth'] + $params['srcHeight'] ) - < $wgSharpenReductionThreshold ) { + / ( $params['srcWidth'] + $params['srcHeight'] ) + < $wgSharpenReductionThreshold + ) { $sharpen = "-sharpen " . wfEscapeShellArg( $wgSharpenParameter ); } if ( version_compare( $this->getMagickVersion(), "6.5.6" ) >= 0 ) { // JPEG decoder hint to reduce memory, available since IM 6.5.6-2 $decoderHint = "-define jpeg:size={$params['physicalDimensions']}"; } - } elseif ( $params['mimeType'] == 'image/png' ) { $quality = "-quality 95"; // zlib 9, adaptive filtering @@ -311,7 +321,6 @@ class BitmapHandler extends ImageHandler { // Extract initial frame only; we're so big it'll // be a total drag. :P $scene = 0; - } elseif ( $this->isAnimatedImage( $image ) ) { // Coalesce is needed to scale animated GIFs properly (bug 1017). $animation_pre = '-coalesce'; @@ -363,6 +372,7 @@ class BitmapHandler extends ImageHandler { if ( $retval !== 0 ) { $this->logErrorForExternalProcess( $retval, $err, $cmd ); + return $this->getMediaTransformError( $params, $err ); } @@ -387,8 +397,9 @@ class BitmapHandler extends ImageHandler { if ( $params['mimeType'] == 'image/jpeg' ) { // Sharpening, see bug 6193 if ( ( $params['physicalWidth'] + $params['physicalHeight'] ) - / ( $params['srcWidth'] + $params['srcHeight'] ) - < $wgSharpenReductionThreshold ) { + / ( $params['srcWidth'] + $params['srcHeight'] ) + < $wgSharpenReductionThreshold + ) { // Hack, since $wgSharpenParamater is written specifically for the command line convert list( $radius, $sigma ) = explode( 'x', $wgSharpenParameter ); $im->sharpenImage( $radius, $sigma ); @@ -437,13 +448,11 @@ class BitmapHandler extends ImageHandler { return $this->getMediaTransformError( $params, "Unable to write thumbnail to {$params['dstPath']}" ); } - } catch ( ImagickException $e ) { return $this->getMediaTransformError( $params, $e->getMessage() ); } return false; - } /** @@ -473,8 +482,10 @@ class BitmapHandler extends ImageHandler { if ( $retval !== 0 ) { $this->logErrorForExternalProcess( $retval, $err, $cmd ); + return $this->getMediaTransformError( $params, $err ); } + return false; # No error } @@ -488,8 +499,9 @@ class BitmapHandler extends ImageHandler { protected function logErrorForExternalProcess( $retval, $err, $cmd ) { wfDebugLog( 'thumbnail', sprintf( 'thumbnail failed on %s: error %d "%s" from "%s"', - wfHostname(), $retval, trim( $err ), $cmd ) ); + wfHostname(), $retval, trim( $err ), $cmd ) ); } + /** * Get a MediaTransformError with error 'thumbnail_error' * @@ -499,7 +511,7 @@ class BitmapHandler extends ImageHandler { */ public function getMediaTransformError( $params, $errMsg ) { return new MediaTransformError( 'thumbnail_error', $params['clientWidth'], - $params['clientHeight'], $errMsg ); + $params['clientHeight'], $errMsg ); } /** @@ -517,16 +529,17 @@ class BitmapHandler extends ImageHandler { # input routine for this. $typemap = array( - 'image/gif' => array( 'imagecreatefromgif', 'palette', 'imagegif' ), - 'image/jpeg' => array( 'imagecreatefromjpeg', 'truecolor', array( __CLASS__, 'imageJpegWrapper' ) ), - 'image/png' => array( 'imagecreatefrompng', 'bits', 'imagepng' ), - 'image/vnd.wap.wbmp' => array( 'imagecreatefromwbmp', 'palette', 'imagewbmp' ), - 'image/xbm' => array( 'imagecreatefromxbm', 'palette', 'imagexbm' ), + 'image/gif' => array( 'imagecreatefromgif', 'palette', 'imagegif' ), + 'image/jpeg' => array( 'imagecreatefromjpeg', 'truecolor', array( __CLASS__, 'imageJpegWrapper' ) ), + 'image/png' => array( 'imagecreatefrompng', 'bits', 'imagepng' ), + 'image/vnd.wap.wbmp' => array( 'imagecreatefromwbmp', 'palette', 'imagewbmp' ), + 'image/xbm' => array( 'imagecreatefromxbm', 'palette', 'imagexbm' ), ); if ( !isset( $typemap[$params['mimeType']] ) ) { $err = 'Image type not supported'; wfDebug( "$err\n" ); $errMsg = wfMessage( 'thumbnail_image-type' )->text(); + return $this->getMediaTransformError( $params, $errMsg ); } list( $loader, $colorStyle, $saveType ) = $typemap[$params['mimeType']]; @@ -535,6 +548,7 @@ class BitmapHandler extends ImageHandler { $err = "Incomplete GD library configuration: missing function $loader"; wfDebug( "$err\n" ); $errMsg = wfMessage( 'thumbnail_gd-library', $loader )->text(); + return $this->getMediaTransformError( $params, $errMsg ); } @@ -542,6 +556,7 @@ class BitmapHandler extends ImageHandler { $err = "File seems to be missing: {$params['srcPath']}"; wfDebug( "$err\n" ); $errMsg = wfMessage( 'thumbnail_image-missing', $params['srcPath'] )->text(); + return $this->getMediaTransformError( $params, $errMsg ); } @@ -600,6 +615,7 @@ class BitmapHandler extends ImageHandler { if ( strlen( $s ) > 0 && ( $s[0] === '-' || $s[0] === '@' ) ) { $s = '\\' . $s; } + return $s; } @@ -640,6 +656,7 @@ class BitmapHandler extends ImageHandler { */ function escapeMagickOutput( $path, $scene = false ) { $path = str_replace( '%', '%%', $path ); + return $this->escapeMagickPath( $path, $scene ); } @@ -673,6 +690,7 @@ class BitmapHandler extends ImageHandler { } else { $path .= "[$scene]"; } + return $path; } @@ -695,11 +713,14 @@ class BitmapHandler extends ImageHandler { $x = preg_match( '/Version: ImageMagick ([0-9]*\.[0-9]*\.[0-9]*)/', $return, $matches ); if ( $x != 1 ) { wfDebug( __METHOD__ . ": ImageMagick version check failed\n" ); + return null; } $wgMemc->set( "imagemagick-version", $matches[1], 3600 ); + return $matches[1]; } + return $cache; } @@ -735,7 +756,7 @@ class BitmapHandler extends ImageHandler { /** * @param $file File * @param array $params Rotate parameters. - * 'rotation' clockwise rotation in degrees, allowed are multiples of 90 + * 'rotation' clockwise rotation in degrees, allowed are multiples of 90 * @since 1.21 * @return bool */ @@ -759,8 +780,10 @@ class BitmapHandler extends ImageHandler { wfProfileOut( 'convert' ); if ( $retval !== 0 ) { $this->logErrorForExternalProcess( $retval, $err, $cmd ); + return new MediaTransformError( 'thumbnail_error', 0, 0, $err ); } + return false; case 'imext': $im = new Imagick(); @@ -774,6 +797,7 @@ class BitmapHandler extends ImageHandler { return new MediaTransformError( 'thumbnail_error', 0, 0, "Unable to write image to {$params['dstPath']}" ); } + return false; default: return new MediaTransformError( 'thumbnail_error', 0, 0, diff --git a/includes/media/BitmapMetadataHandler.php b/includes/media/BitmapMetadataHandler.php index 00b4298bd9..f85f15e50a 100644 --- a/includes/media/BitmapMetadataHandler.php +++ b/includes/media/BitmapMetadataHandler.php @@ -32,8 +32,8 @@ * @ingroup Media */ class BitmapMetadataHandler { - private $metadata = array(); + private $metaPriority = array( 20 => array( 'other' ), 40 => array( 'native' ), @@ -44,6 +44,7 @@ class BitmapMetadataHandler { 100 => array( 'iptc-bad-hash' ), 120 => array( 'exif' ), ); + private $iptcType = 'iptc-no-hash'; /** @@ -89,6 +90,7 @@ class BitmapMetadataHandler { } } } + /** Add misc metadata. Warning: atm if the metadata category * doesn't have a priority, it will be silently discarded. * @@ -138,6 +140,7 @@ class BitmapMetadataHandler { } } } + return $temp; } @@ -168,7 +171,6 @@ class BitmapMetadataHandler { * is not well tested and a bit fragile. */ $xmp->parseExtended( $xmpExt ); - } $res = $xmp->getResults(); foreach ( $res as $type => $array ) { @@ -178,6 +180,7 @@ class BitmapMetadataHandler { if ( isset( $seg['byteOrder'] ) ) { $meta->getExif( $filename, $seg['byteOrder'] ); } + return $meta->getMetadataArray(); } @@ -207,6 +210,7 @@ class BitmapMetadataHandler { unset( $array['text'] ); $array['metadata'] = $meta->getMetadataArray(); $array['metadata']['_MW_PNG_VERSION'] = PNGMetadataExtractor::VERSION; + return $array; } @@ -234,7 +238,6 @@ class BitmapMetadataHandler { foreach ( $xmpRes as $type => $xmpSection ) { $meta->addMetadata( $xmpSection, $type ); } - } unset( $baseArray['comment'] ); @@ -242,6 +245,7 @@ class BitmapMetadataHandler { $baseArray['metadata'] = $meta->getMetadataArray(); $baseArray['metadata']['_MW_GIF_VERSION'] = GIFMetadataExtractor::VERSION; + return $baseArray; } @@ -269,6 +273,7 @@ class BitmapMetadataHandler { $data = $exif->getFilteredData(); if ( $data ) { $data['MEDIAWIKI_EXIF_VERSION'] = Exif::version(); + return $data; } else { throw new MWException( "Could not extract data from tiff file $filename" ); @@ -277,6 +282,7 @@ class BitmapMetadataHandler { throw new MWException( "File doesn't exist - $filename" ); } } + /** * Read the first 2 bytes of a tiff file to figure out * Little Endian or Big Endian. Needed for exif stuff. diff --git a/includes/media/Bitmap_ClientOnly.php b/includes/media/Bitmap_ClientOnly.php index 63af2552c3..ac751fe2cf 100644 --- a/includes/media/Bitmap_ClientOnly.php +++ b/includes/media/Bitmap_ClientOnly.php @@ -30,7 +30,6 @@ * @ingroup Media */ class BitmapHandler_ClientOnly extends BitmapHandler { - /** * @param $image File * @param $params @@ -52,6 +51,7 @@ class BitmapHandler_ClientOnly extends BitmapHandler { if ( !$this->normaliseParams( $image, $params ) ) { return new TransformParameterError( $params ); } + return new ThumbnailImage( $image, $image->getURL(), $image->getLocalRefPath(), $params ); } } diff --git a/includes/media/DjVu.php b/includes/media/DjVu.php index b9e89d9d22..fe3313a4e8 100644 --- a/includes/media/DjVu.php +++ b/includes/media/DjVu.php @@ -27,7 +27,6 @@ * @ingroup Media */ class DjVuHandler extends ImageHandler { - /** * @return bool */ @@ -35,6 +34,7 @@ class DjVuHandler extends ImageHandler { global $wgDjvuRenderer, $wgDjvuDump, $wgDjvuToXML; if ( !$wgDjvuRenderer || ( !$wgDjvuDump && !$wgDjvuToXML ) ) { wfDebug( "DjVu is disabled, please set \$wgDjvuRenderer and \$wgDjvuDump\n" ); + return false; } else { return true; @@ -93,6 +93,7 @@ class DjVuHandler extends ImageHandler { if ( !isset( $params['width'] ) ) { return false; } + return "page{$page}-{$params['width']}px"; } @@ -137,6 +138,7 @@ class DjVuHandler extends ImageHandler { if ( !$xml ) { $width = isset( $params['width'] ) ? $params['width'] : 0; $height = isset( $params['height'] ) ? $params['height'] : 0; + return new MediaTransformError( 'thumbnail_error', $width, $height, wfMessage( 'djvu_no_xml' )->text() ); } @@ -162,6 +164,7 @@ class DjVuHandler extends ImageHandler { 'height' => $height, 'page' => $page ); + return new ThumbnailImage( $image, $dstUrl, $dstPath, $params ); } @@ -195,6 +198,7 @@ class DjVuHandler extends ImageHandler { wfDebugLog( 'thumbnail', sprintf( 'thumbnail failed on %s: error %d "%s" from "%s"', wfHostname(), $retval, trim( $err ), $cmd ) ); + return new MediaTransformError( 'thumbnail_error', $width, $height, $err ); } else { $params = array( @@ -202,6 +206,7 @@ class DjVuHandler extends ImageHandler { 'height' => $height, 'page' => $page ); + return new ThumbnailImage( $image, $dstUrl, $dstPath, $params ); } } @@ -219,6 +224,7 @@ class DjVuHandler extends ImageHandler { } else { $deja = $image->dejaImage; } + return $deja; } @@ -236,6 +242,7 @@ class DjVuHandler extends ImageHandler { $metadata = $image->getMetadata(); if ( !$this->isMetadataValid( $image, $metadata ) ) { wfDebug( "DjVu XML metadata is invalid or missing, should have been fixed in upgradeRow\n" ); + return false; } wfProfileIn( __METHOD__ ); @@ -280,11 +287,13 @@ class DjVuHandler extends ImageHandler { $magic = MimeMagic::singleton(); $mime = $magic->guessTypesForExtension( $wgDjvuOutputExtension ); } + return array( $wgDjvuOutputExtension, $mime ); } function getMetadata( $image, $path ) { wfDebug( "Getting DjVu metadata for $path\n" ); + return $this->getDjVuImage( $image, $path )->retrieveMetaData(); } @@ -301,6 +310,7 @@ class DjVuHandler extends ImageHandler { if ( !$tree ) { return false; } + return count( $tree->xpath( '//OBJECT' ) ); } @@ -330,11 +340,10 @@ class DjVuHandler extends ImageHandler { $o = $tree->BODY[0]->PAGE[$page - 1]; if ( $o ) { $txt = $o['value']; + return $txt; } else { return false; } - } - } diff --git a/includes/media/DjVuImage.php b/includes/media/DjVuImage.php index 54efe7a833..f201c28ab8 100644 --- a/includes/media/DjVuImage.php +++ b/includes/media/DjVuImage.php @@ -54,6 +54,7 @@ class DjVuImage { */ public function isValid() { $info = $this->getInfo(); + return $info !== false; } @@ -71,6 +72,7 @@ class DjVuImage { return array( $width, $height, 'DjVu', "width=\"$width\" height=\"$height\"" ); } + return false; } @@ -120,6 +122,7 @@ class DjVuImage { wfRestoreWarnings(); if ( $file === false ) { wfDebug( __METHOD__ . ": missing or failed file read\n" ); + return false; } @@ -145,6 +148,7 @@ class DjVuImage { } } fclose( $file ); + return $info; } @@ -155,6 +159,7 @@ class DjVuImage { } else { // @todo FIXME: Would be good to replace this extract() call with something that explicitly initializes local variables. extract( unpack( 'a4chunk/Nlength', $header ) ); + return array( $chunk, $length ); } } @@ -182,6 +187,7 @@ class DjVuImage { $subtype = fread( $file, 4 ); if ( $subtype == 'DJVU' ) { wfDebug( __METHOD__ . ": found first subpage\n" ); + return $this->getPageInfo( $file, $length ); } $this->skipChunk( $file, $length - 4 ); @@ -192,6 +198,7 @@ class DjVuImage { } while ( $length != 0 && !feof( $file ) && ftell( $file ) - $start < $formLength ); wfDebug( __METHOD__ . ": multi-page DJVU file contained no pages\n" ); + return false; } @@ -199,16 +206,19 @@ class DjVuImage { list( $chunk, $length ) = $this->readChunk( $file ); if ( $chunk != 'INFO' ) { wfDebug( __METHOD__ . ": expected INFO chunk, got '$chunk'\n" ); + return false; } if ( $length < 9 ) { wfDebug( __METHOD__ . ": INFO should be 9 or 10 bytes, found $length\n" ); + return false; } $data = fread( $file, $length ); if ( strlen( $data ) < $length ) { wfDebug( __METHOD__ . ": INFO chunk cut off\n" ); + return false; } @@ -220,6 +230,7 @@ class DjVuImage { 'Cmajor/' . 'vresolution/' . 'Cgamma', $data ) ); + # Newer files have rotation info in byte 10, but we don't use it yet. return array( @@ -284,6 +295,7 @@ EOR; } } wfProfileOut( __METHOD__ ); + return $xml; } @@ -334,6 +346,7 @@ EOT; if ( preg_match( '/^ *DIRM.*indirect/', $line ) ) { wfDebug( "Indirect multi-page DjVu document, bad for server!\n" ); + return false; } if ( preg_match( '/^ *FORM:DJVU/', $line ) ) { @@ -352,6 +365,7 @@ EOT; } $xml .= "\n\n"; + return $xml; } @@ -368,7 +382,8 @@ EOT; } if ( preg_match( '/^ *INFO *\[\d*\] *DjVu *(\d+)x(\d+), *\w*, *(\d+) *dpi, *gamma=([0-9.-]+)/', $line, $m ) ) { - $xml .= Xml::tags( 'OBJECT', + $xml .= Xml::tags( + 'OBJECT', array( #'data' => '', #'type' => 'image/x.djvu', @@ -377,13 +392,15 @@ EOT; #'usemap' => '', ), "\n" . - Xml::element( 'PARAM', array( 'name' => 'DPI', 'value' => $m[3] ) ) . "\n" . - Xml::element( 'PARAM', array( 'name' => 'GAMMA', 'value' => $m[4] ) ) . "\n" + Xml::element( 'PARAM', array( 'name' => 'DPI', 'value' => $m[3] ) ) . "\n" . + Xml::element( 'PARAM', array( 'name' => 'GAMMA', 'value' => $m[4] ) ) . "\n" ) . "\n"; + return true; } $line = strtok( "\n" ); } + # Not found return false; } diff --git a/includes/media/Exif.php b/includes/media/Exif.php index 50dd26684b..5f40fe97f9 100644 --- a/includes/media/Exif.php +++ b/includes/media/Exif.php @@ -30,7 +30,6 @@ * @ingroup Media */ class Exif { - const BYTE = 1; //!< An 8-bit (1-byte) unsigned integer. const ASCII = 2; //!< An 8-bit byte containing one 7-bit ASCII code. The final byte is terminated with NULL. const SHORT = 3; //!< A 16-bit (2-byte) unsigned integer. @@ -98,6 +97,7 @@ class Exif { * extension doesn't fully process some obscure props. */ private $byteOrder; + //@} /** @@ -125,122 +125,122 @@ class Exif { # TIFF Rev. 6.0 Attribute Information (p22) 'IFD0' => array( # Tags relating to image structure - 'ImageWidth' => Exif::SHORT_OR_LONG, # Image width - 'ImageLength' => Exif::SHORT_OR_LONG, # Image height - 'BitsPerSample' => array( Exif::SHORT, 3 ), # Number of bits per component + 'ImageWidth' => Exif::SHORT_OR_LONG, # Image width + 'ImageLength' => Exif::SHORT_OR_LONG, # Image height + 'BitsPerSample' => array( Exif::SHORT, 3 ), # Number of bits per component # "When a primary image is JPEG compressed, this designation is not" # "necessary and is omitted." (p23) - 'Compression' => Exif::SHORT, # Compression scheme #p23 - 'PhotometricInterpretation' => Exif::SHORT, # Pixel composition #p23 - 'Orientation' => Exif::SHORT, # Orientation of image #p24 - 'SamplesPerPixel' => Exif::SHORT, # Number of components - 'PlanarConfiguration' => Exif::SHORT, # Image data arrangement #p24 - 'YCbCrSubSampling' => array( Exif::SHORT, 2 ), # Subsampling ratio of Y to C #p24 - 'YCbCrPositioning' => Exif::SHORT, # Y and C positioning #p24-25 - 'XResolution' => Exif::RATIONAL, # Image resolution in width direction - 'YResolution' => Exif::RATIONAL, # Image resolution in height direction - 'ResolutionUnit' => Exif::SHORT, # Unit of X and Y resolution #(p26) + 'Compression' => Exif::SHORT, # Compression scheme #p23 + 'PhotometricInterpretation' => Exif::SHORT, # Pixel composition #p23 + 'Orientation' => Exif::SHORT, # Orientation of image #p24 + 'SamplesPerPixel' => Exif::SHORT, # Number of components + 'PlanarConfiguration' => Exif::SHORT, # Image data arrangement #p24 + 'YCbCrSubSampling' => array( Exif::SHORT, 2 ), # Subsampling ratio of Y to C #p24 + 'YCbCrPositioning' => Exif::SHORT, # Y and C positioning #p24-25 + 'XResolution' => Exif::RATIONAL, # Image resolution in width direction + 'YResolution' => Exif::RATIONAL, # Image resolution in height direction + 'ResolutionUnit' => Exif::SHORT, # Unit of X and Y resolution #(p26) # Tags relating to recording offset - 'StripOffsets' => Exif::SHORT_OR_LONG, # Image data location - 'RowsPerStrip' => Exif::SHORT_OR_LONG, # Number of rows per strip - 'StripByteCounts' => Exif::SHORT_OR_LONG, # Bytes per compressed strip - 'JPEGInterchangeFormat' => Exif::SHORT_OR_LONG, # Offset to JPEG SOI - 'JPEGInterchangeFormatLength' => Exif::SHORT_OR_LONG, # Bytes of JPEG data + 'StripOffsets' => Exif::SHORT_OR_LONG, # Image data location + 'RowsPerStrip' => Exif::SHORT_OR_LONG, # Number of rows per strip + 'StripByteCounts' => Exif::SHORT_OR_LONG, # Bytes per compressed strip + 'JPEGInterchangeFormat' => Exif::SHORT_OR_LONG, # Offset to JPEG SOI + 'JPEGInterchangeFormatLength' => Exif::SHORT_OR_LONG, # Bytes of JPEG data # Tags relating to image data characteristics - 'TransferFunction' => Exif::IGNORE, # Transfer function - 'WhitePoint' => array( Exif::RATIONAL, 2 ), # White point chromaticity - 'PrimaryChromaticities' => array( Exif::RATIONAL, 6 ), # Chromaticities of primarities - 'YCbCrCoefficients' => array( Exif::RATIONAL, 3 ), # Color space transformation matrix coefficients #p27 - 'ReferenceBlackWhite' => array( Exif::RATIONAL, 6 ), # Pair of black and white reference values + 'TransferFunction' => Exif::IGNORE, # Transfer function + 'WhitePoint' => array( Exif::RATIONAL, 2 ), # White point chromaticity + 'PrimaryChromaticities' => array( Exif::RATIONAL, 6 ), # Chromaticities of primarities + 'YCbCrCoefficients' => array( Exif::RATIONAL, 3 ), # Color space transformation matrix coefficients #p27 + 'ReferenceBlackWhite' => array( Exif::RATIONAL, 6 ), # Pair of black and white reference values # Other tags - 'DateTime' => Exif::ASCII, # File change date and time - 'ImageDescription' => Exif::ASCII, # Image title - 'Make' => Exif::ASCII, # Image input equipment manufacturer - 'Model' => Exif::ASCII, # Image input equipment model - 'Software' => Exif::ASCII, # Software used - 'Artist' => Exif::ASCII, # Person who created the image - 'Copyright' => Exif::ASCII, # Copyright holder + 'DateTime' => Exif::ASCII, # File change date and time + 'ImageDescription' => Exif::ASCII, # Image title + 'Make' => Exif::ASCII, # Image input equipment manufacturer + 'Model' => Exif::ASCII, # Image input equipment model + 'Software' => Exif::ASCII, # Software used + 'Artist' => Exif::ASCII, # Person who created the image + 'Copyright' => Exif::ASCII, # Copyright holder ), # Exif IFD Attribute Information (p30-31) 'EXIF' => array( # TODO: NOTE: Nonexistence of this field is taken to mean nonconformance # to the Exif 2.1 AND 2.2 standards - 'ExifVersion' => Exif::UNDEFINED, # Exif version - 'FlashPixVersion' => Exif::UNDEFINED, # Supported Flashpix version #p32 + 'ExifVersion' => Exif::UNDEFINED, # Exif version + 'FlashPixVersion' => Exif::UNDEFINED, # Supported Flashpix version #p32 # Tags relating to Image Data Characteristics - 'ColorSpace' => Exif::SHORT, # Color space information #p32 + 'ColorSpace' => Exif::SHORT, # Color space information #p32 # Tags relating to image configuration - 'ComponentsConfiguration' => Exif::UNDEFINED, # Meaning of each component #p33 - 'CompressedBitsPerPixel' => Exif::RATIONAL, # Image compression mode - 'PixelYDimension' => Exif::SHORT_OR_LONG, # Valid image width - 'PixelXDimension' => Exif::SHORT_OR_LONG, # Valid image height + 'ComponentsConfiguration' => Exif::UNDEFINED, # Meaning of each component #p33 + 'CompressedBitsPerPixel' => Exif::RATIONAL, # Image compression mode + 'PixelYDimension' => Exif::SHORT_OR_LONG, # Valid image width + 'PixelXDimension' => Exif::SHORT_OR_LONG, # Valid image height # Tags relating to related user information - 'MakerNote' => Exif::IGNORE, # Manufacturer notes - 'UserComment' => Exif::UNDEFINED, # User comments #p34 + 'MakerNote' => Exif::IGNORE, # Manufacturer notes + 'UserComment' => Exif::UNDEFINED, # User comments #p34 # Tags relating to related file information - 'RelatedSoundFile' => Exif::ASCII, # Related audio file + 'RelatedSoundFile' => Exif::ASCII, # Related audio file # Tags relating to date and time - 'DateTimeOriginal' => Exif::ASCII, # Date and time of original data generation #p36 - 'DateTimeDigitized' => Exif::ASCII, # Date and time of original data generation - 'SubSecTime' => Exif::ASCII, # DateTime subseconds - 'SubSecTimeOriginal' => Exif::ASCII, # DateTimeOriginal subseconds - 'SubSecTimeDigitized' => Exif::ASCII, # DateTimeDigitized subseconds + 'DateTimeOriginal' => Exif::ASCII, # Date and time of original data generation #p36 + 'DateTimeDigitized' => Exif::ASCII, # Date and time of original data generation + 'SubSecTime' => Exif::ASCII, # DateTime subseconds + 'SubSecTimeOriginal' => Exif::ASCII, # DateTimeOriginal subseconds + 'SubSecTimeDigitized' => Exif::ASCII, # DateTimeDigitized subseconds # Tags relating to picture-taking conditions (p31) - 'ExposureTime' => Exif::RATIONAL, # Exposure time - 'FNumber' => Exif::RATIONAL, # F Number - 'ExposureProgram' => Exif::SHORT, # Exposure Program #p38 - 'SpectralSensitivity' => Exif::ASCII, # Spectral sensitivity - 'ISOSpeedRatings' => Exif::SHORT, # ISO speed rating + 'ExposureTime' => Exif::RATIONAL, # Exposure time + 'FNumber' => Exif::RATIONAL, # F Number + 'ExposureProgram' => Exif::SHORT, # Exposure Program #p38 + 'SpectralSensitivity' => Exif::ASCII, # Spectral sensitivity + 'ISOSpeedRatings' => Exif::SHORT, # ISO speed rating 'OECF' => Exif::IGNORE, # Optoelectronic conversion factor. Note: We don't have support for this atm. - 'ShutterSpeedValue' => Exif::SRATIONAL, # Shutter speed - 'ApertureValue' => Exif::RATIONAL, # Aperture - 'BrightnessValue' => Exif::SRATIONAL, # Brightness - 'ExposureBiasValue' => Exif::SRATIONAL, # Exposure bias - 'MaxApertureValue' => Exif::RATIONAL, # Maximum land aperture - 'SubjectDistance' => Exif::RATIONAL, # Subject distance - 'MeteringMode' => Exif::SHORT, # Metering mode #p40 - 'LightSource' => Exif::SHORT, # Light source #p40-41 - 'Flash' => Exif::SHORT, # Flash #p41-42 - 'FocalLength' => Exif::RATIONAL, # Lens focal length - 'SubjectArea' => array( Exif::SHORT, 4 ), # Subject area - 'FlashEnergy' => Exif::RATIONAL, # Flash energy - 'SpatialFrequencyResponse' => Exif::IGNORE, # Spatial frequency response. Not supported atm. - 'FocalPlaneXResolution' => Exif::RATIONAL, # Focal plane X resolution - 'FocalPlaneYResolution' => Exif::RATIONAL, # Focal plane Y resolution - 'FocalPlaneResolutionUnit' => Exif::SHORT, # Focal plane resolution unit #p46 - 'SubjectLocation' => array( Exif::SHORT, 2 ), # Subject location - 'ExposureIndex' => Exif::RATIONAL, # Exposure index - 'SensingMethod' => Exif::SHORT, # Sensing method #p46 - 'FileSource' => Exif::UNDEFINED, # File source #p47 - 'SceneType' => Exif::UNDEFINED, # Scene type #p47 - 'CFAPattern' => Exif::IGNORE, # CFA pattern. not supported atm. - 'CustomRendered' => Exif::SHORT, # Custom image processing #p48 - 'ExposureMode' => Exif::SHORT, # Exposure mode #p48 - 'WhiteBalance' => Exif::SHORT, # White Balance #p49 - 'DigitalZoomRatio' => Exif::RATIONAL, # Digital zoom ration - 'FocalLengthIn35mmFilm' => Exif::SHORT, # Focal length in 35 mm film - 'SceneCaptureType' => Exif::SHORT, # Scene capture type #p49 - 'GainControl' => Exif::SHORT, # Scene control #p49-50 - 'Contrast' => Exif::SHORT, # Contrast #p50 - 'Saturation' => Exif::SHORT, # Saturation #p50 - 'Sharpness' => Exif::SHORT, # Sharpness #p50 + 'ShutterSpeedValue' => Exif::SRATIONAL, # Shutter speed + 'ApertureValue' => Exif::RATIONAL, # Aperture + 'BrightnessValue' => Exif::SRATIONAL, # Brightness + 'ExposureBiasValue' => Exif::SRATIONAL, # Exposure bias + 'MaxApertureValue' => Exif::RATIONAL, # Maximum land aperture + 'SubjectDistance' => Exif::RATIONAL, # Subject distance + 'MeteringMode' => Exif::SHORT, # Metering mode #p40 + 'LightSource' => Exif::SHORT, # Light source #p40-41 + 'Flash' => Exif::SHORT, # Flash #p41-42 + 'FocalLength' => Exif::RATIONAL, # Lens focal length + 'SubjectArea' => array( Exif::SHORT, 4 ), # Subject area + 'FlashEnergy' => Exif::RATIONAL, # Flash energy + 'SpatialFrequencyResponse' => Exif::IGNORE, # Spatial frequency response. Not supported atm. + 'FocalPlaneXResolution' => Exif::RATIONAL, # Focal plane X resolution + 'FocalPlaneYResolution' => Exif::RATIONAL, # Focal plane Y resolution + 'FocalPlaneResolutionUnit' => Exif::SHORT, # Focal plane resolution unit #p46 + 'SubjectLocation' => array( Exif::SHORT, 2 ), # Subject location + 'ExposureIndex' => Exif::RATIONAL, # Exposure index + 'SensingMethod' => Exif::SHORT, # Sensing method #p46 + 'FileSource' => Exif::UNDEFINED, # File source #p47 + 'SceneType' => Exif::UNDEFINED, # Scene type #p47 + 'CFAPattern' => Exif::IGNORE, # CFA pattern. not supported atm. + 'CustomRendered' => Exif::SHORT, # Custom image processing #p48 + 'ExposureMode' => Exif::SHORT, # Exposure mode #p48 + 'WhiteBalance' => Exif::SHORT, # White Balance #p49 + 'DigitalZoomRatio' => Exif::RATIONAL, # Digital zoom ration + 'FocalLengthIn35mmFilm' => Exif::SHORT, # Focal length in 35 mm film + 'SceneCaptureType' => Exif::SHORT, # Scene capture type #p49 + 'GainControl' => Exif::SHORT, # Scene control #p49-50 + 'Contrast' => Exif::SHORT, # Contrast #p50 + 'Saturation' => Exif::SHORT, # Saturation #p50 + 'Sharpness' => Exif::SHORT, # Sharpness #p50 'DeviceSettingDescription' => Exif::IGNORE, # Device settings description. This could maybe be supported. Need to find an # example file that uses this to see if it has stuff of interest in it. - 'SubjectDistanceRange' => Exif::SHORT, # Subject distance range #p51 + 'SubjectDistanceRange' => Exif::SHORT, # Subject distance range #p51 - 'ImageUniqueID' => Exif::ASCII, # Unique image ID + 'ImageUniqueID' => Exif::ASCII, # Unique image ID ), # GPS Attribute Information (p52) @@ -248,38 +248,38 @@ class Exif { 'GPSVersion' => Exif::UNDEFINED, # Should be an array of 4 Exif::BYTE's. However php treats it as an undefined # Note exif standard calls this GPSVersionID, but php doesn't like the id suffix - 'GPSLatitudeRef' => Exif::ASCII, # North or South Latitude #p52-53 - 'GPSLatitude' => array( Exif::RATIONAL, 3 ), # Latitude - 'GPSLongitudeRef' => Exif::ASCII, # East or West Longitude #p53 - 'GPSLongitude' => array( Exif::RATIONAL, 3 ), # Longitude + 'GPSLatitudeRef' => Exif::ASCII, # North or South Latitude #p52-53 + 'GPSLatitude' => array( Exif::RATIONAL, 3 ), # Latitude + 'GPSLongitudeRef' => Exif::ASCII, # East or West Longitude #p53 + 'GPSLongitude' => array( Exif::RATIONAL, 3 ), # Longitude 'GPSAltitudeRef' => Exif::UNDEFINED, # Altitude reference. Note, the exif standard says this should be an EXIF::Byte, # but php seems to disagree. - 'GPSAltitude' => Exif::RATIONAL, # Altitude - 'GPSTimeStamp' => array( Exif::RATIONAL, 3 ), # GPS time (atomic clock) - 'GPSSatellites' => Exif::ASCII, # Satellites used for measurement - 'GPSStatus' => Exif::ASCII, # Receiver status #p54 - 'GPSMeasureMode' => Exif::ASCII, # Measurement mode #p54-55 - 'GPSDOP' => Exif::RATIONAL, # Measurement precision - 'GPSSpeedRef' => Exif::ASCII, # Speed unit #p55 - 'GPSSpeed' => Exif::RATIONAL, # Speed of GPS receiver - 'GPSTrackRef' => Exif::ASCII, # Reference for direction of movement #p55 - 'GPSTrack' => Exif::RATIONAL, # Direction of movement - 'GPSImgDirectionRef' => Exif::ASCII, # Reference for direction of image #p56 - 'GPSImgDirection' => Exif::RATIONAL, # Direction of image - 'GPSMapDatum' => Exif::ASCII, # Geodetic survey data used - 'GPSDestLatitudeRef' => Exif::ASCII, # Reference for latitude of destination #p56 - 'GPSDestLatitude' => array( Exif::RATIONAL, 3 ), # Latitude destination - 'GPSDestLongitudeRef' => Exif::ASCII, # Reference for longitude of destination #p57 - 'GPSDestLongitude' => array( Exif::RATIONAL, 3 ), # Longitude of destination - 'GPSDestBearingRef' => Exif::ASCII, # Reference for bearing of destination #p57 - 'GPSDestBearing' => Exif::RATIONAL, # Bearing of destination - 'GPSDestDistanceRef' => Exif::ASCII, # Reference for distance to destination #p57-58 - 'GPSDestDistance' => Exif::RATIONAL, # Distance to destination - 'GPSProcessingMethod' => Exif::UNDEFINED, # Name of GPS processing method - 'GPSAreaInformation' => Exif::UNDEFINED, # Name of GPS area - 'GPSDateStamp' => Exif::ASCII, # GPS date - 'GPSDifferential' => Exif::SHORT, # GPS differential correction + 'GPSAltitude' => Exif::RATIONAL, # Altitude + 'GPSTimeStamp' => array( Exif::RATIONAL, 3 ), # GPS time (atomic clock) + 'GPSSatellites' => Exif::ASCII, # Satellites used for measurement + 'GPSStatus' => Exif::ASCII, # Receiver status #p54 + 'GPSMeasureMode' => Exif::ASCII, # Measurement mode #p54-55 + 'GPSDOP' => Exif::RATIONAL, # Measurement precision + 'GPSSpeedRef' => Exif::ASCII, # Speed unit #p55 + 'GPSSpeed' => Exif::RATIONAL, # Speed of GPS receiver + 'GPSTrackRef' => Exif::ASCII, # Reference for direction of movement #p55 + 'GPSTrack' => Exif::RATIONAL, # Direction of movement + 'GPSImgDirectionRef' => Exif::ASCII, # Reference for direction of image #p56 + 'GPSImgDirection' => Exif::RATIONAL, # Direction of image + 'GPSMapDatum' => Exif::ASCII, # Geodetic survey data used + 'GPSDestLatitudeRef' => Exif::ASCII, # Reference for latitude of destination #p56 + 'GPSDestLatitude' => array( Exif::RATIONAL, 3 ), # Latitude destination + 'GPSDestLongitudeRef' => Exif::ASCII, # Reference for longitude of destination #p57 + 'GPSDestLongitude' => array( Exif::RATIONAL, 3 ), # Longitude of destination + 'GPSDestBearingRef' => Exif::ASCII, # Reference for bearing of destination #p57 + 'GPSDestBearing' => Exif::RATIONAL, # Bearing of destination + 'GPSDestDistanceRef' => Exif::ASCII, # Reference for distance to destination #p57-58 + 'GPSDestDistance' => Exif::RATIONAL, # Distance to destination + 'GPSProcessingMethod' => Exif::UNDEFINED, # Name of GPS processing method + 'GPSAreaInformation' => Exif::UNDEFINED, # Name of GPS area + 'GPSDateStamp' => Exif::ASCII, # GPS date + 'GPSDifferential' => Exif::SHORT, # GPS differential correction ), ); @@ -379,7 +379,7 @@ class Exif { $this->mFilteredExifData['GPSAltitude'] = $num / $denom; if ( $this->mFilteredExifData['GPSAltitudeRef'] === "\1" ) { - $this->mFilteredExifData['GPSAltitude'] *= - 1; + $this->mFilteredExifData['GPSAltitude'] *= -1; } unset( $this->mFilteredExifData['GPSAltitudeRef'] ); } @@ -432,8 +432,8 @@ class Exif { } unset( $this->mFilteredExifData['GPSVersion'] ); } - } + /** * Do userComment tags and similar. See pg. 34 of exif standard. * basically first 8 bytes is charset, rest is value. @@ -448,6 +448,7 @@ class Exif { $this->debug( $this->mFilteredExifData[$prop], __FUNCTION__, false ); unset( $this->mFilteredExifData[$prop] ); + return; } $charCode = substr( $this->mFilteredExifData[$prop], 0, 8 ); @@ -488,6 +489,7 @@ class Exif { //only whitespace. $this->debug( $this->mFilteredExifData[$prop], __FUNCTION__, "$prop: Is only whitespace" ); unset( $this->mFilteredExifData[$prop] ); + return; } @@ -495,6 +497,7 @@ class Exif { $this->mFilteredExifData[$prop] = $val; } } + /** * Convert an Exif::UNDEFINED from a raw binary string * to its value. This is sometimes needed depending on @@ -506,6 +509,7 @@ class Exif { $this->mFilteredExifData[$prop] = ord( $this->mFilteredExifData[$prop] ); } } + /** * Convert gps in exif form to a single floating point number * for example 10 degress 20`40`` S -> -10.34444 @@ -525,7 +529,7 @@ class Exif { $res += ( $num / $denom ) * ( 1 / 3600 ); if ( $dir === 'S' || $dir === 'W' ) { - $res *= - 1; // make negative + $res *= -1; // make negative } } @@ -551,6 +555,7 @@ class Exif { $this->mFormattedExifData = FormatMetadata::getFormattedData( $this->mFilteredExifData ); } + /**#@-*/ /**#@+ @@ -584,8 +589,10 @@ class Exif { if ( !$this->mFormattedExifData ) { $this->makeFormattedData(); } + return $this->mFormattedExifData; } + /**#@-*/ /** @@ -615,9 +622,11 @@ class Exif { private function isByte( $in ) { if ( !is_array( $in ) && sprintf( '%d', $in ) == $in && $in >= 0 && $in <= 255 ) { $this->debug( $in, __FUNCTION__, true ); + return true; } else { $this->debug( $in, __FUNCTION__, false ); + return false; } } @@ -633,11 +642,13 @@ class Exif { if ( preg_match( "/[^\x0a\x20-\x7e]/", $in ) ) { $this->debug( $in, __FUNCTION__, 'found a character not in our whitelist' ); + return false; } if ( preg_match( '/^\s*$/', $in ) ) { $this->debug( $in, __FUNCTION__, 'input consisted solely of whitespace' ); + return false; } @@ -651,9 +662,11 @@ class Exif { private function isShort( $in ) { if ( !is_array( $in ) && sprintf( '%d', $in ) == $in && $in >= 0 && $in <= 65536 ) { $this->debug( $in, __FUNCTION__, true ); + return true; } else { $this->debug( $in, __FUNCTION__, false ); + return false; } } @@ -665,9 +678,11 @@ class Exif { private function isLong( $in ) { if ( !is_array( $in ) && sprintf( '%d', $in ) == $in && $in >= 0 && $in <= 4294967296 ) { $this->debug( $in, __FUNCTION__, true ); + return true; } else { $this->debug( $in, __FUNCTION__, false ); + return false; } } @@ -682,6 +697,7 @@ class Exif { return $this->isLong( $m[1] ) && $this->isLong( $m[2] ); } else { $this->debug( $in, __FUNCTION__, 'fed a non-fraction value' ); + return false; } } @@ -692,6 +708,7 @@ class Exif { */ private function isUndefined( $in ) { $this->debug( $in, __FUNCTION__, true ); + return true; } @@ -702,9 +719,11 @@ class Exif { private function isSlong( $in ) { if ( $this->isLong( abs( $in ) ) ) { $this->debug( $in, __FUNCTION__, true ); + return true; } else { $this->debug( $in, __FUNCTION__, false ); + return false; } } @@ -719,9 +738,11 @@ class Exif { return $this->isSlong( $m[0] ) && $this->isSlong( $m[1] ); } else { $this->debug( $in, __FUNCTION__, 'fed a non-fraction value' ); + return false; } } + /**#@-*/ /** @@ -747,6 +768,7 @@ class Exif { $count = count( $val ); if ( $ecount != $count ) { $this->debug( $val, __FUNCTION__, "Expected $ecount elements for $tag but got $count" ); + return false; } if ( $count > 1 ) { @@ -755,42 +777,54 @@ class Exif { return false; } } + return true; } // Does not work if not typecast switch ( (string)$etype ) { case (string)Exif::BYTE: $this->debug( $val, __FUNCTION__, $debug ); + return $this->isByte( $val ); case (string)Exif::ASCII: $this->debug( $val, __FUNCTION__, $debug ); + return $this->isASCII( $val ); case (string)Exif::SHORT: $this->debug( $val, __FUNCTION__, $debug ); + return $this->isShort( $val ); case (string)Exif::LONG: $this->debug( $val, __FUNCTION__, $debug ); + return $this->isLong( $val ); case (string)Exif::RATIONAL: $this->debug( $val, __FUNCTION__, $debug ); + return $this->isRational( $val ); case (string)Exif::SHORT_OR_LONG: $this->debug( $val, __FUNCTION__, $debug ); + return $this->isShort( $val ) || $this->isLong( $val ); case (string)Exif::UNDEFINED: $this->debug( $val, __FUNCTION__, $debug ); + return $this->isUndefined( $val ); case (string)Exif::SLONG: $this->debug( $val, __FUNCTION__, $debug ); + return $this->isSlong( $val ); case (string)Exif::SRATIONAL: $this->debug( $val, __FUNCTION__, $debug ); + return $this->isSrational( $val ); case (string)Exif::IGNORE: $this->debug( $val, __FUNCTION__, $debug ); + return false; default: $this->debug( $val, __FUNCTION__, "The tag '$tag' is unknown" ); + return false; } } diff --git a/includes/media/ExifBitmap.php b/includes/media/ExifBitmap.php index aa9db3aae7..0a14ca80fd 100644 --- a/includes/media/ExifBitmap.php +++ b/includes/media/ExifBitmap.php @@ -28,7 +28,6 @@ * @ingroup Media */ class ExifBitmapHandler extends BitmapHandler { - const BROKEN_FILE = '-1'; // error extracting metadata const OLD_BROKEN_FILE = '0'; // outdated error extracting metadata. @@ -76,6 +75,7 @@ class ExifBitmapHandler extends BitmapHandler { } } $metadata['MEDIAWIKI_EXIF_VERSION'] = 1; + return $metadata; } @@ -89,6 +89,7 @@ class ExifBitmapHandler extends BitmapHandler { # Old special value indicating that there is no Exif data in the file. # or that there was an error well extracting the metadata. wfDebug( __METHOD__ . ": back-compat version\n" ); + return self::METADATA_COMPATIBLE; } if ( $metadata === self::BROKEN_FILE ) { @@ -105,12 +106,15 @@ class ExifBitmapHandler extends BitmapHandler { ) { //back-compatible but old wfDebug( __METHOD__ . ": back-compat version\n" ); + return self::METADATA_COMPATIBLE; } # Wrong (non-compatible) version wfDebug( __METHOD__ . ": wrong version\n" ); + return self::METADATA_BAD; } + return self::METADATA_GOOD; } @@ -177,6 +181,7 @@ class ExifBitmapHandler extends BitmapHandler { $gis[0] = $gis[1]; $gis[1] = $width; } + return $gis; } @@ -199,6 +204,7 @@ class ExifBitmapHandler extends BitmapHandler { } $data = $file->getMetadata(); + return $this->getRotationForExif( $data ); } @@ -231,6 +237,7 @@ class ExifBitmapHandler extends BitmapHandler { return 0; } } + return 0; } } diff --git a/includes/media/FormatMetadata.php b/includes/media/FormatMetadata.php index 74e51cfb8e..f79284f0c7 100644 --- a/includes/media/FormatMetadata.php +++ b/includes/media/FormatMetadata.php @@ -46,7 +46,6 @@ * @since 1.23 the class extends ContextSource and various formerly-public internal methods are private */ class FormatMetadata extends ContextSource { - /** * Only output a single language for multi-language fields * @var boolean @@ -82,6 +81,7 @@ class FormatMetadata extends ContextSource { if ( $context ) { $obj->setContext( $context ); } + return $obj->makeFormattedData( $tags ); } @@ -166,701 +166,807 @@ class FormatMetadata extends ContextSource { foreach ( $vals as &$val ) { switch ( $tag ) { - case 'Compression': - switch ( $val ) { - case 1: case 2: case 3: case 4: - case 5: case 6: case 7: case 8: - case 32773: case 32946: case 34712: - $val = $this->exifMsg( $tag, $val ); - break; - default: - /* If not recognized, display as is. */ + case 'Compression': + switch ( $val ) { + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + case 7: + case 8: + case 32773: + case 32946: + case 34712: + $val = $this->exifMsg( $tag, $val ); + break; + default: + /* If not recognized, display as is. */ + break; + } break; - } - break; - case 'PhotometricInterpretation': - switch ( $val ) { - case 2: case 6: - $val = $this->exifMsg( $tag, $val ); - break; - default: - /* If not recognized, display as is. */ + case 'PhotometricInterpretation': + switch ( $val ) { + case 2: + case 6: + $val = $this->exifMsg( $tag, $val ); + break; + default: + /* If not recognized, display as is. */ + break; + } break; - } - break; - case 'Orientation': - switch ( $val ) { - case 1: case 2: case 3: case 4: case 5: case 6: case 7: case 8: - $val = $this->exifMsg( $tag, $val ); - break; - default: - /* If not recognized, display as is. */ + case 'Orientation': + switch ( $val ) { + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + case 7: + case 8: + $val = $this->exifMsg( $tag, $val ); + break; + default: + /* If not recognized, display as is. */ + break; + } break; - } - break; - case 'PlanarConfiguration': - switch ( $val ) { - case 1: case 2: - $val = $this->exifMsg( $tag, $val ); - break; - default: - /* If not recognized, display as is. */ - break; - } - break; - - // TODO: YCbCrSubSampling - case 'YCbCrPositioning': - switch ( $val ) { - case 1: - case 2: - $val = $this->exifMsg( $tag, $val ); - break; - default: - /* If not recognized, display as is. */ + case 'PlanarConfiguration': + switch ( $val ) { + case 1: + case 2: + $val = $this->exifMsg( $tag, $val ); + break; + default: + /* If not recognized, display as is. */ + break; + } break; - } - break; - - case 'XResolution': - case 'YResolution': - switch ( $resolutionunit ) { - case 2: - $val = $this->exifMsg( 'XYResolution', 'i', $this->formatNum( $val ) ); - break; - case 3: - $val = $this->exifMsg( 'XYResolution', 'c', $this->formatNum( $val ) ); - break; - default: - /* If not recognized, display as is. */ - break; - } - break; - // TODO: YCbCrCoefficients #p27 (see annex E) - case 'ExifVersion': case 'FlashpixVersion': - $val = "$val" / 100; - break; - - case 'ColorSpace': - switch ( $val ) { - case 1: case 65535: - $val = $this->exifMsg( $tag, $val ); - break; - default: - /* If not recognized, display as is. */ + // TODO: YCbCrSubSampling + case 'YCbCrPositioning': + switch ( $val ) { + case 1: + case 2: + $val = $this->exifMsg( $tag, $val ); + break; + default: + /* If not recognized, display as is. */ + break; + } break; - } - break; - case 'ComponentsConfiguration': - switch ( $val ) { - case 0: case 1: case 2: case 3: case 4: case 5: case 6: - $val = $this->exifMsg( $tag, $val ); + case 'XResolution': + case 'YResolution': + switch ( $resolutionunit ) { + case 2: + $val = $this->exifMsg( 'XYResolution', 'i', $this->formatNum( $val ) ); + break; + case 3: + $val = $this->exifMsg( 'XYResolution', 'c', $this->formatNum( $val ) ); + break; + default: + /* If not recognized, display as is. */ + break; + } break; - default: - /* If not recognized, display as is. */ + + // TODO: YCbCrCoefficients #p27 (see annex E) + case 'ExifVersion': + case 'FlashpixVersion': + $val = "$val" / 100; break; - } - break; - - case 'DateTime': - case 'DateTimeOriginal': - case 'DateTimeDigitized': - case 'DateTimeReleased': - case 'DateTimeExpires': - case 'GPSDateStamp': - case 'dc-date': - case 'DateTimeMetadata': - if ( $val == '0000:00:00 00:00:00' || $val == ' : : : : ' ) { - $val = $this->msg( 'exif-unknowndate' )->text(); - } elseif ( preg_match( '/^(?:\d{4}):(?:\d\d):(?:\d\d) (?:\d\d):(?:\d\d):(?:\d\d)$/D', $val ) ) { - // Full date. - $time = wfTimestamp( TS_MW, $val ); - if ( $time && intval( $time ) > 0 ) { - $val = $this->getLanguage()->timeanddate( $time ); - } - } elseif ( preg_match( '/^(?:\d{4}):(?:\d\d):(?:\d\d) (?:\d\d):(?:\d\d)$/D', $val ) ) { - // No second field. Still format the same - // since timeanddate doesn't include seconds anyways, - // but second still available in api - $time = wfTimestamp( TS_MW, $val . ':00' ); - if ( $time && intval( $time ) > 0 ) { - $val = $this->getLanguage()->timeanddate( $time ); - } - } elseif ( preg_match( '/^(?:\d{4}):(?:\d\d):(?:\d\d)$/D', $val ) ) { - // If only the date but not the time is filled in. - $time = wfTimestamp( TS_MW, substr( $val, 0, 4 ) - . substr( $val, 5, 2 ) - . substr( $val, 8, 2 ) - . '000000' ); - if ( $time && intval( $time ) > 0 ) { - $val = $this->getLanguage()->date( $time ); - } - } - // else it will just output $val without formatting it. - break; - case 'ExposureProgram': - switch ( $val ) { - case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7: case 8: - $val = $this->exifMsg( $tag, $val ); + case 'ColorSpace': + switch ( $val ) { + case 1: + case 65535: + $val = $this->exifMsg( $tag, $val ); + break; + default: + /* If not recognized, display as is. */ + break; + } break; - default: - /* If not recognized, display as is. */ + + case 'ComponentsConfiguration': + switch ( $val ) { + case 0: + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + $val = $this->exifMsg( $tag, $val ); + break; + default: + /* If not recognized, display as is. */ + break; + } break; - } - break; - case 'SubjectDistance': - $val = $this->exifMsg( $tag, '', $this->formatNum( $val ) ); - break; + case 'DateTime': + case 'DateTimeOriginal': + case 'DateTimeDigitized': + case 'DateTimeReleased': + case 'DateTimeExpires': + case 'GPSDateStamp': + case 'dc-date': + case 'DateTimeMetadata': + if ( $val == '0000:00:00 00:00:00' || $val == ' : : : : ' ) { + $val = $this->msg( 'exif-unknowndate' )->text(); + } elseif ( preg_match( '/^(?:\d{4}):(?:\d\d):(?:\d\d) (?:\d\d):(?:\d\d):(?:\d\d)$/D', $val ) ) { + // Full date. + $time = wfTimestamp( TS_MW, $val ); + if ( $time && intval( $time ) > 0 ) { + $val = $this->getLanguage()->timeanddate( $time ); + } + } elseif ( preg_match( '/^(?:\d{4}):(?:\d\d):(?:\d\d) (?:\d\d):(?:\d\d)$/D', $val ) ) { + // No second field. Still format the same + // since timeanddate doesn't include seconds anyways, + // but second still available in api + $time = wfTimestamp( TS_MW, $val . ':00' ); + if ( $time && intval( $time ) > 0 ) { + $val = $this->getLanguage()->timeanddate( $time ); + } + } elseif ( preg_match( '/^(?:\d{4}):(?:\d\d):(?:\d\d)$/D', $val ) ) { + // If only the date but not the time is filled in. + $time = wfTimestamp( TS_MW, substr( $val, 0, 4 ) + . substr( $val, 5, 2 ) + . substr( $val, 8, 2 ) + . '000000' ); + if ( $time && intval( $time ) > 0 ) { + $val = $this->getLanguage()->date( $time ); + } + } + // else it will just output $val without formatting it. + break; - case 'MeteringMode': - switch ( $val ) { - case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7: case 255: - $val = $this->exifMsg( $tag, $val ); + case 'ExposureProgram': + switch ( $val ) { + case 0: + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + case 7: + case 8: + $val = $this->exifMsg( $tag, $val ); + break; + default: + /* If not recognized, display as is. */ + break; + } break; - default: - /* If not recognized, display as is. */ + + case 'SubjectDistance': + $val = $this->exifMsg( $tag, '', $this->formatNum( $val ) ); break; - } - break; - - case 'LightSource': - switch ( $val ) { - case 0: case 1: case 2: case 3: case 4: case 9: case 10: case 11: - case 12: case 13: case 14: case 15: case 17: case 18: case 19: case 20: - case 21: case 22: case 23: case 24: case 255: - $val = $this->exifMsg( $tag, $val ); + + case 'MeteringMode': + switch ( $val ) { + case 0: + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + case 7: + case 255: + $val = $this->exifMsg( $tag, $val ); + break; + default: + /* If not recognized, display as is. */ + break; + } break; - default: - /* If not recognized, display as is. */ + + case 'LightSource': + switch ( $val ) { + case 0: + case 1: + case 2: + case 3: + case 4: + case 9: + case 10: + case 11: + case 12: + case 13: + case 14: + case 15: + case 17: + case 18: + case 19: + case 20: + case 21: + case 22: + case 23: + case 24: + case 255: + $val = $this->exifMsg( $tag, $val ); + break; + default: + /* If not recognized, display as is. */ + break; + } break; - } - break; - - case 'Flash': - $flashDecode = array( - 'fired' => $val & bindec( '00000001' ), - 'return' => ( $val & bindec( '00000110' ) ) >> 1, - 'mode' => ( $val & bindec( '00011000' ) ) >> 3, - 'function' => ( $val & bindec( '00100000' ) ) >> 5, - 'redeye' => ( $val & bindec( '01000000' ) ) >> 6, + + case 'Flash': + $flashDecode = array( + 'fired' => $val & bindec( '00000001' ), + 'return' => ( $val & bindec( '00000110' ) ) >> 1, + 'mode' => ( $val & bindec( '00011000' ) ) >> 3, + 'function' => ( $val & bindec( '00100000' ) ) >> 5, + 'redeye' => ( $val & bindec( '01000000' ) ) >> 6, // 'reserved' => ($val & bindec( '10000000' )) >> 7, - ); - $flashMsgs = array(); - # We do not need to handle unknown values since all are used. - foreach ( $flashDecode as $subTag => $subValue ) { - # We do not need any message for zeroed values. - if ( $subTag != 'fired' && $subValue == 0 ) { - continue; + ); + $flashMsgs = array(); + # We do not need to handle unknown values since all are used. + foreach ( $flashDecode as $subTag => $subValue ) { + # We do not need any message for zeroed values. + if ( $subTag != 'fired' && $subValue == 0 ) { + continue; + } + $fullTag = $tag . '-' . $subTag; + $flashMsgs[] = $this->exifMsg( $fullTag, $subValue ); } - $fullTag = $tag . '-' . $subTag; - $flashMsgs[] = $this->exifMsg( $fullTag, $subValue ); - } - $val = $this->getLanguage()->commaList( $flashMsgs ); - break; - - case 'FocalPlaneResolutionUnit': - switch ( $val ) { - case 2: - $val = $this->exifMsg( $tag, $val ); - break; - default: - /* If not recognized, display as is. */ + $val = $this->getLanguage()->commaList( $flashMsgs ); break; - } - break; - case 'SensingMethod': - switch ( $val ) { - case 1: case 2: case 3: case 4: case 5: case 7: case 8: - $val = $this->exifMsg( $tag, $val ); - break; - default: - /* If not recognized, display as is. */ + case 'FocalPlaneResolutionUnit': + switch ( $val ) { + case 2: + $val = $this->exifMsg( $tag, $val ); + break; + default: + /* If not recognized, display as is. */ + break; + } break; - } - break; - case 'FileSource': - switch ( $val ) { - case 3: - $val = $this->exifMsg( $tag, $val ); - break; - default: - /* If not recognized, display as is. */ + case 'SensingMethod': + switch ( $val ) { + case 1: + case 2: + case 3: + case 4: + case 5: + case 7: + case 8: + $val = $this->exifMsg( $tag, $val ); + break; + default: + /* If not recognized, display as is. */ + break; + } break; - } - break; - case 'SceneType': - switch ( $val ) { - case 1: - $val = $this->exifMsg( $tag, $val ); - break; - default: - /* If not recognized, display as is. */ + case 'FileSource': + switch ( $val ) { + case 3: + $val = $this->exifMsg( $tag, $val ); + break; + default: + /* If not recognized, display as is. */ + break; + } break; - } - break; - case 'CustomRendered': - switch ( $val ) { - case 0: case 1: - $val = $this->exifMsg( $tag, $val ); - break; - default: - /* If not recognized, display as is. */ + case 'SceneType': + switch ( $val ) { + case 1: + $val = $this->exifMsg( $tag, $val ); + break; + default: + /* If not recognized, display as is. */ + break; + } break; - } - break; - case 'ExposureMode': - switch ( $val ) { - case 0: case 1: case 2: - $val = $this->exifMsg( $tag, $val ); - break; - default: - /* If not recognized, display as is. */ + case 'CustomRendered': + switch ( $val ) { + case 0: + case 1: + $val = $this->exifMsg( $tag, $val ); + break; + default: + /* If not recognized, display as is. */ + break; + } break; - } - break; - case 'WhiteBalance': - switch ( $val ) { - case 0: case 1: - $val = $this->exifMsg( $tag, $val ); + case 'ExposureMode': + switch ( $val ) { + case 0: + case 1: + case 2: + $val = $this->exifMsg( $tag, $val ); + break; + default: + /* If not recognized, display as is. */ + break; + } break; - default: - /* If not recognized, display as is. */ + + case 'WhiteBalance': + switch ( $val ) { + case 0: + case 1: + $val = $this->exifMsg( $tag, $val ); + break; + default: + /* If not recognized, display as is. */ + break; + } break; - } - break; - case 'SceneCaptureType': - switch ( $val ) { - case 0: case 1: case 2: case 3: - $val = $this->exifMsg( $tag, $val ); + case 'SceneCaptureType': + switch ( $val ) { + case 0: + case 1: + case 2: + case 3: + $val = $this->exifMsg( $tag, $val ); + break; + default: + /* If not recognized, display as is. */ + break; + } break; - default: - /* If not recognized, display as is. */ + + case 'GainControl': + switch ( $val ) { + case 0: + case 1: + case 2: + case 3: + case 4: + $val = $this->exifMsg( $tag, $val ); + break; + default: + /* If not recognized, display as is. */ + break; + } break; - } - break; - case 'GainControl': - switch ( $val ) { - case 0: case 1: case 2: case 3: case 4: - $val = $this->exifMsg( $tag, $val ); + case 'Contrast': + switch ( $val ) { + case 0: + case 1: + case 2: + $val = $this->exifMsg( $tag, $val ); + break; + default: + /* If not recognized, display as is. */ + break; + } break; - default: - /* If not recognized, display as is. */ + + case 'Saturation': + switch ( $val ) { + case 0: + case 1: + case 2: + $val = $this->exifMsg( $tag, $val ); + break; + default: + /* If not recognized, display as is. */ + break; + } break; - } - break; - case 'Contrast': - switch ( $val ) { - case 0: case 1: case 2: - $val = $this->exifMsg( $tag, $val ); + case 'Sharpness': + switch ( $val ) { + case 0: + case 1: + case 2: + $val = $this->exifMsg( $tag, $val ); + break; + default: + /* If not recognized, display as is. */ + break; + } break; - default: - /* If not recognized, display as is. */ + + case 'SubjectDistanceRange': + switch ( $val ) { + case 0: + case 1: + case 2: + case 3: + $val = $this->exifMsg( $tag, $val ); + break; + default: + /* If not recognized, display as is. */ + break; + } break; - } - break; - case 'Saturation': - switch ( $val ) { - case 0: case 1: case 2: - $val = $this->exifMsg( $tag, $val ); + //The GPS...Ref values are kept for compatibility, probably won't be reached. + case 'GPSLatitudeRef': + case 'GPSDestLatitudeRef': + switch ( $val ) { + case 'N': + case 'S': + $val = $this->exifMsg( 'GPSLatitude', $val ); + break; + default: + /* If not recognized, display as is. */ + break; + } break; - default: - /* If not recognized, display as is. */ + + case 'GPSLongitudeRef': + case 'GPSDestLongitudeRef': + switch ( $val ) { + case 'E': + case 'W': + $val = $this->exifMsg( 'GPSLongitude', $val ); + break; + default: + /* If not recognized, display as is. */ + break; + } break; - } - break; - case 'Sharpness': - switch ( $val ) { - case 0: case 1: case 2: - $val = $this->exifMsg( $tag, $val ); + case 'GPSAltitude': + if ( $val < 0 ) { + $val = $this->exifMsg( 'GPSAltitude', 'below-sealevel', $this->formatNum( -$val, 3 ) ); + } else { + $val = $this->exifMsg( 'GPSAltitude', 'above-sealevel', $this->formatNum( $val, 3 ) ); + } break; - default: - /* If not recognized, display as is. */ + + case 'GPSStatus': + switch ( $val ) { + case 'A': + case 'V': + $val = $this->exifMsg( $tag, $val ); + break; + default: + /* If not recognized, display as is. */ + break; + } break; - } - break; - case 'SubjectDistanceRange': - switch ( $val ) { - case 0: case 1: case 2: case 3: - $val = $this->exifMsg( $tag, $val ); + case 'GPSMeasureMode': + switch ( $val ) { + case 2: + case 3: + $val = $this->exifMsg( $tag, $val ); + break; + default: + /* If not recognized, display as is. */ + break; + } break; - default: - /* If not recognized, display as is. */ + + case 'GPSTrackRef': + case 'GPSImgDirectionRef': + case 'GPSDestBearingRef': + switch ( $val ) { + case 'T': + case 'M': + $val = $this->exifMsg( 'GPSDirection', $val ); + break; + default: + /* If not recognized, display as is. */ + break; + } break; - } - break; - - //The GPS...Ref values are kept for compatibility, probably won't be reached. - case 'GPSLatitudeRef': - case 'GPSDestLatitudeRef': - switch ( $val ) { - case 'N': case 'S': - $val = $this->exifMsg( 'GPSLatitude', $val ); + + case 'GPSLatitude': + case 'GPSDestLatitude': + $val = $this->formatCoords( $val, 'latitude' ); break; - default: - /* If not recognized, display as is. */ + case 'GPSLongitude': + case 'GPSDestLongitude': + $val = $this->formatCoords( $val, 'longitude' ); break; - } - break; - case 'GPSLongitudeRef': - case 'GPSDestLongitudeRef': - switch ( $val ) { - case 'E': case 'W': - $val = $this->exifMsg( 'GPSLongitude', $val ); - break; - default: - /* If not recognized, display as is. */ + case 'GPSSpeedRef': + switch ( $val ) { + case 'K': + case 'M': + case 'N': + $val = $this->exifMsg( 'GPSSpeed', $val ); + break; + default: + /* If not recognized, display as is. */ + break; + } break; - } - break; - case 'GPSAltitude': - if ( $val < 0 ) { - $val = $this->exifMsg( 'GPSAltitude', 'below-sealevel', $this->formatNum( -$val, 3 ) ); - } else { - $val = $this->exifMsg( 'GPSAltitude', 'above-sealevel', $this->formatNum( $val, 3 ) ); - } - break; + case 'GPSDestDistanceRef': + switch ( $val ) { + case 'K': + case 'M': + case 'N': + $val = $this->exifMsg( 'GPSDestDistance', $val ); + break; + default: + /* If not recognized, display as is. */ + break; + } + break; - case 'GPSStatus': - switch ( $val ) { - case 'A': case 'V': - $val = $this->exifMsg( $tag, $val ); + case 'GPSDOP': + // See http://en.wikipedia.org/wiki/Dilution_of_precision_(GPS) + if ( $val <= 2 ) { + $val = $this->exifMsg( $tag, 'excellent', $this->formatNum( $val ) ); + } elseif ( $val <= 5 ) { + $val = $this->exifMsg( $tag, 'good', $this->formatNum( $val ) ); + } elseif ( $val <= 10 ) { + $val = $this->exifMsg( $tag, 'moderate', $this->formatNum( $val ) ); + } elseif ( $val <= 20 ) { + $val = $this->exifMsg( $tag, 'fair', $this->formatNum( $val ) ); + } else { + $val = $this->exifMsg( $tag, 'poor', $this->formatNum( $val ) ); + } break; - default: - /* If not recognized, display as is. */ + + // This is not in the Exif standard, just a special + // case for our purposes which enables wikis to wikify + // the make, model and software name to link to their articles. + case 'Make': + case 'Model': + $val = $this->exifMsg( $tag, '', $val ); break; - } - break; - case 'GPSMeasureMode': - switch ( $val ) { - case 2: case 3: - $val = $this->exifMsg( $tag, $val ); + case 'Software': + if ( is_array( $val ) ) { + //if its a software, version array. + $val = $this->msg( 'exif-software-version-value', $val[0], $val[1] )->text(); + } else { + $val = $this->exifMsg( $tag, '', $val ); + } break; - default: - /* If not recognized, display as is. */ + + case 'ExposureTime': + // Show the pretty fraction as well as decimal version + $val = $this->msg( 'exif-exposuretime-format', + $this->formatFraction( $val ), $this->formatNum( $val ) )->text(); break; - } - break; - - case 'GPSTrackRef': - case 'GPSImgDirectionRef': - case 'GPSDestBearingRef': - switch ( $val ) { - case 'T': case 'M': - $val = $this->exifMsg( 'GPSDirection', $val ); + case 'ISOSpeedRatings': + // If its = 65535 that means its at the + // limit of the size of Exif::short and + // is really higher. + if ( $val == '65535' ) { + $val = $this->exifMsg( $tag, 'overflow' ); + } else { + $val = $this->formatNum( $val ); + } break; - default: - /* If not recognized, display as is. */ + case 'FNumber': + $val = $this->msg( 'exif-fnumber-format', + $this->formatNum( $val ) )->text(); break; - } - break; - - case 'GPSLatitude': - case 'GPSDestLatitude': - $val = $this->formatCoords( $val, 'latitude' ); - break; - case 'GPSLongitude': - case 'GPSDestLongitude': - $val = $this->formatCoords( $val, 'longitude' ); - break; - - case 'GPSSpeedRef': - switch ( $val ) { - case 'K': case 'M': case 'N': - $val = $this->exifMsg( 'GPSSpeed', $val ); + + case 'FocalLength': + case 'FocalLengthIn35mmFilm': + $val = $this->msg( 'exif-focallength-format', + $this->formatNum( $val ) )->text(); break; - default: - /* If not recognized, display as is. */ + + case 'MaxApertureValue': + if ( strpos( $val, '/' ) !== false ) { + // need to expand this earlier to calculate fNumber + list( $n, $d ) = explode( '/', $val ); + if ( is_numeric( $n ) && is_numeric( $d ) ) { + $val = $n / $d; + } + } + if ( is_numeric( $val ) ) { + $fNumber = pow( 2, $val / 2 ); + if ( $fNumber !== false ) { + $val = $this->msg( 'exif-maxaperturevalue-value', + $this->formatNum( $val ), + $this->formatNum( $fNumber, 2 ) + )->text(); + } + } break; - } - break; - case 'GPSDestDistanceRef': - switch ( $val ) { - case 'K': case 'M': case 'N': - $val = $this->exifMsg( 'GPSDestDistance', $val ); + case 'iimCategory': + switch ( strtolower( $val ) ) { + // See pg 29 of IPTC photo + // metadata standard. + case 'ace': + case 'clj': + case 'dis': + case 'fin': + case 'edu': + case 'evn': + case 'hth': + case 'hum': + case 'lab': + case 'lif': + case 'pol': + case 'rel': + case 'sci': + case 'soi': + case 'spo': + case 'war': + case 'wea': + $val = $this->exifMsg( + 'iimcategory', + $val + ); + } break; - default: - /* If not recognized, display as is. */ + case 'SubjectNewsCode': + // Essentially like iimCategory. + // 8 (numeric) digit hierarchical + // classification. We decode the + // first 2 digits, which provide + // a broad category. + $val = $this->convertNewsCode( $val ); break; - } - break; - - case 'GPSDOP': - // See http://en.wikipedia.org/wiki/Dilution_of_precision_(GPS) - if ( $val <= 2 ) { - $val = $this->exifMsg( $tag, 'excellent', $this->formatNum( $val ) ); - } elseif ( $val <= 5 ) { - $val = $this->exifMsg( $tag, 'good', $this->formatNum( $val ) ); - } elseif ( $val <= 10 ) { - $val = $this->exifMsg( $tag, 'moderate', $this->formatNum( $val ) ); - } elseif ( $val <= 20 ) { - $val = $this->exifMsg( $tag, 'fair', $this->formatNum( $val ) ); - } else { - $val = $this->exifMsg( $tag, 'poor', $this->formatNum( $val ) ); - } - break; - - // This is not in the Exif standard, just a special - // case for our purposes which enables wikis to wikify - // the make, model and software name to link to their articles. - case 'Make': - case 'Model': - $val = $this->exifMsg( $tag, '', $val ); - break; - - case 'Software': - if ( is_array( $val ) ) { - //if its a software, version array. - $val = $this->msg( 'exif-software-version-value', $val[0], $val[1] )->text(); - } else { - $val = $this->exifMsg( $tag, '', $val ); - } - break; - - case 'ExposureTime': - // Show the pretty fraction as well as decimal version - $val = $this->msg( 'exif-exposuretime-format', - $this->formatFraction( $val ), $this->formatNum( $val ) )->text(); - break; - case 'ISOSpeedRatings': - // If its = 65535 that means its at the - // limit of the size of Exif::short and - // is really higher. - if ( $val == '65535' ) { - $val = $this->exifMsg( $tag, 'overflow' ); - } else { - $val = $this->formatNum( $val ); - } - break; - case 'FNumber': - $val = $this->msg( 'exif-fnumber-format', - $this->formatNum( $val ) )->text(); - break; - - case 'FocalLength': case 'FocalLengthIn35mmFilm': - $val = $this->msg( 'exif-focallength-format', - $this->formatNum( $val ) )->text(); - break; - - case 'MaxApertureValue': - if ( strpos( $val, '/' ) !== false ) { - // need to expand this earlier to calculate fNumber - list( $n, $d ) = explode( '/', $val ); - if ( is_numeric( $n ) && is_numeric( $d ) ) { - $val = $n / $d; + case 'Urgency': + // 1-8 with 1 being highest, 5 normal + // 0 is reserved, and 9 is 'user-defined'. + $urgency = ''; + if ( $val == 0 || $val == 9 ) { + $urgency = 'other'; + } elseif ( $val < 5 && $val > 1 ) { + $urgency = 'high'; + } elseif ( $val == 5 ) { + $urgency = 'normal'; + } elseif ( $val <= 8 && $val > 5 ) { + $urgency = 'low'; } - } - if ( is_numeric( $val ) ) { - $fNumber = pow( 2, $val / 2 ); - if ( $fNumber !== false ) { - $val = $this->msg( 'exif-maxaperturevalue-value', - $this->formatNum( $val ), - $this->formatNum( $fNumber, 2 ) - )->text(); - } - } - break; - - case 'iimCategory': - switch ( strtolower( $val ) ) { - // See pg 29 of IPTC photo - // metadata standard. - case 'ace': case 'clj': - case 'dis': case 'fin': - case 'edu': case 'evn': - case 'hth': case 'hum': - case 'lab': case 'lif': - case 'pol': case 'rel': - case 'sci': case 'soi': - case 'spo': case 'war': - case 'wea': - $val = $this->exifMsg( - 'iimcategory', - $val + + if ( $urgency !== '' ) { + $val = $this->exifMsg( 'urgency', + $urgency, $val ); - } - break; - case 'SubjectNewsCode': - // Essentially like iimCategory. - // 8 (numeric) digit hierarchical - // classification. We decode the - // first 2 digits, which provide - // a broad category. - $val = $this->convertNewsCode( $val ); - break; - case 'Urgency': - // 1-8 with 1 being highest, 5 normal - // 0 is reserved, and 9 is 'user-defined'. - $urgency = ''; - if ( $val == 0 || $val == 9 ) { - $urgency = 'other'; - } elseif ( $val < 5 && $val > 1 ) { - $urgency = 'high'; - } elseif ( $val == 5 ) { - $urgency = 'normal'; - } elseif ( $val <= 8 && $val > 5 ) { - $urgency = 'low'; - } + } + break; - if ( $urgency !== '' ) { - $val = $this->exifMsg( 'urgency', - $urgency, $val - ); - } - break; - - // Things that have a unit of pixels. - case 'OriginalImageHeight': - case 'OriginalImageWidth': - case 'PixelXDimension': - case 'PixelYDimension': - case 'ImageWidth': - case 'ImageLength': - $val = $this->formatNum( $val ) . ' ' . $this->msg( 'unit-pixel' )->text(); - break; - - // Do not transform fields with pure text. - // For some languages the formatNum() - // conversion results to wrong output like - // foo,bar@example,com or fooÙ«bar@exampleÙ«com. - // Also some 'numeric' things like Scene codes - // are included here as we really don't want - // commas inserted. - case 'ImageDescription': - case 'Artist': - case 'Copyright': - case 'RelatedSoundFile': - case 'ImageUniqueID': - case 'SpectralSensitivity': - case 'GPSSatellites': - case 'GPSVersionID': - case 'GPSMapDatum': - case 'Keywords': - case 'WorldRegionDest': - case 'CountryDest': - case 'CountryCodeDest': - case 'ProvinceOrStateDest': - case 'CityDest': - case 'SublocationDest': - case 'WorldRegionCreated': - case 'CountryCreated': - case 'CountryCodeCreated': - case 'ProvinceOrStateCreated': - case 'CityCreated': - case 'SublocationCreated': - case 'ObjectName': - case 'SpecialInstructions': - case 'Headline': - case 'Credit': - case 'Source': - case 'EditStatus': - case 'FixtureIdentifier': - case 'LocationDest': - case 'LocationDestCode': - case 'Writer': - case 'JPEGFileComment': - case 'iimSupplementalCategory': - case 'OriginalTransmissionRef': - case 'Identifier': - case 'dc-contributor': - case 'dc-coverage': - case 'dc-publisher': - case 'dc-relation': - case 'dc-rights': - case 'dc-source': - case 'dc-type': - case 'Lens': - case 'SerialNumber': - case 'CameraOwnerName': - case 'Label': - case 'Nickname': - case 'RightsCertificate': - case 'CopyrightOwner': - case 'UsageTerms': - case 'WebStatement': - case 'OriginalDocumentID': - case 'LicenseUrl': - case 'MorePermissionsUrl': - case 'AttributionUrl': - case 'PreferredAttributionName': - case 'PNGFileComment': - case 'Disclaimer': - case 'ContentWarning': - case 'GIFFileComment': - case 'SceneCode': - case 'IntellectualGenre': - case 'Event': - case 'OrginisationInImage': - case 'PersonInImage': - - $val = htmlspecialchars( $val ); - break; - - case 'ObjectCycle': - switch ( $val ) { - case 'a': case 'p': case 'b': - $val = $this->exifMsg( $tag, $val ); + // Things that have a unit of pixels. + case 'OriginalImageHeight': + case 'OriginalImageWidth': + case 'PixelXDimension': + case 'PixelYDimension': + case 'ImageWidth': + case 'ImageLength': + $val = $this->formatNum( $val ) . ' ' . $this->msg( 'unit-pixel' )->text(); break; - default: + + // Do not transform fields with pure text. + // For some languages the formatNum() + // conversion results to wrong output like + // foo,bar@example,com or fooÙ«bar@exampleÙ«com. + // Also some 'numeric' things like Scene codes + // are included here as we really don't want + // commas inserted. + case 'ImageDescription': + case 'Artist': + case 'Copyright': + case 'RelatedSoundFile': + case 'ImageUniqueID': + case 'SpectralSensitivity': + case 'GPSSatellites': + case 'GPSVersionID': + case 'GPSMapDatum': + case 'Keywords': + case 'WorldRegionDest': + case 'CountryDest': + case 'CountryCodeDest': + case 'ProvinceOrStateDest': + case 'CityDest': + case 'SublocationDest': + case 'WorldRegionCreated': + case 'CountryCreated': + case 'CountryCodeCreated': + case 'ProvinceOrStateCreated': + case 'CityCreated': + case 'SublocationCreated': + case 'ObjectName': + case 'SpecialInstructions': + case 'Headline': + case 'Credit': + case 'Source': + case 'EditStatus': + case 'FixtureIdentifier': + case 'LocationDest': + case 'LocationDestCode': + case 'Writer': + case 'JPEGFileComment': + case 'iimSupplementalCategory': + case 'OriginalTransmissionRef': + case 'Identifier': + case 'dc-contributor': + case 'dc-coverage': + case 'dc-publisher': + case 'dc-relation': + case 'dc-rights': + case 'dc-source': + case 'dc-type': + case 'Lens': + case 'SerialNumber': + case 'CameraOwnerName': + case 'Label': + case 'Nickname': + case 'RightsCertificate': + case 'CopyrightOwner': + case 'UsageTerms': + case 'WebStatement': + case 'OriginalDocumentID': + case 'LicenseUrl': + case 'MorePermissionsUrl': + case 'AttributionUrl': + case 'PreferredAttributionName': + case 'PNGFileComment': + case 'Disclaimer': + case 'ContentWarning': + case 'GIFFileComment': + case 'SceneCode': + case 'IntellectualGenre': + case 'Event': + case 'OrginisationInImage': + case 'PersonInImage': + $val = htmlspecialchars( $val ); break; - } - break; - case 'Copyrighted': - switch ( $val ) { - case 'True': case 'False': - $val = $this->exifMsg( $tag, $val ); + + case 'ObjectCycle': + switch ( $val ) { + case 'a': + case 'p': + case 'b': + $val = $this->exifMsg( $tag, $val ); + break; + default: + $val = htmlspecialchars( $val ); + break; + } + break; + case 'Copyrighted': + switch ( $val ) { + case 'True': + case 'False': + $val = $this->exifMsg( $tag, $val ); + break; + } + break; + case 'Rating': + if ( $val == '-1' ) { + $val = $this->exifMsg( $tag, 'rejected' ); + } else { + $val = $this->formatNum( $val ); + } break; - } - break; - case 'Rating': - if ( $val == '-1' ) { - $val = $this->exifMsg( $tag, 'rejected' ); - } else { - $val = $this->formatNum( $val ); - } - break; - case 'LanguageCode': - $lang = Language::fetchLanguageName( strtolower( $val ), $this->getLanguage()->getCode() ); - if ( $lang ) { - $val = htmlspecialchars( $lang ); - } else { - $val = htmlspecialchars( $val ); - } - break; + case 'LanguageCode': + $lang = Language::fetchLanguageName( strtolower( $val ), $this->getLanguage()->getCode() ); + if ( $lang ) { + $val = htmlspecialchars( $lang ); + } else { + $val = htmlspecialchars( $val ); + } + break; - default: - $val = $this->formatNum( $val ); - break; + default: + $val = $this->formatNum( $val ); + break; } } // End formatting values, start flattening arrays. $vals = $this->flattenArrayReal( $vals, $type ); - } + return $tags; } @@ -887,6 +993,7 @@ class FormatMetadata extends ContextSource { $context = new DerivativeContext( $obj->getContext() ); $context->setLanguage( $wgContLang ); $obj->setContext( $context ); + return $obj->flattenArrayReal( $vals, $type, $noHtml ); } @@ -908,6 +1015,7 @@ class FormatMetadata extends ContextSource { if ( $context ) { $obj->setContext( $context ); } + return $obj->flattenArrayReal( $vals, $type, $noHtml ); } @@ -939,102 +1047,103 @@ class FormatMetadata extends ContextSource { if ( !is_array( $vals ) ) { return $vals; // do nothing if not an array; - } - elseif ( count( $vals ) === 1 && $type !== 'lang' ) { + } elseif ( count( $vals ) === 1 && $type !== 'lang' ) { return $vals[0]; - } - elseif ( count( $vals ) === 0 ) { + } elseif ( count( $vals ) === 0 ) { wfDebug( __METHOD__ . " metadata array with 0 elements!\n" ); + return ""; // paranoia. This should never happen - } - /* @todo FIXME: This should hide some of the list entries if there are - * say more than four. Especially if a field is translated into 20 - * languages, we don't want to show them all by default - */ - else { + } else { + /* @todo FIXME: This should hide some of the list entries if there are + * say more than four. Especially if a field is translated into 20 + * languages, we don't want to show them all by default + */ switch ( $type ) { - case 'lang': - // Display default, followed by ContLang, - // followed by the rest in no particular - // order. - - // Todo: hide some items if really long list. - - $content = ''; - - $priorityLanguages = $this->getPriorityLanguages(); - $defaultItem = false; - $defaultLang = false; - - // If default is set, save it for later, - // as we don't know if it's equal to - // one of the lang codes. (In xmp - // you specify the language for a - // default property by having both - // a default prop, and one in the language - // that are identical) - if ( isset( $vals['x-default'] ) ) { - $defaultItem = $vals['x-default']; - unset( $vals['x-default'] ); - } - foreach ( $priorityLanguages as $pLang ) { - if ( isset( $vals[$pLang] ) ) { - $isDefault = false; - if ( $vals[$pLang] === $defaultItem ) { - $defaultItem = false; - $isDefault = true; + case 'lang': + // Display default, followed by ContLang, + // followed by the rest in no particular + // order. + + // Todo: hide some items if really long list. + + $content = ''; + + $priorityLanguages = $this->getPriorityLanguages(); + $defaultItem = false; + $defaultLang = false; + + // If default is set, save it for later, + // as we don't know if it's equal to + // one of the lang codes. (In xmp + // you specify the language for a + // default property by having both + // a default prop, and one in the language + // that are identical) + if ( isset( $vals['x-default'] ) ) { + $defaultItem = $vals['x-default']; + unset( $vals['x-default'] ); + } + foreach ( $priorityLanguages as $pLang ) { + if ( isset( $vals[$pLang] ) ) { + $isDefault = false; + if ( $vals[$pLang] === $defaultItem ) { + $defaultItem = false; + $isDefault = true; + } + $content .= $this->langItem( + $vals[$pLang], $pLang, + $isDefault, $noHtml ); + + unset( $vals[$pLang] ); + + if ( $this->singleLang ) { + return Html::rawElement( 'span', + array( 'lang' => $pLang ), $vals[$pLang] ); + } } - $content .= $this->langItem( - $vals[$pLang], $pLang, - $isDefault, $noHtml ); - - unset( $vals[$pLang] ); + } + // Now do the rest. + foreach ( $vals as $lang => $item ) { + if ( $item === $defaultItem ) { + $defaultLang = $lang; + continue; + } + $content .= $this->langItem( $item, + $lang, false, $noHtml ); if ( $this->singleLang ) { return Html::rawElement( 'span', - array( 'lang' => $pLang ), $vals[$pLang] ); + array( 'lang' => $lang ), $item ); } } - } - - // Now do the rest. - foreach ( $vals as $lang => $item ) { - if ( $item === $defaultItem ) { - $defaultLang = $lang; - continue; - } - $content .= $this->langItem( $item, - $lang, false, $noHtml ); - if ( $this->singleLang ) { - return Html::rawElement( 'span', - array( 'lang' => $lang ), $item ); + if ( $defaultItem !== false ) { + $content = $this->langItem( $defaultItem, + $defaultLang, true, $noHtml ) . + $content; + if ( $this->singleLang ) { + return $defaultItem; + } } - } - if ( $defaultItem !== false ) { - $content = $this->langItem( $defaultItem, - $defaultLang, true, $noHtml ) . - $content; - if ( $this->singleLang ) { - return $defaultItem; + if ( $noHtml ) { + return $content; } - } - if ( $noHtml ) { - return $content; - } - return '