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