[ Index ]

PHP Cross Reference of MediaWiki-1.24.0

title

Body

[close]

/includes/media/ -> GIFMetadataExtractor.php (source)

   1  <?php
   2  /**
   3   * GIF frame counter.
   4   *
   5   * Originally written in Perl by Steve Sanbeg.
   6   * Ported to PHP by Andrew Garrett
   7   * Deliberately not using MWExceptions to avoid external dependencies, encouraging
   8   * redistribution.
   9   *
  10   * This program is free software; you can redistribute it and/or modify
  11   * it under the terms of the GNU General Public License as published by
  12   * the Free Software Foundation; either version 2 of the License, or
  13   * (at your option) any later version.
  14   *
  15   * This program is distributed in the hope that it will be useful,
  16   * but WITHOUT ANY WARRANTY; without even the implied warranty of
  17   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  18   * GNU General Public License for more details.
  19   *
  20   * You should have received a copy of the GNU General Public License along
  21   * with this program; if not, write to the Free Software Foundation, Inc.,
  22   * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  23   * http://www.gnu.org/copyleft/gpl.html
  24   *
  25   * @file
  26   * @ingroup Media
  27   */
  28  
  29  /**
  30   * GIF frame counter.
  31   *
  32   * @ingroup Media
  33   */
  34  class GIFMetadataExtractor {
  35      /** @var string */
  36      private static $gifFrameSep;
  37  
  38      /** @var string */
  39      private static $gifExtensionSep;
  40  
  41      /** @var string */
  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  
  51      /**
  52       * @throws Exception
  53       * @param string $filename
  54       * @return array
  55       */
  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 ) {
 164                          wfSuppressWarnings();
 165                          $data = iconv( 'windows-1252', 'UTF-8', $data );
 166                          wfRestoreWarnings();
 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  
 263      /**
 264       * @param resource $fh
 265       * @param int $bpp
 266       * @return void
 267       */
 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  
 277      /**
 278       * @param string $data
 279       * @throws Exception
 280       * @return int
 281       */
 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  
 296      /**
 297       * @param resource $fh
 298       * @throws Exception
 299       */
 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  
 315      /**
 316       * Read a block. In the GIF format, a block is made up of
 317       * several sub-blocks. Each sub block starts with one byte
 318       * saying how long the sub-block is, followed by the sub-block.
 319       * The entire block is terminated by a sub-block of length
 320       * 0.
 321       * @param resource $fh File handle
 322       * @param bool $includeLengths Include the length bytes of the
 323       *  sub-blocks in the returned value. Normally this is false,
 324       *  except XMP is weird and does a hack where you need to keep
 325       *  these length bytes.
 326       * @throws Exception
 327       * @return string The data.
 328       */
 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  }


Generated: Fri Nov 28 14:03:12 2014 Cross-referenced by PHPXref 0.7.1