Add a new PNG parser in order to recognize APNG (animated PNG) images.
This commit is contained in:
parent
d28bcad488
commit
0ccd98bdb6
8 changed files with 182 additions and 2 deletions
|
|
@ -444,6 +444,8 @@ $wgAutoloadLocalClasses = array(
|
|||
'MediaHandler' => 'includes/media/Generic.php',
|
||||
'MediaTransformError' => 'includes/media/MediaTransformOutput.php',
|
||||
'MediaTransformOutput' => 'includes/media/MediaTransformOutput.php',
|
||||
'PNGHandler' => 'includes/media/PNG.php',
|
||||
'PNGMetadataExtractor' => 'includes/media/PNGMetadataExtractor.php',
|
||||
'SvgHandler' => 'includes/media/SVG.php',
|
||||
'ThumbnailImage' => 'includes/media/MediaTransformOutput.php',
|
||||
'TiffHandler' => 'includes/media/Tiff.php',
|
||||
|
|
|
|||
|
|
@ -562,7 +562,7 @@ $wgTrustedMediaFormats = array(
|
|||
*/
|
||||
$wgMediaHandlers = array(
|
||||
'image/jpeg' => 'BitmapHandler',
|
||||
'image/png' => 'BitmapHandler',
|
||||
'image/png' => 'PNGHandler',
|
||||
'image/gif' => 'GIFHandler',
|
||||
'image/tiff' => 'TiffHandler',
|
||||
'image/x-ms-bmp' => 'BmpHandler',
|
||||
|
|
|
|||
82
includes/media/PNG.php
Normal file
82
includes/media/PNG.php
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
<?php
|
||||
/**
|
||||
* @file
|
||||
* @ingroup Media
|
||||
*/
|
||||
|
||||
/**
|
||||
* Handler for PNG images.
|
||||
*
|
||||
* @ingroup Media
|
||||
*/
|
||||
class PNGHandler extends BitmapHandler {
|
||||
|
||||
function getMetadata( $image, $filename ) {
|
||||
if ( !isset($image->parsedPNGMetadata) ) {
|
||||
try {
|
||||
$image->parsedPNGMetadata = PNGMetadataExtractor::getMetadata( $filename );
|
||||
} catch( Exception $e ) {
|
||||
// Broken file?
|
||||
wfDebug( __METHOD__ . ': ' . $e->getMessage() . "\n" );
|
||||
return '0';
|
||||
}
|
||||
}
|
||||
|
||||
return serialize($image->parsedPNGMetadata);
|
||||
|
||||
}
|
||||
|
||||
function formatMetadata( $image ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
function isAnimatedImage( $image ) {
|
||||
$ser = $image->getMetadata();
|
||||
if ($ser) {
|
||||
$metadata = unserialize($ser);
|
||||
if( $metadata['frameCount'] > 1 ) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function getMetadataType( $image ) {
|
||||
return 'parsed-png';
|
||||
}
|
||||
|
||||
function isMetadataValid( $image, $metadata ) {
|
||||
wfSuppressWarnings();
|
||||
$data = unserialize( $metadata );
|
||||
wfRestoreWarnings();
|
||||
return (boolean) $data;
|
||||
}
|
||||
function getLongDesc( $image ) {
|
||||
global $wgUser, $wgLang;
|
||||
$sk = $wgUser->getSkin();
|
||||
$original = parent::getLongDesc( $image );
|
||||
|
||||
wfSuppressWarnings();
|
||||
$metadata = unserialize($image->getMetadata());
|
||||
wfRestoreWarnings();
|
||||
|
||||
if( !metadata || $metadata['frameCount'] == 0 )
|
||||
return $original;
|
||||
|
||||
$info[] = substr( $original, 1, strlen( $original )-2 );
|
||||
|
||||
if ($metadata['loopCount'] == 0)
|
||||
$info[] = wfMsgExt( 'file-info-png-looped', 'parseinline' );
|
||||
elseif ($metadata['loopCount'] > 1)
|
||||
$info[] = wfMsgExt( 'file-info-png-repeat', 'parseinline', $metadata['loopCount'] );;
|
||||
|
||||
if ($metadata['frameCount'] > 0)
|
||||
$info[] = wfMsgExt( 'file-info-png-frames', 'parseinline', $metadata['frameCount'] );
|
||||
|
||||
if ($metadata['duration'])
|
||||
$info[] = $wgLang->formatTimePeriod( $metadata['duration'] );
|
||||
|
||||
$infoString = $wgLang->commaList( $info );
|
||||
|
||||
return "($infoString)";
|
||||
}
|
||||
|
||||
}
|
||||
87
includes/media/PNGMetadataExtractor.php
Normal file
87
includes/media/PNGMetadataExtractor.php
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
<?php
|
||||
/**
|
||||
* PNG frame counter.
|
||||
* Based on
|
||||
* Deliberately not using MWExceptions to avoid external dependencies, encouraging
|
||||
* redistribution.
|
||||
*/
|
||||
|
||||
class PNGMetadataExtractor {
|
||||
static $png_sig;
|
||||
static $CRC_size;
|
||||
|
||||
static function getMetadata( $filename ) {
|
||||
self::$png_sig = pack( "C8", 137, 80, 78, 71, 13, 10, 26, 10 );
|
||||
self::$CRC_size = 4;
|
||||
|
||||
$frameCount = 0;
|
||||
$loopCount = 1;
|
||||
$duration = 0.0;
|
||||
|
||||
if (!$filename)
|
||||
throw new Exception( __METHOD__ . "No file name specified" );
|
||||
elseif ( !file_exists($filename) || is_dir($filename) )
|
||||
throw new Exception( __METHOD__ . "File $filename does not exist" );
|
||||
|
||||
$fh = fopen( $filename, 'r' );
|
||||
|
||||
if (!$fh)
|
||||
throw new Exception( __METHOD__ . "Unable to open file $filename" );
|
||||
|
||||
// Check for the PNG header
|
||||
$buf = fread( $fh, 8 );
|
||||
if ( !($buf == self::$png_sig) ) {
|
||||
throw new Exception( __METHOD__ . "Not a valid PNG file; header: $buf" );
|
||||
}
|
||||
|
||||
// Read chunks
|
||||
while( !feof( $fh ) ) {
|
||||
$buf = fread( $fh, 4 );
|
||||
if( !$buf ) { throw new Exception( __METHOD__ . "Read error" ); return; }
|
||||
$chunk_size = unpack( "N", $buf);
|
||||
$chunk_size = $chunk_size[1];
|
||||
|
||||
$chunk_type = fread( $fh, 4 );
|
||||
if( !$chunk_type ) { throw new Exception( __METHOD__ . "Read error" ); return; }
|
||||
|
||||
if ( $chunk_type == "acTL" ) {
|
||||
$buf = fread( $fh, $chunk_size );
|
||||
if( !$buf ) { throw new Exception( __METHOD__ . "Read error" ); return; }
|
||||
|
||||
$actl = unpack( "Nframes/Nplays", $buf );
|
||||
$frameCount = $actl['frames'];
|
||||
$loopCount = $actl['plays'];
|
||||
} elseif ( $chunk_type == "fcTL" ) {
|
||||
$buf = fread( $fh, $chunk_size );
|
||||
if( !$buf ) { throw new Exception( __METHOD__ . "Read error" ); return; }
|
||||
$buf = substr( $buf, 20 );
|
||||
|
||||
$fctldur = unpack( "ndelay_num/ndelay_den", $buf );
|
||||
if( $fctldur['delay_den'] == 0 ) $fctldur['delay_den'] = 100;
|
||||
if( $fctldur['delay_num'] ) {
|
||||
$duration += $fctldur['delay_num'] / $fctldur['delay_den'];
|
||||
}
|
||||
} elseif ( ( $chunk_type == "IDAT" || $chunk_type == "IEND" ) && $frameCount == 0 ) {
|
||||
// Not a valid animated image. No point in continuing.
|
||||
break;
|
||||
} elseif ( $chunk_type == "IEND" ) {
|
||||
break;
|
||||
} else {
|
||||
fseek( $fh, $chunk_size, SEEK_CUR );
|
||||
}
|
||||
fseek( $fh, self::$CRC_size, SEEK_CUR );
|
||||
}
|
||||
fclose( $fh );
|
||||
|
||||
if( $loopCount > 1 ) {
|
||||
$duration *= $loopCount;
|
||||
}
|
||||
|
||||
return array(
|
||||
'frameCount' => $frameCount,
|
||||
'loopCount' => $loopCount,
|
||||
'duration' => $duration
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -78,7 +78,7 @@ image/cgm cgm
|
|||
image/gif gif
|
||||
image/ief ief
|
||||
image/jpeg jpeg jpg jpe
|
||||
image/png png
|
||||
image/png png apng
|
||||
image/svg+xml svg
|
||||
image/tiff tiff tif
|
||||
image/vnd.djvu djvu djv
|
||||
|
|
|
|||
|
|
@ -3552,6 +3552,9 @@ By executing it, your system may be compromised.<hr />",
|
|||
'show-big-image-thumb' => '<small>Size of this preview: $1 × $2 pixels</small>',
|
||||
'file-info-gif-looped' => 'looped',
|
||||
'file-info-gif-frames' => '$1 {{PLURAL:$1|frame|frames}}',
|
||||
'file-info-png-looped' => 'looped',
|
||||
'file-info-png-repeat' => 'played $1 times',
|
||||
'file-info-png-frames' => '$1 {{PLURAL:$1|frame|frames}}',
|
||||
|
||||
# Special:NewFiles
|
||||
'newimages' => 'Gallery of new files',
|
||||
|
|
|
|||
|
|
@ -2933,6 +2933,9 @@ The message appears after the name of the patroller.',
|
|||
'show-big-image-thumb' => 'File info displayed on file description page.',
|
||||
'file-info-gif-looped' => 'Part of the information provided about a [http://en.wikipedia.org/wiki/Gif .gif file] on its file description page. Looped means repeating in the context of an animated gif. It is a sequence of images, each displayed after the other, and the first one displayed after the last, in a never ending loop. For example of message in use see [[:File:Mouse10.gif]].',
|
||||
'file-info-gif-frames' => 'Part of the information provided about a [http://en.wikipedia.org/wiki/Gif .gif file] on its file description page.
|
||||
'file-info-png-looped' => 'Part of the information provided about a [http://en.wikipedia.org/wiki/APNG .apng file] on its file description page. Looped means repeating indefinetly in the context of an animated png. It is a sequence of images, each displayed after the other, and the first one displayed after the last, in a never ending loop.',
|
||||
'file-info-png-repeat' => 'Part of the information provided about a [http://en.wikipedia.org/wiki/APNG .apng file] on its file description page. The sequence of images is repeating a limited amount of time. It is a sequence of images, each displayed after the other, and the first one displayed after the last, for $1 times.',
|
||||
'file-info-png-frames' => 'Part of the information provided about a [http://en.wikipedia.org/wiki/APNG .apng file] on its file description page.
|
||||
|
||||
The variable $1 is the number of individual frames in an animated gif file.
|
||||
|
||||
|
|
|
|||
|
|
@ -2510,6 +2510,9 @@ $wgMessageStructure = array(
|
|||
'show-big-image-thumb',
|
||||
'file-info-gif-looped',
|
||||
'file-info-gif-frames',
|
||||
'file-info-png-looped',
|
||||
'file-info-png-repeat',
|
||||
'file-info-png-frames',
|
||||
),
|
||||
'newfiles' => array(
|
||||
'newimages',
|
||||
|
|
|
|||
Loading…
Reference in a new issue