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