26use Wikimedia\XMPReader\Reader as XMPReader;
37 private const BROKEN_FILE =
'0';
41 private const MINIMUM_CHUNK_HEADER_LENGTH = 18;
45 private const MAX_METADATA_CHUNK_SIZE = 1024 * 1024 * 2;
49 private const _MW_WEBP_VERSION = 2;
51 private const VP8X_ICC = 32;
52 private const VP8X_ALPHA = 16;
53 private const VP8X_EXIF = 8;
54 private const VP8X_XMP = 4;
55 private const VP8X_ANIM = 2;
59 if ( !$parsedWebPData ) {
60 return [
'metadata' => [
'_error' => self::BROKEN_FILE ] ];
63 $parsedWebPData[
'metadata'][
'_MW_WEBP_VERSION'] = self::_MW_WEBP_VERSION;
65 'width' => $parsedWebPData[
'width'],
66 'height' => $parsedWebPData[
'height'],
67 'metadata' => $parsedWebPData
77 $data = $image->getMetadataArray();
78 if ( $data === [
'_error' => self::BROKEN_FILE ] ) {
83 if ( !$data || !isset( $data[
'_error'] ) ) {
84 wfDebug( __METHOD__ .
" invalid WebP metadata" );
89 if ( !isset( $data[
'metadata'][
'_MW_WEBP_VERSION'] )
90 || $data[
'metadata'][
'_MW_WEBP_VERSION'] != self::_MW_WEBP_VERSION
92 wfDebug( __METHOD__ .
" old but compatible WebP metadata" );
108 wfDebugLog(
'WebP', __METHOD__ .
": Extracting metadata from $filename" );
111 if ( $info ===
false ) {
112 wfDebugLog(
'WebP', __METHOD__ .
": Not a valid RIFF file" );
116 if ( $info[
'fourCC'] !==
'WEBP' ) {
117 wfDebugLog(
'WebP', __METHOD__ .
': FourCC was not WEBP: ' .
118 bin2hex( $info[
'fourCC'] ) );
123 wfDebugLog(
'WebP', __METHOD__ .
": No VP8 chunks found" );
142 foreach ( $chunks as $chunk ) {
144 if ( !in_array( $chunk[
'fourCC'], [
'VP8 ',
'VP8L',
'VP8X',
'EXIF',
'XMP ',
"XMP\0" ] ) ) {
149 $chunkHeader = file_get_contents( $filename,
false,
null,
150 $chunk[
'start'], self::MINIMUM_CHUNK_HEADER_LENGTH );
151 wfDebugLog(
'WebP', __METHOD__ .
": {$chunk['fourCC']}" );
153 switch ( $chunk[
'fourCC'] ) {
155 $vp8Info = array_merge( $vp8Info,
156 self::decodeLossyChunkHeader( $chunkHeader ) );
159 $vp8Info = array_merge( $vp8Info,
160 self::decodeLosslessChunkHeader( $chunkHeader ) );
163 $vp8Info = array_merge( $vp8Info,
164 self::decodeExtendedChunkHeader( $chunkHeader ) );
169 $exifData ??= self::extractChunk( $chunk, $filename );
173 $xmpData ??= self::extractChunk( $chunk, $filename );
177 $vp8Info = array_merge( $vp8Info,
178 self::decodeMediaMetadata( $exifData, $xmpData, $filename ) );
190 private static function decodeMediaMetadata( $exifData, $xmpData, $filename ) {
191 if ( $exifData ===
null && $xmpData ===
null ) {
197 if ( $xmpData && XMPReader::isSupported() ) {
198 $xmpReader =
new XMPReader( LoggerFactory::getInstance(
'XMP' ), $filename );
199 $xmpReader->parse( $xmpData );
200 $res = $xmpReader->getResults();
201 foreach ( $res as $type => $array ) {
202 $bitmapMetadataHandler->addMetadata( $array, $type );
210 if ( substr( $exifData, 0, 6 ) ===
"Exif\x00\x00" ) {
211 $exifData = substr( $exifData, 6 );
213 $tmpFile = MediaWikiServices::getInstance()->
214 getTempFSFileFactory()->
215 newTempFSFile(
'webp-exif_',
'tiff' );
217 $exifDataFile = $tmpFile->getPath();
218 file_put_contents( $exifDataFile, $exifData );
220 $bitmapMetadataHandler->getExif( $exifDataFile, $byteOrder );
222 return [
'media-metadata' => $bitmapMetadataHandler->getMetadataArray() ];
230 private static function extractChunk( $chunk, $filename ) {
231 if ( $chunk[
'size'] > self::MAX_METADATA_CHUNK_SIZE || $chunk[
'size'] < 1 ) {
236 return file_get_contents( $filename,
false,
null, $chunk[
'start'] + 8, $chunk[
'size'] );
249 $syncCode = substr(
$header, 11, 3 );
250 if ( $syncCode !==
"\x9D\x01\x2A" ) {
251 wfDebugLog(
'WebP', __METHOD__ .
': Invalid sync code: ' .
252 bin2hex( $syncCode ) );
256 $imageSize = unpack(
'v2', substr(
$header, 14, 4 ) );
259 'compression' =>
'lossy',
260 'width' => $imageSize[1] & 0x3FFF,
261 'height' => $imageSize[2] & 0x3FFF
275 wfDebugLog(
'WebP', __METHOD__ .
': Invalid signature: ' .
281 $imageSize = unpack(
'C4', substr(
$header, 9, 4 ) );
283 'compression' =>
'lossless',
284 'width' => ( $imageSize[1] | ( ( $imageSize[2] & 0x3F ) << 8 ) ) + 1,
285 'height' => ( ( ( $imageSize[2] & 0xC0 ) >> 6 ) |
286 ( $imageSize[3] << 2 ) | ( ( $imageSize[4] & 0x03 ) << 10 ) ) + 1
299 $flags = unpack(
'c', substr(
$header, 8, 1 ) );
302 $width = unpack(
'V', substr(
$header, 12, 3 ) .
"\x00" );
303 $height = unpack(
'V', substr(
$header, 15, 3 ) .
"\x00" );
306 'compression' =>
'unknown',
307 'animated' => ( $flags[1] & self::VP8X_ANIM ) === self::VP8X_ANIM,
308 'transparency' => ( $flags[1] & self::VP8X_ALPHA ) === self::VP8X_ALPHA,
309 'width' => ( $width[1] & 0xFFFFFF ) + 1,
310 'height' => ( $height[1] & 0xFFFFFF ) + 1
338 $metadata = $image->getMetadataArray();
339 if ( isset( $metadata[
'animated'] ) && $metadata[
'animated'] ===
true ) {
359 return [
'png',
'image/png' ];
363 return function_exists(
'gd_info' ) && ( gd_info()[
'WebP Support'] ?? false );
368 return $meta[
'media-metadata'] ?? [];
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.
array $params
The job parameters.
Generic handler for bitmap images.
Implements some public methods and some protected utility functions which are required by multiple ch...
getMetadataArray()
Get the unserialized handler-specific metadata STUB.
Handler for Google's WebP format https://developers.google.com/speed/webp/
getCommonMetaArray(File $image)
Get an array of standard (FormatMetadata type) metadata values.
canAnimateThumbnail( $file)
If the material is animated, we can animate the thumbnail.
isFileMetadataValid( $image)
Check if the metadata is valid for this handler.
static extractMetadataFromChunks( $chunks, $filename)
Extracts the image size and WebP type from a file based on the chunk list.
static decodeLossyChunkHeader( $header)
Decodes a lossy chunk header.
getSizeAndMetadata( $state, $filename)
Get image size information and metadata array.
formatMetadata( $image, $context=false)
Get an array structure that looks like this:
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.
hasGDSupport()
Whether the php-gd extension supports this type of file.
static extractMetadata( $filename)
Extracts the image size and WebP type from a file.
static decodeExtendedChunkHeader( $header)
Decodes an extended chunk header.