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