function getMetadata($image, $filename) { if (!isset($image->parsedGIFMetadata)) { try { $image->parsedGIFMetadata = GIFMetadataExtractor::getMetadata($filename); } catch (Exception $e) { // Broken file? wfDebug(__METHOD__ . ': ' . $e->getMessage() . "\n"); return '0'; } } return serialize($image->parsedGIFMetadata); }
/** * Put in a file, and see if the metadata coming out is as expected. * @param $filename String * @param $expected Array The extracted metadata. * @dataProvider provideGetMetadata */ public function testGetMetadata($filename, $expected) { $actual = GIFMetadataExtractor::getMetadata($this->mediaPath . $filename); $this->assertEquals($expected, $actual); }
static function getMetadata($filename) { self::$gif_frame_sep = pack("C", ord(",")); self::$gif_extension_sep = pack("C", ord("!")); self::$gif_term = pack("C", ord(";")); $frameCount = 0; $duration = 0.0; $isLooped = false; if (!$filename) { throw new Exception("No file name specified"); } elseif (!file_exists($filename) || is_dir($filename)) { throw new Exception("File {$filename} does not exist"); } $fh = fopen($filename, 'r'); if (!$fh) { throw new Exception("Unable to open file {$filename}"); } // Check for the GIF header $buf = fread($fh, 6); if (!($buf == 'GIF87a' || $buf == 'GIF89a')) { throw new Exception("Not a valid GIF file; header: {$buf}"); } // Skip over width and height. fread($fh, 4); // Read BPP $buf = fread($fh, 1); $bpp = self::decodeBPP($buf); // Skip over background and aspect ratio fread($fh, 2); // Skip over the GCT self::readGCT($fh, $bpp); while (!feof($fh)) { $buf = fread($fh, 1); if ($buf == self::$gif_frame_sep) { // Found a frame $frameCount++; ## Skip bounding box fread($fh, 8); ## Read BPP $buf = fread($fh, 1); $bpp = self::decodeBPP($buf); ## Read GCT self::readGCT($fh, $bpp); fread($fh, 1); self::skipBlock($fh); } elseif ($buf == self::$gif_extension_sep) { $buf = fread($fh, 1); $extension_code = unpack('C', $buf); $extension_code = $extension_code[1]; if ($extension_code == 0xf9) { // Graphics Control Extension. fread($fh, 1); // Block size fread($fh, 1); // Transparency, disposal method, user input $buf = fread($fh, 2); // Delay, in hundredths of seconds. $delay = unpack('v', $buf); $delay = $delay[1]; $duration += $delay * 0.01; fread($fh, 1); // Transparent colour index $term = fread($fh, 1); // Should be a terminator $term = unpack('C', $term); $term = $term[1]; if ($term != 0) { throw new Exception("Malformed Graphics Control Extension block"); } } elseif ($extension_code == 0xff) { // Application extension (Netscape info about the animated gif) $blockLength = fread($fh, 1); $blockLength = unpack('C', $blockLength); $blockLength = $blockLength[1]; $data = fread($fh, $blockLength); // NETSCAPE2.0 (application name) if ($blockLength != 11 || $data != 'NETSCAPE2.0') { fseek($fh, -($blockLength + 1), SEEK_CUR); self::skipBlock($fh); continue; } $data = fread($fh, 2); // Block length and introduction, should be 03 01 if ($data != "") { throw new Exception("Expected , got {$data}"); } // Unsigned little-endian integer, loop count or zero for "forever" $loopData = fread($fh, 2); $loopData = unpack('v', $loopData); $loopCount = $loopData[1]; if ($loopCount != 1) { $isLooped = true; } // Read out terminator byte fread($fh, 1); } else { self::skipBlock($fh); } } elseif ($buf == self::$gif_term) { break; } else { $byte = unpack('C', $buf); $byte = $byte[1]; throw new Exception("At position: " . ftell($fh) . ", Unknown byte " . $byte); } } return array('frameCount' => $frameCount, 'looped' => $isLooped, 'duration' => $duration); }
/** function for gif images. * * They don't really have native metadata, so just merges together * XMP and image comment. * * @param string $filename full path to file * @return Array metadata array */ public static function GIF($filename) { $meta = new self(); $baseArray = GIFMetadataExtractor::getMetadata($filename); if (count($baseArray['comment']) > 0) { $meta->addMetadata(array('GIFFileComment' => $baseArray['comment']), 'native'); } if ($baseArray['xmp'] !== '' && function_exists('xml_parser_create_ns')) { $xmp = new XMPReader(); $xmp->parse($baseArray['xmp']); $xmpRes = $xmp->getResults(); foreach ($xmpRes as $type => $xmpSection) { $meta->addMetadata($xmpSection, $type); } } unset($baseArray['comment']); unset($baseArray['xmp']); $baseArray['metadata'] = $meta->getMetadataArray(); $baseArray['metadata']['_MW_GIF_VERSION'] = GIFMetadataExtractor::VERSION; return $baseArray; }
/** * @throws Exception * @param $filename string * @return array */ static function getMetadata($filename) { self::$gif_frame_sep = pack("C", ord(",")); self::$gif_extension_sep = pack("C", ord("!")); self::$gif_term = pack("C", ord(";")); $frameCount = 0; $duration = 0.0; $isLooped = false; $xmp = ""; $comment = array(); if (!$filename) { throw new Exception("No file name specified"); } elseif (!file_exists($filename) || is_dir($filename)) { throw new Exception("File {$filename} does not exist"); } $fh = fopen($filename, 'rb'); if (!$fh) { throw new Exception("Unable to open file {$filename}"); } // Check for the GIF header $buf = fread($fh, 6); if (!($buf == 'GIF87a' || $buf == 'GIF89a')) { throw new Exception("Not a valid GIF file; header: {$buf}"); } // Skip over width and height. fread($fh, 4); // Read BPP $buf = fread($fh, 1); $bpp = self::decodeBPP($buf); // Skip over background and aspect ratio fread($fh, 2); // Skip over the GCT self::readGCT($fh, $bpp); while (!feof($fh)) { $buf = fread($fh, 1); if ($buf == self::$gif_frame_sep) { // Found a frame $frameCount++; ## Skip bounding box fread($fh, 8); ## Read BPP $buf = fread($fh, 1); $bpp = self::decodeBPP($buf); ## Read GCT self::readGCT($fh, $bpp); fread($fh, 1); self::skipBlock($fh); } elseif ($buf == self::$gif_extension_sep) { $buf = fread($fh, 1); if (strlen($buf) < 1) { throw new Exception("Ran out of input"); } $extension_code = unpack('C', $buf); $extension_code = $extension_code[1]; if ($extension_code == 0xf9) { // Graphics Control Extension. fread($fh, 1); // Block size fread($fh, 1); // Transparency, disposal method, user input $buf = fread($fh, 2); // Delay, in hundredths of seconds. if (strlen($buf) < 2) { throw new Exception("Ran out of input"); } $delay = unpack('v', $buf); $delay = $delay[1]; $duration += $delay * 0.01; fread($fh, 1); // Transparent colour index $term = fread($fh, 1); // Should be a terminator if (strlen($term) < 1) { throw new Exception("Ran out of input"); } $term = unpack('C', $term); $term = $term[1]; if ($term != 0) { throw new Exception("Malformed Graphics Control Extension block"); } } elseif ($extension_code == 0xfe) { // Comment block(s). $data = self::readBlock($fh); if ($data === "") { throw new Exception('Read error, zero-length comment block'); } // The standard says this should be ASCII, however its unclear if // thats true in practise. Check to see if its valid utf-8, if so // assume its that, otherwise assume its windows-1252 (iso-8859-1) $dataCopy = $data; // quickIsNFCVerify has the side effect of replacing any invalid characters UtfNormal::quickIsNFCVerify($dataCopy); if ($dataCopy !== $data) { wfSuppressWarnings(); $data = iconv('windows-1252', 'UTF-8', $data); wfRestoreWarnings(); } $commentCount = count($comment); if ($commentCount === 0 || $comment[$commentCount - 1] !== $data) { // Some applications repeat the same comment on each // frame of an animated GIF image, so if this comment // is identical to the last, only extract once. $comment[] = $data; } } elseif ($extension_code == 0xff) { // Application extension (Netscape info about the animated gif) // or XMP (or theoretically any other type of extension block) $blockLength = fread($fh, 1); if (strlen($blockLength) < 1) { throw new Exception("Ran out of input"); } $blockLength = unpack('C', $blockLength); $blockLength = $blockLength[1]; $data = fread($fh, $blockLength); if ($blockLength != 11) { wfDebug(__METHOD__ . ' GIF application block with wrong length'); fseek($fh, -($blockLength + 1), SEEK_CUR); self::skipBlock($fh); continue; } // NETSCAPE2.0 (application name for animated gif) if ($data == 'NETSCAPE2.0') { $data = fread($fh, 2); // Block length and introduction, should be 03 01 if ($data != "") { throw new Exception("Expected , got {$data}"); } // Unsigned little-endian integer, loop count or zero for "forever" $loopData = fread($fh, 2); if (strlen($loopData) < 2) { throw new Exception("Ran out of input"); } $loopData = unpack('v', $loopData); $loopCount = $loopData[1]; if ($loopCount != 1) { $isLooped = true; } // Read out terminator byte fread($fh, 1); } elseif ($data == 'XMP DataXMP') { // application name for XMP data. // see pg 18 of XMP spec part 3. $xmp = self::readBlock($fh, true); if (substr($xmp, -257, 3) !== "ÿþ" || substr($xmp, -4) !== "") { // this is just a sanity check. throw new Exception("XMP does not have magic trailer!"); } // strip out trailer. $xmp = substr($xmp, 0, -257); } else { // unrecognized extension block fseek($fh, -($blockLength + 1), SEEK_CUR); self::skipBlock($fh); continue; } } else { self::skipBlock($fh); } } elseif ($buf == self::$gif_term) { break; } else { if (strlen($buf) < 1) { throw new Exception("Ran out of input"); } $byte = unpack('C', $buf); $byte = $byte[1]; throw new Exception("At position: " . ftell($fh) . ", Unknown byte " . $byte); } } return array('frameCount' => $frameCount, 'looped' => $isLooped, 'duration' => $duration, 'xmp' => $xmp, 'comment' => $comment); }
/** function for gif images. * * They don't really have native metadata, so just merges together * XMP and image comment. * * @param string $filename Full path to file * @return array Metadata array */ public static function GIF($filename) { $meta = new self(); $baseArray = GIFMetadataExtractor::getMetadata($filename); if (count($baseArray['comment']) > 0) { $meta->addMetadata(['GIFFileComment' => $baseArray['comment']], 'native'); } if ($baseArray['xmp'] !== '' && XMPReader::isSupported()) { $xmp = new XMPReader(LoggerFactory::getInstance('XMP')); $xmp->parse($baseArray['xmp']); $xmpRes = $xmp->getResults(); foreach ($xmpRes as $type => $xmpSection) { $meta->addMetadata($xmpSection, $type); } } unset($baseArray['comment']); unset($baseArray['xmp']); $baseArray['metadata'] = $meta->getMetadataArray(); $baseArray['metadata']['_MW_GIF_VERSION'] = GIFMetadataExtractor::VERSION; return $baseArray; }