MediaWiki fundraising/REL1_35
WebPHandler.php
Go to the documentation of this file.
1<?php
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.
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 }
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
189 public static function decodeLosslessChunkHeader( $header ) {
190 // Bytes 0-3 are 'VP8L'
191 // Bytes 4-7 are chunk stream size
192 // Byte 8 is 0x2F called the signature
193 if ( $header[8] != "\x2F" ) {
194 wfDebugLog( 'WebP', __METHOD__ . ': Invalid signature: ' .
195 bin2hex( $header[8] ) );
196 return [];
197 }
198 // Bytes 9-12 contain the image size
199 // Bits 0-13 are width-1; bits 15-27 are height-1
200 $imageSize = unpack( 'C4', substr( $header, 9, 4 ) );
201 return [
202 'compression' => 'lossless',
203 'width' => ( $imageSize[1] | ( ( $imageSize[2] & 0x3F ) << 8 ) ) + 1,
204 'height' => ( ( ( $imageSize[2] & 0xC0 ) >> 6 ) |
205 ( $imageSize[3] << 2 ) | ( ( $imageSize[4] & 0x03 ) << 10 ) ) + 1
206 ];
207 }
208
214 public static function decodeExtendedChunkHeader( $header ) {
215 // Bytes 0-3 are 'VP8X'
216 // Byte 4-7 are chunk length
217 // Byte 8-11 are a flag bytes
218 $flags = unpack( 'c', substr( $header, 8, 1 ) );
219
220 // Byte 12-17 are image size (24 bits)
221 $width = unpack( 'V', substr( $header, 12, 3 ) . "\x00" );
222 $height = unpack( 'V', substr( $header, 15, 3 ) . "\x00" );
223
224 return [
225 'compression' => 'unknown',
226 'animated' => ( $flags[1] & self::VP8X_ANIM ) == self::VP8X_ANIM,
227 'transparency' => ( $flags[1] & self::VP8X_ALPHA ) == self::VP8X_ALPHA,
228 'width' => ( $width[1] & 0xFFFFFF ) + 1,
229 'height' => ( $height[1] & 0xFFFFFF ) + 1
230 ];
231 }
232
233 public function getImageSize( $file, $path, $metadata = false ) {
234 if ( $file === null ) {
235 $metadata = self::getMetadata( $file, $path );
236 }
237 if ( $metadata === false && $file instanceof File ) {
238 $metadata = $file->getMetadata();
239 }
240
241 Wikimedia\suppressWarnings();
242 $metadata = unserialize( $metadata );
243 Wikimedia\restoreWarnings();
244
245 if ( $metadata == false ) {
246 return false;
247 }
248 return [ $metadata['width'], $metadata['height'] ];
249 }
250
255 public function mustRender( $file ) {
256 return true;
257 }
258
263 public function canRender( $file ) {
264 if ( self::isAnimatedImage( $file ) ) {
265 return false;
266 }
267 return true;
268 }
269
274 public function isAnimatedImage( $image ) {
275 $ser = $image->getMetadata();
276 if ( $ser ) {
277 $metadata = unserialize( $ser );
278 if ( isset( $metadata['animated'] ) && $metadata['animated'] === true ) {
279 return true;
280 }
281 }
282
283 return false;
284 }
285
286 public function canAnimateThumbnail( $file ) {
287 return false;
288 }
289
298 public function getThumbType( $ext, $mime, $params = null ) {
299 return [ 'png', 'image/png' ];
300 }
301
309 protected function getScalerType( $dstPath, $checkDstPath = true ) {
310 return 'im';
311 }
312}
serialize()
unserialize( $serialized)
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
wfDebugLog( $logGroup, $text, $dest='all', array $context=[])
Send a line to a supplementary debug log file, if configured, or main debug log if not.
Generic handler for bitmap images.
Implements some public methods and some protected utility functions which are required by multiple ch...
Definition File.php:63
const METADATA_COMPATIBLE
const METADATA_GOOD
static findChunksFromFile( $filename, $maxChunks=-1)
Handler for Google's WebP format https://developers.google.com/speed/webp/
mustRender( $file)
const VP8X_ALPHA
isMetadataValid( $image, $metadata)
Check if the metadata string is valid for this handler.
canAnimateThumbnail( $file)
If the material is animated, we can animate the thumbnail.
const _MW_WEBP_VERSION
Version of the metadata stored in db records.
static extractMetadataFromChunks( $chunks, $filename)
Extracts the image size and WebP type from a file based on the chunk list.
isAnimatedImage( $image)
getMetadata( $image, $filename)
Get handler-specific metadata which will be saved in the img_metadata field.
getScalerType( $dstPath, $checkDstPath=true)
Must use "im" for XCF.
static decodeLossyChunkHeader( $header)
Decodes a lossy chunk header.
getImageSize( $file, $path, $metadata=false)
const BROKEN_FILE
Value to store in img_metadata if there was an error extracting metadata.
const VP8X_EXIF
const MINIMUM_CHUNK_HEADER_LENGTH
Minimum chunk header size to be able to read all header types.
canRender( $file)
const VP8X_ANIM
getThumbType( $ext, $mime, $params=null)
Render files as PNG.
static decodeLosslessChunkHeader( $header)
Decodes a lossless chunk header.
getMetadataType( $image)
Get a string describing the type of metadata, for display purposes.
static extractMetadata( $filename)
Extracts the image size and WebP type from a file.
static decodeExtendedChunkHeader( $header)
Decodes an extended chunk header.
$mime
Definition router.php:60
if(PHP_SAPI !='cli-server') if(!isset( $_SERVER['SCRIPT_FILENAME'])) $file
Item class for a filearchive table row.
Definition router.php:42
if(!is_readable( $file)) $ext
Definition router.php:48
$header