001
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
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 }