Index: trunk/extensions/TimedMediaHandler/TimedMediaHandler_body.php |
— | — | @@ -199,16 +199,14 @@ |
200 | 200 | |
201 | 201 | $srcWidth = $file->getWidth(); |
202 | 202 | $srcHeight = $file->getHeight(); |
203 | | - |
204 | 203 | $baseConfig = array( |
205 | 204 | 'file' => $file, |
206 | 205 | 'length' => $this->getLength( $file ), |
207 | 206 | 'offset' => $this->getOffset( $file ), |
208 | 207 | 'width' => $params['width'], |
209 | 208 | 'height' => $srcWidth == 0 ? $srcHeight : $params['width']* $srcHeight / $srcWidth, |
210 | | - 'isVideo' => ( $srcHeight == 0 || $srcWidth == 0 ), |
| 209 | + 'isVideo' => ( $srcHeight != 0 && $srcWidth != 0 ) |
211 | 210 | ); |
212 | | - |
213 | 211 | // No thumbs for audio |
214 | 212 | if( $baseConfig['isVideo'] === false ){ |
215 | 213 | return new TimedMediaTransformOutput( $baseConfig ); |
Index: trunk/extensions/TimedMediaHandler/TimedMediaTransformOutput.php |
— | — | @@ -37,7 +37,7 @@ |
38 | 38 | } |
39 | 39 | } |
40 | 40 | |
41 | | - function getTagName(){ |
| 41 | + function getTagName(){ |
42 | 42 | return ( $this->isVideo )? 'video' : 'audio'; |
43 | 43 | } |
44 | 44 | |
— | — | @@ -71,18 +71,24 @@ |
72 | 72 | * supplied arrays |
73 | 73 | */ |
74 | 74 | function getXmlTagOutput( $mediaAttr, $mediaSources, $textSources ){ |
75 | | - // Build the video tag output: |
| 75 | + // Try to get the first source src attribute ( usually this should be the source file ) |
| 76 | + $firstSource = current( reset( $mediaSources ) ); |
| 77 | + if( !$firstSource['url']){ |
| 78 | + // XXX media handlers don't seem to work with exceptions.. |
| 79 | + return 'Error missing media source'; |
| 80 | + } |
| 81 | + // Build the video tag output: |
76 | 82 | $s = Xml::tags( $this->getTagName(), $mediaAttr, |
77 | 83 | |
78 | 84 | // The set of media sources: |
79 | 85 | self::xmlTagSet( 'source', $mediaSources ) . |
80 | 86 | |
81 | 87 | // Timed text: |
82 | | - self::xmlTagSet( 'track', $textAttr ) . |
| 88 | + self::xmlTagSet( 'track', $textSources ) . |
83 | 89 | |
84 | 90 | // Fallback text displayed for browsers without js and without video tag support: |
85 | 91 | /// XXX note we may want to replace this with an image and download link play button |
86 | | - wfMsg('timedmedia-no-player-js', $url) |
| 92 | + wfMsg('timedmedia-no-player-js', $firstSource['src']) |
87 | 93 | ); |
88 | 94 | return $s; |
89 | 95 | } |
— | — | @@ -103,11 +109,6 @@ |
104 | 110 | // Note we set controls to true ( for no-js players ) when mwEmbed rewrites the interface |
105 | 111 | // it updates the controls attribute of the embed video |
106 | 112 | 'controls'=> 'true', |
107 | | - |
108 | | - // Custom data-attributes |
109 | | - 'data-durationhint' => $length, |
110 | | - 'data-startoffset' => $offset, |
111 | | - 'data-mwtitle' => $this->file->getTitle()->getDBKey() |
112 | 113 | ); |
113 | 114 | |
114 | 115 | // Set player skin: |
— | — | @@ -115,7 +116,14 @@ |
116 | 117 | $mediaAttr['class'] = htmlspecialchars ( $wgVideoPlayerSkin ); |
117 | 118 | } |
118 | 119 | |
119 | | - // xxx Note when on the same cluster we should be able to look up sources |
| 120 | + // Custom data-attributes |
| 121 | + $mediaAttr += array( |
| 122 | + 'data-durationhint' => $length, |
| 123 | + 'data-startoffset' => $offset, |
| 124 | + 'data-mwtitle' => $this->file->getTitle()->getDBKey() |
| 125 | + ); |
| 126 | + |
| 127 | + // Add api provider: |
120 | 128 | if( $this->file->getRepoName() != 'local' ){ |
121 | 129 | // Set the api provider name to "commons" for shared ( instant commons convention ) |
122 | 130 | // ( provider names should have identified the provider |
— | — | @@ -126,6 +134,7 @@ |
127 | 135 | } |
128 | 136 | // XXX Note: will probably migrate mwprovider to an escaped api url. |
129 | 137 | $mediaAttr[ 'data-mwprovider' ] = $apiProviderName; |
| 138 | + |
130 | 139 | return $mediaAttr; |
131 | 140 | } |
132 | 141 | |
— | — | @@ -138,7 +147,7 @@ |
139 | 148 | return $this->sources; |
140 | 149 | } |
141 | 150 | |
142 | | - function getTimedTextSources(){ |
| 151 | + function getTextSources(){ |
143 | 152 | // Check local cache: |
144 | 153 | if( $this->textTracks ){ |
145 | 154 | return $this->textTracks; |
— | — | @@ -155,7 +164,7 @@ |
156 | 165 | )); |
157 | 166 | $api = new ApiMain( $params ); |
158 | 167 | $api->execute(); |
159 | | - $data = & $api->getResultData(); |
| 168 | + $data = $api->getResultData(); |
160 | 169 | // Get the list of language Names |
161 | 170 | $langNames = Language::getLanguageNames(); |
162 | 171 | |
Index: trunk/extensions/TimedMediaHandler/TimedMediaHandler.php |
— | — | @@ -36,6 +36,7 @@ |
37 | 37 | $wgAutoloadClasses['TimedMediaTransformOutput'] = "$timedMediaDir/TimedMediaTransformOutput.php"; |
38 | 38 | $wgAutoloadClasses['TimedMediaIframeOutput' ] = "$timedMediaDir/TimedMediaIframeOutput.php"; |
39 | 39 | $wgAutoloadClasses['WebVideoTranscode'] = "$timedMediaDir/WebVideoTranscode/WebVideoTranscode.php"; |
| 40 | +$wgAutoloadClasses['WebVideoTranscodeJob'] = "$timedMediaDir/WebVideoTranscode/WebVideoTranscodeJob.php"; |
40 | 41 | |
41 | 42 | // Register the Timed Media Handler javascript resources ( mwEmbed modules ) |
42 | 43 | MwEmbedResourceManager::register( 'extensions/TimedMediaHandler/resources/EmbedPlayer' ); |
— | — | @@ -74,7 +75,7 @@ |
75 | 76 | $wgOggThumbLocation = '/usr/bin/oggThumb'; |
76 | 77 | |
77 | 78 | // The location of ffmpeg2theora ( for metadata and transcoding ) |
78 | | -$wgffmpeg2theoraPath = '/usr/bin/ffmpeg2theora'; |
| 79 | +$wgffmpeg2theoraLocation = '/usr/bin/ffmpeg2theora'; |
79 | 80 | |
80 | 81 | // Location of the FFmpeg binary ( used to encode WebM and for thumbnails ) |
81 | 82 | $wgFFmpegLocation = '/usr/bin/ffmpeg'; |
Index: trunk/extensions/TimedMediaHandler/WebVideoTranscode/WebVideoTranscodeJob.php |
— | — | @@ -20,9 +20,247 @@ |
21 | 21 | public function __construct( $title, $params, $id = 0 ) { |
22 | 22 | parent::__construct( 'webVideoTranscode', $title, $params, $id ); |
23 | 23 | } |
24 | | - |
| 24 | + // Run the transcode request |
25 | 25 | public function run() { |
| 26 | + // Get the file object |
| 27 | + $file = wfLocalFile( $this->title ); |
| 28 | + $transcodeKey = $this->params['transcodeKey']; |
26 | 29 | |
| 30 | + // Build the destination target |
| 31 | + $destinationFile = WebVideoTranscode::getTargetEncodePath( $file, $transcodeKey ); |
| 32 | + |
| 33 | + $options = WebVideoTranscode::$derivativeSettings[ $transcodeKey ]; |
| 34 | + |
| 35 | + // Check the codec see which encode method to call; |
| 36 | + if( $options['codec'] == 'theora' ){ |
| 37 | + $status = $this->ffmpeg2TheoraEncode( $file, $destinationFile, $options ); |
| 38 | + } else if( $options['codec'] == 'vp8' ){ |
| 39 | + // Check for twopass: |
| 40 | + if( isset( $options['twopass'] ) ){ |
| 41 | + $status = $this->ffmpegEncode( $file, $destinationFile, $options, 1 ); |
| 42 | + if( $status ){ |
| 43 | + $status = $this->ffmpegEncode( $file, $destinationFile, $options, 2 ); |
| 44 | + } |
| 45 | + } else { |
| 46 | + $this->ffmpegEncode( $file, $destinationFile, $options ); |
| 47 | + } |
| 48 | + |
| 49 | + } else { |
| 50 | + wfDebug( 'Error unknown codec:' . $options['codec'] ); |
| 51 | + $status = false; |
| 52 | + } |
| 53 | + |
| 54 | + return $status; |
27 | 55 | } |
28 | 56 | |
| 57 | + /** Utility helper for ffmpeg and ffmpeg2theora mapping **/ |
| 58 | + |
| 59 | + function ffmpegEncode( $file, $target, $options, $pass=0 ){ |
| 60 | + global $wgFFmpegLocation; |
| 61 | + // Get the source |
| 62 | + $source = $file->getFullPath(); |
| 63 | + |
| 64 | + // Set up the base command |
| 65 | + $cmd = wfEscapeShellArg( $wgFFmpegLocation ) . ' ' . wfEscapeShellArg( $source ); |
| 66 | + |
| 67 | + if( isset($options['preset']) ){ |
| 68 | + if ($options['preset'] == "360p") { |
| 69 | + $cmd.= " -vpre libvpx-360p"; |
| 70 | + } else if ( $options['preset'] == "720p" ) { |
| 71 | + $cmd.= " -vpre libvpx-720p"; |
| 72 | + } else if ( $options['preset'] == "1080p" ) { |
| 73 | + $cmd.= " -vpre libvpx-1080p"; |
| 74 | + } |
| 75 | + } |
| 76 | + |
| 77 | + if ( isset( $options['novideo'] ) ) { |
| 78 | + $cmd.= " -vn "; |
| 79 | + } else { |
| 80 | + $cmd.= $this->ffmpegAddVideoOptions( $file, $target, $options, $pass ); |
| 81 | + |
| 82 | + } |
| 83 | + |
| 84 | + // Check for start time |
| 85 | + if( isset( $options['starttime'] ) ){ |
| 86 | + $cmd.= ' -ss ' . wfEscapeShellArg( $options['starttime'] ); |
| 87 | + } else { |
| 88 | + $options['starttime'] = 0; |
| 89 | + } |
| 90 | + // Check for end time: |
| 91 | + if( isset( $options['endtime'] ) ){ |
| 92 | + $cmd.= ' -t ' . intval( $options['endtime'] ) - intval($options['starttime'] ) ; |
| 93 | + } |
| 94 | + |
| 95 | + |
| 96 | + if ( $pass == 1 || $options['noaudio'] ) { |
| 97 | + $cmd.= ' -an'; |
| 98 | + } else { |
| 99 | + $cmd.= $this->ffmpegAddAudioOptions( $file, $target, $options, $pass ); |
| 100 | + } |
| 101 | + |
| 102 | + // Output WebM |
| 103 | + $cmd.=" -f webm"; |
| 104 | + |
| 105 | + if ( $pass != 0 ) { |
| 106 | + $cmd.=" -pass " .wfEscapeShellArg( $pass ) ; |
| 107 | + $cmd.=" -passlogfile " . wfEscapeShellArg( $target .'.log' ); |
| 108 | + } |
| 109 | + // And the output target: |
| 110 | + if ($pass==1) { |
| 111 | + $cmd.= ' /dev/null'; |
| 112 | + } else{ |
| 113 | + $cmd.= $target; |
| 114 | + } |
| 115 | + |
| 116 | + print "Running cmd: \n\n" .$cmd . "\n\n" ; |
| 117 | + |
| 118 | + wfProfileIn( 'ffmpeg_encode' ); |
| 119 | + wfShellExec( $cmd, $retval ); |
| 120 | + wfProfileOut( 'ffmpeg_encode' ); |
| 121 | + |
| 122 | + if( $retval ){ |
| 123 | + return false; |
| 124 | + } |
| 125 | + return true; |
| 126 | + } |
| 127 | + function ffmpegAddVideoOptions( $file, $target, $options, $pass){ |
| 128 | + $cmd =''; |
| 129 | + // Add the boiler plate vp8 ffmpeg command: |
| 130 | + $cmd.="-y -skip_threshold 0 -rc_buf_aggressivity 0 -bufsize 6000k -rc_init_occupancy 4000 -threads 4"; |
| 131 | + |
| 132 | + // Check for video quality: |
| 133 | + if ( isset( $options['videoQuality'] ) && $options['videoQuality'] >= 0 ) { |
| 134 | + // Map 0-10 to 63-0, higher values worse quality |
| 135 | + $quality = 63 - intval( intval( $options['videoQuality'] )/10 * 63 ); |
| 136 | + $cmd .= " -qmin " . wfEscapeShellArg( $quality ); |
| 137 | + $cmd .= " -qmax " . wfEscapeShellArg( $quality ); |
| 138 | + } |
| 139 | + |
| 140 | + // Check for video bitrate: |
| 141 | + if ( isset( $options['videoBitrate'] ) ) { |
| 142 | + $cmd.= " -qmin 1 -qmax 51"; |
| 143 | + $cmd.= " -vp " . wfEscapeShellArg( $options['videoBitrate'] ); |
| 144 | + } |
| 145 | + // Set the codec: |
| 146 | + $cmd.= " -vcodec libvpx"; |
| 147 | + |
| 148 | + die( $file->getWidth() + ':' + $file->getHeight() ); |
| 149 | + |
| 150 | + // Check for aspect ratio |
| 151 | + if ($options['aspect']) { |
| 152 | + $aspectRatio = $options['aspect']; |
| 153 | + } else { |
| 154 | + $aspectRatio = $file->getWidth() + ':' + $file->getHeight(); |
| 155 | + } |
| 156 | + |
| 157 | + $dar = $aspectRatio.split(':'); |
| 158 | + $dar = intval( $aspectRatio[0] ) / intval( $aspectRatio[1] ); |
| 159 | + |
| 160 | + // Check maxSize |
| 161 | + if (isset( $options['maxSize'] ) && intval( $options['maxSize'] ) > 0) { |
| 162 | + $sourceWidth = $file->getWidth(); |
| 163 | + $sourceHeight =$file->getHeight(); |
| 164 | + if ($sourceWidth > $options['maxSize'] ) { |
| 165 | + $width = intval( $options['maxSize'] ); |
| 166 | + $height = intval( $width / $dar); |
| 167 | + } else { |
| 168 | + $height = intval( $options['maxSize'] ); |
| 169 | + $width = intval( $height * $dar); |
| 170 | + } |
| 171 | + $cmd.= ' -s ' . intval( $width ) . 'x' . intval( $height ); |
| 172 | + } else if ( |
| 173 | + (isset( $options['width'] ) && $options['width'] > 0 ) |
| 174 | + && |
| 175 | + (isset( $options['height'] ) && $options['height'] > 0 ) |
| 176 | + ){ |
| 177 | + $cmd.= ' -s ' . intval( $options['width'] ) . 'x' . intval( $options['height'] ); |
| 178 | + } |
| 179 | + |
| 180 | + // Handle crop: |
| 181 | + $optionMap = array( |
| 182 | + 'cropTop' => '-croptop', |
| 183 | + 'cropBottom' => '-cropbottom', |
| 184 | + 'cropLeft' => '-cropleft', |
| 185 | + 'cropRight' => '-cropright' |
| 186 | + ); |
| 187 | + foreach( $optionMap as $name => $cmdArg ){ |
| 188 | + if( isset($options[$name]) ){ |
| 189 | + $cmd.= " $cmdArg " . wfEscapeShellArg( $options[$name] ); |
| 190 | + } |
| 191 | + } |
| 192 | + |
| 193 | + // Check for keyframeInterval |
| 194 | + if( isset( $options['keyframeInterval'] ) ){ |
| 195 | + $cmd.= ' -g ' . wfEscapeShellArg( $options['keyframeInterval'] ); |
| 196 | + $cmd.= ' -keyint_min ' . wfEscapeShellArg( $options['keyframeInterval'] ); |
| 197 | + } |
| 198 | + if( isset( $options['deinterlace'] ) ){ |
| 199 | + $cmd.= ' -deinterlace'; |
| 200 | + } |
| 201 | + |
| 202 | + return $cmd; |
| 203 | + } |
| 204 | + |
| 205 | + function ffmpegAddAudioOptions( $file, $target, $options, $pass){ |
| 206 | + $cmd =''; |
| 207 | + if( isset( $options['audioQuality'] ) ){ |
| 208 | + $cmd.= " -aq " . wfEscapeShellArg( $options['audioQuality'] ); |
| 209 | + } |
| 210 | + if( isset( $options['audioBitrate'] )){ |
| 211 | + $cmd.= ' -ab ' . intval( $options['audioBitrate'] ) * 1000; |
| 212 | + } |
| 213 | + if( isset( $options['samplerate'] ) ){ |
| 214 | + $cmd.= " -ar " . wfEscapeShellArg( $options['samplerate'] ); |
| 215 | + } |
| 216 | + if( isset( $options['channels'] )){ |
| 217 | + $cmd.= " -ac " . wfEscapeShellArg( $options['channels'] ); |
| 218 | + } |
| 219 | + // Always use vorbis for audio: |
| 220 | + $cmd.= " -acodec libvorbis "; |
| 221 | + return $cmd; |
| 222 | + } |
| 223 | + |
| 224 | + |
| 225 | + |
| 226 | + /** |
| 227 | + * ffmpeg2Theora mapping is much simpler since it is the basis of the the firefogg API |
| 228 | + */ |
| 229 | + function ffmpeg2TheoraEncode( $file, $target, $options){ |
| 230 | + global $wgffmpeg2theoraLocation; |
| 231 | + |
| 232 | + // Get the source: |
| 233 | + $source = $file->getFullPath(); |
| 234 | + |
| 235 | + // Set up the base command |
| 236 | + $cmd = wfEscapeShellArg( $wgffmpeg2theoraLocation ) . ' ' . wfEscapeShellArg( $source ); |
| 237 | + |
| 238 | + // Add in the encode settings |
| 239 | + foreach( $options as $key => $val){ |
| 240 | + if( isset( WebVideoTranscode::$foggMap[$key] ) ){ |
| 241 | + if( is_array( WebVideoTranscode::$foggMap[$key] ) ){ |
| 242 | + $cmd.= ' '. implode(' ', WebVideoTranscode::$foggMap[$key] ); |
| 243 | + }else if($val == 'true' || $val === true){ |
| 244 | + $cmd.= ' '. WebVideoTranscode::$foggMap[$key]; |
| 245 | + }else if( $val === false){ |
| 246 | + //ignore "false" flags |
| 247 | + }else{ |
| 248 | + //normal get/set value |
| 249 | + $cmd.= ' '. WebVideoTranscode::$foggMap[$key] . ' ' . wfEscapeShellArg( $val ); |
| 250 | + } |
| 251 | + } |
| 252 | + } |
| 253 | + die( "run\n\n" . $cmd. "\n"); |
| 254 | + // Add the output target: |
| 255 | + $cmd.= ' -o ' . wfEscapeShellArg ( $target ); |
| 256 | + print "Running cmd: \n\n" .$cmd . "\n\n" ; |
| 257 | + |
| 258 | + wfProfileIn( 'ffmpeg2theora_encode' ); |
| 259 | + wfShellExec( $cmd, $retval ); |
| 260 | + wfProfileOut( 'ffmpeg2theora_encode' ); |
| 261 | + |
| 262 | + if( $retval ){ |
| 263 | + return false; |
| 264 | + } |
| 265 | + return true; |
| 266 | + } |
29 | 267 | } |
\ No newline at end of file |
Index: trunk/extensions/TimedMediaHandler/WebVideoTranscode/WebVideoTranscodeCron.old.php |
— | — | @@ -152,13 +152,13 @@ |
153 | 153 | |
154 | 154 | $file = wfFindFile( $fileTitle ); |
155 | 155 | if( !$file ){ |
156 | | - $this->output( "File not found: {$name} (not adding to jobQueue\n" ); |
| 156 | + $maintenance->output( "File not found: {$name} (not adding to jobQueue\n" ); |
157 | 157 | continue; |
158 | 158 | } |
159 | 159 | $targetSize = OggTranscode::$derivativeSettings[ $derivativeKey ]['maxSize']; |
160 | 160 | $sourceSize = $file->getWidth(); |
161 | 161 | if( $targetSize > $sourceSize ){ |
162 | | - $this->output( "File:{$name} is too small for {$derivativeKey} ::\n" . |
| 162 | + $maintenance->output( "File:{$name} is too small for {$derivativeKey} ::\n" . |
163 | 163 | "target: {$targetSize} > source: {$sourceSize} \n\n" ); |
164 | 164 | continue; |
165 | 165 | } |
— | — | @@ -285,7 +285,7 @@ |
286 | 286 | * @param {Array} $encodeSettings Settings to encode the file with |
287 | 287 | */ |
288 | 288 | function doEncode( $source, $target, $encodeSettings ){ |
289 | | - global $wgffmpeg2theoraPath; |
| 289 | + global $wgffmpeg2theoraPath, $maintenance; |
290 | 290 | |
291 | 291 | // Set up the base command |
292 | 292 | $cmd = wfEscapeShellArg( $wgffmpeg2theoraPath ) . ' ' . wfEscapeShellArg( $source ); |
— | — | @@ -307,7 +307,7 @@ |
308 | 308 | } |
309 | 309 | // Add the output target: |
310 | 310 | $cmd.= ' -o ' . wfEscapeShellArg ( $target ); |
311 | | - $this->output( "Running cmd: \n\n" .$cmd . "\n\n" ); |
| 311 | + $maintenance->output( "Running cmd: \n\n" .$cmd . "\n\n" ); |
312 | 312 | wfProfileIn( 'ffmpeg2theora_encode' ); |
313 | 313 | wfShellExec( $cmd, $retval ); |
314 | 314 | wfProfileOut( 'ffmpeg2theora_encode' ); |
Index: trunk/extensions/TimedMediaHandler/WebVideoTranscode/WebVideoTranscode.php |
— | — | @@ -48,7 +48,7 @@ |
49 | 49 | * http://firefogg.org/dev/index.html |
50 | 50 | */ |
51 | 51 | public static $derivativeSettings = array( |
52 | | - WebVideoTranscode::ENC_WEB_2MBS => |
| 52 | + WebVideoTranscode::ENC_OGV_2MBS => |
53 | 53 | array( |
54 | 54 | 'maxSize' => '200', |
55 | 55 | 'videoBitrate' => '128', |
— | — | @@ -59,7 +59,8 @@ |
60 | 60 | 'noUpscaling' => 'true', |
61 | 61 | 'twopass' => 'true', |
62 | 62 | 'keyframeInterval' => '64', |
63 | | - 'bufDelay' => '128' |
| 63 | + 'bufDelay' => '128', |
| 64 | + 'codec' => 'theora', |
64 | 65 | ), |
65 | 66 | WebVideoTranscode::ENC_OGV_4MBS => |
66 | 67 | array( |
— | — | @@ -69,7 +70,8 @@ |
70 | 71 | 'noUpscaling' => 'true', |
71 | 72 | 'twopass' => 'true', |
72 | 73 | 'keyframeInterval' => '128', |
73 | | - 'bufDelay' => '256' |
| 74 | + 'bufDelay' => '256', |
| 75 | + 'codec' => 'theora', |
74 | 76 | ), |
75 | 77 | WebVideoTranscode::ENC_OGV_6MBS => |
76 | 78 | array( |
— | — | @@ -79,9 +81,11 @@ |
80 | 82 | 'noUpscaling' => 'true', |
81 | 83 | 'twopass' => 'true', |
82 | 84 | 'keyframeInterval' => '128', |
83 | | - 'bufDelay' => '256' |
| 85 | + 'bufDelay' => '256', |
| 86 | + 'codec' => 'theora', |
84 | 87 | ), |
85 | 88 | |
| 89 | + // WebM transcode: |
86 | 90 | WebVideoTranscode::ENC_WEBM_6MBS => |
87 | 91 | array( |
88 | 92 | 'maxSize' => '512', |
— | — | @@ -93,12 +97,13 @@ |
94 | 98 | 'bufDelay' => '256', |
95 | 99 | 'codec' => 'vp8', |
96 | 100 | ), |
97 | | - WebVideoTranscode::ENC_OGV_HQ_VBR => |
| 101 | + WebVideoTranscode::ENC_WEBM_HQ_VBR => |
98 | 102 | array( |
99 | 103 | 'maxSize' => '720', |
100 | 104 | 'videoQuality' => 7, |
101 | 105 | 'audioQuality' => 3, |
102 | | - 'noUpscaling' => 'true' |
| 106 | + 'noUpscaling' => 'true', |
| 107 | + 'codec' => 'vp8', |
103 | 108 | ) |
104 | 109 | ); |
105 | 110 | /** |
— | — | @@ -126,6 +131,7 @@ |
127 | 132 | 'cropRight' => "--cropright", |
128 | 133 | 'keyframeInterval'=> "--key", |
129 | 134 | 'denoise' => array("--pp", "de"), |
| 135 | + 'deinterlace' => "--deinterlace", |
130 | 136 | 'novideo' => array("--novideo", "--no-skeleton"), |
131 | 137 | 'bufDelay' => "--buf-delay", |
132 | 138 | // audio |
— | — | @@ -145,16 +151,30 @@ |
146 | 152 | 'contact' => "--contact" |
147 | 153 | ); |
148 | 154 | |
| 155 | + static public function getDerivativeFilePath($file, $transcodeKey){ |
| 156 | + return dirname( |
| 157 | + $file->getThumbPath( |
| 158 | + $file->thumbName( array() ) |
| 159 | + ) |
| 160 | + ) . '/' . |
| 161 | + $file->getName() . '.' . |
| 162 | + $transcodeKey ; |
| 163 | + } |
| 164 | + static public function getTargetEncodePath( $file, $transcodeKey ){ |
| 165 | + return self::getDerivativeFilePath( $file, $transcodeKey ) . '.tmp'; |
| 166 | + } |
| 167 | + |
149 | 168 | /** |
150 | 169 | * Static function to get the set of video assets |
151 | 170 | * |
152 | 171 | * Based on the $wgEnabledTranscodeSet set of enabled derivatives |
153 | 172 | * |
154 | 173 | * In progress assets have .tmp extension and we don't add jobQueue for them. |
155 | | - * This lets us do relatively cheap stat calls and avoid costly jobQueue sql queries |
| 174 | + * This assumes "cheap" stat calls and "costly" jobQueue sql queries |
156 | 175 | * |
157 | 176 | * If no transcode is in progress or ready add the job to the jobQueue |
158 | 177 | * |
| 178 | + * @param {Object} File object |
159 | 179 | * @returns an associative array of sources suitable for <source> tag output |
160 | 180 | */ |
161 | 181 | static public function getSources( $file ){ |
— | — | @@ -163,35 +183,79 @@ |
164 | 184 | |
165 | 185 | // Setup local variables |
166 | 186 | $fileName = $file->getName(); |
| 187 | + |
167 | 188 | // Add the source file: |
168 | 189 | $sources[] = array( |
169 | 190 | 'src' => $file->getUrl() |
170 | 191 | ); |
171 | 192 | |
172 | | - $thumbName = $file->thumbName( array() ); |
173 | | - $thumbPath = $file->getThumbPath( $thumbName ); |
174 | | - $thumbDir = dirname( $thumbPath ); |
| 193 | + $thumbName = $file->thumbName( array() ); |
175 | 194 | $thumbUrl = $file->getThumbUrl( $thumbName ); |
176 | | - $thumbUrlDir = dirname( $thumbUrl ); |
177 | | - |
| 195 | + $thumbUrlDir = dirname( $thumbUrl ); |
| 196 | + |
| 197 | + $hasOggFlag = false; |
| 198 | + $hasWebMFlag = false; |
| 199 | + // Check the source file for .webm extension |
| 200 | + if( preg_match( "/$.webm/i", $fileName ) ) { |
| 201 | + $hasWebMFlag = true; |
| 202 | + } else { |
| 203 | + // we only support ogg and webm so assume oky if we have .webm |
| 204 | + $hasOggFlag = true; |
| 205 | + } |
| 206 | + |
178 | 207 | foreach($wgEnabledTranscodeSet as $transcodeKey){ |
179 | | - $derivativeFile = $thumbPath . '/' . $fileName . '.' . $transcodeKey ; |
| 208 | + $derivativeFile = self::getDerivativeFilePath( $file, $transcodeKey); |
180 | 209 | if( is_file( $derivativeFile ) ){ |
181 | | - $sources[] = array( |
| 210 | + $sources[] = array( |
182 | 211 | 'src' => $thumbUrlDir . '/' .$fileName . '.' . $transcodeKey |
183 | 212 | ); |
184 | 213 | } else { |
| 214 | + // Skip if transcode is smaller than source |
| 215 | + // And we have at least one ogg and one WebM encode for this source |
| 216 | + $codec = self::$derivativeSettings[$transcodeKey]['codec']; |
| 217 | + if( ! self::isTranscodeSmallerThanSource( $file, $transcodeKey ) |
| 218 | + && |
| 219 | + // Check if we need to process the $transcodeKey because we don't |
| 220 | + // have an ogg or webm source yet: |
| 221 | + ! ( |
| 222 | + ( !$hasOggFlag && $codec == 'theora' ) |
| 223 | + || |
| 224 | + ( !$hasWebMFlag && $codec == 'vp8' ) |
| 225 | + ) |
| 226 | + ){ |
| 227 | + continue; |
| 228 | + } |
185 | 229 | // TranscodeKey not found ( check if the file is in progress ) ( tmp transcode location ) |
186 | | - if( is_file( $derivativeFile . '.tmp' ) ){ |
| 230 | + //if( is_file( self::getEncodeTargetFilePath( $file, $transcodeKey ) ){ |
187 | 231 | // file in progress / in queue |
188 | 232 | // XXX Note we could check date and flag as failure somewhere |
189 | | - } else { |
| 233 | + //} else { |
190 | 234 | // no in-progress file add to job queue and touch the target |
191 | | - //touch( $derivativeFile . '.tmp' ); |
192 | | - } |
| 235 | + $job = new WebVideoTranscodeJob( $file->getTitle(), array( |
| 236 | + 'transcodeMode' => 'derivative', |
| 237 | + 'transcodeKey' => $transcodeKey, |
| 238 | + ) ); |
| 239 | + $jobId = $job->insert(); |
| 240 | + if( $jobId ){ |
| 241 | + // If the job was inserted: |
| 242 | + touch( self::getTargetEncodePath( $file, $transcodeKey ) ); |
| 243 | + } |
| 244 | + //} |
193 | 245 | } |
194 | 246 | } |
195 | 247 | return $sources; |
196 | 248 | } |
| 249 | + /** |
| 250 | + * Test if a given transcode target is smaller than the source file |
| 251 | + * |
| 252 | + * @param $transcodeKey The static transcode key |
| 253 | + * @param $file {Object} File object |
| 254 | + */ |
| 255 | + public static function isTranscodeSmallerThanSource( $file, $transcodeKey){ |
| 256 | + return ( self::$derivativeSettings[$transcodeKey]['maxSize'] < $file->getWidth() |
| 257 | + && |
| 258 | + self::$derivativeSettings[$transcodeKey]['maxSize'] < $file->getHeight() |
| 259 | + ); |
| 260 | + } |
197 | 261 | } |
198 | 262 | |
Index: trunk/extensions/TimedMediaHandler/TimedMediaHandler.hooks.php |
— | — | @@ -10,7 +10,7 @@ |
11 | 11 | class TimedMediaHandlerHooks { |
12 | 12 | // Register TimedMediaHandler Hooks |
13 | 13 | static function register(){ |
14 | | - global $wgParserOutputHooks, $wgHooks; |
| 14 | + global $wgParserOutputHooks, $wgHooks, $wgJobClasses, $wgJobExplitRequestTypes; |
15 | 15 | |
16 | 16 | // Parser hook for TimedMediaHandler output |
17 | 17 | $wgParserOutputHooks['TimedMediaHandler'] = array( 'TimedMediaHandler', 'outputHook' ); |
— | — | @@ -18,7 +18,15 @@ |
19 | 19 | // Setup a hook for iframe embed handling: |
20 | 20 | $wgHooks['ArticleFromTitle'][] = 'TimedMediaIframeOutput::iframeHook'; |
21 | 21 | |
22 | | - |
| 22 | + // Add transcode job class: |
| 23 | + $wgJobClasses+= array( |
| 24 | + 'webVideoTranscode' => 'WebVideoTranscodeJob' |
| 25 | + ); |
| 26 | + // Transcode jobs must be explicitly requested from the job queue: |
| 27 | + $wgJobExplitRequestTypes+= array( |
| 28 | + 'webVideoTranscode' |
| 29 | + ); |
| 30 | + |
23 | 31 | /** |
24 | 32 | * Add support for the "timedText" NameSpace |
25 | 33 | */ |
Index: trunk/extensions/TimedMediaHandler/README |
— | — | @@ -67,7 +67,7 @@ |
68 | 68 | |
69 | 69 | Set the ffmpeg2theora binary location with: |
70 | 70 | |
71 | | - $wgffmpeg2theoraPath = '/path/to/ffmpeg2theora'; |
| 71 | + $wgffmpeg2theoraLocation = '/path/to/ffmpeg2theora'; |
72 | 72 | |
73 | 73 | Download ffmpeg2theora from: http://v2v.cc/~j/ffmpeg2theora/ |
74 | 74 | |