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