MediaWiki  1.23.6
GIFMetadataExtractor.php
Go to the documentation of this file.
1 <?php
36  private static $gifFrameSep;
37 
39  private static $gifExtensionSep;
40 
42  private static $gifTerm;
43 
44  const VERSION = 1;
45 
46  // Each sub-block is less than or equal to 255 bytes.
47  // Most of the time its 255 bytes, except for in XMP
48  // blocks, where it's usually between 32-127 bytes each.
49  const MAX_SUBBLOCKS = 262144; // 5mb divided by 20.
50 
56  static function getMetadata( $filename ) {
57  self::$gifFrameSep = pack( "C", ord( "," ) );
58  self::$gifExtensionSep = pack( "C", ord( "!" ) );
59  self::$gifTerm = pack( "C", ord( ";" ) );
60 
61  $frameCount = 0;
62  $duration = 0.0;
63  $isLooped = false;
64  $xmp = "";
65  $comment = array();
66 
67  if ( !$filename ) {
68  throw new Exception( "No file name specified" );
69  } elseif ( !file_exists( $filename ) || is_dir( $filename ) ) {
70  throw new Exception( "File $filename does not exist" );
71  }
72 
73  $fh = fopen( $filename, 'rb' );
74 
75  if ( !$fh ) {
76  throw new Exception( "Unable to open file $filename" );
77  }
78 
79  // Check for the GIF header
80  $buf = fread( $fh, 6 );
81  if ( !( $buf == 'GIF87a' || $buf == 'GIF89a' ) ) {
82  throw new Exception( "Not a valid GIF file; header: $buf" );
83  }
84 
85  // Skip over width and height.
86  fread( $fh, 4 );
87 
88  // Read BPP
89  $buf = fread( $fh, 1 );
90  $bpp = self::decodeBPP( $buf );
91 
92  // Skip over background and aspect ratio
93  fread( $fh, 2 );
94 
95  // Skip over the GCT
96  self::readGCT( $fh, $bpp );
97 
98  while ( !feof( $fh ) ) {
99  $buf = fread( $fh, 1 );
100 
101  if ( $buf == self::$gifFrameSep ) {
102  // Found a frame
103  $frameCount++;
104 
105  ## Skip bounding box
106  fread( $fh, 8 );
107 
108  ## Read BPP
109  $buf = fread( $fh, 1 );
110  $bpp = self::decodeBPP( $buf );
111 
112  ## Read GCT
113  self::readGCT( $fh, $bpp );
114  fread( $fh, 1 );
115  self::skipBlock( $fh );
116  } elseif ( $buf == self::$gifExtensionSep ) {
117  $buf = fread( $fh, 1 );
118  if ( strlen( $buf ) < 1 ) {
119  throw new Exception( "Ran out of input" );
120  }
121  $extension_code = unpack( 'C', $buf );
122  $extension_code = $extension_code[1];
123 
124  if ( $extension_code == 0xF9 ) {
125  // Graphics Control Extension.
126  fread( $fh, 1 ); // Block size
127 
128  fread( $fh, 1 ); // Transparency, disposal method, user input
129 
130  $buf = fread( $fh, 2 ); // Delay, in hundredths of seconds.
131  if ( strlen( $buf ) < 2 ) {
132  throw new Exception( "Ran out of input" );
133  }
134  $delay = unpack( 'v', $buf );
135  $delay = $delay[1];
136  $duration += $delay * 0.01;
137 
138  fread( $fh, 1 ); // Transparent colour index
139 
140  $term = fread( $fh, 1 ); // Should be a terminator
141  if ( strlen( $term ) < 1 ) {
142  throw new Exception( "Ran out of input" );
143  }
144  $term = unpack( 'C', $term );
145  $term = $term[1];
146  if ( $term != 0 ) {
147  throw new Exception( "Malformed Graphics Control Extension block" );
148  }
149  } elseif ( $extension_code == 0xFE ) {
150  // Comment block(s).
151  $data = self::readBlock( $fh );
152  if ( $data === "" ) {
153  throw new Exception( 'Read error, zero-length comment block' );
154  }
155 
156  // The standard says this should be ASCII, however its unclear if
157  // thats true in practise. Check to see if its valid utf-8, if so
158  // assume its that, otherwise assume its windows-1252 (iso-8859-1)
159  $dataCopy = $data;
160  // quickIsNFCVerify has the side effect of replacing any invalid characters
161  UtfNormal::quickIsNFCVerify( $dataCopy );
162 
163  if ( $dataCopy !== $data ) {
165  $data = iconv( 'windows-1252', 'UTF-8', $data );
167  }
168 
169  $commentCount = count( $comment );
170  if ( $commentCount === 0
171  || $comment[$commentCount - 1] !== $data
172  ) {
173  // Some applications repeat the same comment on each
174  // frame of an animated GIF image, so if this comment
175  // is identical to the last, only extract once.
176  $comment[] = $data;
177  }
178  } elseif ( $extension_code == 0xFF ) {
179  // Application extension (Netscape info about the animated gif)
180  // or XMP (or theoretically any other type of extension block)
181  $blockLength = fread( $fh, 1 );
182  if ( strlen( $blockLength ) < 1 ) {
183  throw new Exception( "Ran out of input" );
184  }
185  $blockLength = unpack( 'C', $blockLength );
186  $blockLength = $blockLength[1];
187  $data = fread( $fh, $blockLength );
188 
189  if ( $blockLength != 11 ) {
190  wfDebug( __METHOD__ . " GIF application block with wrong length\n" );
191  fseek( $fh, -( $blockLength + 1 ), SEEK_CUR );
192  self::skipBlock( $fh );
193  continue;
194  }
195 
196  // NETSCAPE2.0 (application name for animated gif)
197  if ( $data == 'NETSCAPE2.0' ) {
198  $data = fread( $fh, 2 ); // Block length and introduction, should be 03 01
199 
200  if ( $data != "\x03\x01" ) {
201  throw new Exception( "Expected \x03\x01, got $data" );
202  }
203 
204  // Unsigned little-endian integer, loop count or zero for "forever"
205  $loopData = fread( $fh, 2 );
206  if ( strlen( $loopData ) < 2 ) {
207  throw new Exception( "Ran out of input" );
208  }
209  $loopData = unpack( 'v', $loopData );
210  $loopCount = $loopData[1];
211 
212  if ( $loopCount != 1 ) {
213  $isLooped = true;
214  }
215 
216  // Read out terminator byte
217  fread( $fh, 1 );
218  } elseif ( $data == 'XMP DataXMP' ) {
219  // application name for XMP data.
220  // see pg 18 of XMP spec part 3.
221 
222  $xmp = self::readBlock( $fh, true );
223 
224  if ( substr( $xmp, -257, 3 ) !== "\x01\xFF\xFE"
225  || substr( $xmp, -4 ) !== "\x03\x02\x01\x00"
226  ) {
227  // this is just a sanity check.
228  throw new Exception( "XMP does not have magic trailer!" );
229  }
230 
231  // strip out trailer.
232  $xmp = substr( $xmp, 0, -257 );
233  } else {
234  // unrecognized extension block
235  fseek( $fh, -( $blockLength + 1 ), SEEK_CUR );
236  self::skipBlock( $fh );
237  continue;
238  }
239  } else {
240  self::skipBlock( $fh );
241  }
242  } elseif ( $buf == self::$gifTerm ) {
243  break;
244  } else {
245  if ( strlen( $buf ) < 1 ) {
246  throw new Exception( "Ran out of input" );
247  }
248  $byte = unpack( 'C', $buf );
249  $byte = $byte[1];
250  throw new Exception( "At position: " . ftell( $fh ) . ", Unknown byte " . $byte );
251  }
252  }
253 
254  return array(
255  'frameCount' => $frameCount,
256  'looped' => $isLooped,
257  'duration' => $duration,
258  'xmp' => $xmp,
259  'comment' => $comment,
260  );
261  }
262 
268  static function readGCT( $fh, $bpp ) {
269  if ( $bpp > 0 ) {
270  $max = pow( 2, $bpp );
271  for ( $i = 1; $i <= $max; ++$i ) {
272  fread( $fh, 3 );
273  }
274  }
275  }
276 
282  static function decodeBPP( $data ) {
283  if ( strlen( $data ) < 1 ) {
284  throw new Exception( "Ran out of input" );
285  }
286  $buf = unpack( 'C', $data );
287  $buf = $buf[1];
288  $bpp = ( $buf & 7 ) + 1;
289  $buf >>= 7;
290 
291  $have_map = $buf & 1;
292 
293  return $have_map ? $bpp : 0;
294  }
295 
300  static function skipBlock( $fh ) {
301  while ( !feof( $fh ) ) {
302  $buf = fread( $fh, 1 );
303  if ( strlen( $buf ) < 1 ) {
304  throw new Exception( "Ran out of input" );
305  }
306  $block_len = unpack( 'C', $buf );
307  $block_len = $block_len[1];
308  if ( $block_len == 0 ) {
309  return;
310  }
311  fread( $fh, $block_len );
312  }
313  }
314 
329  static function readBlock( $fh, $includeLengths = false ) {
330  $data = '';
331  $subLength = fread( $fh, 1 );
332  $blocks = 0;
333 
334  while ( $subLength !== "\0" ) {
335  $blocks++;
336  if ( $blocks > self::MAX_SUBBLOCKS ) {
337  throw new Exception( "MAX_SUBBLOCKS exceeded (over $blocks sub-blocks)" );
338  }
339  if ( feof( $fh ) ) {
340  throw new Exception( "Read error: Unexpected EOF." );
341  }
342  if ( $includeLengths ) {
343  $data .= $subLength;
344  }
345 
346  $data .= fread( $fh, ord( $subLength ) );
347  $subLength = fread( $fh, 1 );
348  }
349 
350  return $data;
351  }
352 }
GIFMetadataExtractor\$gifTerm
static $gifTerm
Definition: GIFMetadataExtractor.php:42
GIFMetadataExtractor
GIF frame counter.
Definition: GIFMetadataExtractor.php:34
php
skin txt MediaWiki includes four core it has been set as the default in MediaWiki since the replacing Monobook it had been been the default skin since before being replaced by Vector largely rewritten in while keeping its appearance Several legacy skins were removed in the as the burden of supporting them became too heavy to bear Those in etc for skin dependent CSS etc for skin dependent JavaScript These can also be customised on a per user by etc This feature has led to a wide variety of user styles becoming that gallery is a good place to ending in php
Definition: skin.txt:62
GIFMetadataExtractor\MAX_SUBBLOCKS
const MAX_SUBBLOCKS
Definition: GIFMetadataExtractor.php:49
wfSuppressWarnings
wfSuppressWarnings( $end=false)
Reference-counted warning suppression.
Definition: GlobalFunctions.php:2387
wfRestoreWarnings
wfRestoreWarnings()
Restore error level to previous value.
Definition: GlobalFunctions.php:2417
GIFMetadataExtractor\$gifExtensionSep
static $gifExtensionSep
Definition: GIFMetadataExtractor.php:39
array
the array() calling protocol came about after MediaWiki 1.4rc1.
List of Api Query prop modules.
$comment
$comment
Definition: importImages.php:107
GIFMetadataExtractor\getMetadata
static getMetadata( $filename)
Definition: GIFMetadataExtractor.php:56
wfDebug
wfDebug( $text, $dest='all')
Sends a line to the debug log if enabled or, optionally, to a comment in output.
Definition: GlobalFunctions.php:933
GIFMetadataExtractor\readBlock
static readBlock( $fh, $includeLengths=false)
Read a block.
Definition: GIFMetadataExtractor.php:329
GIFMetadataExtractor\decodeBPP
static decodeBPP( $data)
Definition: GIFMetadataExtractor.php:282
$term
the value to return A Title object or null whereas SearchGetNearMatch runs after $term
Definition: hooks.txt:2125
GIFMetadataExtractor\readGCT
static readGCT( $fh, $bpp)
Definition: GIFMetadataExtractor.php:268
GIFMetadataExtractor\skipBlock
static skipBlock( $fh)
Definition: GIFMetadataExtractor.php:300
GIFMetadataExtractor\VERSION
const VERSION
Definition: GIFMetadataExtractor.php:44
GIFMetadataExtractor\$gifFrameSep
static $gifFrameSep
Definition: GIFMetadataExtractor.php:36
UtfNormal\quickIsNFCVerify
static quickIsNFCVerify(&$string)
Returns true if the string is definitely in NFC.
Definition: UtfNormal.php:243