Add a new PNG parser in order to recognize APNG (animated PNG) images.

This commit is contained in:
Derk-Jan Hartman 2010-06-20 16:09:12 +00:00
parent d28bcad488
commit 0ccd98bdb6
8 changed files with 182 additions and 2 deletions

View file

@ -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',

View file

@ -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
View 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)";
}
}

View 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
);
}
}

View file

@ -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

View file

@ -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',

View file

@ -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.

View 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',