MediaWiki  master
WebPHandler.php
Go to the documentation of this file.
1 <?php
29 class WebPHandler extends BitmapHandler {
30  const BROKEN_FILE = '0'; // value to store in img_metadata if error extracting metadata.
34  const MINIMUM_CHUNK_HEADER_LENGTH = 18;
38  const _MW_WEBP_VERSION = 1;
39 
40  const VP8X_ICC = 32;
41  const VP8X_ALPHA = 16;
42  const VP8X_EXIF = 8;
43  const VP8X_XMP = 4;
44  const VP8X_ANIM = 2;
45 
46  public function getMetadata( $image, $filename ) {
47  $parsedWebPData = self::extractMetadata( $filename );
48  if ( !$parsedWebPData ) {
49  return self::BROKEN_FILE;
50  }
51 
52  $parsedWebPData['metadata']['_MW_WEBP_VERSION'] = self::_MW_WEBP_VERSION;
53  return serialize( $parsedWebPData );
54  }
55 
56  public function getMetadataType( $image ) {
57  return 'parsed-webp';
58  }
59 
60  public function isMetadataValid( $image, $metadata ) {
61  if ( $metadata === self::BROKEN_FILE ) {
62  // Do not repetitivly regenerate metadata on broken file.
63  return self::METADATA_GOOD;
64  }
65 
66  Wikimedia\suppressWarnings();
67  $data = unserialize( $metadata );
68  Wikimedia\restoreWarnings();
69 
70  if ( !$data || !is_array( $data ) ) {
71  wfDebug( __METHOD__ . " invalid WebP metadata\n" );
72 
73  return self::METADATA_BAD;
74  }
75 
76  if ( !isset( $data['metadata']['_MW_WEBP_VERSION'] )
77  || $data['metadata']['_MW_WEBP_VERSION'] != self::_MW_WEBP_VERSION
78  ) {
79  wfDebug( __METHOD__ . " old but compatible WebP metadata\n" );
80 
81  return self::METADATA_COMPATIBLE;
82  }
83  return self::METADATA_GOOD;
84  }
85 
94  public static function extractMetadata( $filename ) {
95  wfDebugLog( 'WebP', __METHOD__ . ": Extracting metadata from $filename\n" );
96 
97  $info = RiffExtractor::findChunksFromFile( $filename, 100 );
98  if ( $info === false ) {
99  wfDebugLog( 'WebP', __METHOD__ . ": Not a valid RIFF file\n" );
100  return false;
101  }
102 
103  if ( $info['fourCC'] != 'WEBP' ) {
104  wfDebugLog( 'WebP', __METHOD__ . ': FourCC was not WEBP: ' .
105  bin2hex( $info['fourCC'] ) . " \n" );
106  return false;
107  }
108 
109  $metadata = self::extractMetadataFromChunks( $info['chunks'], $filename );
110  if ( !$metadata ) {
111  wfDebugLog( 'WebP', __METHOD__ . ": No VP8 chunks found\n" );
112  return false;
113  }
114 
115  return $metadata;
116  }
117 
125  public static function extractMetadataFromChunks( $chunks, $filename ) {
126  $vp8Info = [];
127 
128  foreach ( $chunks as $chunk ) {
129  if ( !in_array( $chunk['fourCC'], [ 'VP8 ', 'VP8L', 'VP8X' ] ) ) {
130  // Not a chunk containing interesting metadata
131  continue;
132  }
133 
134  $chunkHeader = file_get_contents( $filename, false, null,
135  $chunk['start'], self::MINIMUM_CHUNK_HEADER_LENGTH );
136  wfDebugLog( 'WebP', __METHOD__ . ": {$chunk['fourCC']}\n" );
137 
138  switch ( $chunk['fourCC'] ) {
139  case 'VP8 ':
140  return array_merge( $vp8Info,
141  self::decodeLossyChunkHeader( $chunkHeader ) );
142  case 'VP8L':
143  return array_merge( $vp8Info,
144  self::decodeLosslessChunkHeader( $chunkHeader ) );
145  case 'VP8X':
146  $vp8Info = array_merge( $vp8Info,
147  self::decodeExtendedChunkHeader( $chunkHeader ) );
148  // Continue looking for other chunks to improve the metadata
149  break;
150  }
151  }
152  return $vp8Info;
153  }
154 
160  protected static function decodeLossyChunkHeader( $header ) {
161  // Bytes 0-3 are 'VP8 '
162  // Bytes 4-7 are the VP8 stream size
163  // Bytes 8-10 are the frame tag
164  // Bytes 11-13 are 0x9D 0x01 0x2A called the sync code
165  $syncCode = substr( $header, 11, 3 );
166  if ( $syncCode != "\x9D\x01\x2A" ) {
167  wfDebugLog( 'WebP', __METHOD__ . ': Invalid sync code: ' .
168  bin2hex( $syncCode ) . "\n" );
169  return [];
170  }
171  // Bytes 14-17 are image size
172  $imageSize = unpack( 'v2', substr( $header, 14, 4 ) );
173  // Image sizes are 14 bit, 2 MSB are scaling parameters which are ignored here
174  return [
175  'compression' => 'lossy',
176  'width' => $imageSize[1] & 0x3FFF,
177  'height' => $imageSize[2] & 0x3FFF
178  ];
179  }
180 
187  public static function decodeLosslessChunkHeader( $header ) {
188  // Bytes 0-3 are 'VP8L'
189  // Bytes 4-7 are chunk stream size
190  // Byte 8 is 0x2F called the signature
191  if ( $header{8} != "\x2F" ) {
192  wfDebugLog( 'WebP', __METHOD__ . ': Invalid signature: ' .
193  bin2hex( $header{8} ) . "\n" );
194  return [];
195  }
196  // Bytes 9-12 contain the image size
197  // Bits 0-13 are width-1; bits 15-27 are height-1
198  $imageSize = unpack( 'C4', substr( $header, 9, 4 ) );
199  return [
200  'compression' => 'lossless',
201  'width' => ( $imageSize[1] | ( ( $imageSize[2] & 0x3F ) << 8 ) ) + 1,
202  'height' => ( ( ( $imageSize[2] & 0xC0 ) >> 6 ) |
203  ( $imageSize[3] << 2 ) | ( ( $imageSize[4] & 0x03 ) << 10 ) ) + 1
204  ];
205  }
206 
212  public static function decodeExtendedChunkHeader( $header ) {
213  // Bytes 0-3 are 'VP8X'
214  // Byte 4-7 are chunk length
215  // Byte 8-11 are a flag bytes
216  $flags = unpack( 'c', substr( $header, 8, 1 ) );
217 
218  // Byte 12-17 are image size (24 bits)
219  $width = unpack( 'V', substr( $header, 12, 3 ) . "\x00" );
220  $height = unpack( 'V', substr( $header, 15, 3 ) . "\x00" );
221 
222  return [
223  'compression' => 'unknown',
224  'animated' => ( $flags[1] & self::VP8X_ANIM ) == self::VP8X_ANIM,
225  'transparency' => ( $flags[1] & self::VP8X_ALPHA ) == self::VP8X_ALPHA,
226  'width' => ( $width[1] & 0xFFFFFF ) + 1,
227  'height' => ( $height[1] & 0xFFFFFF ) + 1
228  ];
229  }
230 
231  public function getImageSize( $file, $path, $metadata = false ) {
232  if ( $file === null ) {
233  $metadata = self::getMetadata( $file, $path );
234  }
235  if ( $metadata === false && $file instanceof File ) {
236  $metadata = $file->getMetadata();
237  }
238 
239  Wikimedia\suppressWarnings();
240  $metadata = unserialize( $metadata );
241  Wikimedia\restoreWarnings();
242 
243  if ( $metadata == false ) {
244  return false;
245  }
246  return [ $metadata['width'], $metadata['height'] ];
247  }
248 
253  public function mustRender( $file ) {
254  return true;
255  }
256 
261  public function canRender( $file ) {
262  if ( self::isAnimatedImage( $file ) ) {
263  return false;
264  }
265  return true;
266  }
267 
272  public function isAnimatedImage( $image ) {
273  $ser = $image->getMetadata();
274  if ( $ser ) {
275  $metadata = unserialize( $ser );
276  if ( isset( $metadata['animated'] ) && $metadata['animated'] === true ) {
277  return true;
278  }
279  }
280 
281  return false;
282  }
283 
284  public function canAnimateThumbnail( $file ) {
285  return false;
286  }
287 
296  public function getThumbType( $ext, $mime, $params = null ) {
297  return [ 'png', 'image/png' ];
298  }
299 
307  protected function getScalerType( $dstPath, $checkDstPath = true ) {
308  return 'im';
309  }
310 }
if(PHP_SAPI !='cli-server') if(!isset( $_SERVER['SCRIPT_FILENAME'])) $file
Item class for a filearchive table row.
Definition: router.php:42
getThumbType( $ext, $mime, $params=null)
Render files as PNG.
serialize()
static extractMetadata( $filename)
Extracts the image size and WebP type from a file.
Definition: WebPHandler.php:94
isAnimatedImage( $image)
canRender( $file)
getScalerType( $dstPath, $checkDstPath=true)
Must use "im" for XCF.
Handler for Google&#39;s WebP format https://developers.google.com/speed/webp/
Definition: WebPHandler.php:29
mustRender( $file)
const VP8X_XMP
Definition: WebPHandler.php:43
static decodeLosslessChunkHeader( $header)
Decodes a lossless chunk header.
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
getMetadata( $image, $filename)
Definition: WebPHandler.php:46
static decodeLossyChunkHeader( $header)
Decodes a lossy chunk header.
unserialize( $serialized)
$header
getImageSize( $file, $path, $metadata=false)
canAnimateThumbnail( $file)
wfDebugLog( $logGroup, $text, $dest='all', array $context=[])
Send a line to a supplementary debug log file, if configured, or main debug log if not...
const VP8X_ALPHA
Definition: WebPHandler.php:41
static findChunksFromFile( $filename, $maxChunks=-1)
static extractMetadataFromChunks( $chunks, $filename)
Extracts the image size and WebP type from a file based on the chunk list.
isMetadataValid( $image, $metadata)
Definition: WebPHandler.php:60
if(!is_readable( $file)) $ext
Definition: router.php:48
getMetadataType( $image)
Definition: WebPHandler.php:56
Generic handler for bitmap images.
const VP8X_ANIM
Definition: WebPHandler.php:44
const BROKEN_FILE
Definition: WebPHandler.php:30
Implements some public methods and some protected utility functions which are required by multiple ch...
Definition: File.php:61
const VP8X_ICC
Definition: WebPHandler.php:40
const VP8X_EXIF
Definition: WebPHandler.php:42
static decodeExtendedChunkHeader( $header)
Decodes an extended chunk header.