<?php
/**
 * PNG frame counter.
 * Slightly derived from GIFMetadataExtractor.php
 * Deliberately not using MWExceptions to avoid external dependencies, encouraging
 * redistribution.
 *
 * @file
 * @ingroup Media
 */

/**
 * PNG frame counter.
 *
 * @ingroup Media
 */
class PNGMetadataExtractor {
	static $png_sig;
	static $CRC_size;

	static function getMetadata( $filename ) {
		self::$png_sig = pack( "C8", 137, 80, 78, 71, 13, 10, 26, 10 );
		self::$CRC_size = 4;
		
		$frameCount = 0;
		$loopCount = 1;
		$duration = 0.0;
		$bitDepth = 0;
		$colorType = 'unknown';

		if (!$filename)
			throw new Exception( __METHOD__ . ": No file name specified" );
		elseif ( !file_exists($filename) || is_dir($filename) )
			throw new Exception( __METHOD__ . ": File $filename does not exist" );
		
		$fh = fopen( $filename, 'r' );
		
		if (!$fh) {
			throw new Exception( __METHOD__ . ": Unable to open file $filename" );
		}
		
		// Check for the PNG header
		$buf = fread( $fh, 8 );
		if ( $buf != self::$png_sig ) {
			throw new Exception( __METHOD__ . ": Not a valid PNG file; header: $buf" );
		}

		// Read chunks
		while( !feof( $fh ) ) {
			$buf = fread( $fh, 4 );
			if( !$buf ) {
				throw new Exception( __METHOD__ . ": Read error" );
			}
			$chunk_size = unpack( "N", $buf);
			$chunk_size = $chunk_size[1];

			$chunk_type = fread( $fh, 4 );
			if( !$chunk_type ) {
				throw new Exception( __METHOD__ . ": Read error" );
			}

			if ( $chunk_type == "IHDR" ) {
				$buf = fread( $fh, $chunk_size );
				if( !$buf ) {
					throw new Exception( __METHOD__ . ": Read error" );
				}
				$bitDepth = ord( substr( $buf, 8, 1 ) );
				// Detect the color type in British English as per the spec
				// http://www.w3.org/TR/PNG/#11IHDR
				switch ( ord( substr( $buf, 9, 1 ) ) ) {
					case 0:
						$colorType = 'greyscale';
						break;
					case 2: 
						$colorType = 'truecolour';
						break;
					case 3:
						$colorType = 'index-coloured';
						break;
					case 4:
						$colorType = 'greyscale-alpha';
						break;
					case 6:
						$colorType = 'truecolour-alpha';
						break;
					default:
						$colorType = 'unknown';
						break;
				}
			} elseif ( $chunk_type == "acTL" ) {
				$buf = fread( $fh, $chunk_size );
				if( !$buf ) {
					throw new Exception( __METHOD__ . ": Read error" );
				}

				$actl = unpack( "Nframes/Nplays", $buf );
				$frameCount = $actl['frames'];
				$loopCount = $actl['plays'];
			} elseif ( $chunk_type == "fcTL" ) {
				$buf = fread( $fh, $chunk_size );
				if( !$buf ) {
					throw new Exception( __METHOD__ . ": Read error" );
				}
				$buf = substr( $buf, 20 );	

				$fctldur = unpack( "ndelay_num/ndelay_den", $buf );
				if( $fctldur['delay_den'] == 0 ) $fctldur['delay_den'] = 100;
				if( $fctldur['delay_num'] ) {
					$duration += $fctldur['delay_num'] / $fctldur['delay_den'];
				}
			} elseif ( ( $chunk_type == "IDAT" || $chunk_type == "IEND" ) && $frameCount == 0 ) {
				// Not a valid animated image. No point in continuing.
				break;
			} elseif ( $chunk_type == "IEND" ) {
				break;
			} else {
				fseek( $fh, $chunk_size, SEEK_CUR );
			}
			fseek( $fh, self::$CRC_size, SEEK_CUR );
		}
		fclose( $fh );

		if( $loopCount > 1 ) {
			$duration *= $loopCount;
		}

		return array(
			'frameCount' => $frameCount,
			'loopCount' => $loopCount,
			'duration' => $duration,
			'bitDepth' => $bitDepth,
			'colorType' => $colorType,
		);
		
	}
}
