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