Current File : /home/jvzmxxx/wiki1/extensions/TimedMediaHandler/TimedMediaThumbnail.php
<?php
class TimedMediaThumbnail {

	/**
	 * @param $options array()
	 * @return bool|MediaTransformError
	 */
	static function get( $options ) {
		if ( !is_dir( dirname( $options['dstPath'] ) ) ) {
			wfMkdirParents( dirname( $options['dstPath'] ), null, __METHOD__ );
		}

		wfDebug( "Creating video thumbnail at " .  $options['dstPath']  . "\n" );
		if (
			isset( $options['width'] ) && isset( $options['height'] ) &&
			$options['width'] != $options['file']->getWidth() &&
			$options['height'] != $options['file']->getHeight()
		) {
			return self::resizeThumb( $options );
		}
		// try OggThumb, and fallback to ffmpeg
		$result = self::tryOggThumb( $options );
		if ( $result === false ) {
			return self::tryFfmpegThumb( $options );
		}
		return $result;
	}

	/**
	 * Run oggThumb to generate a still image from a video file, using a frame
	 * close to the given number of seconds from the start.
	 *
	 * @param $options array
	 * @return bool|MediaTransformError
	 *
	 */
	static function tryOggThumb( $options ) {
		global $wgOggThumbLocation;

		// Check that the file is 'ogg' format
		if ( $options['file']->getHandler()->getMetadataType( $options['file'] ) != 'ogg' ) {
			return false;
		}

		// Check for $wgOggThumbLocation
		if ( !$wgOggThumbLocation || !is_file( $wgOggThumbLocation ) ) {
			return false;
		}

		$time = self::getThumbTime( $options );
		$dstPath = $options['dstPath'];
		$videoPath = $options['file']->getLocalRefPath();

		$cmd = wfEscapeShellArg( $wgOggThumbLocation )
			. ' -t ' . floatval( $time );
		// Set the output size if set in options:
		if ( isset( $options['width'] ) && isset( $options['height'] ) ) {
			$cmd .= ' -s ' . intval( $options['width'] ) . 'x' . intval( $options['height'] );
		}
		$cmd .= ' -n ' . wfEscapeShellArg( $dstPath ) .
			' ' . wfEscapeShellArg( $videoPath ) . ' 2>&1';
		$retval = 0;
		$returnText = wfShellExec( $cmd, $retval );

		if ( $options['file']->getHandler()->removeBadFile( $dstPath, $retval ) || $retval ) {
			// oggThumb spams both stderr and stdout with useless progress
			// messages, and then often forgets to output anything when
			// something actually does go wrong. So interpreting its output is
			// a challenge.
			$lines = explode( "\n", str_replace( "\r\n", "\n", $returnText ) );
			if ( count( $lines ) > 0
				&& preg_match( '/invalid option -- \'n\'$/', $lines[0] ) )
			{
				$returnText = wfMessage( 'timedmedia-oggThumb-version', '0.9' )->inContentLanguage()->text();
			} else {
				$returnText = wfMessage( 'timedmedia-oggThumb-failed' )->inContentLanguage()->text();
			}
			return new MediaTransformError( 'thumbnail_error',
				$options['width'], $options['height'], $returnText );
		}
		return true;
	}

	/**
	 * @param $options array
	 * @return bool|MediaTransformError
	 */
	static function tryFfmpegThumb( $options ) {
		global $wgFFmpegLocation, $wgMaxShellMemory;

		if ( !$wgFFmpegLocation || !is_file( $wgFFmpegLocation ) ) {
			return false;
		}

		$cmd = wfEscapeShellArg( $wgFFmpegLocation ) . ' -threads 1 ';

		$offset = intval( self::getThumbTime( $options ) );
		/*
		This is a workaround until ffmpegs ogg demuxer properly seeks to keyframes.
		Seek N seconds before offset and seek in decoded stream after that.
		 -ss before input seeks without decode
		 -ss after input seeks in decoded stream

		 N depends on framerate of input, keyframe interval defaults
		 to 64 for most encoders, seeking a bit before that
		 */

		$framerate = $options['file']->getHandler()->getFramerate( $options['file'] );
		if ( $framerate > 0 ) {
			$seekoffset = 1 + intval( 64 / $framerate );
		} else {
			$seekoffset = 3;
		}

		if ( $offset > $seekoffset ) {
			$cmd .= ' -ss ' . floatval( $offset - $seekoffset );
			$offset = $seekoffset;
		}

		// try to get temorary local url to file
		$backend = $options['file']->getRepo()->getBackend();
		// getFileHttpUrl was only added in mw 1.21, dont fail if it does not exist
		if ( method_exists( $backend, 'getFileHttpUrl' ) ) {
			$src = $backend->getFileHttpUrl( [
				'src' =>  $options['file']->getPath()
			] );
		} else {
			$src = null;
		}
		if ( $src == null ) {
			$src = $options['file']->getLocalRefPath();
		}

		$cmd .= ' -y -i ' . wfEscapeShellArg( $src );
		$cmd .= ' -ss ' . $offset . ' ';

		// Set the output size if set in options:
		if ( isset( $options['width'] ) && isset( $options['height'] ) ) {
			$cmd .= ' -s '. intval( $options['width'] ) . 'x' . intval( $options['height'] );
		}

		// MJPEG, that's the same as JPEG except it's supported by the windows build of ffmpeg
		// No audio, one frame
		$cmd .= ' -f mjpeg -an -vframes 1 ' .
			wfEscapeShellArg( $options['dstPath'] ) . ' 2>&1';

		$retval = 0;
		$returnText = wfShellExec( $cmd, $retval );
		// Check if it was successful
		if ( !$options['file']->getHandler()->removeBadFile( $options['dstPath'], $retval ) ) {
			return true;
		}
		$returnText = $cmd . "\nwgMaxShellMemory: $wgMaxShellMemory\n" . $returnText;
		// Return error box
		return new MediaTransformError(
			'thumbnail_error', $options['width'], $options['height'], $returnText
		);
	}

	/**
	 * @param $options array
	 * @return bool|MediaTransformError
	 */
	static function resizeThumb( $options ) {
		$file = $options['file'];
		$params = [];
		foreach ( [ 'start', 'thumbtime' ] as $key ) {
			if ( isset( $options[ $key ] ) ) {
				$params[ $key ] = $options[ $key ];
			}
		}
		$params["width"] = $file->getWidth();
		$params["height"] = $file->getHeight();

		$poolKey = $file->getRepo()->getSharedCacheKey( 'file', md5( $file->getName() ) );
		$posOptions = array_flip( [ 'start', 'thumbtime' ] );
		$poolKey = wfAppendQuery( $poolKey, array_intersect_key( $options, $posOptions ) );

		if ( class_exists( 'PoolCounterWorkViaCallback' ) ) {
			$work = new PoolCounterWorkViaCallback( 'TMHTransformFrame',
				'_tmh:frame:' . $poolKey,
				[ 'doWork' => function() use ( $file, $params ) {
					return $file->transform( $params, File::RENDER_NOW );
				} ] );
			$thumb = $work->execute();
		} else {
			$thumb = $file->transform( $params, File::RENDER_NOW );
		}

		if ( !$thumb || $thumb->isError() ) {
			return $thumb;
		}
		$src = $thumb->getStoragePath();
		if ( !$src ) {
			return false;
		}
		$thumbFile = new UnregisteredLocalFile( $file->getTitle(),
			RepoGroup::singleton()->getLocalRepo(), $src, false );
		$thumbParams = [
			"width" => $options['width'],
			"height" => $options['height']
		];
		$handler = $thumbFile->getHandler();
		if ( !$handler ) {
			return false;
		}
		$scaledThumb = $handler->doTransform(
			$thumbFile,
			$options['dstPath'],
			$options['dstPath'],
			$thumbParams
		);

		if ( !$scaledThumb || $scaledThumb->isError() ) {
			return $scaledThumb;
		}
		return true;
	}

	/**
	 * @param $options array
	 * @return bool|float|int
	 */
	static function getThumbTime( $options ) {
		$length = $options['file']->getLength();

		// If start time param isset use that for the thumb:
		if ( isset( $options['start'] ) ) {
			$thumbtime = TimedMediaHandler::parseTimeString( $options['start'], $length );
			if ( $thumbtime !== false ) {
				return $thumbtime;
			}
		}
		// else use thumbtime
		if ( isset( $options['thumbtime'] ) ) {
			$thumbtime = TimedMediaHandler::parseTimeString( $options['thumbtime'], $length );
			if ( $thumbtime !== false ) {
				return $thumbtime;
			}
		}
		// Seek to midpoint by default, it tends to be more interesting than the start
		return $length / 2;
	}
}