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