Add a new PNG parser in order to recognize APNG (animated PNG) images.
authorDerk-Jan Hartman <hartman@users.mediawiki.org>
Sun, 20 Jun 2010 16:09:12 +0000 (16:09 +0000)
committerDerk-Jan Hartman <hartman@users.mediawiki.org>
Sun, 20 Jun 2010 16:09:12 +0000 (16:09 +0000)
includes/AutoLoader.php
includes/DefaultSettings.php
includes/media/PNG.php [new file with mode: 0644]
includes/media/PNGMetadataExtractor.php [new file with mode: 0644]
includes/mime.types
languages/messages/MessagesEn.php
languages/messages/MessagesQqq.php
maintenance/language/messages.inc

index 312f20a..2c4598d 100644 (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',
index 2bc8f6a..e27ac42 100644 (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',
diff --git a/includes/media/PNG.php b/includes/media/PNG.php
new file mode 100644 (file)
index 0000000..babadde
--- /dev/null
@@ -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)";
+       }
+
+}
diff --git a/includes/media/PNGMetadataExtractor.php b/includes/media/PNGMetadataExtractor.php
new file mode 100644 (file)
index 0000000..2e87ff3
--- /dev/null
@@ -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
+               );
+               
+       }
+}
index df27d1f..76e63a6 100644 (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
index e78edc9..e3c41db 100644 (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',
index 46d3e98..e48bfbd 100644 (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.
 
index dee1c0e..4931c37 100644 (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',