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