1<?php 2/** 3 * Handler for GIF images. 4 * 5 * This program is free software; you can redistribute it and/or modify 6 * it under the terms of the GNU General Public License as published by 7 * the Free Software Foundation; either version 2 of the License, or 8 * (at your option) any later version. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License along 16 * with this program; if not, write to the Free Software Foundation, Inc., 17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 18 * http://www.gnu.org/copyleft/gpl.html 19 * 20 * @file 21 * @ingroup Media 22 */ 23 24use Wikimedia\RequestTimeout\TimeoutException; 25 26/** 27 * Handler for GIF images. 28 * 29 * @ingroup Media 30 */ 31class GIFHandler extends BitmapHandler { 32 /** 33 * Value to store in img_metadata if there was error extracting metadata 34 */ 35 private const BROKEN_FILE = '0'; 36 37 public function getSizeAndMetadata( $state, $filename ) { 38 try { 39 $parsedGIFMetadata = BitmapMetadataHandler::GIF( $filename ); 40 } catch ( TimeoutException $e ) { 41 throw $e; 42 } catch ( Exception $e ) { 43 // Broken file? 44 wfDebug( __METHOD__ . ': ' . $e->getMessage() ); 45 46 return [ 'metadata' => [ '_error' => self::BROKEN_FILE ] ]; 47 } 48 49 return [ 50 'width' => $parsedGIFMetadata['width'], 51 'height' => $parsedGIFMetadata['height'], 52 'bits' => $parsedGIFMetadata['bits'], 53 'metadata' => array_diff_key( 54 $parsedGIFMetadata, 55 [ 'width' => true, 'height' => true, 'bits' => true ] 56 ) 57 ]; 58 } 59 60 /** 61 * @param File $image 62 * @param IContextSource|false $context 63 * @return array[]|false 64 */ 65 public function formatMetadata( $image, $context = false ) { 66 $meta = $this->getCommonMetaArray( $image ); 67 if ( !$meta ) { 68 return false; 69 } 70 71 return $this->formatMetadataHelper( $meta, $context ); 72 } 73 74 /** 75 * Return the standard metadata elements for #filemetadata parser func. 76 * @param File $image 77 * @return array|bool 78 */ 79 public function getCommonMetaArray( File $image ) { 80 $meta = $image->getMetadataArray(); 81 if ( !isset( $meta['metadata'] ) ) { 82 return []; 83 } 84 unset( $meta['metadata']['_MW_GIF_VERSION'] ); 85 86 return $meta['metadata']; 87 } 88 89 /** 90 * @todo Add unit tests 91 * 92 * @param File $image 93 * @return bool 94 */ 95 public function getImageArea( $image ) { 96 $metadata = $image->getMetadataArray(); 97 if ( isset( $metadata['frameCount'] ) && $metadata['frameCount'] > 0 ) { 98 return $image->getWidth() * $image->getHeight() * $metadata['frameCount']; 99 } else { 100 return $image->getWidth() * $image->getHeight(); 101 } 102 } 103 104 /** 105 * @param File $image 106 * @return bool 107 */ 108 public function isAnimatedImage( $image ) { 109 $metadata = $image->getMetadataArray(); 110 if ( isset( $metadata['frameCount'] ) && $metadata['frameCount'] > 1 ) { 111 return true; 112 } 113 114 return false; 115 } 116 117 /** 118 * We cannot animate thumbnails that are bigger than a particular size 119 * @param File $file 120 * @return bool 121 */ 122 public function canAnimateThumbnail( $file ) { 123 global $wgMaxAnimatedGifArea; 124 125 return $this->getImageArea( $file ) <= $wgMaxAnimatedGifArea; 126 } 127 128 public function getMetadataType( $image ) { 129 return 'parsed-gif'; 130 } 131 132 public function isFileMetadataValid( $image ) { 133 $data = $image->getMetadataArray(); 134 if ( $data === [ '_error' => self::BROKEN_FILE ] ) { 135 // Do not repetitively regenerate metadata on broken file. 136 return self::METADATA_GOOD; 137 } 138 139 if ( !$data || isset( $data['_error'] ) ) { 140 wfDebug( __METHOD__ . " invalid GIF metadata" ); 141 142 return self::METADATA_BAD; 143 } 144 145 if ( !isset( $data['metadata']['_MW_GIF_VERSION'] ) 146 || $data['metadata']['_MW_GIF_VERSION'] != GIFMetadataExtractor::VERSION 147 ) { 148 wfDebug( __METHOD__ . " old but compatible GIF metadata" ); 149 150 return self::METADATA_COMPATIBLE; 151 } 152 153 return self::METADATA_GOOD; 154 } 155 156 /** 157 * @param File $image 158 * @return string 159 */ 160 public function getLongDesc( $image ) { 161 global $wgLang; 162 163 $original = parent::getLongDesc( $image ); 164 165 $metadata = $image->getMetadataArray(); 166 167 if ( !$metadata || isset( $metadata['_error'] ) || $metadata['frameCount'] <= 0 ) { 168 return $original; 169 } 170 171 /* Preserve original image info string, but strip the last char ')' so we can add even more */ 172 $info = []; 173 $info[] = $original; 174 175 if ( $metadata['looped'] ) { 176 $info[] = wfMessage( 'file-info-gif-looped' )->parse(); 177 } 178 179 if ( $metadata['frameCount'] > 1 ) { 180 $info[] = wfMessage( 'file-info-gif-frames' )->numParams( $metadata['frameCount'] )->parse(); 181 } 182 183 if ( $metadata['duration'] ) { 184 $info[] = $wgLang->formatTimePeriod( $metadata['duration'] ); 185 } 186 187 return $wgLang->commaList( $info ); 188 } 189 190 /** 191 * Return the duration of the GIF file. 192 * 193 * Shown in the &query=imageinfo&iiprop=size api query. 194 * 195 * @param File $file 196 * @return float The duration of the file. 197 */ 198 public function getLength( $file ) { 199 $metadata = $file->getMetadataArray(); 200 201 if ( !$metadata || !isset( $metadata['duration'] ) || !$metadata['duration'] ) { 202 return 0.0; 203 } else { 204 return (float)$metadata['duration']; 205 } 206 } 207} 208