001
014
015 package com.liferay.portlet.documentlibrary.util;
016
017 import com.liferay.portal.kernel.log.Log;
018 import com.liferay.portal.kernel.log.LogFactoryUtil;
019 import com.liferay.portal.kernel.util.GetterUtil;
020 import com.liferay.portal.kernel.util.PropsKeys;
021 import com.liferay.portal.kernel.util.StringPool;
022
023 import com.xuggle.xuggler.Configuration;
024 import com.xuggle.xuggler.IAudioResampler;
025 import com.xuggle.xuggler.IAudioSamples;
026 import com.xuggle.xuggler.ICodec;
027 import com.xuggle.xuggler.IContainer;
028 import com.xuggle.xuggler.IContainerFormat;
029 import com.xuggle.xuggler.IPacket;
030 import com.xuggle.xuggler.IPixelFormat.Type;
031 import com.xuggle.xuggler.IRational;
032 import com.xuggle.xuggler.IStream;
033 import com.xuggle.xuggler.IStreamCoder;
034 import com.xuggle.xuggler.IVideoPicture;
035 import com.xuggle.xuggler.IVideoResampler;
036
037 import java.io.File;
038
039 import java.util.Properties;
040
041
047 public class LiferayVideoConverter extends LiferayConverter {
048
049 public LiferayVideoConverter(
050 String inputURL, String outputURL, String videoContainer,
051 Properties videoProperties, Properties ffpresetProperties) {
052
053 _inputURL = inputURL;
054 _outputURL = outputURL;
055 _videoContainer = videoContainer;
056 _ffpresetProperties = ffpresetProperties;
057
058 _height = GetterUtil.getInteger(
059 videoProperties.getProperty(
060 PropsKeys.DL_FILE_ENTRY_PREVIEW_VIDEO_HEIGHT),
061 _height);
062 _width = GetterUtil.getInteger(
063 videoProperties.getProperty(
064 PropsKeys.DL_FILE_ENTRY_PREVIEW_VIDEO_WIDTH),
065 _width);
066
067 initVideoBitRate(videoProperties);
068 initVideoFrameRate(videoProperties);
069 }
070
071 @Override
072 public void convert() throws Exception {
073 try {
074 doConvert();
075 }
076 finally {
077 if ((_inputIContainer != null) && _inputIContainer.isOpened()) {
078 _inputIContainer.close();
079 }
080
081 if ((_outputIContainer != null) && _outputIContainer.isOpened()) {
082 _outputIContainer.close();
083 }
084 }
085
086 createMP4FastStart();
087 }
088
089 protected void createMP4FastStart() {
090 File videoFile = new File(_outputURL);
091
092 if (!_videoContainer.equals("mp4") || !videoFile.exists()) {
093 return;
094 }
095
096 File tempFile = new File(_outputURL + ".tmp");
097
098 try {
099 JQTFastStart.convert(videoFile, tempFile);
100
101 if (tempFile.exists() && (tempFile.length() > 0)) {
102 videoFile.delete();
103
104 tempFile.renameTo(videoFile);
105 }
106 }
107 catch (Exception e) {
108 if (_log.isWarnEnabled()) {
109 _log.warn("Unable to move MOOV atom to front of MP4 file");
110 }
111 }
112 finally {
113 tempFile.delete();
114 }
115 }
116
117 protected void doConvert() throws Exception {
118 _inputIContainer = IContainer.make();
119 _outputIContainer = IContainer.make();
120
121 openContainer(_inputIContainer, _inputURL, false);
122 openContainer(_outputIContainer, _outputURL, true);
123
124 int inputStreamsCount = _inputIContainer.getNumStreams();
125
126 if (inputStreamsCount < 0) {
127 throw new RuntimeException("Input URL does not have any streams");
128 }
129
130 IAudioResampler[] iAudioResamplers =
131 new IAudioResampler[inputStreamsCount];
132 IVideoResampler[] iVideoResamplers =
133 new IVideoResampler[inputStreamsCount];
134
135 IAudioSamples[] inputIAudioSamples =
136 new IAudioSamples[inputStreamsCount];
137 IAudioSamples[] outputIAudioSamples =
138 new IAudioSamples[inputStreamsCount];
139
140 IVideoPicture[] inputIVideoPictures =
141 new IVideoPicture[inputStreamsCount];
142 IVideoPicture[] outputIVideoPictures =
143 new IVideoPicture[inputStreamsCount];
144
145 IStream[] outputIStreams = new IStream[inputStreamsCount];
146
147 IStreamCoder[] inputIStreamCoders = new IStreamCoder[inputStreamsCount];
148 IStreamCoder[] outputIStreamCoders =
149 new IStreamCoder[inputStreamsCount];
150
151 for (int i = 0; i < inputStreamsCount; i++) {
152 IStream inputIStream = _inputIContainer.getStream(i);
153
154 IStreamCoder inputIStreamCoder = inputIStream.getStreamCoder();
155
156 inputIStreamCoders[i] = inputIStreamCoder;
157
158 ICodec.Type inputICodecType = inputIStreamCoder.getCodecType();
159
160 if (inputICodecType == ICodec.Type.CODEC_TYPE_AUDIO) {
161 prepareAudio(
162 iAudioResamplers, inputIAudioSamples, outputIAudioSamples,
163 inputIStreamCoder, outputIStreamCoders, _outputIContainer,
164 outputIStreams, inputICodecType, _outputURL, i);
165 }
166 else if (inputICodecType == ICodec.Type.CODEC_TYPE_VIDEO) {
167 prepareVideo(
168 iVideoResamplers, inputIVideoPictures, outputIVideoPictures,
169 inputIStreamCoder, outputIStreamCoders, _outputIContainer,
170 outputIStreams, inputICodecType, _outputURL, i);
171 }
172
173 openStreamCoder(inputIStreamCoders[i]);
174 openStreamCoder(outputIStreamCoders[i]);
175 }
176
177 if (_outputIContainer.writeHeader() < 0) {
178 throw new RuntimeException("Unable to write container header");
179 }
180
181 boolean keyPacketFound = false;
182 int nonKeyAfterKeyCount = 0;
183 boolean onlyDecodeKeyPackets = false;
184 int previousPacketSize = -1;
185
186 IPacket inputIPacket = IPacket.make();
187 IPacket outputIPacket = IPacket.make();
188
189 while (_inputIContainer.readNextPacket(inputIPacket) == 0) {
190 if (_log.isDebugEnabled()) {
191 _log.debug("Current packet size " + inputIPacket.getSize());
192 }
193
194 int streamIndex = inputIPacket.getStreamIndex();
195
196 IStreamCoder inputIStreamCoder = inputIStreamCoders[streamIndex];
197 IStreamCoder outputIStreamCoder = outputIStreamCoders[streamIndex];
198
199 if (outputIStreamCoder == null) {
200 continue;
201 }
202
203 IStream iStream = _inputIContainer.getStream(streamIndex);
204
205 long timeStampOffset = getStreamTimeStampOffset(iStream);
206
207 if (inputIStreamCoder.getCodecType() ==
208 ICodec.Type.CODEC_TYPE_AUDIO) {
209
210 decodeAudio(
211 iAudioResamplers[streamIndex],
212 inputIAudioSamples[streamIndex],
213 outputIAudioSamples[streamIndex], inputIPacket,
214 outputIPacket, inputIStreamCoder, outputIStreamCoder,
215 _outputIContainer, inputIPacket.getSize(),
216 previousPacketSize, streamIndex, timeStampOffset);
217 }
218 else if (inputIStreamCoder.getCodecType() ==
219 ICodec.Type.CODEC_TYPE_VIDEO) {
220
221 keyPacketFound = isKeyPacketFound(inputIPacket, keyPacketFound);
222
223 nonKeyAfterKeyCount = countNonKeyAfterKey(
224 inputIPacket, keyPacketFound, nonKeyAfterKeyCount);
225
226 if (isStartDecoding(
227 inputIPacket, inputIStreamCoder, keyPacketFound,
228 nonKeyAfterKeyCount, onlyDecodeKeyPackets)) {
229
230 int value = decodeVideo(
231 iVideoResamplers[streamIndex],
232 inputIVideoPictures[streamIndex],
233 outputIVideoPictures[streamIndex], inputIPacket,
234 outputIPacket, inputIStreamCoder, outputIStreamCoder,
235 _outputIContainer, null, null, 0, 0, timeStampOffset);
236
237 if (value <= 0) {
238 if (inputIPacket.isKey()) {
239 throw new RuntimeException(
240 "Unable to decode video stream " + streamIndex);
241 }
242
243 onlyDecodeKeyPackets = true;
244
245 continue;
246 }
247 }
248 else {
249 if (_log.isDebugEnabled()) {
250 _log.debug("Do not decode video stream " + streamIndex);
251 }
252 }
253 }
254
255 previousPacketSize = inputIPacket.getSize();
256 }
257
258 flush(outputIStreamCoders, _outputIContainer);
259
260 if (_outputIContainer.writeTrailer() < 0) {
261 throw new RuntimeException(
262 "Unable to write trailer to output file");
263 }
264
265 cleanUp(iAudioResamplers, iVideoResamplers);
266 cleanUp(inputIAudioSamples, outputIAudioSamples);
267 cleanUp(inputIVideoPictures, outputIVideoPictures);
268 cleanUp(inputIStreamCoders, outputIStreamCoders);
269 cleanUp(inputIPacket, outputIPacket);
270 }
271
272 @Override
273 protected IContainer getInputIContainer() {
274 return _inputIContainer;
275 }
276
277 protected int getVideoBitRate(int originalBitRate) {
278 return getProperty(originalBitRate, _videoBitRate, _VIDEO_BIT_RATE_MAX);
279 }
280
281 protected ICodec getVideoEncodingICodec(
282 ICodec.Type inputICodecType, String outputURL) {
283
284 IContainerFormat iContainerFormat =
285 _outputIContainer.getContainerFormat();
286
287 String outputFormat = iContainerFormat.getOutputFormatShortName();
288
289 if (outputFormat.equals("mp4")) {
290 return ICodec.findEncodingCodec(ICodec.ID.CODEC_ID_H264);
291 }
292 else {
293 return ICodec.guessEncodingCodec(
294 null, null, outputURL, null, inputICodecType);
295 }
296 }
297
298 protected IRational getVideoFrameRate(IRational originalFrameRate) {
299 if (_videoFrameRate != null) {
300 originalFrameRate = _videoFrameRate;
301 }
302
303 return originalFrameRate;
304 }
305
306 protected void initVideoBitRate(Properties videoProperties) {
307 _videoBitRate = getProperty(
308 videoProperties, PropsKeys.DL_FILE_ENTRY_PREVIEW_VIDEO_BIT_RATE,
309 "video bit rate", _videoContainer, _VIDEO_BIT_RATE_DEFAULT,
310 _VIDEO_BIT_RATE_MAX);
311 }
312
313 protected void initVideoFrameRate(Properties videoProperties) {
314 int numerator = GetterUtil.getInteger(
315 videoProperties.getProperty(
316 PropsKeys.DL_FILE_ENTRY_PREVIEW_VIDEO_FRAME_RATE_NUMERATOR +
317 "[" + _videoContainer + "]"));
318 int denominator = GetterUtil.getInteger(
319 videoProperties.getProperty(
320 PropsKeys.DL_FILE_ENTRY_PREVIEW_VIDEO_FRAME_RATE_DENOMINATOR +
321 StringPool.OPEN_BRACKET + _videoContainer +
322 StringPool.CLOSE_BRACKET));
323
324 if ((numerator > 0) && (denominator > 0)) {
325 _videoFrameRate = IRational.make(numerator, denominator);
326
327 if (_log.isInfoEnabled()) {
328 _log.info(
329 "Default frame rate for " + _videoContainer +
330 " configured to " + _videoFrameRate.getNumerator() +
331 "/" + _videoFrameRate.getDenominator());
332 }
333 }
334 }
335
336 protected void prepareVideo(
337 IVideoResampler[] iVideoResamplers,
338 IVideoPicture[] inputIVideoPictures,
339 IVideoPicture[] outputIVideoPictures,
340 IStreamCoder inputIStreamCoder, IStreamCoder[] outputIStreamCoders,
341 IContainer outputIContainer, IStream[] outputIStreams,
342 ICodec.Type inputICodecType, String outputURL, int index)
343 throws Exception {
344
345 ICodec iCodec = getVideoEncodingICodec(inputICodecType, outputURL);
346
347 if (iCodec == null) {
348 throw new RuntimeException(
349 "Unable to determine " + inputICodecType + " encoder for " +
350 outputURL);
351 }
352
353 IStream outputIStream = outputIContainer.addNewStream(iCodec);
354
355 outputIStreams[index] = outputIStream;
356
357 IStreamCoder outputIStreamCoder = outputIStream.getStreamCoder();
358
359 outputIStreamCoders[index] = outputIStreamCoder;
360
361 int bitRate = inputIStreamCoder.getBitRate();
362
363 if (_log.isInfoEnabled()) {
364 _log.info("Original video bitrate " + bitRate);
365 }
366
367 bitRate = getVideoBitRate(bitRate);
368
369 if (_log.isInfoEnabled()) {
370 _log.info("Modified video bitrate " + bitRate);
371 }
372
373 outputIStreamCoder.setBitRate(bitRate);
374
375 IRational iRational = inputIStreamCoder.getFrameRate();
376
377 if (_log.isInfoEnabled()) {
378 _log.info(
379 "Original frame rate " + iRational.getNumerator() + "/" +
380 iRational.getDenominator());
381 }
382
383 iRational = getVideoFrameRate(iRational);
384
385 if (_log.isInfoEnabled()) {
386 _log.info(
387 "Modified frame rate " + iRational.getNumerator() + "/" +
388 iRational.getDenominator());
389 }
390
391 outputIStreamCoder.setFrameRate(iRational);
392
393 if (inputIStreamCoder.getHeight() <= 0) {
394 throw new RuntimeException(
395 "Unable to determine height for " + _inputURL);
396 }
397
398 if (_height == 0) {
399 _height = inputIStreamCoder.getHeight();
400 }
401
402 outputIStreamCoder.setHeight(_height);
403
404 outputIStreamCoder.setPixelType(Type.YUV420P);
405 outputIStreamCoder.setTimeBase(
406 IRational.make(
407 iRational.getDenominator(), iRational.getNumerator()));
408
409 if (inputIStreamCoder.getWidth() <= 0) {
410 throw new RuntimeException(
411 "Unable to determine width for " + _inputURL);
412 }
413
414 if (_width == 0) {
415 _width = inputIStreamCoder.getWidth();
416 }
417
418 outputIStreamCoder.setWidth(_width);
419
420 iVideoResamplers[index] = createIVideoResampler(
421 inputIStreamCoder, outputIStreamCoder, _height, _width);
422
423 inputIVideoPictures[index] = IVideoPicture.make(
424 inputIStreamCoder.getPixelType(), inputIStreamCoder.getWidth(),
425 inputIStreamCoder.getHeight());
426 outputIVideoPictures[index] = IVideoPicture.make(
427 outputIStreamCoder.getPixelType(), outputIStreamCoder.getWidth(),
428 outputIStreamCoder.getHeight());
429
430 ICodec.ID iCodecID = iCodec.getID();
431
432 if (iCodecID.equals(ICodec.ID.CODEC_ID_H264)) {
433 Configuration.configure(_ffpresetProperties, outputIStreamCoder);
434 }
435 }
436
437 private static final int _VIDEO_BIT_RATE_DEFAULT = 250000;
438
439 private static final int _VIDEO_BIT_RATE_MAX = 1200000;
440
441 private static final Log _log = LogFactoryUtil.getLog(
442 LiferayVideoConverter.class);
443
444 private final Properties _ffpresetProperties;
445 private int _height = 0;
446 private IContainer _inputIContainer;
447 private final String _inputURL;
448 private IContainer _outputIContainer;
449 private final String _outputURL;
450 private int _videoBitRate;
451 private final String _videoContainer;
452 private IRational _videoFrameRate;
453 private int _width = 0;
454
455 }