16use Wikimedia\XMPReader\Reader as XMPReader;
27 private const BROKEN_FILE =
'0';
31 private const MINIMUM_CHUNK_HEADER_LENGTH = 18;
35 private const MAX_METADATA_CHUNK_SIZE = 1024 * 1024 * 2;
39 private const _MW_WEBP_VERSION = 2;
41 private const VP8X_ICC = 32;
42 private const VP8X_ALPHA = 16;
43 private const VP8X_EXIF = 8;
44 private const VP8X_XMP = 4;
45 private const VP8X_ANIM = 2;
50 if ( !$parsedWebPData ) {
51 return [
'metadata' => [
'_error' => self::BROKEN_FILE ] ];
54 $parsedWebPData[
'metadata'][
'_MW_WEBP_VERSION'] = self::_MW_WEBP_VERSION;
56 'width' => $parsedWebPData[
'width'],
57 'height' => $parsedWebPData[
'height'],
58 'metadata' => $parsedWebPData
70 $data = $image->getMetadataArray();
71 if ( $data === [
'_error' => self::BROKEN_FILE ] ) {
76 if ( !$data || !isset( $data[
'_error'] ) ) {
77 wfDebug( __METHOD__ .
" invalid WebP metadata" );
82 if ( !isset( $data[
'metadata'][
'_MW_WEBP_VERSION'] )
83 || $data[
'metadata'][
'_MW_WEBP_VERSION'] != self::_MW_WEBP_VERSION
85 wfDebug( __METHOD__ .
" old but compatible WebP metadata" );
101 wfDebugLog(
'WebP', __METHOD__ .
": Extracting metadata from $filename" );
104 if ( $info ===
false ) {
105 wfDebugLog(
'WebP', __METHOD__ .
": Not a valid RIFF file" );
109 if ( $info[
'fourCC'] !==
'WEBP' ) {
110 wfDebugLog(
'WebP', __METHOD__ .
': FourCC was not WEBP: ' .
111 bin2hex( $info[
'fourCC'] ) );
116 wfDebugLog(
'WebP', __METHOD__ .
": No VP8 chunks found" );
135 foreach ( $chunks as $chunk ) {
137 if ( !in_array( $chunk[
'fourCC'], [
'VP8 ',
'VP8L',
'VP8X',
'EXIF',
'XMP ',
"XMP\0" ] ) ) {
142 $chunkHeader = file_get_contents( $filename,
false,
null,
143 $chunk[
'start'], self::MINIMUM_CHUNK_HEADER_LENGTH );
144 wfDebugLog(
'WebP', __METHOD__ .
": {$chunk['fourCC']}" );
146 switch ( $chunk[
'fourCC'] ) {
148 $vp8Info = array_merge( $vp8Info,
149 self::decodeLossyChunkHeader( $chunkHeader ) );
152 $vp8Info = array_merge( $vp8Info,
153 self::decodeLosslessChunkHeader( $chunkHeader ) );
156 $vp8Info = array_merge( $vp8Info,
157 self::decodeExtendedChunkHeader( $chunkHeader ) );
162 $exifData ??= self::extractChunk( $chunk, $filename );
166 $xmpData ??= self::extractChunk( $chunk, $filename );
170 $vp8Info = array_merge( $vp8Info,
171 self::decodeMediaMetadata( $exifData, $xmpData, $filename ) );
183 private static function decodeMediaMetadata( $exifData, $xmpData, $filename ) {
184 if ( $exifData ===
null && $xmpData ===
null ) {
188 $bitmapMetadataHandler =
new BitmapMetadataHandler;
190 if ( $xmpData && XMPReader::isSupported() ) {
191 $xmpReader =
new XMPReader( LoggerFactory::getInstance(
'XMP' ), $filename );
192 $xmpReader->parse( $xmpData );
193 $res = $xmpReader->getResults();
194 foreach ( $res as $type => $array ) {
195 $bitmapMetadataHandler->addMetadata( $array, $type );
203 if ( str_starts_with( $exifData,
"Exif\x00\x00" ) ) {
204 $exifData = substr( $exifData, 6 );
207 getTempFSFileFactory()->
208 newTempFSFile(
'webp-exif_',
'tiff' );
210 $exifDataFile = $tmpFile->getPath();
211 file_put_contents( $exifDataFile, $exifData );
213 $bitmapMetadataHandler->getExif( $exifDataFile, $byteOrder );
215 return [
'media-metadata' => $bitmapMetadataHandler->getMetadataArray() ];
223 private static function extractChunk( $chunk, $filename ) {
224 if ( $chunk[
'size'] > self::MAX_METADATA_CHUNK_SIZE || $chunk[
'size'] < 1 ) {
229 return file_get_contents( $filename,
false,
null, $chunk[
'start'] + 8, $chunk[
'size'] );
242 $syncCode = substr( $header, 11, 3 );
243 if ( $syncCode !==
"\x9D\x01\x2A" ) {
244 wfDebugLog(
'WebP', __METHOD__ .
': Invalid sync code: ' .
245 bin2hex( $syncCode ) );
249 $imageSize = unpack(
'v2', substr( $header, 14, 4 ) );
252 'compression' =>
'lossy',
253 'width' => $imageSize[1] & 0x3FFF,
254 'height' => $imageSize[2] & 0x3FFF
267 if ( $header[8] !==
"\x2F" ) {
268 wfDebugLog(
'WebP', __METHOD__ .
': Invalid signature: ' .
269 bin2hex( $header[8] ) );
274 $imageSize = unpack(
'C4', substr( $header, 9, 4 ) );
276 'compression' =>
'lossless',
277 'width' => ( $imageSize[1] | ( ( $imageSize[2] & 0x3F ) << 8 ) ) + 1,
278 'height' => ( ( ( $imageSize[2] & 0xC0 ) >> 6 ) |
279 ( $imageSize[3] << 2 ) | ( ( $imageSize[4] & 0x0F ) << 10 ) ) + 1
292 $flags = unpack(
'c', substr( $header, 8, 1 ) );
295 $width = unpack(
'V', substr( $header, 12, 3 ) .
"\x00" );
296 $height = unpack(
'V', substr( $header, 15, 3 ) .
"\x00" );
299 'compression' =>
'unknown',
300 'animated' => ( $flags[1] & self::VP8X_ANIM ) === self::VP8X_ANIM,
301 'transparency' => ( $flags[1] & self::VP8X_ALPHA ) === self::VP8X_ALPHA,
302 'width' => ( $width[1] & 0xFFFFFF ) + 1,
303 'height' => ( $height[1] & 0xFFFFFF ) + 1
331 $metadata = $image->getMetadataArray();
332 if ( isset( $metadata[
'animated'] ) && $metadata[
'animated'] ===
true ) {
353 return [
'png',
'image/png' ];
358 return function_exists(
'gd_info' ) && ( gd_info()[
'WebP Support'] ?? false );
364 return $meta[
'media-metadata'] ?? [];
379class_alias( WebPHandler::class,
'WebPHandler' );
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.