summaryrefslogtreecommitdiff
path: root/includes/media
diff options
context:
space:
mode:
Diffstat (limited to 'includes/media')
-rw-r--r--includes/media/Bitmap.php85
-rw-r--r--includes/media/BitmapMetadataHandler.php19
-rw-r--r--includes/media/Bitmap_ClientOnly.php2
-rw-r--r--includes/media/DjVu.php9
-rw-r--r--includes/media/DjVuImage.php379
-rw-r--r--includes/media/Exif.php5
-rw-r--r--includes/media/ExifBitmap.php10
-rw-r--r--includes/media/FormatMetadata.php11
-rw-r--r--includes/media/GIF.php23
-rw-r--r--includes/media/Generic.php122
-rw-r--r--includes/media/JpegMetadataExtractor.php10
-rw-r--r--includes/media/MediaTransformOutput.php86
-rw-r--r--includes/media/SVG.php12
-rw-r--r--includes/media/SVGMetadataExtractor.php8
-rw-r--r--includes/media/XCF.php137
-rw-r--r--includes/media/XMP.php6
-rw-r--r--includes/media/XMPInfo.php23
-rw-r--r--includes/media/XMPValidate.php53
18 files changed, 836 insertions, 164 deletions
diff --git a/includes/media/Bitmap.php b/includes/media/Bitmap.php
index 3a66d8c9..619485cc 100644
--- a/includes/media/Bitmap.php
+++ b/includes/media/Bitmap.php
@@ -12,7 +12,6 @@
* @ingroup Media
*/
class BitmapHandler extends ImageHandler {
-
/**
* @param $image File
* @param $params array Transform parameters. Entries with the keys 'width'
@@ -21,12 +20,10 @@ class BitmapHandler extends ImageHandler {
* @return bool
*/
function normaliseParams( $image, &$params ) {
- global $wgMaxImageArea;
if ( !parent::normaliseParams( $image, $params ) ) {
return false;
}
- $mimeType = $image->getMimeType();
# Obtain the source, pre-rotation dimensions
$srcWidth = $image->getWidth( $params['page'] );
$srcHeight = $image->getHeight( $params['page'] );
@@ -43,19 +40,27 @@ class BitmapHandler extends ImageHandler {
}
}
- # Don't thumbnail an image so big that it will fill hard drives and send servers into swap
- # JPEG has the handy property of allowing thumbnailing without full decompression, so we make
- # an exception for it.
- # @todo FIXME: This actually only applies to ImageMagick
- if ( $mimeType !== 'image/jpeg' &&
- $srcWidth * $srcHeight > $wgMaxImageArea )
- {
- return false;
+ # Check if the file is smaller than the maximum image area for thumbnailing
+ $checkImageAreaHookResult = null;
+ wfRunHooks( 'BitmapHandlerCheckImageArea', array( $image, &$params, &$checkImageAreaHookResult ) );
+ if ( is_null( $checkImageAreaHookResult ) ) {
+ global $wgMaxImageArea;
+
+ 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;
+ }
+ } else {
+ return $checkImageAreaHookResult;
}
return true;
}
+
/**
* Extracts the width/height if the image will be scaled before rotating
*
@@ -81,10 +86,15 @@ class BitmapHandler extends ImageHandler {
}
- // Function that returns the number of pixels to be thumbnailed.
- // Intended for animated GIFs to multiply by the number of frames.
- function getImageArea( $image, $width, $height ) {
- return $width * $height;
+ /**
+ * Function that returns the number of pixels to be thumbnailed.
+ * Intended for animated GIFs to multiply by the number of frames.
+ *
+ * @param File $image
+ * @return int
+ */
+ function getImageArea( $image ) {
+ return $image->getWidth() * $image->getHeight();
}
/**
@@ -115,12 +125,14 @@ class BitmapHandler extends ImageHandler {
'srcWidth' => $image->getWidth(),
'srcHeight' => $image->getHeight(),
'mimeType' => $image->getMimeType(),
- 'srcPath' => $image->getPath(),
'dstPath' => $dstPath,
'dstUrl' => $dstUrl,
);
- wfDebug( __METHOD__ . ": creating {$scalerParams['physicalDimensions']} thumbnail at $dstPath\n" );
+ # Determine scaler type
+ $scaler = self::getScalerType( $dstPath );
+
+ wfDebug( __METHOD__ . ": creating {$scalerParams['physicalDimensions']} thumbnail at $dstPath using scaler $scaler\n" );
if ( !$image->mustRender() &&
$scalerParams['physicalWidth'] == $scalerParams['srcWidth']
@@ -131,9 +143,6 @@ class BitmapHandler extends ImageHandler {
return $this->getClientScalingThumbnailImage( $image, $scalerParams );
}
- # Determine scaler type
- $scaler = self::getScalerType( $dstPath );
- wfDebug( __METHOD__ . ": scaler $scaler\n" );
if ( $scaler == 'client' ) {
# Client-side image scaling, use the source URL
@@ -144,15 +153,18 @@ class BitmapHandler extends ImageHandler {
if ( $flags & self::TRANSFORM_LATER ) {
wfDebug( __METHOD__ . ": Transforming later per flags.\n" );
return new ThumbnailImage( $image, $dstUrl, $scalerParams['clientWidth'],
- $scalerParams['clientHeight'], $dstPath );
+ $scalerParams['clientHeight'], false );
}
# Try to make a target path for the thumbnail
- if ( !wfMkdirParents( dirname( $dstPath ) ) ) {
+ 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 );
}
+ # Transform functions and binaries need a FS source file
+ $scalerParams['srcPath'] = $image->getLocalRefPath();
+
# Try a hook
$mto = null;
wfRunHooks( 'BitmapHandlerTransform', array( $this, $image, &$scalerParams, &$mto ) );
@@ -223,13 +235,6 @@ class BitmapHandler extends ImageHandler {
} else {
$scaler = 'client';
}
-
- if ( $scaler != 'client' && $dstPath ) {
- if ( !wfMkdirParents( dirname( $dstPath ) ) ) {
- # Unable to create a path for the thumbnail
- return 'client';
- }
- }
return $scaler;
}
@@ -245,7 +250,7 @@ class BitmapHandler extends ImageHandler {
*/
protected function getClientScalingThumbnailImage( $image, $params ) {
return new ThumbnailImage( $image, $image->getURL(),
- $params['clientWidth'], $params['clientHeight'], $params['srcPath'] );
+ $params['clientWidth'], $params['clientHeight'], null );
}
/**
@@ -276,15 +281,16 @@ class BitmapHandler extends ImageHandler {
< $wgSharpenReductionThreshold ) {
$sharpen = "-sharpen " . wfEscapeShellArg( $wgSharpenParameter );
}
- // JPEG decoder hint to reduce memory, available since IM 6.5.6-2
- $decoderHint = "-define jpeg:size={$params['physicalDimensions']}";
+ 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
} elseif ( $params['mimeType'] == 'image/gif' ) {
- if ( $this->getImageArea( $image, $params['srcWidth'],
- $params['srcHeight'] ) > $wgMaxAnimatedGifArea ) {
+ if ( $this->getImageArea( $image ) > $wgMaxAnimatedGifArea ) {
// Extract initial frame only; we're so big it'll
// be a total drag. :P
$scene = 0;
@@ -298,6 +304,8 @@ class BitmapHandler extends ImageHandler {
$animation_post = '-fuzz 5% -layers optimizeTransparency';
}
}
+ } elseif ( $params['mimeType'] == 'image/x-xcf' ) {
+ $animation_post = '-layers merge';
}
// Use one thread only, to avoid deadlock bugs on OOM
@@ -372,8 +380,7 @@ class BitmapHandler extends ImageHandler {
} elseif( $params['mimeType'] == 'image/png' ) {
$im->setCompressionQuality( 95 );
} elseif ( $params['mimeType'] == 'image/gif' ) {
- if ( $this->getImageArea( $image, $params['srcWidth'],
- $params['srcHeight'] ) > $wgMaxAnimatedGifArea ) {
+ if ( $this->getImageArea( $image ) > $wgMaxAnimatedGifArea ) {
// Extract initial frame only; we're so big it'll
// be a total drag. :P
$im->setImageScene( 0 );
@@ -502,7 +509,7 @@ class BitmapHandler extends ImageHandler {
if ( !isset( $typemap[$params['mimeType']] ) ) {
$err = 'Image type not supported';
wfDebug( "$err\n" );
- $errMsg = wfMsg ( 'thumbnail_image-type' );
+ $errMsg = wfMsg( 'thumbnail_image-type' );
return $this->getMediaTransformError( $params, $errMsg );
}
list( $loader, $colorStyle, $saveType ) = $typemap[$params['mimeType']];
@@ -510,14 +517,14 @@ class BitmapHandler extends ImageHandler {
if ( !function_exists( $loader ) ) {
$err = "Incomplete GD library configuration: missing function $loader";
wfDebug( "$err\n" );
- $errMsg = wfMsg ( 'thumbnail_gd-library', $loader );
+ $errMsg = wfMsg( 'thumbnail_gd-library', $loader );
return $this->getMediaTransformError( $params, $errMsg );
}
if ( !file_exists( $params['srcPath'] ) ) {
$err = "File seems to be missing: {$params['srcPath']}";
wfDebug( "$err\n" );
- $errMsg = wfMsg ( 'thumbnail_image-missing', $params['srcPath'] );
+ $errMsg = wfMsg( 'thumbnail_image-missing', $params['srcPath'] );
return $this->getMediaTransformError( $params, $errMsg );
}
diff --git a/includes/media/BitmapMetadataHandler.php b/includes/media/BitmapMetadataHandler.php
index d1caa67a..746dddda 100644
--- a/includes/media/BitmapMetadataHandler.php
+++ b/includes/media/BitmapMetadataHandler.php
@@ -32,7 +32,15 @@ class BitmapMetadataHandler {
* @param String $app13 String containing app13 block from jpeg file
*/
private function doApp13 ( $app13 ) {
- $this->iptcType = JpegMetadataExtractor::doPSIR( $app13 );
+ try {
+ $this->iptcType = JpegMetadataExtractor::doPSIR( $app13 );
+ } catch ( MWException $e ) {
+ // Error reading the iptc hash information.
+ // This probably means the App13 segment is something other than what we expect.
+ // However, still try to read it, and treat it as if the hash didn't exist.
+ wfDebug( "Error parsing iptc data of file: " . $e->getMessage() . "\n" );
+ $this->iptcType = 'iptc-no-hash';
+ }
$iptc = IPTC::parse( $app13 );
$this->addMetadata( $iptc, $this->iptcType );
@@ -44,7 +52,10 @@ class BitmapMetadataHandler {
* Basically what used to be in BitmapHandler::getMetadata().
* Just calls stuff in the Exif class.
*
+ * Parameters are passed to the Exif class.
+ *
* @param $filename string
+ * @param $byteOrder string
*/
function getExif ( $filename, $byteOrder ) {
global $wgShowEXIF;
@@ -122,8 +133,10 @@ class BitmapMetadataHandler {
if ( isset( $seg['COM'] ) && isset( $seg['COM'][0] ) ) {
$meta->addMetadata( Array( 'JPEGFileComment' => $seg['COM'] ), 'native' );
}
- if ( isset( $seg['PSIR'] ) ) {
- $meta->doApp13( $seg['PSIR'] );
+ if ( isset( $seg['PSIR'] ) && count( $seg['PSIR'] ) > 0 ) {
+ foreach( $seg['PSIR'] as $curPSIRValue ) {
+ $meta->doApp13( $curPSIRValue );
+ }
}
if ( isset( $seg['XMP'] ) && $showXMP ) {
$xmp = new XMPReader();
diff --git a/includes/media/Bitmap_ClientOnly.php b/includes/media/Bitmap_ClientOnly.php
index 50679229..3c5d9738 100644
--- a/includes/media/Bitmap_ClientOnly.php
+++ b/includes/media/Bitmap_ClientOnly.php
@@ -38,6 +38,6 @@ class BitmapHandler_ClientOnly extends BitmapHandler {
return new TransformParameterError( $params );
}
return new ThumbnailImage( $image, $image->getURL(), $params['width'],
- $params['height'], $image->getPath() );
+ $params['height'], $image->getLocalRefPath() );
}
}
diff --git a/includes/media/DjVu.php b/includes/media/DjVu.php
index 2833f683..dedbee0d 100644
--- a/includes/media/DjVu.php
+++ b/includes/media/DjVu.php
@@ -131,7 +131,7 @@ class DjVuHandler extends ImageHandler {
}
$width = $params['width'];
$height = $params['height'];
- $srcPath = $image->getPath();
+ $srcPath = $image->getLocalRefPath();
$page = $params['page'];
if ( $page > $this->pageCount( $image ) ) {
return new MediaTransformError( 'thumbnail_error', $width, $height, wfMsg( 'djvu_page_error' ) );
@@ -141,13 +141,13 @@ class DjVuHandler extends ImageHandler {
return new ThumbnailImage( $image, $dstUrl, $width, $height, $dstPath, $page );
}
- if ( !wfMkdirParents( dirname( $dstPath ) ) ) {
+ if ( !wfMkdirParents( dirname( $dstPath ), null, __METHOD__ ) ) {
return new MediaTransformError( 'thumbnail_error', $width, $height, wfMsg( 'thumbnail_dest_directory' ) );
}
# Use a subshell (brackets) to aggregate stderr from both pipeline commands
# before redirecting it to the overall stdout. This works in both Linux and Windows XP.
- $cmd = '(' . wfEscapeShellArg( $wgDjvuRenderer ) . " -format=ppm -page={$page}" .
+ $cmd = '(' . wfEscapeShellArg( $wgDjvuRenderer ) . " -format=ppm -page={$page}" .
" -size={$params['physicalWidth']}x{$params['physicalHeight']} " .
wfEscapeShellArg( $srcPath );
if ( $wgDjvuPostProcessor ) {
@@ -190,6 +190,7 @@ class DjVuHandler extends ImageHandler {
/**
* Cache a document tree for the DjVu XML metadata
* @param $image File
+ * @param $gettext Boolean: DOCUMENT (Default: false)
*/
function getMetaTree( $image , $gettext = false ) {
if ( isset( $image->dejaMetaTree ) ) {
@@ -222,7 +223,7 @@ class DjVuHandler extends ImageHandler {
$image->dejaMetaTree = $tree;
}
} catch( Exception $e ) {
- wfDebug( "Bogus multipage XML metadata on '$image->name'\n" );
+ wfDebug( "Bogus multipage XML metadata on '{$image->getName()}'\n" );
}
wfRestoreWarnings();
wfProfileOut( __METHOD__ );
diff --git a/includes/media/DjVuImage.php b/includes/media/DjVuImage.php
new file mode 100644
index 00000000..80b7408c
--- /dev/null
+++ b/includes/media/DjVuImage.php
@@ -0,0 +1,379 @@
+<?php
+/**
+ * DjVu image handler
+ *
+ * Copyright © 2006 Brion Vibber <brion@pobox.com>
+ * http://www.mediawiki.org/
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+/**
+ * Support for detecting/validating DjVu image files and getting
+ * some basic file metadata (resolution etc)
+ *
+ * File format docs are available in source package for DjVuLibre:
+ * http://djvulibre.djvuzone.org/
+ *
+ * @ingroup Media
+ */
+class DjVuImage {
+ function __construct( $filename ) {
+ $this->mFilename = $filename;
+ }
+
+ /**
+ * Check if the given file is indeed a valid DjVu image file
+ * @return bool
+ */
+ public function isValid() {
+ $info = $this->getInfo();
+ return $info !== false;
+ }
+
+
+ /**
+ * Return data in the style of getimagesize()
+ * @return array or false on failure
+ */
+ public function getImageSize() {
+ $data = $this->getInfo();
+
+ if( $data !== false ) {
+ $width = $data['width'];
+ $height = $data['height'];
+
+ return array( $width, $height, 'DjVu',
+ "width=\"$width\" height=\"$height\"" );
+ }
+ return false;
+ }
+
+ // ---------
+
+ /**
+ * For debugging; dump the IFF chunk structure
+ */
+ function dump() {
+ $file = fopen( $this->mFilename, 'rb' );
+ $header = fread( $file, 12 );
+ // @todo FIXME: Would be good to replace this extract() call with something that explicitly initializes local variables.
+ extract( unpack( 'a4magic/a4chunk/NchunkLength', $header ) );
+ echo "$chunk $chunkLength\n";
+ $this->dumpForm( $file, $chunkLength, 1 );
+ fclose( $file );
+ }
+
+ private function dumpForm( $file, $length, $indent ) {
+ $start = ftell( $file );
+ $secondary = fread( $file, 4 );
+ echo str_repeat( ' ', $indent * 4 ) . "($secondary)\n";
+ while( ftell( $file ) - $start < $length ) {
+ $chunkHeader = fread( $file, 8 );
+ if( $chunkHeader == '' ) {
+ break;
+ }
+ // @todo FIXME: Would be good to replace this extract() call with something that explicitly initializes local variables.
+ extract( unpack( 'a4chunk/NchunkLength', $chunkHeader ) );
+ echo str_repeat( ' ', $indent * 4 ) . "$chunk $chunkLength\n";
+
+ if( $chunk == 'FORM' ) {
+ $this->dumpForm( $file, $chunkLength, $indent + 1 );
+ } else {
+ fseek( $file, $chunkLength, SEEK_CUR );
+ if( $chunkLength & 1 == 1 ) {
+ // Padding byte between chunks
+ fseek( $file, 1, SEEK_CUR );
+ }
+ }
+ }
+ }
+
+ function getInfo() {
+ wfSuppressWarnings();
+ $file = fopen( $this->mFilename, 'rb' );
+ wfRestoreWarnings();
+ if( $file === false ) {
+ wfDebug( __METHOD__ . ": missing or failed file read\n" );
+ return false;
+ }
+
+ $header = fread( $file, 16 );
+ $info = false;
+
+ if( strlen( $header ) < 16 ) {
+ wfDebug( __METHOD__ . ": too short file header\n" );
+ } else {
+ // @todo FIXME: Would be good to replace this extract() call with something that explicitly initializes local variables.
+ extract( unpack( 'a4magic/a4form/NformLength/a4subtype', $header ) );
+
+ if( $magic != 'AT&T' ) {
+ wfDebug( __METHOD__ . ": not a DjVu file\n" );
+ } elseif( $subtype == 'DJVU' ) {
+ // Single-page document
+ $info = $this->getPageInfo( $file, $formLength );
+ } elseif( $subtype == 'DJVM' ) {
+ // Multi-page document
+ $info = $this->getMultiPageInfo( $file, $formLength );
+ } else {
+ wfDebug( __METHOD__ . ": unrecognized DJVU file type '$formType'\n" );
+ }
+ }
+ fclose( $file );
+ return $info;
+ }
+
+ private function readChunk( $file ) {
+ $header = fread( $file, 8 );
+ if( strlen( $header ) < 8 ) {
+ return array( false, 0 );
+ } 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 );
+ }
+ }
+
+ private function skipChunk( $file, $chunkLength ) {
+ fseek( $file, $chunkLength, SEEK_CUR );
+
+ if( $chunkLength & 0x01 == 1 && !feof( $file ) ) {
+ // padding byte
+ fseek( $file, 1, SEEK_CUR );
+ }
+ }
+
+ private function getMultiPageInfo( $file, $formLength ) {
+ // For now, we'll just look for the first page in the file
+ // and report its information, hoping others are the same size.
+ $start = ftell( $file );
+ do {
+ list( $chunk, $length ) = $this->readChunk( $file );
+ if( !$chunk ) {
+ break;
+ }
+
+ if( $chunk == 'FORM' ) {
+ $subtype = fread( $file, 4 );
+ if( $subtype == 'DJVU' ) {
+ wfDebug( __METHOD__ . ": found first subpage\n" );
+ return $this->getPageInfo( $file, $length );
+ }
+ $this->skipChunk( $file, $length - 4 );
+ } else {
+ wfDebug( __METHOD__ . ": skipping '$chunk' chunk\n" );
+ $this->skipChunk( $file, $length );
+ }
+ } while( $length != 0 && !feof( $file ) && ftell( $file ) - $start < $formLength );
+
+ wfDebug( __METHOD__ . ": multi-page DJVU file contained no pages\n" );
+ return false;
+ }
+
+ private function getPageInfo( $file, $formLength ) {
+ 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;
+ }
+
+ // @todo FIXME: Would be good to replace this extract() call with something that explicitly initializes local variables.
+ extract( unpack(
+ 'nwidth/' .
+ 'nheight/' .
+ 'Cminor/' .
+ 'Cmajor/' .
+ 'vresolution/' .
+ 'Cgamma', $data ) );
+ # Newer files have rotation info in byte 10, but we don't use it yet.
+
+ return array(
+ 'width' => $width,
+ 'height' => $height,
+ 'version' => "$major.$minor",
+ 'resolution' => $resolution,
+ 'gamma' => $gamma / 10.0 );
+ }
+
+ /**
+ * Return an XML string describing the DjVu image
+ * @return string
+ */
+ function retrieveMetaData() {
+ global $wgDjvuToXML, $wgDjvuDump, $wgDjvuTxt;
+ wfProfileIn( __METHOD__ );
+
+ if ( isset( $wgDjvuDump ) ) {
+ # djvudump is faster as of version 3.5
+ # http://sourceforge.net/tracker/index.php?func=detail&aid=1704049&group_id=32953&atid=406583
+ wfProfileIn( 'djvudump' );
+ $cmd = wfEscapeShellArg( $wgDjvuDump ) . ' ' . wfEscapeShellArg( $this->mFilename );
+ $dump = wfShellExec( $cmd );
+ $xml = $this->convertDumpToXML( $dump );
+ wfProfileOut( 'djvudump' );
+ } elseif ( isset( $wgDjvuToXML ) ) {
+ wfProfileIn( 'djvutoxml' );
+ $cmd = wfEscapeShellArg( $wgDjvuToXML ) . ' --without-anno --without-text ' .
+ wfEscapeShellArg( $this->mFilename );
+ $xml = wfShellExec( $cmd );
+ wfProfileOut( 'djvutoxml' );
+ } else {
+ $xml = null;
+ }
+ # Text layer
+ if ( isset( $wgDjvuTxt ) ) {
+ wfProfileIn( 'djvutxt' );
+ $cmd = wfEscapeShellArg( $wgDjvuTxt ) . ' --detail=page ' . wfEscapeShellArg( $this->mFilename ) ;
+ wfDebug( __METHOD__.": $cmd\n" );
+ $retval = '';
+ $txt = wfShellExec( $cmd, $retval );
+ wfProfileOut( 'djvutxt' );
+ if( $retval == 0) {
+ # Strip some control characters
+ $txt = preg_replace( "/[\013\035\037]/", "", $txt );
+ $reg = <<<EOR
+ /\(page\s[\d-]*\s[\d-]*\s[\d-]*\s[\d-]*\s*"
+ ((?> # Text to match is composed of atoms of either:
+ \\\\. # - any escaped character
+ | # - any character different from " and \
+ [^"\\\\]+
+ )*?)
+ "\s*\)
+ | # Or page can be empty ; in this case, djvutxt dumps ()
+ \(\s*()\)/sx
+EOR;
+ $txt = preg_replace_callback( $reg, array( $this, 'pageTextCallback' ), $txt );
+ $txt = "<DjVuTxt>\n<HEAD></HEAD>\n<BODY>\n" . $txt . "</BODY>\n</DjVuTxt>\n";
+ $xml = preg_replace( "/<DjVuXML>/", "<mw-djvu><DjVuXML>", $xml, 1 );
+ $xml = $xml . $txt. '</mw-djvu>' ;
+ }
+ }
+ wfProfileOut( __METHOD__ );
+ return $xml;
+ }
+
+ function pageTextCallback( $matches ) {
+ # Get rid of invalid UTF-8, strip control characters
+ return '<PAGE value="' . htmlspecialchars( UtfNormal::cleanUp( $matches[1] ) ) . '" />';
+ }
+
+ /**
+ * Hack to temporarily work around djvutoxml bug
+ */
+ function convertDumpToXML( $dump ) {
+ if ( strval( $dump ) == '' ) {
+ return false;
+ }
+
+ $xml = <<<EOT
+<?xml version="1.0" ?>
+<!DOCTYPE DjVuXML PUBLIC "-//W3C//DTD DjVuXML 1.1//EN" "pubtext/DjVuXML-s.dtd">
+<DjVuXML>
+<HEAD></HEAD>
+<BODY>
+EOT;
+
+ $dump = str_replace( "\r", '', $dump );
+ $line = strtok( $dump, "\n" );
+ $m = false;
+ $good = false;
+ if ( preg_match( '/^( *)FORM:DJVU/', $line, $m ) ) {
+ # Single-page
+ if ( $this->parseFormDjvu( $line, $xml ) ) {
+ $good = true;
+ } else {
+ return false;
+ }
+ } elseif ( preg_match( '/^( *)FORM:DJVM/', $line, $m ) ) {
+ # Multi-page
+ $parentLevel = strlen( $m[1] );
+ # Find DIRM
+ $line = strtok( "\n" );
+ while ( $line !== false ) {
+ $childLevel = strspn( $line, ' ' );
+ if ( $childLevel <= $parentLevel ) {
+ # End of chunk
+ break;
+ }
+
+ if ( preg_match( '/^ *DIRM.*indirect/', $line ) ) {
+ wfDebug( "Indirect multi-page DjVu document, bad for server!\n" );
+ return false;
+ }
+ if ( preg_match( '/^ *FORM:DJVU/', $line ) ) {
+ # Found page
+ if ( $this->parseFormDjvu( $line, $xml ) ) {
+ $good = true;
+ } else {
+ return false;
+ }
+ }
+ $line = strtok( "\n" );
+ }
+ }
+ if ( !$good ) {
+ return false;
+ }
+
+ $xml .= "</BODY>\n</DjVuXML>\n";
+ return $xml;
+ }
+
+ function parseFormDjvu( $line, &$xml ) {
+ $parentLevel = strspn( $line, ' ' );
+ $line = strtok( "\n" );
+
+ # Find INFO
+ while ( $line !== false ) {
+ $childLevel = strspn( $line, ' ' );
+ if ( $childLevel <= $parentLevel ) {
+ # End of chunk
+ break;
+ }
+
+ if ( preg_match( '/^ *INFO *\[\d*\] *DjVu *(\d+)x(\d+), *\w*, *(\d+) *dpi, *gamma=([0-9.-]+)/', $line, $m ) ) {
+ $xml .= Xml::tags( 'OBJECT',
+ array(
+ #'data' => '',
+ #'type' => 'image/x.djvu',
+ 'height' => $m[2],
+ 'width' => $m[1],
+ #'usemap' => '',
+ ),
+ "\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 345a6f19..a4acdfe0 100644
--- a/includes/media/Exif.php
+++ b/includes/media/Exif.php
@@ -101,6 +101,7 @@ class Exif {
* Constructor
*
* @param $file String: filename.
+ * @param $byteOrder String Type of byte ordering either 'BE' (Big Endian) or 'LE' (Little Endian). Default ''.
* @todo FIXME: The following are broke:
* SubjectArea. Need to test the more obscure tags.
*
@@ -537,7 +538,7 @@ class Exif {
* @deprecated since 1.18
*/
function makeFormattedData( ) {
- wfDeprecated( __METHOD__ );
+ wfDeprecated( __METHOD__, '1.18' );
$this->mFormattedExifData = FormatMetadata::getFormattedData(
$this->mFilteredExifData );
}
@@ -569,7 +570,7 @@ class Exif {
* @deprecated since 1.18
*/
function getFormattedData() {
- wfDeprecated( __METHOD__ );
+ wfDeprecated( __METHOD__, '1.18' );
if (!$this->mFormattedExifData) {
$this->makeFormattedData();
}
diff --git a/includes/media/ExifBitmap.php b/includes/media/ExifBitmap.php
index 05ce161b..7b9867f7 100644
--- a/includes/media/ExifBitmap.php
+++ b/includes/media/ExifBitmap.php
@@ -34,8 +34,8 @@ class ExifBitmapHandler extends BitmapHandler {
// Treat Software as a special case because in can contain
// an array of (SoftwareName, Version).
- if (isset( $metadata['Software'] )
- && is_array( $metadata['Software'] )
+ if (isset( $metadata['Software'] )
+ && is_array( $metadata['Software'] )
&& is_array( $metadata['Software'][0])
&& isset( $metadata['Software'][0][0] )
&& isset( $metadata['Software'][0][1])
@@ -136,8 +136,8 @@ class ExifBitmapHandler extends BitmapHandler {
function getImageSize( $image, $path ) {
global $wgEnableAutoRotation;
$gis = parent::getImageSize( $image, $path );
-
- // Don't just call $image->getMetadata(); File::getPropsFromPath() calls us with a bogus object.
+
+ // Don't just call $image->getMetadata(); FSFile::getPropsFromPath() calls us with a bogus object.
// This may mean we read EXIF data twice on initial upload.
if ( $wgEnableAutoRotation ) {
$meta = $this->getMetadata( $image, $path );
@@ -171,7 +171,7 @@ class ExifBitmapHandler extends BitmapHandler {
if ( !$wgEnableAutoRotation ) {
return 0;
}
-
+
$data = $file->getMetadata();
return $this->getRotationForExif( $data );
}
diff --git a/includes/media/FormatMetadata.php b/includes/media/FormatMetadata.php
index 47fc1adc..91cb6914 100644
--- a/includes/media/FormatMetadata.php
+++ b/includes/media/FormatMetadata.php
@@ -233,10 +233,19 @@ class FormatMetadata {
if ( $val == '0000:00:00 00:00:00' || $val == ' : : : : ' ) {
$val = wfMsg( 'exif-unknowndate' );
} 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 = $wgLang->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 = $wgLang->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 )
@@ -1174,7 +1183,7 @@ class FormatMetadata {
* Format a coordinate value, convert numbers from floating point
* into degree minute second representation.
*
- * @param $coords Array: degrees, minutes and seconds
+ * @param $coord Array: degrees, minutes and seconds
* @param $type String: latitude or longitude (for if its a NWS or E)
* @return mixed A floating point number or whatever we were fed
*/
diff --git a/includes/media/GIF.php b/includes/media/GIF.php
index 3bfa45a1..32618e94 100644
--- a/includes/media/GIF.php
+++ b/includes/media/GIF.php
@@ -14,7 +14,7 @@
class GIFHandler extends BitmapHandler {
const BROKEN_FILE = '0'; // value to store in img_metadata if error extracting metadata.
-
+
function getMetadata( $image, $filename ) {
try {
$parsedGIFMetadata = BitmapMetadataHandler::GIF( $filename );
@@ -50,17 +50,16 @@ class GIFHandler extends BitmapHandler {
/**
* @param $image File
- * @param $width
- * @param $height
- * @return
+ * @todo unittests
+ * @return bool
*/
- function getImageArea( $image, $width, $height ) {
+ function getImageArea( $image ) {
$ser = $image->getMetadata();
if ( $ser ) {
$metadata = unserialize( $ser );
- return $width * $height * $metadata['frameCount'];
+ return $image->getWidth() * $image->getHeight() * $metadata['frameCount'];
} else {
- return $width * $height;
+ return $image->getWidth() * $image->getHeight();
}
}
@@ -118,7 +117,7 @@ class GIFHandler extends BitmapHandler {
wfSuppressWarnings();
$metadata = unserialize($image->getMetadata());
wfRestoreWarnings();
-
+
if (!$metadata || $metadata['frameCount'] <= 1) {
return $original;
}
@@ -126,19 +125,19 @@ class GIFHandler extends BitmapHandler {
/* Preserve original image info string, but strip the last char ')' so we can add even more */
$info = array();
$info[] = $original;
-
+
if ( $metadata['looped'] ) {
$info[] = wfMsgExt( 'file-info-gif-looped', 'parseinline' );
}
-
+
if ( $metadata['frameCount'] > 1 ) {
$info[] = wfMsgExt( 'file-info-gif-frames', 'parseinline', $metadata['frameCount'] );
}
-
+
if ( $metadata['duration'] ) {
$info[] = $wgLang->formatTimePeriod( $metadata['duration'] );
}
-
+
return $wgLang->commaList( $info );
}
}
diff --git a/includes/media/Generic.php b/includes/media/Generic.php
index 6ef21e1e..271d3a8d 100644
--- a/includes/media/Generic.php
+++ b/includes/media/Generic.php
@@ -89,7 +89,7 @@ abstract class MediaHandler {
*
* @param $image File: the image object, or false if there isn't one
* @param $path String: the filename
- * @return Array
+ * @return Array Follow the format of PHP getimagesize() internal function. See http://www.php.net/getimagesize
*/
abstract function getImageSize( $image, $path );
@@ -97,7 +97,7 @@ abstract class MediaHandler {
* Get handler-specific metadata which will be saved in the img_metadata field.
*
* @param $image File: the image object, or false if there isn't one.
- * Warning, File::getPropsFromPath might pass an (object)array() instead (!)
+ * Warning, FSFile::getPropsFromPath might pass an (object)array() instead (!)
* @param $path String: the filename
* @return String
*/
@@ -187,7 +187,7 @@ abstract class MediaHandler {
* @param $dstUrl String: Destination URL to use in output HTML
* @param $params Array: Arbitrary set of parameters validated by $this->validateParam()
*/
- function getTransform( $image, $dstPath, $dstUrl, $params ) {
+ final function getTransform( $image, $dstPath, $dstUrl, $params ) {
return $this->doTransform( $image, $dstPath, $dstUrl, $params, self::TRANSFORM_LATER );
}
@@ -200,6 +200,8 @@ abstract class MediaHandler {
* @param $dstUrl String: destination URL to use in output HTML
* @param $params Array: arbitrary set of parameters validated by $this->validateParam()
* @param $flags Integer: a bitfield, may contain self::TRANSFORM_LATER
+ *
+ * @return MediaTransformOutput
*/
abstract function doTransform( $image, $dstPath, $dstUrl, $params, $flags = 0 );
@@ -258,7 +260,7 @@ abstract class MediaHandler {
* @param $image File
*/
function getPageDimensions( $image, $page ) {
- $gis = $this->getImageSize( $image, $image->getPath() );
+ $gis = $this->getImageSize( $image, $image->getLocalRefPath() );
return array(
'width' => $gis[0],
'height' => $gis[1]
@@ -362,7 +364,7 @@ abstract class MediaHandler {
* @param &$array Array An array containing elements for each type of visibility
* and each of those elements being an array of metadata items. This function adds
* a value to that array.
- * @param $visbility string ('visible' or 'collapsed') if this value is hidden
+ * @param $visibility string ('visible' or 'collapsed') if this value is hidden
* by default.
* @param $type String type of metadata tag (currently always 'exif')
* @param $id String the name of the metadata tag (like 'artist' for example).
@@ -378,8 +380,10 @@ abstract class MediaHandler {
* Note, everything here is passed through the parser later on (!)
*/
protected static function addMeta( &$array, $visibility, $type, $id, $value, $param = false ) {
- $msgName = "$type-$id";
- if ( wfEmptyMsg( $msgName ) ) {
+ $msg = wfMessage( "$type-$id", $param );
+ if ( $msg->exists() ) {
+ $name = $msg->text();
+ } else {
// This is for future compatibility when using instant commons.
// So as to not display as ugly a name if a new metadata
// property is defined that we don't know about
@@ -387,8 +391,6 @@ abstract class MediaHandler {
// by default).
wfDebug( __METHOD__ . ' Unknown metadata name: ' . $id . "\n" );
$name = wfEscapeWikiText( $id );
- } else {
- $name = wfMsg( $msgName, $param );
}
$array[$visibility][] = array(
'id' => "$type-$id",
@@ -403,9 +405,7 @@ abstract class MediaHandler {
*/
function getShortDesc( $file ) {
global $wgLang;
- $nbytes = wfMsgExt( 'nbytes', array( 'parsemag', 'escape' ),
- $wgLang->formatNum( $file->getSize() ) );
- return "$nbytes";
+ return htmlspecialchars( $wgLang->formatSize( $file->getSize() ) );
}
/**
@@ -414,9 +414,8 @@ abstract class MediaHandler {
*/
function getLongDesc( $file ) {
global $wgLang;
- return wfMsgExt( 'file-info', 'parseinline',
- $wgLang->formatSize( $file->getSize() ),
- $file->getMimeType() );
+ return wfMessage( 'file-info', htmlspecialchars( $wgLang->formatSize( $file->getSize() ) ),
+ $file->getMimeType() )->parse();
}
/**
@@ -425,9 +424,7 @@ abstract class MediaHandler {
*/
static function getGeneralShortDesc( $file ) {
global $wgLang;
- $nbytes = wfMsgExt( 'nbytes', array( 'parsemag', 'escape' ),
- $wgLang->formatNum( $file->getSize() ) );
- return "$nbytes";
+ return $wgLang->formatSize( $file->getSize() );
}
/**
@@ -436,9 +433,26 @@ abstract class MediaHandler {
*/
static function getGeneralLongDesc( $file ) {
global $wgLang;
- return wfMsgExt( 'file-info', 'parseinline',
- $wgLang->formatSize( $file->getSize() ),
- $file->getMimeType() );
+ return wfMessage( 'file-info', $wgLang->formatSize( $file->getSize() ),
+ $file->getMimeType() )->parse();
+ }
+
+ /**
+ * Calculate the largest thumbnail width for a given original file size
+ * such that the thumbnail's height is at most $maxHeight.
+ * @param $boxWidth Integer Width of the thumbnail box.
+ * @param $boxHeight Integer Height of the thumbnail box.
+ * @param $maxHeight Integer Maximum height expected for the thumbnail.
+ * @return Integer.
+ */
+ public static function fitBoxWidth( $boxWidth, $boxHeight, $maxHeight ) {
+ $idealWidth = $boxWidth * $maxHeight / $boxHeight;
+ $roundedUp = ceil( $idealWidth );
+ if( round( $roundedUp * $boxHeight / $boxWidth ) > $maxHeight ) {
+ return floor( $idealWidth );
+ } else {
+ return $roundedUp;
+ }
}
function getDimensionsString( $file ) {
@@ -476,15 +490,32 @@ abstract class MediaHandler {
if( file_exists( $dstPath ) ) {
$thumbstat = stat( $dstPath );
if( $thumbstat['size'] == 0 || $retval != 0 ) {
- wfDebugLog( 'thumbnail',
- sprintf( 'Removing bad %d-byte thumbnail "%s"',
- $thumbstat['size'], $dstPath ) );
- unlink( $dstPath );
+ $result = unlink( $dstPath );
+
+ if ( $result ) {
+ wfDebugLog( 'thumbnail',
+ sprintf( 'Removing bad %d-byte thumbnail "%s". unlink() succeeded',
+ $thumbstat['size'], $dstPath ) );
+ } else {
+ wfDebugLog( 'thumbnail',
+ sprintf( 'Removing bad %d-byte thumbnail "%s". unlink() failed',
+ $thumbstat['size'], $dstPath ) );
+ }
return true;
}
}
return false;
}
+
+ /**
+ * Remove files from the purge list
+ *
+ * @param array $files
+ * @param array $options
+ */
+ public function filterThumbnailPurgeList( &$files, $options ) {
+ // Do nothing
+ }
}
/**
@@ -575,7 +606,7 @@ abstract class ImageHandler extends MediaHandler {
# Height & width were both set
if ( $params['width'] * $srcHeight > $params['height'] * $srcWidth ) {
# Height is the relative smaller dimension, so scale width accordingly
- $params['width'] = wfFitBoxWidth( $srcWidth, $srcHeight, $params['height'] );
+ $params['width'] = self::fitBoxWidth( $srcWidth, $srcHeight, $params['height'] );
if ( $params['width'] == 0 ) {
# Very small image, so we need to rely on client side scaling :(
@@ -614,13 +645,6 @@ abstract class ImageHandler extends MediaHandler {
}
/**
- * Get a transform output object without actually doing the transform
- */
- function getTransform( $image, $dstPath, $dstUrl, $params ) {
- return $this->doTransform( $image, $dstPath, $dstUrl, $params, self::TRANSFORM_LATER );
- }
-
- /**
* Validate thumbnail parameters and fill in the correct height
*
* @param $width Integer: specified width (input/output)
@@ -686,9 +710,8 @@ abstract class ImageHandler extends MediaHandler {
*/
function getShortDesc( $file ) {
global $wgLang;
- $nbytes = wfMsgExt( 'nbytes', array( 'parsemag', 'escape' ),
- $wgLang->formatNum( $file->getSize() ) );
- $widthheight = wfMsgHtml( 'widthheight', $wgLang->formatNum( $file->getWidth() ) ,$wgLang->formatNum( $file->getHeight() ) );
+ $nbytes = htmlspecialchars( $wgLang->formatSize( $file->getSize() ) );
+ $widthheight = wfMessage( 'widthheight' )->numParams( $file->getWidth(), $file->getHeight() )->escaped();
return "$widthheight ($nbytes)";
}
@@ -700,19 +723,15 @@ abstract class ImageHandler extends MediaHandler {
function getLongDesc( $file ) {
global $wgLang;
$pages = $file->pageCount();
+ $size = htmlspecialchars( $wgLang->formatSize( $file->getSize() ) );
if ( $pages === false || $pages <= 1 ) {
- $msg = wfMsgExt('file-info-size', 'parseinline',
- $wgLang->formatNum( $file->getWidth() ),
- $wgLang->formatNum( $file->getHeight() ),
- $wgLang->formatSize( $file->getSize() ),
- $file->getMimeType() );
+ $msg = wfMessage( 'file-info-size' )->numParams( $file->getWidth(),
+ $file->getHeight() )->params( $size,
+ $file->getMimeType() )->parse();
} else {
- $msg = wfMsgExt('file-info-size-pages', 'parseinline',
- $wgLang->formatNum( $file->getWidth() ),
- $wgLang->formatNum( $file->getHeight() ),
- $wgLang->formatSize( $file->getSize() ),
- $file->getMimeType(),
- $wgLang->formatNum( $pages ) );
+ $msg = wfMessage( 'file-info-size-pages' )->numParams( $file->getWidth(),
+ $file->getHeight() )->params( $size,
+ $file->getMimeType() )->numParams( $pages )->parse();
}
return $msg;
}
@@ -722,16 +741,11 @@ abstract class ImageHandler extends MediaHandler {
* @return string
*/
function getDimensionsString( $file ) {
- global $wgLang;
$pages = $file->pageCount();
- $width = $wgLang->formatNum( $file->getWidth() );
- $height = $wgLang->formatNum( $file->getHeight() );
- $pagesFmt = $wgLang->formatNum( $pages );
-
if ( $pages > 1 ) {
- return wfMsgExt( 'widthheightpage', 'parsemag', $width, $height, $pagesFmt );
+ return wfMessage( 'widthheightpage' )->numParams( $file->getWidth(), $file->getHeight(), $pages )->text();
} else {
- return wfMsg( 'widthheight', $width, $height );
+ return wfMessage( 'widthheight' )->numParams( $file->getWidth(), $file->getHeight() )->text();
}
}
}
diff --git a/includes/media/JpegMetadataExtractor.php b/includes/media/JpegMetadataExtractor.php
index 4769bf8e..224b4a2b 100644
--- a/includes/media/JpegMetadataExtractor.php
+++ b/includes/media/JpegMetadataExtractor.php
@@ -31,6 +31,7 @@ class JpegMetadataExtractor {
$segments = array(
'XMP_ext' => array(),
'COM' => array(),
+ 'PSIR' => array(),
);
if ( !$filename ) {
@@ -122,7 +123,7 @@ class JpegMetadataExtractor {
// APP13 - PSIR. IPTC and some photoshop stuff
$temp = self::jpegExtractMarker( $fh );
if ( substr( $temp, 0, 14 ) === "Photoshop 3.0\x00" ) {
- $segments["PSIR"] = $temp;
+ $segments["PSIR"][] = $temp;
}
} elseif ( $buffer === "\xD9" || $buffer === "\xDA" ) {
// EOI - end of image or SOS - start of scan. either way we're past any interesting segments
@@ -162,11 +163,12 @@ class JpegMetadataExtractor {
* This should generally be called by BitmapMetadataHandler::doApp13()
*
* @param String $app13 photoshop psir app13 block from jpg.
+ * @throws MWException (It gets caught next level up though)
* @return String if the iptc hash is good or not.
*/
public static function doPSIR ( $app13 ) {
if ( !$app13 ) {
- return;
+ throw new MWException( "No App13 segment given" );
}
// First compare hash with real thing
// 0x404 contains IPTC, 0x425 has hash
@@ -218,8 +220,8 @@ class JpegMetadataExtractor {
// this should not happen, but check.
if ( $lenData['len'] + $offset > $appLen ) {
- wfDebug( __METHOD__ . " PSIR data too long.\n" );
- return 'iptc-no-hash';
+ throw new MWException( "PSIR data too long. (item length=" . $lenData['len']
+ . "; offset=$offset; total length=$appLen)" );
}
if ( $valid ) {
diff --git a/includes/media/MediaTransformOutput.php b/includes/media/MediaTransformOutput.php
index f170bb9d..fcfb2f45 100644
--- a/includes/media/MediaTransformOutput.php
+++ b/includes/media/MediaTransformOutput.php
@@ -18,33 +18,42 @@ abstract class MediaTransformOutput {
var $file;
var $width, $height, $url, $page, $path;
+ protected $storagePath = false;
/**
* Get the width of the output box
*/
- function getWidth() {
+ public function getWidth() {
return $this->width;
}
/**
* Get the height of the output box
*/
- function getHeight() {
+ public function getHeight() {
return $this->height;
}
/**
* @return string The thumbnail URL
*/
- function getUrl() {
+ public function getUrl() {
return $this->url;
}
/**
- * @return String: destination file path (local filesystem)
+ * @return string|false The permanent thumbnail storage path
*/
- function getPath() {
- return $this->path;
+ public function getStoragePath() {
+ return $this->storagePath;
+ }
+
+ /**
+ * @param $storagePath string The permanent storage path
+ * @return void
+ */
+ public function setStoragePath( $storagePath ) {
+ $this->storagePath = $storagePath;
}
/**
@@ -67,16 +76,66 @@ abstract class MediaTransformOutput {
*
* @return string
*/
- abstract function toHtml( $options = array() );
+ abstract public function toHtml( $options = array() );
/**
* This will be overridden to return true in error classes
*/
- function isError() {
+ public function isError() {
return false;
}
/**
+ * Check if an output thumbnail file actually exists.
+ * This will return false if there was an error, the
+ * thumbnail is to be handled client-side only, or if
+ * transformation was deferred via TRANSFORM_LATER.
+ *
+ * @return Bool
+ */
+ public function hasFile() {
+ // If TRANSFORM_LATER, $this->path will be false.
+ // Note: a null path means "use the source file".
+ return ( !$this->isError() && ( $this->path || $this->path === null ) );
+ }
+
+ /**
+ * Check if the output thumbnail is the same as the source.
+ * This can occur if the requested width was bigger than the source.
+ *
+ * @return Bool
+ */
+ public function fileIsSource() {
+ return ( !$this->isError() && $this->path === null );
+ }
+
+ /**
+ * Get the path of a file system copy of the thumbnail.
+ * Callers should never write to this path.
+ *
+ * @return string|false Returns false if there isn't one
+ */
+ public function getLocalCopyPath() {
+ if ( $this->isError() ) {
+ return false;
+ } elseif ( $this->path === null ) {
+ return $this->file->getLocalRefPath();
+ } else {
+ return $this->path; // may return false
+ }
+ }
+
+ /**
+ * Stream the file if there were no errors
+ *
+ * @param $headers Array Additional HTTP headers to send on success
+ * @return Bool success
+ */
+ public function streamFile( $headers = array() ) {
+ return $this->path && StreamFile::stream( $this->getLocalCopyPath(), $headers );
+ }
+
+ /**
* Wrap some XHTML text in an anchor tag with the given attributes
*
* @param $linkAttribs array
@@ -97,7 +156,7 @@ abstract class MediaTransformOutput {
* @param $params array
* @return array
*/
- function getDescLinkAttribs( $title = null, $params = '' ) {
+ public function getDescLinkAttribs( $title = null, $params = '' ) {
$query = $this->page ? ( 'page=' . urlencode( $this->page ) ) : '';
if( $params ) {
$query .= $query ? '&'.$params : $params;
@@ -119,13 +178,16 @@ abstract class MediaTransformOutput {
* @ingroup Media
*/
class ThumbnailImage extends MediaTransformOutput {
-
/**
+ * Get a thumbnail object from a file and parameters.
+ * If $path is set to null, the output file is treated as a source copy.
+ * If $path is set to false, no output file will be created.
+ *
* @param $file File object
* @param $url String: URL path to the thumb
* @param $width Integer: file's width
* @param $height Integer: file's height
- * @param $path String: filesystem path to the thumb
+ * @param $path String|false|null: filesystem path to the thumb
* @param $page Integer: page number, for multipage files
* @private
*/
@@ -185,7 +247,7 @@ class ThumbnailImage extends MediaTransformOutput {
} elseif ( !empty( $options['custom-title-link'] ) ) {
$title = $options['custom-title-link'];
$linkAttribs = array(
- 'href' => $title->getLinkUrl(),
+ 'href' => $title->getLinkURL(),
'title' => empty( $options['title'] ) ? $title->getFullText() : $options['title']
);
} elseif ( !empty( $options['desc-link'] ) ) {
diff --git a/includes/media/SVG.php b/includes/media/SVG.php
index ceffd7c3..aac838e1 100644
--- a/includes/media/SVG.php
+++ b/includes/media/SVG.php
@@ -93,13 +93,13 @@ class SvgHandler extends ImageHandler {
$clientHeight = $params['height'];
$physicalWidth = $params['physicalWidth'];
$physicalHeight = $params['physicalHeight'];
- $srcPath = $image->getPath();
+ $srcPath = $image->getLocalRefPath();
if ( $flags & self::TRANSFORM_LATER ) {
return new ThumbnailImage( $image, $dstUrl, $clientWidth, $clientHeight, $dstPath );
}
- if ( !wfMkdirParents( dirname( $dstPath ) ) ) {
+ if ( !wfMkdirParents( dirname( $dstPath ), null, __METHOD__ ) ) {
return new MediaTransformError( 'thumbnail_error', $clientWidth, $clientHeight,
wfMsg( 'thumbnail_dest_directory' ) );
}
@@ -119,7 +119,7 @@ class SvgHandler extends ImageHandler {
* @param string $dstPath
* @param string $width
* @param string $height
- * @returns TRUE/MediaTransformError
+ * @return true|MediaTransformError
*/
public function rasterize( $srcPath, $dstPath, $width, $height ) {
global $wgSVGConverters, $wgSVGConverter, $wgSVGConverterPath;
@@ -129,7 +129,7 @@ class SvgHandler extends ImageHandler {
if ( is_array( $wgSVGConverters[$wgSVGConverter] ) ) {
// This is a PHP callable
$func = $wgSVGConverters[$wgSVGConverter][0];
- $args = array_merge( array( $srcPath, $dstPath, $width, $height ),
+ $args = array_merge( array( $srcPath, $dstPath, $width, $height ),
array_slice( $wgSVGConverters[$wgSVGConverter], 1 ) );
if ( !is_callable( $func ) ) {
throw new MWException( "$func is not callable" );
@@ -161,13 +161,13 @@ class SvgHandler extends ImageHandler {
}
return true;
}
-
+
public static function rasterizeImagickExt( $srcPath, $dstPath, $width, $height ) {
$im = new Imagick( $srcPath );
$im->setImageFormat( 'png' );
$im->setBackgroundColor( 'transparent' );
$im->setImageDepth( 8 );
-
+
if ( !$im->thumbnailImage( intval( $width ), intval( $height ), /* fit */ false ) ) {
return 'Could not resize image';
}
diff --git a/includes/media/SVGMetadataExtractor.php b/includes/media/SVGMetadataExtractor.php
index 22ef8e61..db9f05fd 100644
--- a/includes/media/SVGMetadataExtractor.php
+++ b/includes/media/SVGMetadataExtractor.php
@@ -68,6 +68,12 @@ class SVGReader {
$this->reader->open( $source, null, LIBXML_NOERROR | LIBXML_NOWARNING );
}
+ // Expand entities, since Adobe Illustrator uses them for xmlns
+ // attributes (bug 31719). Note that libxml2 has some protection
+ // against large recursive entity expansions so this is not as
+ // insecure as it might appear to be.
+ $this->reader->setParserProperty( XMLReader::SUBST_ENTITIES, true );
+
$this->metadata['width'] = self::DEFAULT_WIDTH;
$this->metadata['height'] = self::DEFAULT_HEIGHT;
@@ -166,7 +172,7 @@ class SVGReader {
}
}
- /*
+ /**
* Read an XML snippet from an element
*
* @param String $metafield that we will fill with the result
diff --git a/includes/media/XCF.php b/includes/media/XCF.php
new file mode 100644
index 00000000..806db73c
--- /dev/null
+++ b/includes/media/XCF.php
@@ -0,0 +1,137 @@
+<?php
+/**
+ * Handler for the Gimp's native file format (XCF)
+ *
+ * Overview:
+ * http://en.wikipedia.org/wiki/XCF_(file_format)
+ * Specification in Gnome repository:
+ * http://svn.gnome.org/viewvc/gimp/trunk/devel-docs/xcf.txt?view=markup
+ *
+ * @file
+ * @ingroup Media
+ */
+
+/**
+ * Handler for the Gimp's native file format; getimagesize() doesn't
+ * support these files
+ *
+ * @ingroup Media
+ */
+class XCFHandler extends BitmapHandler {
+
+ /**
+ * @param $file
+ * @return bool
+ */
+ function mustRender( $file ) {
+ return true;
+ }
+
+ /**
+ * Render files as PNG
+ *
+ * @param $ext
+ * @param $mime
+ * @param $params
+ * @return array
+ */
+ function getThumbType( $ext, $mime, $params = null ) {
+ return array( 'png', 'image/png' );
+ }
+
+ /**
+ * Get width and height from the XCF header.
+ *
+ * @param $image
+ * @param $filename
+ * @return array
+ */
+ function getImageSize( $image, $filename ) {
+ return self::getXCFMetaData( $filename );
+ }
+
+ /**
+ * Metadata for a given XCF file
+ *
+ * Will return false if file magic signature is not recognized
+ * @author Hexmode
+ * @author Hashar
+ *
+ * @param $filename String Full path to a XCF file
+ * @return false|metadata array just like PHP getimagesize()
+ */
+ static function getXCFMetaData( $filename ) {
+ # Decode master structure
+ $f = fopen( $filename, 'rb' );
+ if( !$f ) {
+ return false;
+ }
+ # The image structure always starts at offset 0 in the XCF file.
+ # So we just read it :-)
+ $binaryHeader = fread( $f, 26 );
+ fclose($f);
+
+ # Master image structure:
+ #
+ # byte[9] "gimp xcf " File type magic
+ # byte[4] version XCF version
+ # "file" - version 0
+ # "v001" - version 1
+ # "v002" - version 2
+ # byte 0 Zero-terminator for version tag
+ # uint32 width With of canvas
+ # uint32 height Height of canvas
+ # uint32 base_type Color mode of the image; one of
+ # 0: RGB color
+ # 1: Grayscale
+ # 2: Indexed color
+ # (enum GimpImageBaseType in libgimpbase/gimpbaseenums.h)
+ try {
+ $header = wfUnpack(
+ "A9magic" # A: space padded
+ . "/a5version" # a: zero padded
+ . "/Nwidth" # \
+ . "/Nheight" # N: unsigned long 32bit big endian
+ . "/Nbase_type" # /
+ , $binaryHeader
+ );
+ } catch( MWException $mwe ) {
+ return false;
+ }
+
+ # Check values
+ if( $header['magic'] !== 'gimp xcf' ) {
+ wfDebug( __METHOD__ . " '$filename' has invalid magic signature.\n" );
+ return false;
+ }
+ # TODO: we might want to check for sane values of width and height
+
+ wfDebug( __METHOD__ . ": canvas size of '$filename' is {$header['width']} x {$header['height']} px\n" );
+
+ # Forge a return array containing metadata information just like getimagesize()
+ # See PHP documentation at: http://www.php.net/getimagesize
+ $metadata = array();
+ $metadata[0] = $header['width'];
+ $metadata[1] = $header['height'];
+ $metadata[2] = null; # IMAGETYPE constant, none exist for XCF.
+ $metadata[3] = sprintf(
+ 'height="%s" width="%s"', $header['height'], $header['width']
+ );
+ $metadata['mime'] = 'image/x-xcf';
+ $metadata['channels'] = null;
+ $metadata['bits'] = 8; # Always 8-bits per color
+
+ assert( '7 == count($metadata); # return array must contains 7 elements just like getimagesize() return' );
+
+ return $metadata;
+ }
+
+ /**
+ * Must use "im" for XCF
+ *
+ * @return string
+ */
+ protected static function getScalerType( $dstPath, $checkDstPath = true ) {
+ return "im";
+ }
+}
diff --git a/includes/media/XMP.php b/includes/media/XMP.php
index 1e578582..0dbf5632 100644
--- a/includes/media/XMP.php
+++ b/includes/media/XMP.php
@@ -210,9 +210,9 @@ class XMPReader {
* Also catches any errors during processing, writes them to
* debug log, blanks result array and returns false.
*
- * @param String: $content XMP data
- * @param Boolean: $allOfIt If this is all the data (true) or if its split up (false). Default true
- * @param Boolean: $reset - does xml parser need to be reset. Default false
+ * @param $content String: XMP data
+ * @param $allOfIt Boolean: If this is all the data (true) or if its split up (false). Default true
+ * @param $reset Boolean: does xml parser need to be reset. Default false
* @return Boolean success.
*/
public function parse( $content, $allOfIt = true, $reset = false ) {
diff --git a/includes/media/XMPInfo.php b/includes/media/XMPInfo.php
index 1d580ff7..156d9b50 100644
--- a/includes/media/XMPInfo.php
+++ b/includes/media/XMPInfo.php
@@ -631,12 +631,23 @@ class XMPInfo {
'validate' => 'validateClosed',
'choices' => array( '1' => true, '2' => true ),
),
- 'YCbCrSubSampling' => array(
- 'map_group' => 'exif',
- 'mode' => XMPReader::MODE_SEQ,
- 'validate' => 'validateClosed',
- 'choices' => array( '1' => true, '2' => true ),
- ),
+ /********
+ * Disable extracting this property (bug 31944)
+ * Several files have a string instead of a Seq
+ * for this property. XMPReader doesn't handle
+ * mismatched types very gracefully (it marks
+ * the entire file as invalid, instead of just
+ * the relavent prop). Since this prop
+ * doesn't communicate all that useful information
+ * just disable this prop for now, until such
+ * XMPReader is more graceful (bug 32172)
+ * 'YCbCrSubSampling' => array(
+ * 'map_group' => 'exif',
+ * 'mode' => XMPReader::MODE_SEQ,
+ * 'validate' => 'validateClosed',
+ * 'choices' => array( '1' => true, '2' => true ),
+ * ),
+ */
),
'http://ns.adobe.com/exif/1.0/aux/' => array(
'Lens' => array(
diff --git a/includes/media/XMPValidate.php b/includes/media/XMPValidate.php
index 0f1d375c..600d99de 100644
--- a/includes/media/XMPValidate.php
+++ b/includes/media/XMPValidate.php
@@ -201,10 +201,20 @@ class XMPValidate {
}
/**
- * function to validate date properties, and convert to Exif format.
+ * function to validate date properties, and convert to (partial) Exif format.
+ *
+ * Dates can be one of the following formats:
+ * YYYY
+ * YYYY-MM
+ * YYYY-MM-DD
+ * YYYY-MM-DDThh:mmTZD
+ * YYYY-MM-DDThh:mm:ssTZD
+ * YYYY-MM-DDThh:mm:ss.sTZD
*
* @param $info Array information about current property
* @param &$val Mixed current value to validate. Converts to TS_EXIF as a side-effect.
+ * in cases where there's only a partial date, it will give things like
+ * 2011:04.
* @param $standalone Boolean if this is a simple property or array
*/
public static function validateDate( $info, &$val, $standalone ) {
@@ -240,25 +250,41 @@ class XMPValidate {
$val = null;
return;
}
- //if month, etc unspecified, full out as 01.
- $res[2] = isset( $res[2] ) ? $res[2] : '01'; //month
- $res[3] = isset( $res[3] ) ? $res[3] : '01'; //day
+
if ( !isset( $res[4] ) ) { //hour
- //just have the year month day
- $val = $res[1] . ':' . $res[2] . ':' . $res[3];
+ //just have the year month day (if that)
+ $val = $res[1];
+ if ( isset( $res[2] ) ) {
+ $val .= ':' . $res[2];
+ }
+ if ( isset( $res[3] ) ) {
+ $val .= ':' . $res[3];
+ }
return;
}
- //if hour is set, so is minute or regex above will fail.
- //Extra check for empty string necessary due to TZ but no second case.
- $res[6] = isset( $res[6] ) && $res[6] != '' ? $res[6] : '00';
if ( !isset( $res[7] ) || $res[7] === 'Z' ) {
+ //if hour is set, then minute must also be or regex above will fail.
$val = $res[1] . ':' . $res[2] . ':' . $res[3]
- . ' ' . $res[4] . ':' . $res[5] . ':' . $res[6];
+ . ' ' . $res[4] . ':' . $res[5];
+ if ( isset( $res[6] ) && $res[6] !== '' ) {
+ $val .= ':' . $res[6];
+ }
return;
}
- //do timezone processing. We've already done the case that tz = Z.
+
+ // Extra check for empty string necessary due to TZ but no second case.
+ $stripSeconds = false;
+ if ( !isset( $res[6] ) || $res[6] === '' ) {
+ $res[6] = '00';
+ $stripSeconds = true;
+ }
+
+ // Do timezone processing. We've already done the case that tz = Z.
+
+ // We know that if we got to this step, year, month day hour and min must be set
+ // by virtue of regex not failing.
$unix = wfTimestamp( TS_UNIX, $res[1] . $res[2] . $res[3] . $res[4] . $res[5] . $res[6] );
$offset = intval( substr( $res[7], 1, 2 ) ) * 60 * 60;
@@ -267,6 +293,11 @@ class XMPValidate {
$offset = -$offset;
}
$val = wfTimestamp( TS_EXIF, $unix + $offset );
+
+ if ( $stripSeconds ) {
+ // If seconds weren't specified, remove the trailing ':00'.
+ $val = substr( $val, 0, -3 );
+ }
}
}