/**
* Copyright (c) 2000-present Liferay, Inc. All rights reserved.
*
* This library is free software; you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 2.1 of the License, or (at your option)
* any later version.
*
* This library is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
* details.
*/
package com.liferay.portlet.documentlibrary.util;
import com.liferay.portal.kernel.log.Log;
import com.liferay.portal.kernel.log.LogFactoryUtil;
import com.liferay.portal.kernel.util.GetterUtil;
import com.liferay.portal.kernel.util.PropsKeys;
import com.liferay.portal.kernel.util.StringPool;
import com.xuggle.xuggler.Configuration;
import com.xuggle.xuggler.IAudioResampler;
import com.xuggle.xuggler.IAudioSamples;
import com.xuggle.xuggler.ICodec;
import com.xuggle.xuggler.IContainer;
import com.xuggle.xuggler.IContainerFormat;
import com.xuggle.xuggler.IPacket;
import com.xuggle.xuggler.IPixelFormat.Type;
import com.xuggle.xuggler.IRational;
import com.xuggle.xuggler.IStream;
import com.xuggle.xuggler.IStreamCoder;
import com.xuggle.xuggler.IVideoPicture;
import com.xuggle.xuggler.IVideoResampler;
import java.io.File;
import java.util.Properties;
/**
* @author Juan González
* @author Sergio González
* @author Brian Wing Shun Chan
* @author Alexander Chow
*/
public class LiferayVideoConverter extends LiferayConverter {
public LiferayVideoConverter(
String inputURL, String outputURL, String videoContainer,
Properties videoProperties, Properties ffpresetProperties) {
_inputURL = inputURL;
_outputURL = outputURL;
_videoContainer = videoContainer;
_ffpresetProperties = ffpresetProperties;
_height = GetterUtil.getInteger(
videoProperties.getProperty(
PropsKeys.DL_FILE_ENTRY_PREVIEW_VIDEO_HEIGHT),
_height);
_width = GetterUtil.getInteger(
videoProperties.getProperty(
PropsKeys.DL_FILE_ENTRY_PREVIEW_VIDEO_WIDTH),
_width);
initVideoBitRate(videoProperties);
initVideoFrameRate(videoProperties);
}
@Override
public void convert() throws Exception {
try {
doConvert();
}
finally {
if ((_inputIContainer != null) && _inputIContainer.isOpened()) {
_inputIContainer.close();
}
if ((_outputIContainer != null) && _outputIContainer.isOpened()) {
_outputIContainer.close();
}
}
createMP4FastStart();
}
protected void createMP4FastStart() {
File videoFile = new File(_outputURL);
if (!_videoContainer.equals("mp4") || !videoFile.exists()) {
return;
}
File tempFile = new File(_outputURL + ".tmp");
try {
JQTFastStart.convert(videoFile, tempFile);
if (tempFile.exists() && (tempFile.length() > 0)) {
videoFile.delete();
tempFile.renameTo(videoFile);
}
}
catch (Exception e) {
if (_log.isWarnEnabled()) {
_log.warn("Unable to move MOOV atom to front of MP4 file");
}
}
finally {
tempFile.delete();
}
}
protected void doConvert() throws Exception {
_inputIContainer = IContainer.make();
_outputIContainer = IContainer.make();
openContainer(_inputIContainer, _inputURL, false);
openContainer(_outputIContainer, _outputURL, true);
int inputStreamsCount = _inputIContainer.getNumStreams();
if (inputStreamsCount < 0) {
throw new RuntimeException("Input URL does not have any streams");
}
IAudioResampler[] iAudioResamplers =
new IAudioResampler[inputStreamsCount];
IVideoResampler[] iVideoResamplers =
new IVideoResampler[inputStreamsCount];
IAudioSamples[] inputIAudioSamples =
new IAudioSamples[inputStreamsCount];
IAudioSamples[] outputIAudioSamples =
new IAudioSamples[inputStreamsCount];
IVideoPicture[] inputIVideoPictures =
new IVideoPicture[inputStreamsCount];
IVideoPicture[] outputIVideoPictures =
new IVideoPicture[inputStreamsCount];
IStream[] outputIStreams = new IStream[inputStreamsCount];
IStreamCoder[] inputIStreamCoders = new IStreamCoder[inputStreamsCount];
IStreamCoder[] outputIStreamCoders =
new IStreamCoder[inputStreamsCount];
for (int i = 0; i < inputStreamsCount; i++) {
IStream inputIStream = _inputIContainer.getStream(i);
IStreamCoder inputIStreamCoder = inputIStream.getStreamCoder();
inputIStreamCoders[i] = inputIStreamCoder;
ICodec.Type inputICodecType = inputIStreamCoder.getCodecType();
if (inputICodecType == ICodec.Type.CODEC_TYPE_AUDIO) {
prepareAudio(
iAudioResamplers, inputIAudioSamples, outputIAudioSamples,
inputIStreamCoder, outputIStreamCoders, _outputIContainer,
outputIStreams, inputICodecType, _outputURL, i);
}
else if (inputICodecType == ICodec.Type.CODEC_TYPE_VIDEO) {
prepareVideo(
iVideoResamplers, inputIVideoPictures, outputIVideoPictures,
inputIStreamCoder, outputIStreamCoders, _outputIContainer,
outputIStreams, inputICodecType, _outputURL, i);
}
openStreamCoder(inputIStreamCoders[i]);
openStreamCoder(outputIStreamCoders[i]);
}
if (_outputIContainer.writeHeader() < 0) {
throw new RuntimeException("Unable to write container header");
}
boolean keyPacketFound = false;
int nonKeyAfterKeyCount = 0;
boolean onlyDecodeKeyPackets = false;
int previousPacketSize = -1;
IPacket inputIPacket = IPacket.make();
IPacket outputIPacket = IPacket.make();
while (_inputIContainer.readNextPacket(inputIPacket) == 0) {
if (_log.isDebugEnabled()) {
_log.debug("Current packet size " + inputIPacket.getSize());
}
int streamIndex = inputIPacket.getStreamIndex();
IStreamCoder inputIStreamCoder = inputIStreamCoders[streamIndex];
IStreamCoder outputIStreamCoder = outputIStreamCoders[streamIndex];
if (outputIStreamCoder == null) {
continue;
}
IStream iStream = _inputIContainer.getStream(streamIndex);
long timeStampOffset = getStreamTimeStampOffset(iStream);
if (inputIStreamCoder.getCodecType() ==
ICodec.Type.CODEC_TYPE_AUDIO) {
decodeAudio(
iAudioResamplers[streamIndex],
inputIAudioSamples[streamIndex],
outputIAudioSamples[streamIndex], inputIPacket,
outputIPacket, inputIStreamCoder, outputIStreamCoder,
_outputIContainer, inputIPacket.getSize(),
previousPacketSize, streamIndex, timeStampOffset);
}
else if (inputIStreamCoder.getCodecType() ==
ICodec.Type.CODEC_TYPE_VIDEO) {
keyPacketFound = isKeyPacketFound(inputIPacket, keyPacketFound);
nonKeyAfterKeyCount = countNonKeyAfterKey(
inputIPacket, keyPacketFound, nonKeyAfterKeyCount);
if (isStartDecoding(
inputIPacket, inputIStreamCoder, keyPacketFound,
nonKeyAfterKeyCount, onlyDecodeKeyPackets)) {
int value = decodeVideo(
iVideoResamplers[streamIndex],
inputIVideoPictures[streamIndex],
outputIVideoPictures[streamIndex], inputIPacket,
outputIPacket, inputIStreamCoder, outputIStreamCoder,
_outputIContainer, null, null, 0, 0, timeStampOffset);
if (value <= 0) {
if (inputIPacket.isKey()) {
throw new RuntimeException(
"Unable to decode video stream " + streamIndex);
}
onlyDecodeKeyPackets = true;
continue;
}
}
else {
if (_log.isDebugEnabled()) {
_log.debug("Do not decode video stream " + streamIndex);
}
}
}
previousPacketSize = inputIPacket.getSize();
}
flush(outputIStreamCoders, _outputIContainer);
if (_outputIContainer.writeTrailer() < 0) {
throw new RuntimeException(
"Unable to write trailer to output file");
}
cleanUp(iAudioResamplers, iVideoResamplers);
cleanUp(inputIAudioSamples, outputIAudioSamples);
cleanUp(inputIVideoPictures, outputIVideoPictures);
cleanUp(inputIStreamCoders, outputIStreamCoders);
cleanUp(inputIPacket, outputIPacket);
}
@Override
protected IContainer getInputIContainer() {
return _inputIContainer;
}
protected int getVideoBitRate(int originalBitRate) {
return getProperty(originalBitRate, _videoBitRate, _VIDEO_BIT_RATE_MAX);
}
protected ICodec getVideoEncodingICodec(
ICodec.Type inputICodecType, String outputURL) {
IContainerFormat iContainerFormat =
_outputIContainer.getContainerFormat();
String outputFormat = iContainerFormat.getOutputFormatShortName();
if (outputFormat.equals("mp4")) {
return ICodec.findEncodingCodec(ICodec.ID.CODEC_ID_H264);
}
else {
return ICodec.guessEncodingCodec(
null, null, outputURL, null, inputICodecType);
}
}
protected IRational getVideoFrameRate(IRational originalFrameRate) {
if (_videoFrameRate != null) {
originalFrameRate = _videoFrameRate;
}
return originalFrameRate;
}
protected void initVideoBitRate(Properties videoProperties) {
_videoBitRate = getProperty(
videoProperties, PropsKeys.DL_FILE_ENTRY_PREVIEW_VIDEO_BIT_RATE,
"video bit rate", _videoContainer, _VIDEO_BIT_RATE_DEFAULT,
_VIDEO_BIT_RATE_MAX);
}
protected void initVideoFrameRate(Properties videoProperties) {
int numerator = GetterUtil.getInteger(
videoProperties.getProperty(
PropsKeys.DL_FILE_ENTRY_PREVIEW_VIDEO_FRAME_RATE_NUMERATOR +
"[" + _videoContainer + "]"));
int denominator = GetterUtil.getInteger(
videoProperties.getProperty(
PropsKeys.DL_FILE_ENTRY_PREVIEW_VIDEO_FRAME_RATE_DENOMINATOR +
StringPool.OPEN_BRACKET + _videoContainer +
StringPool.CLOSE_BRACKET));
if ((numerator > 0) && (denominator > 0)) {
_videoFrameRate = IRational.make(numerator, denominator);
if (_log.isInfoEnabled()) {
_log.info(
"Default frame rate for " + _videoContainer +
" configured to " + _videoFrameRate.getNumerator() +
"/" + _videoFrameRate.getDenominator());
}
}
}
protected void prepareVideo(
IVideoResampler[] iVideoResamplers,
IVideoPicture[] inputIVideoPictures,
IVideoPicture[] outputIVideoPictures,
IStreamCoder inputIStreamCoder, IStreamCoder[] outputIStreamCoders,
IContainer outputIContainer, IStream[] outputIStreams,
ICodec.Type inputICodecType, String outputURL, int index)
throws Exception {
ICodec iCodec = getVideoEncodingICodec(inputICodecType, outputURL);
if (iCodec == null) {
throw new RuntimeException(
"Unable to determine " + inputICodecType + " encoder for " +
outputURL);
}
IStream outputIStream = outputIContainer.addNewStream(iCodec);
outputIStreams[index] = outputIStream;
IStreamCoder outputIStreamCoder = outputIStream.getStreamCoder();
outputIStreamCoders[index] = outputIStreamCoder;
int bitRate = inputIStreamCoder.getBitRate();
if (_log.isInfoEnabled()) {
_log.info("Original video bitrate " + bitRate);
}
bitRate = getVideoBitRate(bitRate);
if (_log.isInfoEnabled()) {
_log.info("Modified video bitrate " + bitRate);
}
outputIStreamCoder.setBitRate(bitRate);
IRational iRational = inputIStreamCoder.getFrameRate();
if (_log.isInfoEnabled()) {
_log.info(
"Original frame rate " + iRational.getNumerator() + "/" +
iRational.getDenominator());
}
iRational = getVideoFrameRate(iRational);
if (_log.isInfoEnabled()) {
_log.info(
"Modified frame rate " + iRational.getNumerator() + "/" +
iRational.getDenominator());
}
outputIStreamCoder.setFrameRate(iRational);
if (inputIStreamCoder.getHeight() <= 0) {
throw new RuntimeException(
"Unable to determine height for " + _inputURL);
}
if (_height == 0) {
_height = inputIStreamCoder.getHeight();
}
outputIStreamCoder.setHeight(_height);
outputIStreamCoder.setPixelType(Type.YUV420P);
outputIStreamCoder.setTimeBase(
IRational.make(
iRational.getDenominator(), iRational.getNumerator()));
if (inputIStreamCoder.getWidth() <= 0) {
throw new RuntimeException(
"Unable to determine width for " + _inputURL);
}
if (_width == 0) {
_width = inputIStreamCoder.getWidth();
}
outputIStreamCoder.setWidth(_width);
iVideoResamplers[index] = createIVideoResampler(
inputIStreamCoder, outputIStreamCoder, _height, _width);
inputIVideoPictures[index] = IVideoPicture.make(
inputIStreamCoder.getPixelType(), inputIStreamCoder.getWidth(),
inputIStreamCoder.getHeight());
outputIVideoPictures[index] = IVideoPicture.make(
outputIStreamCoder.getPixelType(), outputIStreamCoder.getWidth(),
outputIStreamCoder.getHeight());
ICodec.ID iCodecID = iCodec.getID();
if (iCodecID.equals(ICodec.ID.CODEC_ID_H264)) {
Configuration.configure(_ffpresetProperties, outputIStreamCoder);
}
}
private static final int _VIDEO_BIT_RATE_DEFAULT = 250000;
private static final int _VIDEO_BIT_RATE_MAX = 1200000;
private static final Log _log = LogFactoryUtil.getLog(
LiferayVideoConverter.class);
private final Properties _ffpresetProperties;
private int _height;
private IContainer _inputIContainer;
private final String _inputURL;
private IContainer _outputIContainer;
private final String _outputURL;
private int _videoBitRate;
private final String _videoContainer;
private IRational _videoFrameRate;
private int _width;
}