'Ogg 200';
*/
// Ogg Profiles
const ENC_OGV_160P = '160p.ogv';
const ENC_OGV_240P = '240p.ogv';
const ENC_OGV_360P = '360p.ogv';
const ENC_OGV_480P = '480p.ogv';
const ENC_OGV_720P = '720p.ogv';
const ENC_OGV_1080P = '1080p.ogv';
// WebM VP8/Vorbis profiles:
const ENC_WEBM_160P = '160p.webm';
const ENC_WEBM_360P = '360p.webm';
const ENC_WEBM_480P = '480p.webm';
const ENC_WEBM_720P = '720p.webm';
const ENC_WEBM_1080P = '1080p.webm';
const ENC_WEBM_2160P = '2160p.webm';
// WebM VP9/Opus profiles:
const ENC_VP9_360P = '360p.vp9.webm';
const ENC_VP9_480P = '480p.vp9.webm';
const ENC_VP9_720P = '720p.vp9.webm';
const ENC_VP9_1080P = '1080p.vp9.webm';
const ENC_VP9_2160P = '2160p.vp9.webm';
// mp4 profiles:
const ENC_H264_320P = '320p.mp4';
const ENC_H264_480P = '480p.mp4';
const ENC_H264_720P = '720p.mp4';
const ENC_H264_1080P = '1080p.mp4';
const ENC_H264_2160P = '2160p.mp4';
const ENC_OGG_VORBIS = 'ogg';
const ENC_OGG_OPUS = 'opus';
const ENC_MP3 = 'mp3';
const ENC_AAC = 'm4a';
// Static cache of transcode state per instantiation
public static $transcodeState = array() ;
/**
* Encoding parameters are set via firefogg encode api
*
* For clarity and compatibility with passing down
* client side encode settings at point of upload
*
* http://firefogg.org/dev/index.html
*/
public static $derivativeSettings = array(
WebVideoTranscode::ENC_OGV_160P =>
array(
'maxSize' => '288x160',
'videoBitrate' => '160',
'framerate' => '15',
'audioQuality' => '-1',
'samplerate' => '44100',
'channels' => '2',
'noUpscaling' => 'true',
//'twopass' => 'true', // temporarily disabled for broken ffmpeg2theora
'optimize' => 'true',
'keyframeInterval' => '128',
'bufDelay' => '256',
'videoCodec' => 'theora',
'type' => 'video/ogg; codecs="theora, vorbis"',
),
WebVideoTranscode::ENC_OGV_240P =>
array(
'maxSize' => '426x240',
'videoBitrate' => '512',
'audioQuality' => '0',
'samplerate' => '44100',
'channels' => '2',
'noUpscaling' => 'true',
//'twopass' => 'true', // temporarily disabled for broken ffmpeg2theora
'optimize' => 'true',
'keyframeInterval' => '128',
'bufDelay' => '256',
'videoCodec' => 'theora',
'type' => 'video/ogg; codecs="theora, vorbis"',
),
WebVideoTranscode::ENC_OGV_360P =>
array(
'maxSize' => '640x360',
'videoBitrate' => '1024',
'audioQuality' => '1',
'samplerate' => '44100',
'channels' => '2',
'noUpscaling' => 'true',
//'twopass' => 'true', // temporarily disabled for broken ffmpeg2theora
'optimize' => 'true',
'keyframeInterval' => '128',
'bufDelay' => '256',
'videoCodec' => 'theora',
'type' => 'video/ogg; codecs="theora, vorbis"',
),
WebVideoTranscode::ENC_OGV_480P =>
array(
'maxSize' => '854x480',
'videoBitrate' => '2048',
'audioQuality' => '2',
'samplerate' => '44100',
'channels' => '2',
'noUpscaling' => 'true',
//'twopass' => 'true', // temporarily disabled for broken ffmpeg2theora
'optimize' => 'true',
'keyframeInterval' => '128',
'bufDelay' => '256',
'videoCodec' => 'theora',
'type' => 'video/ogg; codecs="theora, vorbis"',
),
WebVideoTranscode::ENC_OGV_720P =>
array(
'maxSize' => '1280x720',
'videoQuality' => 6,
'audioQuality' => 3,
'noUpscaling' => 'true',
//'twopass' => 'true', // temporarily disabled for broken ffmpeg2theora
'optimize' => 'true',
'keyframeInterval' => '128',
'videoCodec' => 'theora',
'type' => 'video/ogg; codecs="theora, vorbis"',
),
WebVideoTranscode::ENC_OGV_1080P =>
array(
'maxSize' => '1920x1080',
'videoQuality' => 6,
'audioQuality' => 3,
'noUpscaling' => 'true',
//'twopass' => 'true', // temporarily disabled for broken ffmpeg2theora
'optimize' => 'true',
'keyframeInterval' => '128',
'videoCodec' => 'theora',
'type' => 'video/ogg; codecs="theora, vorbis"',
),
// WebM transcode:
WebVideoTranscode::ENC_WEBM_160P =>
array(
'maxSize' => '288x160',
'videoBitrate' => '256',
'audioQuality' => '-1',
'samplerate' => '44100',
'channels' => '2',
'noUpscaling' => 'true',
'twopass' => 'true',
'keyframeInterval' => '128',
'bufDelay' => '256',
'videoCodec' => 'vp8',
'type' => 'video/webm; codecs="vp8, vorbis"',
),
WebVideoTranscode::ENC_WEBM_360P =>
array(
'maxSize' => '640x360',
'videoBitrate' => '512',
'audioQuality' => '1',
'samplerate' => '44100',
'noUpscaling' => 'true',
'twopass' => 'true',
'keyframeInterval' => '128',
'bufDelay' => '256',
'videoCodec' => 'vp8',
'type' => 'video/webm; codecs="vp8, vorbis"',
),
WebVideoTranscode::ENC_WEBM_480P =>
array(
'maxSize' => '854x480',
'videoBitrate' => '1024',
'audioQuality' => '2',
'samplerate' => '44100',
'noUpscaling' => 'true',
'twopass' => 'true',
'keyframeInterval' => '128',
'bufDelay' => '256',
'videoCodec' => 'vp8',
'type' => 'video/webm; codecs="vp8, vorbis"',
),
WebVideoTranscode::ENC_WEBM_720P =>
array(
'maxSize' => '1280x720',
'videoQuality' => 7,
'audioQuality' => 3,
'noUpscaling' => 'true',
'videoCodec' => 'vp8',
'type' => 'video/webm; codecs="vp8, vorbis"',
),
WebVideoTranscode::ENC_WEBM_1080P =>
array(
'maxSize' => '1920x1080',
'videoQuality' => 7,
'audioQuality' => 3,
'noUpscaling' => 'true',
'videoCodec' => 'vp8',
'type' => 'video/webm; codecs="vp8, vorbis"',
),
WebVideoTranscode::ENC_WEBM_2160P =>
array(
'maxSize' => '4096x2160',
'videoQuality' => 7,
'audioQuality' => 3,
'noUpscaling' => 'true',
'videoCodec' => 'vp8',
'type' => 'video/webm; codecs="vp8, vorbis"',
),
// WebM VP9 transcode:
WebVideoTranscode::ENC_VP9_360P =>
array(
'maxSize' => '640x360',
'videoBitrate' => '256',
'samplerate' => '48000',
'noUpscaling' => 'true',
'twopass' => 'true',
'keyframeInterval' => '128',
'bufDelay' => '256',
'videoCodec' => 'vp9',
'audioCodec' => 'opus',
'type' => 'video/webm; codecs="vp9, opus"',
),
WebVideoTranscode::ENC_VP9_480P =>
array(
'maxSize' => '854x480',
'videoBitrate' => '512',
'samplerate' => '48000',
'noUpscaling' => 'true',
'twopass' => 'true',
'keyframeInterval' => '128',
'bufDelay' => '256',
'videoCodec' => 'vp9',
'audioCodec' => 'opus',
'type' => 'video/webm; codecs="vp9, opus"',
),
WebVideoTranscode::ENC_VP9_720P =>
array(
'maxSize' => '1280x720',
'videoBitrate' => '1024',
'samplerate' => '48000',
'noUpscaling' => 'true',
'twopass' => 'true',
'keyframeInterval' => '128',
'bufDelay' => '256',
'videoCodec' => 'vp9',
'audioCodec' => 'opus',
'tileColumns' => '2',
'type' => 'video/webm; codecs="vp9, opus"',
),
WebVideoTranscode::ENC_VP9_1080P =>
array(
'maxSize' => '1920x1080',
'videoBitrate' => '2048',
'samplerate' => '48000',
'noUpscaling' => 'true',
'twopass' => 'true',
'keyframeInterval' => '128',
'bufDelay' => '256',
'videoCodec' => 'vp9',
'audioCodec' => 'opus',
'tileColumns' => '4',
'type' => 'video/webm; codecs="vp9, opus"',
),
WebVideoTranscode::ENC_VP9_2160P =>
array(
'maxSize' => '4096x2160',
'videoBitrate' => '8192',
'samplerate' => '48000',
'noUpscaling' => 'true',
'twopass' => 'true',
'keyframeInterval' => '128',
'bufDelay' => '256',
'videoCodec' => 'vp9',
'audioCodec' => 'opus',
'tileColumns' => '4',
'type' => 'video/webm; codecs="vp9, opus"',
),
// Losly defined per PCF guide to mp4 profiles:
// https://develop.participatoryculture.org/index.php/ConversionMatrix
// and apple HLS profile guide:
// https://developer.apple.com/library/ios/#documentation/networkinginternet/conceptual/streamingmediaguide/UsingHTTPLiveStreaming/UsingHTTPLiveStreaming.html#//apple_ref/doc/uid/TP40008332-CH102-DontLinkElementID_24
WebVideoTranscode::ENC_H264_320P =>
array(
'maxSize' => '480x320',
'videoCodec' => 'h264',
'preset' => 'ipod320',
'videoBitrate' => '400k',
'audioCodec' => 'aac',
'channels' => '2',
'audioBitrate' => '40k',
'type' => 'video/mp4; codecs="avc1.42E01E, mp4a.40.2"',
),
WebVideoTranscode::ENC_H264_480P =>
array(
'maxSize' => '640x480',
'videoCodec' => 'h264',
'preset' => 'ipod640',
'videoBitrate' => '1200k',
'audioCodec' => 'aac',
'channels' => '2',
'audioBitrate' => '64k',
'type' => 'video/mp4; codecs="avc1.42E01E, mp4a.40.2"',
),
WebVideoTranscode::ENC_H264_720P =>
array(
'maxSize' => '1280x720',
'videoCodec' => 'h264',
'preset' => '720p',
'videoBitrate' => '2500k',
'audioCodec' => 'aac',
'channels' => '2',
'audioBitrate' => '128k',
'type' => 'video/mp4; codecs="avc1.42E01E, mp4a.40.2"',
),
WebVideoTranscode::ENC_H264_1080P =>
array(
'maxSize' => '1920x1080',
'videoCodec' => 'h264',
'videoBitrate' => '5000k',
'audioCodec' => 'aac',
'channels' => '2',
'audioBitrate' => '128k',
'type' => 'video/mp4; codecs="avc1.42E01E, mp4a.40.2"',
),
WebVideoTranscode::ENC_H264_2160P =>
array(
'maxSize' => '4096x2160',
'videoCodec' => 'h264',
'videoBitrate' => '16384k',
'audioCodec' => 'aac',
'channels' => '2',
'audioBitrate' => '128k',
'type' => 'video/mp4; codecs="avc1.42E01E, mp4a.40.2"',
),
//Audio profiles
WebVideoTranscode::ENC_OGG_VORBIS =>
array(
'audioCodec' => 'vorbis',
'audioQuality' => '1',
'samplerate' => '44100',
'channels' => '2',
'noUpscaling' => 'true',
'novideo' => 'true',
'type' => 'audio/ogg; codecs="vorbis"',
),
WebVideoTranscode::ENC_OGG_OPUS =>
array(
'audioCodec' => 'opus',
'audioQuality' => '1',
'samplerate' => '44100',
'channels' => '2',
'noUpscaling' => 'true',
'novideo' => 'true',
'type' => 'audio/ogg; codecs="opus"',
),
WebVideoTranscode::ENC_MP3 =>
array(
'audioCodec' => 'mp3',
'audioQuality' => '1',
'samplerate' => '44100',
'channels' => '2',
'noUpscaling' => 'true',
'novideo' => 'true',
'type' => 'audio/mpeg',
),
WebVideoTranscode::ENC_AAC =>
array(
'audioCodec' => 'aac',
'audioQuality' => '1',
'samplerate' => '44100',
'channels' => '2',
'noUpscaling' => 'true',
'novideo' => 'true',
'type' => 'audio/mp4; codecs="mp4a.40.5"',
),
);
/**
* @param $file File
* @param $transcodeKey string
* @return string
*/
static public function getDerivativeFilePath( $file, $transcodeKey ) {
return $file->getTranscodedPath( self::getTranscodeFileBaseName( $file, $transcodeKey ) );
}
/**
* Get the name to use as the base name for the transcode.
*
* Swift has problems where the url-encoded version of
* the path (ie 'filename.ogv/filename.ogv.720p.webm' )
* is greater that > 1024 bytes, so shorten in that case.
*
* Future versions might respect FileRepo::$abbrvThreshold.
*
* @param File $file
* @param String $suffix Optional suffix (e.g. transcode key).
* @return String File name, or the string transcode.
*/
static public function getTranscodeFileBaseName( $file, $suffix = '' ) {
$name = $file->getName();
if ( strlen( urlencode( $name ) ) * 2 + 12 > 1024 ) {
return 'transcode' . '.' . $suffix;
} else {
return $name . '.' . $suffix;
}
}
/**
* Get url for a transcode.
*
* @param $file File
* @param $suffix string Transcode key
* @return string
*/
static public function getTranscodedUrlForFile( $file, $suffix = '' ) {
return $file->getTranscodedUrl( self::getTranscodeFileBaseName( $file, $suffix ) );
}
/**
* Get temp file at target path for video encode
*
* @param $file File
* @param $transcodeKey String
*
* @return TempFSFile at target encode path
*/
static public function getTargetEncodeFile( &$file, $transcodeKey ){
$filePath = self::getDerivativeFilePath( $file, $transcodeKey );
$ext = strtolower( pathinfo( "$filePath", PATHINFO_EXTENSION ) );
// Create a temp FS file with the same extension
$tmpFile = TempFSFile::factory( 'transcode_' . $transcodeKey, $ext);
if ( !$tmpFile ) {
return False;
}
return $tmpFile;
}
/**
* Get the max size of the web stream ( constant bitrate )
* @return int
*/
static public function getMaxSizeWebStream(){
global $wgEnabledTranscodeSet;
$maxSize = 0;
foreach( $wgEnabledTranscodeSet as $transcodeKey ){
if( isset( self::$derivativeSettings[$transcodeKey]['videoBitrate'] ) ){
$currentSize = self::$derivativeSettings[$transcodeKey]['maxSize'];
if( $currentSize > $maxSize ){
$maxSize = $currentSize;
}
}
}
return $maxSize;
}
/**
* Give a rough estimate on file size
* Note this is not always accurate.. especially with variable bitrate codecs ;)
* @param $file File
* @param $transcodeKey string
* @return number
*/
static public function getProjectedFileSize( $file, $transcodeKey ){
$settings = self::$derivativeSettings[$transcodeKey];
if( $settings[ 'videoBitrate' ] && $settings['audioBitrate'] ){
return $file->getLength() * 8 * (
self::$derivativeSettings[$transcodeKey]['videoBitrate']
+
self::$derivativeSettings[$transcodeKey]['audioBitrate']
);
}
// Else just return the size of the source video ( we have no idea how large the actual derivative size will be )
return $file->getLength() * $file->getHandler()->getBitrate( $file ) * 8;
}
/**
* Static function to get the set of video assets
* Checks if the file is local or remote and grabs respective sources
* @param $file File
* @param $options array
* @return array|mixed
*/
static public function getSources( &$file , $options = array() ){
if( $file->isLocal() || $file->repo instanceof ForeignDBViaLBRepo ){
return self::getLocalSources( $file , $options );
} else {
return self::getRemoteSources( $file , $options );
}
}
/**
* Grabs sources from the remote repo via ApiQueryVideoInfo.php entry point.
*
* TODO: This method could use some rethinking. See comments on PS1 of
*
*
* Because this works with commons regardless of whether TimedMediaHandler is installed or not
* @param $file File
* @param $options array
* @return array|mixed
*/
static public function getRemoteSources(&$file , $options = array() ){
global $wgMemc;
// Setup source attribute options
$dataPrefix = in_array( 'nodata', $options )? '': 'data-';
// Use descriptionCacheExpiry as our expire for timed text tracks info
if ( $file->repo->descriptionCacheExpiry > 0 ) {
wfDebug("Attempting to get sources from cache...");
$key = $file->repo->getLocalCacheKey( 'WebVideoSources', 'url', $file->getName() );
$sources = $wgMemc->get($key);
if ( $sources ) {
wfDebug("Success found sources in local cache\n");
return $sources;
}
wfDebug("source cache miss\n");
}
wfDebug("Get Video sources from remote api for " . $file->getName() . "\n");
$query = array(
'action' => 'query',
'prop' => 'videoinfo',
'viprop' => 'derivatives',
'titles' => MWNamespace::getCanonicalName( NS_FILE ) .':'. $file->getTitle()->mTextform
);
$data = $file->repo->fetchImageQuery( $query );
if( isset( $data['warnings'] ) && isset( $data['warnings']['query'] )
&& $data['warnings']['query']['*'] == "Unrecognized value for parameter 'prop': videoinfo" )
{
// Commons does not yet have TimedMediaHandler.
// Use the normal file repo system single source:
return array( self::getPrimarySourceAttributes( $file, array( $dataPrefix ) ) );
}
$sources = array();
// Generate the source list from the data response:
if( isset( $data['query'] ) && $data['query']['pages'] ){
$vidResult = array_shift( $data['query']['pages'] );
if( isset( $vidResult['videoinfo'] ) ) {
$derResult = array_shift( $vidResult['videoinfo'] );
$derivatives = $derResult['derivatives'];
foreach( $derivatives as $derivativeSource ){
$sources[] = $derivativeSource;
}
}
}
// Update the cache:
if ( $sources && $file->repo->descriptionCacheExpiry > 0 ) {
$wgMemc->set( $key, $sources, $file->repo->descriptionCacheExpiry );
}
return $sources;
}
/**
* Based on the $wgEnabledTranscodeSet set of enabled derivatives we
* return sources that are ready.
*
* This will not automatically update or queue anything!
*
* @param $file File object
* @param $options array Options, a set of options:
* 'nodata' Strips the data- attribute, useful when your output is not html
* @return array an associative array of sources suitable for