Index: trunk/phase3/maintenance/language/messages.inc |
— | — | @@ -2510,6 +2510,9 @@ |
2511 | 2511 | 'show-big-image-thumb', |
2512 | 2512 | 'file-info-gif-looped', |
2513 | 2513 | 'file-info-gif-frames', |
| 2514 | + 'file-info-png-looped', |
| 2515 | + 'file-info-png-repeat', |
| 2516 | + 'file-info-png-frames', |
2514 | 2517 | ), |
2515 | 2518 | 'newfiles' => array( |
2516 | 2519 | 'newimages', |
Index: trunk/phase3/includes/media/PNGMetadataExtractor.php |
— | — | @@ -0,0 +1,87 @@ |
| 2 | +<?php |
| 3 | +/** |
| 4 | + * PNG frame counter. |
| 5 | + * Based on |
| 6 | + * Deliberately not using MWExceptions to avoid external dependencies, encouraging |
| 7 | + * redistribution. |
| 8 | + */ |
| 9 | + |
| 10 | +class PNGMetadataExtractor { |
| 11 | + static $png_sig; |
| 12 | + static $CRC_size; |
| 13 | + |
| 14 | + static function getMetadata( $filename ) { |
| 15 | + self::$png_sig = pack( "C8", 137, 80, 78, 71, 13, 10, 26, 10 ); |
| 16 | + self::$CRC_size = 4; |
| 17 | + |
| 18 | + $frameCount = 0; |
| 19 | + $loopCount = 1; |
| 20 | + $duration = 0.0; |
| 21 | + |
| 22 | + if (!$filename) |
| 23 | + throw new Exception( __METHOD__ . "No file name specified" ); |
| 24 | + elseif ( !file_exists($filename) || is_dir($filename) ) |
| 25 | + throw new Exception( __METHOD__ . "File $filename does not exist" ); |
| 26 | + |
| 27 | + $fh = fopen( $filename, 'r' ); |
| 28 | + |
| 29 | + if (!$fh) |
| 30 | + throw new Exception( __METHOD__ . "Unable to open file $filename" ); |
| 31 | + |
| 32 | + // Check for the PNG header |
| 33 | + $buf = fread( $fh, 8 ); |
| 34 | + if ( !($buf == self::$png_sig) ) { |
| 35 | + throw new Exception( __METHOD__ . "Not a valid PNG file; header: $buf" ); |
| 36 | + } |
| 37 | + |
| 38 | + // Read chunks |
| 39 | + while( !feof( $fh ) ) { |
| 40 | + $buf = fread( $fh, 4 ); |
| 41 | + if( !$buf ) { throw new Exception( __METHOD__ . "Read error" ); return; } |
| 42 | + $chunk_size = unpack( "N", $buf); |
| 43 | + $chunk_size = $chunk_size[1]; |
| 44 | + |
| 45 | + $chunk_type = fread( $fh, 4 ); |
| 46 | + if( !$chunk_type ) { throw new Exception( __METHOD__ . "Read error" ); return; } |
| 47 | + |
| 48 | + if ( $chunk_type == "acTL" ) { |
| 49 | + $buf = fread( $fh, $chunk_size ); |
| 50 | + if( !$buf ) { throw new Exception( __METHOD__ . "Read error" ); return; } |
| 51 | + |
| 52 | + $actl = unpack( "Nframes/Nplays", $buf ); |
| 53 | + $frameCount = $actl['frames']; |
| 54 | + $loopCount = $actl['plays']; |
| 55 | + } elseif ( $chunk_type == "fcTL" ) { |
| 56 | + $buf = fread( $fh, $chunk_size ); |
| 57 | + if( !$buf ) { throw new Exception( __METHOD__ . "Read error" ); return; } |
| 58 | + $buf = substr( $buf, 20 ); |
| 59 | + |
| 60 | + $fctldur = unpack( "ndelay_num/ndelay_den", $buf ); |
| 61 | + if( $fctldur['delay_den'] == 0 ) $fctldur['delay_den'] = 100; |
| 62 | + if( $fctldur['delay_num'] ) { |
| 63 | + $duration += $fctldur['delay_num'] / $fctldur['delay_den']; |
| 64 | + } |
| 65 | + } elseif ( ( $chunk_type == "IDAT" || $chunk_type == "IEND" ) && $frameCount == 0 ) { |
| 66 | + // Not a valid animated image. No point in continuing. |
| 67 | + break; |
| 68 | + } elseif ( $chunk_type == "IEND" ) { |
| 69 | + break; |
| 70 | + } else { |
| 71 | + fseek( $fh, $chunk_size, SEEK_CUR ); |
| 72 | + } |
| 73 | + fseek( $fh, self::$CRC_size, SEEK_CUR ); |
| 74 | + } |
| 75 | + fclose( $fh ); |
| 76 | + |
| 77 | + if( $loopCount > 1 ) { |
| 78 | + $duration *= $loopCount; |
| 79 | + } |
| 80 | + |
| 81 | + return array( |
| 82 | + 'frameCount' => $frameCount, |
| 83 | + 'loopCount' => $loopCount, |
| 84 | + 'duration' => $duration |
| 85 | + ); |
| 86 | + |
| 87 | + } |
| 88 | +} |
Property changes on: trunk/phase3/includes/media/PNGMetadataExtractor.php |
___________________________________________________________________ |
Name: svn:eol-style |
1 | 89 | + native |
Index: trunk/phase3/includes/media/PNG.php |
— | — | @@ -0,0 +1,82 @@ |
| 2 | +<?php |
| 3 | +/** |
| 4 | + * @file |
| 5 | + * @ingroup Media |
| 6 | + */ |
| 7 | + |
| 8 | +/** |
| 9 | + * Handler for PNG images. |
| 10 | + * |
| 11 | + * @ingroup Media |
| 12 | + */ |
| 13 | +class PNGHandler extends BitmapHandler { |
| 14 | + |
| 15 | + function getMetadata( $image, $filename ) { |
| 16 | + if ( !isset($image->parsedPNGMetadata) ) { |
| 17 | + try { |
| 18 | + $image->parsedPNGMetadata = PNGMetadataExtractor::getMetadata( $filename ); |
| 19 | + } catch( Exception $e ) { |
| 20 | + // Broken file? |
| 21 | + wfDebug( __METHOD__ . ': ' . $e->getMessage() . "\n" ); |
| 22 | + return '0'; |
| 23 | + } |
| 24 | + } |
| 25 | + |
| 26 | + return serialize($image->parsedPNGMetadata); |
| 27 | + |
| 28 | + } |
| 29 | + |
| 30 | + function formatMetadata( $image ) { |
| 31 | + return false; |
| 32 | + } |
| 33 | + |
| 34 | + function isAnimatedImage( $image ) { |
| 35 | + $ser = $image->getMetadata(); |
| 36 | + if ($ser) { |
| 37 | + $metadata = unserialize($ser); |
| 38 | + if( $metadata['frameCount'] > 1 ) return true; |
| 39 | + } |
| 40 | + return false; |
| 41 | + } |
| 42 | + |
| 43 | + function getMetadataType( $image ) { |
| 44 | + return 'parsed-png'; |
| 45 | + } |
| 46 | + |
| 47 | + function isMetadataValid( $image, $metadata ) { |
| 48 | + wfSuppressWarnings(); |
| 49 | + $data = unserialize( $metadata ); |
| 50 | + wfRestoreWarnings(); |
| 51 | + return (boolean) $data; |
| 52 | + } |
| 53 | + function getLongDesc( $image ) { |
| 54 | + global $wgUser, $wgLang; |
| 55 | + $sk = $wgUser->getSkin(); |
| 56 | + $original = parent::getLongDesc( $image ); |
| 57 | + |
| 58 | + wfSuppressWarnings(); |
| 59 | + $metadata = unserialize($image->getMetadata()); |
| 60 | + wfRestoreWarnings(); |
| 61 | + |
| 62 | + if( !metadata || $metadata['frameCount'] == 0 ) |
| 63 | + return $original; |
| 64 | + |
| 65 | + $info[] = substr( $original, 1, strlen( $original )-2 ); |
| 66 | + |
| 67 | + if ($metadata['loopCount'] == 0) |
| 68 | + $info[] = wfMsgExt( 'file-info-png-looped', 'parseinline' ); |
| 69 | + elseif ($metadata['loopCount'] > 1) |
| 70 | + $info[] = wfMsgExt( 'file-info-png-repeat', 'parseinline', $metadata['loopCount'] );; |
| 71 | + |
| 72 | + if ($metadata['frameCount'] > 0) |
| 73 | + $info[] = wfMsgExt( 'file-info-png-frames', 'parseinline', $metadata['frameCount'] ); |
| 74 | + |
| 75 | + if ($metadata['duration']) |
| 76 | + $info[] = $wgLang->formatTimePeriod( $metadata['duration'] ); |
| 77 | + |
| 78 | + $infoString = $wgLang->commaList( $info ); |
| 79 | + |
| 80 | + return "($infoString)"; |
| 81 | + } |
| 82 | + |
| 83 | +} |
Property changes on: trunk/phase3/includes/media/PNG.php |
___________________________________________________________________ |
Name: svn:eol-style |
1 | 84 | + native |
Index: trunk/phase3/includes/AutoLoader.php |
— | — | @@ -444,6 +444,8 @@ |
445 | 445 | 'MediaHandler' => 'includes/media/Generic.php', |
446 | 446 | 'MediaTransformError' => 'includes/media/MediaTransformOutput.php', |
447 | 447 | 'MediaTransformOutput' => 'includes/media/MediaTransformOutput.php', |
| 448 | + 'PNGHandler' => 'includes/media/PNG.php', |
| 449 | + 'PNGMetadataExtractor' => 'includes/media/PNGMetadataExtractor.php', |
448 | 450 | 'SvgHandler' => 'includes/media/SVG.php', |
449 | 451 | 'ThumbnailImage' => 'includes/media/MediaTransformOutput.php', |
450 | 452 | 'TiffHandler' => 'includes/media/Tiff.php', |
Index: trunk/phase3/includes/mime.types |
— | — | @@ -78,7 +78,7 @@ |
79 | 79 | image/gif gif |
80 | 80 | image/ief ief |
81 | 81 | image/jpeg jpeg jpg jpe |
82 | | -image/png png |
| 82 | +image/png png apng |
83 | 83 | image/svg+xml svg |
84 | 84 | image/tiff tiff tif |
85 | 85 | image/vnd.djvu djvu djv |
Index: trunk/phase3/includes/DefaultSettings.php |
— | — | @@ -562,7 +562,7 @@ |
563 | 563 | */ |
564 | 564 | $wgMediaHandlers = array( |
565 | 565 | 'image/jpeg' => 'BitmapHandler', |
566 | | - 'image/png' => 'BitmapHandler', |
| 566 | + 'image/png' => 'PNGHandler', |
567 | 567 | 'image/gif' => 'GIFHandler', |
568 | 568 | 'image/tiff' => 'TiffHandler', |
569 | 569 | 'image/x-ms-bmp' => 'BmpHandler', |
Index: trunk/phase3/languages/messages/MessagesQqq.php |
— | — | @@ -2933,6 +2933,9 @@ |
2934 | 2934 | 'show-big-image-thumb' => 'File info displayed on file description page.', |
2935 | 2935 | '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]].', |
2936 | 2936 | 'file-info-gif-frames' => 'Part of the information provided about a [http://en.wikipedia.org/wiki/Gif .gif file] on its file description page. |
| 2937 | +'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.', |
| 2938 | +'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.', |
| 2939 | +'file-info-png-frames' => 'Part of the information provided about a [http://en.wikipedia.org/wiki/APNG .apng file] on its file description page. |
2937 | 2940 | |
2938 | 2941 | The variable $1 is the number of individual frames in an animated gif file. |
2939 | 2942 | |
Index: trunk/phase3/languages/messages/MessagesEn.php |
— | — | @@ -3552,6 +3552,9 @@ |
3553 | 3553 | 'show-big-image-thumb' => '<small>Size of this preview: $1 × $2 pixels</small>', |
3554 | 3554 | 'file-info-gif-looped' => 'looped', |
3555 | 3555 | 'file-info-gif-frames' => '$1 {{PLURAL:$1|frame|frames}}', |
| 3556 | +'file-info-png-looped' => 'looped', |
| 3557 | +'file-info-png-repeat' => 'played $1 times', |
| 3558 | +'file-info-png-frames' => '$1 {{PLURAL:$1|frame|frames}}', |
3556 | 3559 | |
3557 | 3560 | # Special:NewFiles |
3558 | 3561 | 'newimages' => 'Gallery of new files', |