New infrastructure for actions, as discussed on wikitech-l. Fairly huge commit.
[lhc/web/wiklou.git] / includes / media / PNGMetadataExtractor.php
1 <?php
2 /**
3 * PNG frame counter.
4 * Slightly derived from GIFMetadataExtractor.php
5 * Deliberately not using MWExceptions to avoid external dependencies, encouraging
6 * redistribution.
7 *
8 * @file
9 * @ingroup Media
10 */
11
12 /**
13 * PNG frame counter.
14 *
15 * @ingroup Media
16 */
17 class PNGMetadataExtractor {
18 static $png_sig;
19 static $CRC_size;
20
21 static function getMetadata( $filename ) {
22 self::$png_sig = pack( "C8", 137, 80, 78, 71, 13, 10, 26, 10 );
23 self::$CRC_size = 4;
24
25 $frameCount = 0;
26 $loopCount = 1;
27 $duration = 0.0;
28 $bitDepth = 0;
29 $colorType = 'unknown';
30
31 if (!$filename)
32 throw new Exception( __METHOD__ . ": No file name specified" );
33 elseif ( !file_exists($filename) || is_dir($filename) )
34 throw new Exception( __METHOD__ . ": File $filename does not exist" );
35
36 $fh = fopen( $filename, 'r' );
37
38 if (!$fh) {
39 throw new Exception( __METHOD__ . ": Unable to open file $filename" );
40 }
41
42 // Check for the PNG header
43 $buf = fread( $fh, 8 );
44 if ( $buf != self::$png_sig ) {
45 throw new Exception( __METHOD__ . ": Not a valid PNG file; header: $buf" );
46 }
47
48 // Read chunks
49 while( !feof( $fh ) ) {
50 $buf = fread( $fh, 4 );
51 if( !$buf ) {
52 throw new Exception( __METHOD__ . ": Read error" );
53 }
54 $chunk_size = unpack( "N", $buf);
55 $chunk_size = $chunk_size[1];
56
57 $chunk_type = fread( $fh, 4 );
58 if( !$chunk_type ) {
59 throw new Exception( __METHOD__ . ": Read error" );
60 }
61
62 if ( $chunk_type == "IHDR" ) {
63 $buf = fread( $fh, $chunk_size );
64 if( !$buf ) {
65 throw new Exception( __METHOD__ . ": Read error" );
66 }
67 $bitDepth = ord( substr( $buf, 8, 1 ) );
68 // Detect the color type in British English as per the spec
69 // http://www.w3.org/TR/PNG/#11IHDR
70 switch ( ord( substr( $buf, 9, 1 ) ) ) {
71 case 0:
72 $colorType = 'greyscale';
73 break;
74 case 2:
75 $colorType = 'truecolour';
76 break;
77 case 3:
78 $colorType = 'index-coloured';
79 break;
80 case 4:
81 $colorType = 'greyscale-alpha';
82 break;
83 case 6:
84 $colorType = 'truecolour-alpha';
85 break;
86 default:
87 $colorType = 'unknown';
88 break;
89 }
90 } elseif ( $chunk_type == "acTL" ) {
91 $buf = fread( $fh, $chunk_size );
92 if( !$buf ) {
93 throw new Exception( __METHOD__ . ": Read error" );
94 }
95
96 $actl = unpack( "Nframes/Nplays", $buf );
97 $frameCount = $actl['frames'];
98 $loopCount = $actl['plays'];
99 } elseif ( $chunk_type == "fcTL" ) {
100 $buf = fread( $fh, $chunk_size );
101 if( !$buf ) {
102 throw new Exception( __METHOD__ . ": Read error" );
103 }
104 $buf = substr( $buf, 20 );
105
106 $fctldur = unpack( "ndelay_num/ndelay_den", $buf );
107 if( $fctldur['delay_den'] == 0 ) $fctldur['delay_den'] = 100;
108 if( $fctldur['delay_num'] ) {
109 $duration += $fctldur['delay_num'] / $fctldur['delay_den'];
110 }
111 } elseif ( ( $chunk_type == "IDAT" || $chunk_type == "IEND" ) && $frameCount == 0 ) {
112 // Not a valid animated image. No point in continuing.
113 break;
114 } elseif ( $chunk_type == "IEND" ) {
115 break;
116 } else {
117 fseek( $fh, $chunk_size, SEEK_CUR );
118 }
119 fseek( $fh, self::$CRC_size, SEEK_CUR );
120 }
121 fclose( $fh );
122
123 if( $loopCount > 1 ) {
124 $duration *= $loopCount;
125 }
126
127 return array(
128 'frameCount' => $frameCount,
129 'loopCount' => $loopCount,
130 'duration' => $duration,
131 'bitDepth' => $bitDepth,
132 'colorType' => $colorType,
133 );
134
135 }
136 }