(bug 16451) Fix application of scaling limits for animated GIFs.
[lhc/web/wiklou.git] / includes / media / GIFMetadataExtractor.php
1 <?php
2 /**
3 * GIF frame counter.
4 * Originally written in Perl by Steve Sanbeg.
5 * Ported to PHP by Andrew Garrett
6 * Deliberately not using MWExceptions to avoid external dependencies, encouraging
7 * redistribution.
8 */
9
10 class GIFMetadataExtractor {
11 static $gif_frame_sep;
12 static $gif_extension_sep;
13 static $gif_term;
14
15 static function getMetadata( $filename ) {
16 wfProfileIn( __METHOD__ );
17
18 self::$gif_frame_sep = pack( "C", ord("," ) );
19 self::$gif_extension_sep = pack( "C", ord("!" ) );
20 self::$gif_term = pack( "C", ord(";" ) );
21
22 $frameCount = 0;
23 $duration = 0.0;
24 $isLooped = false;
25
26 if (!$filename)
27 throw new Exception( "No file name specified" );
28 elseif ( !file_exists($filename) || is_dir($filename) )
29 throw new Exception( "File $filename does not exist" );
30
31 $fh = fopen( $filename, 'r' );
32
33 if (!$fh)
34 throw new Exception( "Unable to open file $filename" );
35
36 // Check for the GIF header
37 $buf = fread( $fh, 6 );
38 if ( !($buf == 'GIF87a' || $buf == 'GIF89a') ) {
39 throw new Exception( "Not a valid GIF file; header: $buf" );
40 }
41
42 // Skip over width and height.
43 fread( $fh, 4 );
44
45 // Read BPP
46 $buf = fread( $fh, 1 );
47 $bpp = self::decodeBPP( $buf );
48
49 // Skip over background and aspect ratio
50 fread( $fh, 2 );
51
52 // Skip over the GCT
53 self::readGCT( $fh, $bpp );
54
55 while( !feof( $fh ) ) {
56 $buf = fread( $fh, 1 );
57
58 if ($buf == self::$gif_frame_sep) {
59 // Found a frame
60 $frameCount++;
61
62 ## Skip dimensions (Why is this 8 bytes and not 4?)
63 fread( $fh, 8 );
64
65 ## Read BPP
66 $buf = fread( $fh, 1 );
67 $bpp = self::decodeBPP( $buf );
68
69 ## Read GCT
70 self::readGCT( $fh, $bpp );
71 fread( $fh, 1 );
72 self::skipBlock( $fh );
73 } elseif ( $buf == self::$gif_extension_sep ) {
74 $buf = fread( $fh, 1 );
75 $extension_code = unpack( 'C', $buf );
76 $extension_code = $extension_code[1];
77
78 if ($extension_code == 0xF9) {
79 // Graphics Control Extension.
80 fread( $fh, 1 ); // Block size
81 fread( $fh, 1 ); // Transparency, disposal method, user input
82
83 $buf = fread( $fh, 2 ); // Delay, in hundredths of seconds.
84 $delay = unpack( 'v', $buf );
85 $delay = $delay[1];
86 $duration += $delay * 0.01;
87
88 fread( $fh, 1 ); // Transparent colour index
89
90 $term = fread( $fh, 1 ); // Should be a terminator
91 $term = unpack( 'C', $term );
92 $term = $term[1];
93 if ($term != 0 )
94 throw new Exception( "Malformed Graphics Control Extension block" );
95 } elseif ($extension_code == 0xFF) {
96 // Application extension (Netscape info about the animated gif)
97 $blockLength = fread( $fh, 1 );
98 $blockLength = unpack( 'C', $blockLength );
99 $blockLength = $blockLength[1];
100 $data = fread( $fh, $blockLength );
101
102 // NETSCAPE2.0 (application name)
103 if ($blockLength != 11 || $data != 'NETSCAPE2.0') {
104 self::skipBlock();
105 continue;
106 }
107
108 fread( $fh, 2 ); // Block length and introduction, should be 03 01
109
110 // Unsigned little-endian integer, loop count or zero for "forever"
111 $loopData = fread( $fh, 2 );
112 $loopData = unpack( 'v', $loopData );
113 $loopCount = $loopData[1];
114
115 if ($loopCount != 1) {
116 $isLooped = true;
117 }
118
119 // Read out terminator byte
120 fread( $fh, 1 );
121 }
122 } elseif ( $buf == self::$gif_term ) {
123 break;
124 } else {
125 $byte = unpack( 'C', $buf );
126 $byte = $byte[1];
127 throw new Exception( "At position: ".ftell($fh). ", Unknown byte ".$byte );
128 }
129 }
130
131 wfProfileOut( __METHOD__ );
132
133 return array(
134 'frameCount' => $frameCount,
135 'looped' => $isLooped,
136 'duration' => $duration
137 );
138
139 }
140
141 static function readGCT( $fh, $bpp ) {
142 if ($bpp > 0) {
143 for( $i=1; $i<=pow(2,$bpp); ++$i ) {
144 fread( $fh, 3 );
145 }
146 }
147 }
148
149 static function decodeBPP( $data ) {
150 $buf = unpack( 'C', $data );
151 $buf = $buf[1];
152 $bpp = ( $buf & 7 ) + 1;
153 $buf >>= 7;
154
155 $have_map = $buf & 1;
156
157 return $have_map ? $bpp : 0;
158 }
159
160 static function skipBlock( $fh ) {
161 while ( !feof( $fh ) ) {
162 $buf = fread( $fh, 1 );
163 $block_len = unpack( 'C', $buf );
164 $block_len = $block_len[1];
165 if ($block_len == 0)
166 return;
167 fread( $fh, $block_len );
168 }
169 }
170
171 }