From a5f917bbc55e295896b8084f6657eb8b6abaf8a8 Mon Sep 17 00:00:00 2001 From: André Fabian Silva Delgado Date: Fri, 15 Jul 2016 15:33:36 -0300 Subject: Add TimedMediaHandler extension that allows display audio and video files in wiki pages, using the same syntax as for image files --- .../TimedMediaHandler/TimedMediaHandler_body.php | 483 +++++++++++++++++++++ 1 file changed, 483 insertions(+) create mode 100644 extensions/TimedMediaHandler/TimedMediaHandler_body.php (limited to 'extensions/TimedMediaHandler/TimedMediaHandler_body.php') diff --git a/extensions/TimedMediaHandler/TimedMediaHandler_body.php b/extensions/TimedMediaHandler/TimedMediaHandler_body.php new file mode 100644 index 00000000..cb689858 --- /dev/null +++ b/extensions/TimedMediaHandler/TimedMediaHandler_body.php @@ -0,0 +1,483 @@ + 'width', + 'timedmedia_thumbtime' => 'thumbtime', + 'timedmedia_starttime' => 'start', + 'timedmedia_endtime' => 'end', + 'timedmedia_disablecontrols' => 'disablecontrols', + ); + } + + /** + * Validate a embed file parameters + * + * @param $name {String} Name of the param + * @param $value {Mixed} Value to validated + * @return bool + */ + function validateParam( $name, $value ) { + if ( $name == 'thumbtime' || $name == 'start' || $name == 'end' ) { + if ( $this->parseTimeString( $value ) === false ) { + return false; + } + } elseif ( $name == 'disablecontrols' ) { + $values = explode( ',', $value); + foreach($values as $v) { + if ( !in_array( $v, array( 'options', 'timedText', 'fullscreen' ) ) ) { + return false; + } + } + } elseif( $name === 'width' || $name === 'height' ) { + return $value > 0; + } + return true; + } + + /** + * TODO we should really have "$file" available here to validate the param string + * @param $params array + * @return string + */ + function makeParamString( $params ) { + // Add the width param string ( same as images {width}px ) + $paramString =''; + $paramString.= ( isset( $params['width'] ) )? $params['width'] . 'px' : ''; + $paramString.= ( $paramString != '' )? '-' : ''; + + // Get the raw thumbTime from thumbtime or start param + if ( isset ( $params['thumbtime'] ) ) { + $thumbTime = $params['thumbtime']; + } elseif ( isset ( $params['start'] ) ) { + $thumbTime = $params['start']; + } else { + $thumbTime = false; + } + + if ( $thumbTime !== false ) { + $time = $this->parseTimeString( $thumbTime ); + if ( $time !== false ) { + return $paramString. 'seek=' . $time; + } + } + + if ( !$paramString ) { + $paramString = 'mid'; + } + return $paramString ; + } + + /** + * Used by thumb.php to find url parameters + * + * @param $str string + * @return array|bool Array of thumbnail parameters, or false if string cannot be parsed + */ + function parseParamString( $str ) { + $params = array(); + if ( preg_match( '/^(mid|(\d*)px-)*(seek=([\d.]+))*$/', $str, $matches ) ) { + $size = $thumbtime = null; + if ( isset( $matches[2] ) ) { + $size = $matches[2]; + } + if ( isset( $matches[4] ) ) { + $thumbtime = $matches[4]; + } + + if ( !is_null( $size ) && $size !== '' ) { + $params['width'] = (int) $size; + } + if ( !is_null( $thumbtime ) ) { + $params['thumbtime'] = (float) $thumbtime; + } + return $params; // valid thumbnail URL + } else { + // invalid parameter string + return false; + } + } + + /** + * @param $image File + * @param $params array + * @return bool + */ + function normaliseParams( $image, &$params ) { + $timeParam = array( 'thumbtime', 'start', 'end' ); + // Parse time values if endtime or thumbtime can't be more than length -1 + foreach($timeParam as $pn){ + if ( isset( $params[$pn] ) && $params[$pn] !== false ) { + $length = $this->getLength( $image ); + $time = $this->parseTimeString( $params[$pn] ); + if ( $time === false ) { + return false; + } elseif ( $time > $length - 1 ) { + $params[$pn] = $length - 1; + } elseif ( $time <= 0 ) { + $params[$pn] = 0; + } + } + } + + if ( $this->isAudio( $image ) ) { + // Assume a default for audio files + $size = array( + 'width' => 220, + 'height' => 23, + ); + } else { + $size = array( + 'width' => $image->getWidth(), + 'height' => $image->getHeight(), + ); + } + // Make sure we don't try and up-scale the asset: + if( isset( $params['width'] ) && (int)$params['width'] > $size['width'] ){ + $params['width'] = $size['width']; + } + + if ( isset( $params['height'] ) && $params['height'] != -1 ) { + if( $params['width'] * $size['height'] > $params['height'] * $size['width'] ) { + $params['width'] = self::fitBoxWidth( $size['width'], $size['height'], $params['height'] ); + } + } + if ( isset( $params['width'] ) ) { + $params['height'] = File::scaleHeight( $size['width'], $size['height'], $params['width'] ); + } + + // Make sure start time is not > than end time + if( isset($params['start']) + && isset($params['end'] ) + && $params['start'] !== false + && $params['end'] !== false + ) { + if ( $this->parseTimeString( $params['start'] ) > $this->parseTimeString( $params['end'] ) ) { + return false; + } + } + + return true; + } + + /** + * @param $parser Parser + * @param $file File + */ + function parserTransformHook( $parser, $file ) { + $parserOutput = $parser->getOutput(); + if ( isset( $parserOutput->hasTimedMediaTransform ) ) { + return ; + } + $parserOutput->hasTimedMediaTransform = true; + $parserOutput->addOutputHook( 'TimedMediaHandler' ); + } + + /** + * Parser output hook only adds the PopUpMediaTransform + * + * The core embedPlayer module is part of a "loaderScript" so it does not need to + * be registered here. + * + * TODO move core loader to on-page script as to not include it on all pages. + * + * @param $outputPage OutputPage + * @param $parserOutput + * @param $data + */ + static function outputHook( $outputPage, $parserOutput, $data ) { + // Add the PopUpMediaTransform code + $outputPage->addModuleScripts( 'mw.PopUpMediaTransform' ); + $outputPage->addModuleStyles( 'mw.PopUpMediaTransform.styles' ); + $outputPage->addModules( 'mw.TMHGalleryHook.js' ); + if ( $parserOutput ) { + // Not present when run from outputpage hooks, like File/Category etc... + $parserOutput->setExtensionData( 'mw_ext_TMH_hasTimedMediaTransform', true ); + } + } + + /** + * Utility functions + * @param $timeString + * @param $length + * @return bool|int + */ + public static function parseTimeString( $timeString, $length = false ) { + $parts = explode( ':', $timeString ); + $time = 0; + // Check for extra :s + if( count( $parts ) > 3 ){ + return false; + } + for ( $i = 0; $i < count( $parts ); $i++ ) { + if ( !is_numeric( $parts[$i] ) ) { + return false; + } + $time += floatval( $parts[$i] ) * pow( 60, count( $parts ) - $i - 1 ); + } + + if ( $time < 0 ) { + wfDebug( __METHOD__.": specified negative time, using zero\n" ); + $time = 0; + } elseif ( $length !== false && $time > $length - 1 ) { + wfDebug( __METHOD__.": specified near-end or past-the-end time {$time}s, using end minus 1s\n" ); + $time = $length - 1; + } + return $time; + } + + /** + * @static + * @param $timePassed + * @return string + */ + public static function getTimePassedMsg( $timePassed ){ + $t = array(); + $t['days'] = floor($timePassed/60/60/24); + $t['hours'] = floor($timePassed/60/60)%24; + $t['minutes'] = floor($timePassed/60)%60; + $t['seconds'] = $timePassed%60; + + foreach( $t as $k => $v ){ + if($v == 0 ){ + unset( $t[$k] ); + } else { + // Give grep a chance to find the usages: + // timedmedia-days, timedmedia-hours, timedmedia-minutes,timedmedia-seconds + $t[$k] = wfMessage( 'timedmedia-' . $k, $v )->text(); + } + } + if( count( $t ) == 0 ){ + $t = array( wfMessage( 'timedmedia-seconds', 0 )->text() ) ; + } + + global $wgLang; + return $wgLang->commaList( $t ); + } + + /** + * Converts seconds to Normal play time (NPT) time format: + * consist of hh:mm:ss.ms + * also see: http://www.ietf.org/rfc/rfc2326.txt section 3.6 + * + * @param $time Number Seconds to be converted to npt time format + * @return bool|string + */ + public static function seconds2npt( $time ){ + if ( !is_numeric( $time ) ) { + wfDebug( __METHOD__.": trying to get npt time on NaN:" + $time); + return false; + } + if( $time < 0 ){ + wfDebug( __METHOD__.": trying to time on negative value:" + $time); + return false; + } + $hours = floor( $time / 3600 ); + $min = floor( ( $time / 60 ) % 60 ); + $sec = ($time % 60 ); + $ms = ( $time - round( $time, 3) != 0 ) ? '.' .( $time - round( $time, 3) ) : ''; + + return "{$hours}:{$min}:{$sec}{$ms}"; + } + + /** + * @param $metadata + * @return bool|mixed + */ + function unpackMetadata( $metadata ) { + wfSuppressWarnings(); + $unser = unserialize( $metadata ); + wfRestoreWarnings(); + if ( isset( $unser['version'] ) ) { + return $unser; + } else { + return false; + } + } + + /** + * @param $image + * @param $metadata + * @return bool + */ + function isMetadataValid( $image, $metadata ) { + return $this->unpackMetadata( $metadata ) !== false; + } + + /** + * @param $ext + * @param $mime + * @param null $params + * @return array + */ + function getThumbType( $ext, $mime, $params = null ) { + return array( 'jpg', 'image/jpeg' ); + } + + /** + * checks if a given file is an audio file + * @param $file File + * @return bool + */ + function isAudio( $file ){ + return ( $file->getWidth() == 0 && $file->getHeight() == 0 ); + } + + /** + * @param $file File + * @param $dstPath String + * @param $dstUrl String + * @param $params array + * @param $flags int + * @return bool|MediaTransformError|MediaTransformOutput|TimedMediaTransformOutput + */ + function doTransform( $file, $dstPath, $dstUrl, $params, $flags = 0 ) { + # Important or height handling is wrong. + if ( !$this->normaliseParams( $file, $params ) ) { + return new TransformParameterError( $params ); + } + + $srcWidth = $file->getWidth(); + $srcHeight = $file->getHeight(); + + // Audio should not be transformed by size, give it a default width and height + if( $this->isAudio( $file ) ){ + $srcWidth = 220; + $srcHeight = 23; + } + + $params['width'] = isset( $params['width'] ) ? $params['width'] : $srcWidth; + + // if height overtakes width use height as max: + $targetWidth = $params['width']; + $targetHeight = $srcWidth == 0 ? $srcHeight : round( $params['width'] * $srcHeight / $srcWidth ); + if( isset( $params['height'] ) && $targetHeight > $params['height'] ){ + $targetHeight = $params['height']; + $targetWidth = round( $params['height'] * $srcWidth / $srcHeight ); + } + $options = array( + 'file' => $file, + 'length' => $this->getLength( $file ), + 'offset' => $this->getOffset( $file ), + 'width' => $targetWidth, + 'height' => $targetHeight, + 'isVideo' => !$this->isAudio( $file ), + 'thumbtime' => isset( $params['thumbtime'] ) ? $params['thumbtime'] : intval( $file->getLength() / 2 ), + 'start' => isset( $params['start'] ) ? $params['start'] : false, + 'end' => isset( $params['end'] ) ? $params['end'] : false, + 'fillwindow' => isset( $params['fillwindow'] ) ? $params['fillwindow'] : false, + 'disablecontrols' => isset ( $params['disablecontrols'] ) ? $params['disablecontrols'] : false + ); + + // No thumbs for audio + if( !$options['isVideo'] ){ + return new TimedMediaTransformOutput( $options ); + } + + // Setup pointer to thumb arguments + $options[ 'thumbUrl' ] = $dstUrl; + $options[ 'dstPath' ] = $dstPath; + $options[ 'path' ] = $dstPath; + + // Check if transform is deferred: + if ( $flags & self::TRANSFORM_LATER ) { + return new TimedMediaTransformOutput($options); + } + + // Generate thumb: + $thumbStatus = TimedMediaThumbnail::get( $options ); + if( $thumbStatus !== true ){ + return $thumbStatus; + } + + return new TimedMediaTransformOutput( $options ); + } + + /** + * @param $file + * @return bool + */ + function canRender( $file ) { return true; } + + /** + * @param $file + * @return bool + */ + function mustRender( $file ) { return true; } + + /** + * Get a stream offset time + * @param $file + * @return int + */ + function getOffset( $file ){ + return 0; + } + + /** + * Get length of a file + * @param $file + * @return int + */ + function getLength( $file ){ + return $file->getLength(); + } + + /** + * @param $file File + * @return String + */ + function getDimensionsString( $file ) { + global $wgLang; + + if ( $file->getWidth() ) { + return wfMessage( 'video-dims', $wgLang->formatTimePeriod( $this->getLength( $file ) ) ) + ->numParams( $file->getWidth(), $file->getHeight() )->text(); + } else { + return $wgLang->formatTimePeriod( $this->getLength( $file ) ); + } + } + + public function filterThumbnailPurgeList( &$files, $options ) { + global $wgEnabledTranscodeSet, $wgEnabledAudioTranscodeSet; + + $transcodeSet = array_merge($wgEnabledTranscodeSet, $wgEnabledAudioTranscodeSet); + + //dont remove derivatives on normal purge + foreach(array_slice($files, 1) as $key => $file) { + foreach( $transcodeSet as $transcodeKey ) { + if ( preg_match('/' . preg_quote($transcodeKey) . '$/', $file) ) { + unset($files[$key]); + break; + } + } + } + } +} -- cgit v1.2.3-54-g00ecf