001    /**
002     * Copyright (c) 2000-2011 Liferay, Inc. All rights reserved.
003     *
004     * The contents of this file are subject to the terms of the Liferay Enterprise
005     * Subscription License ("License"). You may not use this file except in
006     * compliance with the License. You can obtain a copy of the License by
007     * contacting Liferay, Inc. See the License for the specific language governing
008     * permissions and limitations under the License, including but not limited to
009     * distribution rights of the Software.
010     *
011     *
012     *
013     */
014    
015    package com.liferay.portlet.documentlibrary.util;
016    
017    import com.liferay.portal.kernel.image.ImageProcessorUtil;
018    import com.liferay.portal.kernel.log.Log;
019    import com.liferay.portal.kernel.log.LogFactoryUtil;
020    
021    import com.xuggle.xuggler.Global;
022    import com.xuggle.xuggler.IAudioResampler;
023    import com.xuggle.xuggler.IAudioSamples.Format;
024    import com.xuggle.xuggler.IAudioSamples;
025    import com.xuggle.xuggler.ICodec;
026    import com.xuggle.xuggler.IContainer;
027    import com.xuggle.xuggler.IPacket;
028    import com.xuggle.xuggler.IPixelFormat;
029    import com.xuggle.xuggler.IRational;
030    import com.xuggle.xuggler.IStream;
031    import com.xuggle.xuggler.IStreamCoder;
032    import com.xuggle.xuggler.IVideoPicture;
033    import com.xuggle.xuggler.IVideoResampler;
034    import com.xuggle.xuggler.video.ConverterFactory;
035    import com.xuggle.xuggler.video.IConverter;
036    
037    import java.awt.image.BufferedImage;
038    import java.awt.image.RenderedImage;
039    
040    import java.io.File;
041    import java.io.FileOutputStream;
042    
043    import javax.imageio.ImageIO;
044    
045    /**
046     * @author Juan González
047     * @author Sergio González
048     * @author Brian Wing Shun Chan
049     */
050    public abstract class LiferayConverter {
051    
052            protected void cleanUp(
053                    IStreamCoder[] inputIStreamCoders, IStreamCoder[] outputIStreamCoders) {
054    
055                    for (int i = 0; i < inputIStreamCoders.length; i++) {
056                            IStreamCoder inputIStreamCoder = inputIStreamCoders[i];
057    
058                            if (inputIStreamCoder != null) {
059                                    inputIStreamCoder.close();
060                            }
061    
062                            IStreamCoder outputIStreamCoder = outputIStreamCoders[i];
063    
064                            if (outputIStreamCoder != null) {
065                                    outputIStreamCoder.close();
066                            }
067                    }
068            }
069    
070            protected int countNonKeyAfterKey(
071                    IPacket inputIPacket, Boolean keyPacketFound, int nonKeyAfterKeyCount) {
072    
073                    if (inputIPacket.isKey()) {
074                            nonKeyAfterKeyCount = 0;
075                    }
076                    else if (keyPacketFound) {
077                            nonKeyAfterKeyCount++;
078                    }
079    
080                    return nonKeyAfterKeyCount;
081            }
082    
083            protected IAudioResampler createIAudioResampler(
084                            IStreamCoder inputIStreamCoder, IStreamCoder outputIStreamCoder)
085                    throws Exception {
086    
087                    IAudioResampler iAudioResampler = null;
088    
089                    Format inputSampleFormat = inputIStreamCoder.getSampleFormat();
090                    Format outputSampleFormat = inputIStreamCoder.getSampleFormat();
091    
092                    if ((inputIStreamCoder.getChannels() ==
093                                    outputIStreamCoder.getChannels()) &&
094                            (inputIStreamCoder.getSampleRate() ==
095                                    outputIStreamCoder.getSampleRate()) &&
096                            inputSampleFormat.equals(outputSampleFormat)) {
097    
098                            return iAudioResampler;
099                    }
100    
101                    iAudioResampler = IAudioResampler.make(
102                            outputIStreamCoder.getChannels(), inputIStreamCoder.getChannels(),
103                            outputIStreamCoder.getSampleRate(),
104                            inputIStreamCoder.getSampleRate(),
105                            outputIStreamCoder.getSampleFormat(),
106                            inputIStreamCoder.getSampleFormat());
107    
108                    if (iAudioResampler == null) {
109                            throw new RuntimeException("Audio resampling is not supported");
110                    }
111    
112                    return iAudioResampler;
113            }
114    
115            protected IVideoResampler createIVideoResampler(
116                            IStreamCoder inputIStreamCoder, IStreamCoder outputIStreamCoder,
117                            int height, int width)
118                    throws Exception {
119    
120                    IVideoResampler iVideoResampler = null;
121    
122                    IPixelFormat.Type inputIPixelFormatType =
123                            inputIStreamCoder.getPixelType();
124                    IPixelFormat.Type outputIPixelFormatType =
125                            outputIStreamCoder.getPixelType();
126    
127                    if ((height == inputIStreamCoder.getHeight()) &&
128                            (width == inputIStreamCoder.getWidth()) &&
129                            inputIPixelFormatType.equals(outputIPixelFormatType)) {
130    
131                            return iVideoResampler;
132                    }
133    
134                    iVideoResampler = IVideoResampler.make(
135                            width, height, outputIStreamCoder.getPixelType(),
136                            inputIStreamCoder.getWidth(), inputIStreamCoder.getHeight(),
137                            inputIStreamCoder.getPixelType());
138    
139                    if (iVideoResampler == null) {
140                            throw new RuntimeException("Video resampling is not supported");
141                    }
142    
143                    return iVideoResampler;
144            }
145    
146            protected void decodeAudio(
147                            IAudioResampler iAudioResampler, IAudioSamples inputIAudioSample,
148                            IAudioSamples resampledIAudioSample, IPacket inputIPacket,
149                            IPacket outputIPacket, IStreamCoder inputIStreamCoder,
150                            IStreamCoder outputIStreamCoder, IContainer outputIContainer,
151                            int currentPacketSize, int previousPacketSize, int streamIndex,
152                            long timeStampOffset)
153                    throws Exception {
154    
155                    int offset = 0;
156    
157                    while (offset < inputIPacket.getSize()) {
158                            boolean stopDecoding = false;
159    
160                            int value = inputIStreamCoder.decodeAudio(
161                                    inputIAudioSample, inputIPacket, offset);
162    
163                            if (value <= 0) {
164                                    if ((previousPacketSize == currentPacketSize) &&
165                                            (previousPacketSize != -1)) {
166    
167                                            throw new RuntimeException(
168                                                    "Unable to decode audio stream " + streamIndex);
169                                    }
170                                    else {
171                                            stopDecoding = true;
172                                    }
173                            }
174    
175                            updateAudioTimeStamp(inputIAudioSample, timeStampOffset);
176    
177                            offset += value;
178    
179                            IAudioSamples outputIAudioSample = resampleAudio(
180                                    iAudioResampler, inputIAudioSample, resampledIAudioSample);
181    
182                            encodeAudio(
183                                    outputIStreamCoder, outputIPacket, outputIAudioSample,
184                                    outputIContainer);
185    
186                            if (stopDecoding) {
187                                    if (_log.isDebugEnabled()) {
188                                            _log.debug("Stop decoding audio stream " + streamIndex);
189                                    }
190    
191                                    break;
192                            }
193                    }
194            }
195    
196            protected int decodeVideo(
197                            IVideoResampler iVideoResampler, IVideoPicture inputIVideoPicture,
198                            IVideoPicture resampledIVideoPicture, IPacket inputIPacket,
199                            IPacket outputIPacket, IStreamCoder inputIStreamCoder,
200                            IStreamCoder outputIStreamCoder, IContainer outputIContainer,
201                            File thumbnailFile, String thumbnailExtension, int thumbnailHeight,
202                            int thumbnailWidth, long timeStampOffset)
203                    throws Exception {
204    
205                    int value = inputIStreamCoder.decodeVideo(
206                            inputIVideoPicture, inputIPacket, 0);
207    
208                    if (value <= 0) {
209                            return value;
210                    }
211    
212                    updateVideoTimeStamp(inputIVideoPicture, timeStampOffset);
213    
214                    if (!inputIVideoPicture.isComplete()) {
215                            return 1;
216                    }
217    
218                    if (thumbnailFile != null) {
219                            BufferedImage bufferedImage = null;
220    
221                            if (_converterFactoryType == null) {
222                                    _converterFactoryType =
223                                            ConverterFactory.findRegisteredConverter(
224                                                    ConverterFactory.XUGGLER_BGR_24);
225                            }
226    
227                            if (_converterFactoryType == null) {
228                                    throw new UnsupportedOperationException(
229                                            "No converter found for " +
230                                                    ConverterFactory.XUGGLER_BGR_24);
231                            }
232    
233                            if (_videoIConverter == null) {
234                                    _videoIConverter = ConverterFactory.createConverter(
235                                            _converterFactoryType.getDescriptor(),
236                                            inputIVideoPicture);
237                            }
238    
239                            bufferedImage = _videoIConverter.toImage(inputIVideoPicture);
240    
241                            thumbnailFile.createNewFile();
242    
243                            RenderedImage renderedImage = ImageProcessorUtil.scale(
244                                    bufferedImage, thumbnailHeight, thumbnailWidth);
245    
246                            ImageIO.write(
247                                    renderedImage, thumbnailExtension,
248                                    new FileOutputStream(thumbnailFile));
249    
250                            return DECODE_VIDEO_THUMBNAIL;
251                    }
252    
253                    if ((outputIStreamCoder != null) && (outputIContainer != null)) {
254                            IVideoPicture outputIVideoPicture = null;
255    
256                            outputIVideoPicture = resampleVideo(
257                                    iVideoResampler, inputIVideoPicture, resampledIVideoPicture);
258    
259                            outputIVideoPicture.setQuality(0);
260    
261                            encodeVideo(
262                                    outputIStreamCoder, outputIVideoPicture, outputIPacket,
263                                    outputIContainer);
264                    }
265    
266                    return 1;
267            }
268    
269            protected void encodeAudio(
270                            IStreamCoder outputIStreamCoder, IPacket outputIPacket,
271                            IAudioSamples outputIAudioSample, IContainer outputIContainer)
272                    throws Exception {
273    
274                    int consumedSamplesCount = 0;
275    
276                    while (consumedSamplesCount < outputIAudioSample.getNumSamples()) {
277                            int value = outputIStreamCoder.encodeAudio(
278                                    outputIPacket, outputIAudioSample, consumedSamplesCount);
279    
280                            if (value <= 0) {
281                                    throw new RuntimeException("Unable to encode audio");
282                            }
283    
284                            consumedSamplesCount += value;
285    
286                            if (outputIPacket.isComplete()) {
287                                    value = outputIContainer.writePacket(outputIPacket, true);
288    
289                                    if (value < 0) {
290                                            throw new RuntimeException("Unable to write audio packet");
291                                    }
292                            }
293                    }
294            }
295    
296            protected void encodeVideo(
297                            IStreamCoder outputIStreamCoder, IVideoPicture outputIVideoPicture,
298                            IPacket outputIPacket, IContainer outputIContainer)
299                    throws Exception {
300    
301                    if (outputIStreamCoder.encodeVideo(
302                                    outputIPacket, outputIVideoPicture, 0) < 0) {
303    
304                            throw new RuntimeException("Unable to encode video");
305                    }
306    
307                    if (outputIPacket.isComplete()) {
308                            if (outputIContainer.writePacket(outputIPacket, true) < 0) {
309                                    throw new RuntimeException("Unable to write video packet");
310                            }
311                    }
312            }
313    
314            protected void flush(
315                    IStreamCoder[] outputIStreamCoders, IContainer outputIContainer) {
316    
317                    for (IStreamCoder outputIStreamCoder : outputIStreamCoders) {
318                            if (outputIStreamCoder == null) {
319                                    continue;
320                            }
321    
322                            IPacket iPacket = IPacket.make();
323    
324                            if (outputIStreamCoder.getCodecType() ==
325                                            ICodec.Type.CODEC_TYPE_AUDIO) {
326    
327                                    outputIStreamCoder.encodeAudio(iPacket, null, 0);
328                            }
329                            else {
330                                    outputIStreamCoder.encodeVideo(iPacket, null, 0);
331                            }
332    
333                            if (iPacket.isComplete()) {
334                                    outputIContainer.writePacket(iPacket, true);
335                            }
336                    }
337            }
338    
339            protected abstract IContainer getInputIContainer();
340    
341            protected long getSeekTimeStamp(int percentage) throws Exception {
342                    IContainer inputIContainer = getInputIContainer();
343    
344                    long seekTimeStamp = -1;
345    
346                    long videoSeconds = inputIContainer.getDuration() / 1000000L;
347    
348                    long seekSeconds = ((videoSeconds * percentage) / 100L);
349    
350                    for (int i = 0; i < inputIContainer.getNumStreams(); i++) {
351                            IStream inputIStream = inputIContainer.getStream(i);
352    
353                            IStreamCoder inputIStreamCoder = inputIStream.getStreamCoder();
354    
355                            if (inputIStreamCoder.getCodecType() !=
356                                            ICodec.Type.CODEC_TYPE_VIDEO) {
357    
358                                    continue;
359                            }
360    
361                            IRational iRational = inputIStream.getTimeBase();
362    
363                            long timeStampOffset =
364                                    iRational.getDenominator() / iRational.getNumerator() *
365                                            seekSeconds;
366    
367                            seekTimeStamp = inputIContainer.getStartTime() + timeStampOffset;
368    
369                            break;
370                    }
371    
372                    return seekTimeStamp;
373            }
374    
375            protected long getStreamTimeStampOffset(IStream iStream) {
376                    long timeStampOffset = 0;
377    
378                    if ((iStream.getStartTime() != Global.NO_PTS) &&
379                            (iStream.getStartTime() > 0) && (iStream.getTimeBase() != null)) {
380    
381                            IRational iRational = IRational.make(
382                                    1, (int)Global.DEFAULT_PTS_PER_SECOND);
383    
384                            timeStampOffset = iRational.rescale(
385                                    iStream.getStartTime(), iStream.getTimeBase());
386                    }
387    
388                    return timeStampOffset;
389            }
390    
391            protected boolean isKeyPacketFound(
392                    IPacket inputIPacket, boolean keyPacketFound) {
393    
394                    if (inputIPacket.isKey() && !keyPacketFound) {
395                            return true;
396                    }
397    
398                    return keyPacketFound;
399            }
400    
401            protected boolean isStartDecoding(
402                    IPacket inputIPacket, IStreamCoder inputIStreamCoder,
403                    boolean keyPacketFound, int nonKeyAfterKeyCount,
404                    boolean onlyDecodeKeyPackets) {
405    
406                    if (onlyDecodeKeyPackets && !inputIPacket.isKey()) {
407                            return false;
408                    }
409    
410                    ICodec.ID iCodecID = inputIStreamCoder.getCodecID();
411    
412                    if (iCodecID.equals(ICodec.ID.CODEC_ID_MJPEG)) {
413                            return true;
414                    }
415                    else if (iCodecID.equals(ICodec.ID.CODEC_ID_MPEG2VIDEO)) {
416                            if (nonKeyAfterKeyCount != 1) {
417                                    return true;
418                            }
419    
420                            return false;
421                    }
422    
423                    return keyPacketFound;
424            }
425    
426            protected void openContainer(
427                            IContainer iContainer, String url, boolean writeContainer)
428                    throws Exception {
429    
430                    int value = 0;
431    
432                    if (writeContainer) {
433                            value = iContainer.open(url, IContainer.Type.WRITE, null);
434                    }
435                    else {
436                            value = iContainer.open(url, IContainer.Type.READ, null);
437                    }
438    
439                    if (value < 0) {
440                            if (writeContainer) {
441                                    throw new RuntimeException("Unable to open output URL");
442                            }
443                            else {
444                                    throw new RuntimeException("Unable to open input URL");
445                            }
446                    }
447            }
448    
449            protected void openStreamCoder(IStreamCoder iStreamCoder)
450                    throws Exception {
451    
452                    if ((iStreamCoder != null) && (iStreamCoder.open() < 0)) {
453                            throw new RuntimeException("Unable to open coder");
454                    }
455            }
456    
457            protected void prepareAudio(
458                            IAudioResampler[] iAudioResamplers,
459                            IAudioSamples[] inputIAudioSamples,
460                            IAudioSamples[] outputIAudioSamples, IStreamCoder inputIStreamCoder,
461                            IStreamCoder[] outputIStreamCoders, IContainer outputIContainer,
462                            IStream[] outputIStreams, ICodec.Type inputICodecType,
463                            String outputURL, int channels, int rate, int index)
464                    throws Exception {
465    
466                    IStream outputIStream = outputIContainer.addNewStream(index);
467    
468                    outputIStreams[index] = outputIStream;
469    
470                    IStreamCoder outputIStreamCoder = outputIStream.getStreamCoder();
471    
472                    outputIStreamCoders[index] = outputIStreamCoder;
473    
474                    outputIStreamCoder.setBitRate(inputIStreamCoder.getBitRate());
475                    outputIStreamCoder.setChannels(channels);
476    
477                    ICodec iCodec = ICodec.guessEncodingCodec(
478                            null, null, outputURL, null, inputICodecType);
479    
480                    if (iCodec == null) {
481                            throw new RuntimeException(
482                                    "Unable to determine " + inputICodecType + " encoder for " +
483                                            outputURL);
484                    }
485    
486                    outputIStreamCoder.setCodec(iCodec);
487    
488                    outputIStreamCoder.setGlobalQuality(0);
489                    outputIStreamCoder.setSampleRate(rate);
490    
491                    iAudioResamplers[index] = createIAudioResampler(
492                            inputIStreamCoder, outputIStreamCoder);
493    
494                    inputIAudioSamples[index] = IAudioSamples.make(
495                            1024, inputIStreamCoder.getChannels());
496                    outputIAudioSamples[index] = IAudioSamples.make(
497                            1024, outputIStreamCoder.getChannels());
498            }
499    
500            protected IAudioSamples resampleAudio(
501                            IAudioResampler iAudioResampler, IAudioSamples inputIAudioSample,
502                            IAudioSamples resampledIAudioSample)
503                    throws Exception {
504    
505                    if ((iAudioResampler == null) ||
506                            (inputIAudioSample.getNumSamples() <= 0)) {
507    
508                            return inputIAudioSample;
509                    }
510    
511                    iAudioResampler.resample(
512                            resampledIAudioSample, inputIAudioSample,
513                            inputIAudioSample.getNumSamples());
514    
515                    return resampledIAudioSample;
516            }
517    
518            protected IVideoPicture resampleVideo(
519                            IVideoResampler iVideoResampler, IVideoPicture inputIVideoPicture,
520                            IVideoPicture resampledIVideoPicture)
521                    throws Exception {
522    
523                    if (iVideoResampler == null) {
524                            return inputIVideoPicture;
525                    }
526    
527                    if (iVideoResampler.resample(
528                                    resampledIVideoPicture, inputIVideoPicture) < 0) {
529    
530                            throw new RuntimeException("Unable to resample video");
531                    }
532    
533                    return resampledIVideoPicture;
534            }
535    
536            protected void rewind() throws Exception {
537                    IContainer inputIContainer = getInputIContainer();
538    
539                    if (inputIContainer == null) {
540                            return;
541                    }
542    
543                    int value = 0;
544    
545                    for (int i = 0; i < inputIContainer.getNumStreams(); i++) {
546                            IStream inputIStream = inputIContainer.getStream(i);
547    
548                            IStreamCoder inputIStreamCoder = inputIStream.getStreamCoder();
549    
550                            if (inputIStreamCoder.getCodecType() !=
551                                            ICodec.Type.CODEC_TYPE_VIDEO) {
552    
553                                    continue;
554                            }
555    
556                            value = rewind(i);
557    
558                            if (value < 0) {
559                                    throw new RuntimeException("Error while seeking file");
560                            }
561    
562                            break;
563                    }
564            }
565    
566            protected int rewind(int index) throws Exception {
567                    IContainer inputIContainer = getInputIContainer();
568    
569                    if (inputIContainer == null) {
570                            return -1;
571                    }
572    
573                    int value = inputIContainer.seekKeyFrame(index, -1, 0);
574    
575                    if (value < 0) {
576                            throw new RuntimeException("Error while seeking file");
577                    }
578    
579                    return value;
580            }
581    
582            protected int seek(int index, long timeStamp) throws Exception {
583                    IContainer inputIContainer = getInputIContainer();
584    
585                    if (inputIContainer == null) {
586                            return -1;
587                    }
588    
589                    int value = inputIContainer.seekKeyFrame(index, timeStamp, 0);
590    
591                    if (value < 0) {
592                            throw new RuntimeException("Error while seeking file");
593                    }
594    
595                    return value;
596            }
597    
598            protected long seek(long timeStamp) throws Exception {
599                    IContainer inputIContainer = getInputIContainer();
600    
601                    if (inputIContainer == null) {
602                            return -1;
603                    }
604    
605                    int value = 0;
606    
607                    for (int i = 0; i < inputIContainer.getNumStreams(); i++) {
608                            IStream inputIStream = inputIContainer.getStream(i);
609    
610                            IStreamCoder inputIStreamCoder = inputIStream.getStreamCoder();
611    
612                            if (inputIStreamCoder.getCodecType() !=
613                                            ICodec.Type.CODEC_TYPE_VIDEO) {
614    
615                                    continue;
616                            }
617    
618                            value = seek(i, timeStamp);
619    
620                            if (value < 0) {
621                                    throw new RuntimeException("Error while seeking file");
622                            }
623    
624                            break;
625                    }
626    
627                    return value;
628            }
629    
630            protected void updateAudioTimeStamp(
631                    IAudioSamples inputAudioSample, long timeStampOffset) {
632    
633                    if (inputAudioSample.getTimeStamp() != Global.NO_PTS) {
634                            inputAudioSample.setTimeStamp(
635                                    inputAudioSample.getTimeStamp() - timeStampOffset);
636                    }
637            }
638    
639            protected void updateVideoTimeStamp(
640                    IVideoPicture inputIVideoPicture, long timeStampOffset) {
641    
642                    if (inputIVideoPicture.getTimeStamp() != Global.NO_PTS) {
643                            inputIVideoPicture.setTimeStamp(
644                                    inputIVideoPicture.getTimeStamp() - timeStampOffset);
645                    }
646            }
647    
648            protected static final int DECODE_VIDEO_THUMBNAIL = 2;
649    
650            private static Log _log = LogFactoryUtil.getLog(LiferayConverter.class);
651    
652            private ConverterFactory.Type _converterFactoryType;
653            private IConverter _videoIConverter;
654    
655    }