Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
59.38% covered (warning)
59.38%
38 / 64
20.00% covered (danger)
20.00%
2 / 10
CRAP
0.00% covered (danger)
0.00%
0 / 1
GIFHandler
59.38% covered (warning)
59.38%
38 / 64
20.00% covered (danger)
20.00%
2 / 10
100.66
0.00% covered (danger)
0.00%
0 / 1
 getSizeAndMetadata
93.33% covered (success)
93.33%
14 / 15
0.00% covered (danger)
0.00%
0 / 1
3.00
 formatMetadata
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 getCommonMetaArray
80.00% covered (warning)
80.00%
4 / 5
0.00% covered (danger)
0.00%
0 / 1
2.03
 getImageArea
75.00% covered (warning)
75.00%
3 / 4
0.00% covered (danger)
0.00%
0 / 1
3.14
 isAnimatedImage
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
3
 canAnimateThumbnail
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 getMetadataType
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 isFileMetadataValid
81.82% covered (warning)
81.82%
9 / 11
0.00% covered (danger)
0.00%
0 / 1
6.22
 getLongDesc
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
56
 getLength
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
4
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 MediaWiki\Context\IContextSource;
25use MediaWiki\MainConfigNames;
26use MediaWiki\MediaWikiServices;
27use Wikimedia\RequestTimeout\TimeoutException;
28
29/**
30 * Handler for GIF images.
31 *
32 * @ingroup Media
33 */
34class GIFHandler extends BitmapHandler {
35    /**
36     * Value to store in img_metadata if there was error extracting metadata
37     */
38    private const BROKEN_FILE = '0';
39
40    public function getSizeAndMetadata( $state, $filename ) {
41        try {
42            $parsedGIFMetadata = BitmapMetadataHandler::GIF( $filename );
43        } catch ( TimeoutException $e ) {
44            throw $e;
45        } catch ( Exception $e ) {
46            // Broken file?
47            wfDebug( __METHOD__ . ': ' . $e->getMessage() );
48
49            return [ 'metadata' => [ '_error' => self::BROKEN_FILE ] ];
50        }
51
52        return [
53            'width' => $parsedGIFMetadata['width'],
54            'height' => $parsedGIFMetadata['height'],
55            'bits' => $parsedGIFMetadata['bits'],
56            'metadata' => array_diff_key(
57                $parsedGIFMetadata,
58                [ 'width' => true, 'height' => true, 'bits' => true ]
59            )
60        ];
61    }
62
63    /**
64     * @param File $image
65     * @param IContextSource|false $context
66     * @return array[]|false
67     */
68    public function formatMetadata( $image, $context = false ) {
69        $meta = $this->getCommonMetaArray( $image );
70        if ( !$meta ) {
71            return false;
72        }
73
74        return $this->formatMetadataHelper( $meta, $context );
75    }
76
77    /**
78     * Return the standard metadata elements for #filemetadata parser func.
79     * @param File $image
80     * @return array
81     */
82    public function getCommonMetaArray( File $image ) {
83        $meta = $image->getMetadataArray();
84        if ( !isset( $meta['metadata'] ) ) {
85            return [];
86        }
87        unset( $meta['metadata']['_MW_GIF_VERSION'] );
88
89        return $meta['metadata'];
90    }
91
92    /**
93     * @todo Add unit tests
94     *
95     * @param File $image
96     * @return int
97     */
98    public function getImageArea( $image ) {
99        $metadata = $image->getMetadataArray();
100        if ( isset( $metadata['frameCount'] ) && $metadata['frameCount'] > 0 ) {
101            return $image->getWidth() * $image->getHeight() * $metadata['frameCount'];
102        }
103        return $image->getWidth() * $image->getHeight();
104    }
105
106    /**
107     * @param File $image
108     * @return bool
109     */
110    public function isAnimatedImage( $image ) {
111        $metadata = $image->getMetadataArray();
112        if ( isset( $metadata['frameCount'] ) && $metadata['frameCount'] > 1 ) {
113            return true;
114        }
115
116        return false;
117    }
118
119    /**
120     * We cannot animate thumbnails that are bigger than a particular size
121     * @param File $file
122     * @return bool
123     */
124    public function canAnimateThumbnail( $file ) {
125        $maxAnimatedGifArea = MediaWikiServices::getInstance()->getMainConfig()
126            ->get( MainConfigNames::MaxAnimatedGifArea );
127
128        return $this->getImageArea( $file ) <= $maxAnimatedGifArea;
129    }
130
131    public function getMetadataType( $image ) {
132        return 'parsed-gif';
133    }
134
135    public function isFileMetadataValid( $image ) {
136        $data = $image->getMetadataArray();
137        if ( $data === [ '_error' => self::BROKEN_FILE ] ) {
138            // Do not repetitively regenerate metadata on broken file.
139            return self::METADATA_GOOD;
140        }
141
142        if ( !$data || isset( $data['_error'] ) ) {
143            wfDebug( __METHOD__ . " invalid GIF metadata" );
144
145            return self::METADATA_BAD;
146        }
147
148        if ( !isset( $data['metadata']['_MW_GIF_VERSION'] )
149            || $data['metadata']['_MW_GIF_VERSION'] !== GIFMetadataExtractor::VERSION
150        ) {
151            wfDebug( __METHOD__ . " old but compatible GIF metadata" );
152
153            return self::METADATA_COMPATIBLE;
154        }
155
156        return self::METADATA_GOOD;
157    }
158
159    /**
160     * @param File $image
161     * @return string
162     */
163    public function getLongDesc( $image ) {
164        global $wgLang;
165
166        $original = parent::getLongDesc( $image );
167
168        $metadata = $image->getMetadataArray();
169
170        if ( !$metadata || isset( $metadata['_error'] ) || $metadata['frameCount'] <= 0 ) {
171            return $original;
172        }
173
174        /* Preserve original image info string, but strip the last char ')' so we can add even more */
175        $info = [];
176        $info[] = $original;
177
178        if ( $metadata['looped'] ) {
179            $info[] = wfMessage( 'file-info-gif-looped' )->parse();
180        }
181
182        if ( $metadata['frameCount'] > 1 ) {
183            $info[] = wfMessage( 'file-info-gif-frames' )->numParams( $metadata['frameCount'] )->parse();
184        }
185
186        if ( $metadata['duration'] ) {
187            $info[] = $wgLang->formatTimePeriod( $metadata['duration'] );
188        }
189
190        return $wgLang->commaList( $info );
191    }
192
193    /**
194     * Return the duration of the GIF file.
195     *
196     * Shown in the &query=imageinfo&iiprop=size api query.
197     *
198     * @param File $file
199     * @return float The duration of the file.
200     */
201    public function getLength( $file ) {
202        $metadata = $file->getMetadataArray();
203
204        if ( !$metadata || !isset( $metadata['duration'] ) || !$metadata['duration'] ) {
205            return 0.0;
206        }
207        return (float)$metadata['duration'];
208    }
209}