package test.org.korsakow.encoding; import java.io.File; import java.util.Properties; import org.junit.Assert; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.korsakow.ide.Main; import org.korsakow.ide.resources.media.FFMpegMediaInfoFactory; import org.korsakow.ide.resources.media.MediaInfo; import org.korsakow.ide.resources.media.QTMediaInfoFactory; import org.korsakow.ide.util.ExternalsResourceManager; import org.korsakow.ide.util.FileUtil; import org.korsakow.ide.util.Platform; import org.korsakow.ide.util.ResourceManager; import org.korsakow.services.encoders.video.ffmpeg.FFMpegEncoder; import org.korsakow.services.encoders.video.ffmpeg.plaf.FFMpegEncoderOSX; import org.korsakow.services.encoders.video.ffmpeg.plaf.FFMpegEncoderWin32; import org.korsakow.services.export.IVideoEncodingProfile; import org.korsakow.services.export.PropertiesVideoEncodingProfile; import org.korsakow.services.export.task.VideoExportTask; import quicktime.QTSession; import test.Warning; import test.util.BaseTestCase; public class TestVideoExport extends BaseTestCase { /** * Tolerance History. * * 1001: testDIVX_MP2 has a delta of 1000 * 1002: testDIVX_MP2 has a delta of 1001 (slightly different encoding options) * */ private static final long SIZE_TOLERANCE = 1002; private File parentDir; private File dest; private MediaInfo beforeInfoQT; private MediaInfo beforeInfoFFMpeg; public TestVideoExport() { Main.setupPlatformEncoders(); } private FFMpegEncoder getPlatformEncoder(File parentDir) throws Exception { switch (Platform.getOS()) { case MAC: return new FFMpegEncoderOSX(); case WIN: return new FFMpegEncoderWin32(); case NIX: default: Assert.fail("No Encoder for platform: " + Platform.getOS().getCanonicalName()); break; } throw new AssertionError(); } @Override @Before public void setUp() throws Exception { QTSession.open(); parentDir = FileUtil.createTempDirectory("test", ""); parentDir.deleteOnExit(); } @Override @After public void tearDown() { parentDir = null; dest = null; beforeInfoQT = null; beforeInfoFFMpeg = null; QTSession.close(); } private void doPre(File src, File dest) throws Exception { // dest.deleteOnExit(); Assert.assertTrue("Can read source file", src.canRead()); Assert.assertTrue("Can write destination file", dest.canWrite()); try { beforeInfoQT = QTMediaInfoFactory.getInfo(src); } catch (Throwable t) { beforeInfoQT = null; } try { beforeInfoFFMpeg = FFMpegMediaInfoFactory.getInfo(src); } catch (Throwable t) { beforeInfoFFMpeg = null; } } private void doPost(File dest) throws Exception { Assert.assertNotSame(dest.length(), 0); MediaInfo afterInfoQT = null; try { afterInfoQT = QTMediaInfoFactory.getInfo(dest); } catch (Throwable t) { } MediaInfo afterInfoFFMPeg = null; try { afterInfoFFMPeg = FFMpegMediaInfoFactory.getInfo(dest); } catch (Throwable t) { } // FileUtil.copyFile(dest, new File("/Users/d/Desktop/test.mov")); if (beforeInfoFFMpeg != null && afterInfoFFMPeg != null) Warning.warnTrue(String.format("FFMPEG: Math.abs(%d - %d) = %d < %d", beforeInfoFFMpeg.duration, afterInfoFFMPeg.duration, Math.abs(beforeInfoFFMpeg.duration - afterInfoFFMPeg.duration), SIZE_TOLERANCE), Math.abs(beforeInfoFFMpeg.duration - afterInfoFFMPeg.duration) < SIZE_TOLERANCE); else Warning.warnTrue("No FFMpegInfo", false); if (beforeInfoQT != null && afterInfoQT != null) Warning.warnTrue(String.format("QT: Math.abs(%d - %d) = %d < %d", beforeInfoQT.duration, afterInfoQT.duration, Math.abs(beforeInfoQT.duration - afterInfoQT.duration), SIZE_TOLERANCE), Math.abs(beforeInfoQT.duration - afterInfoQT.duration) < SIZE_TOLERANCE); else Warning.warnTrue("No QTInfo", false); // ShellExec.revealInPlatformFilesystemBrowser(dest.getAbsolutePath()); } private void test(String source) throws Exception { doTestFLVLow(source); doTestFLVMed(source); doTestFLVHigh(source); // doTestFLVMax(source); doTestX264Low(source); doTestX264Med(source); doTestX264High(source); // doTestX264Max(source); // doTestX264Lossless(source); } private void doTestFLVLow(String source) throws Exception { doTestCommon(source, "flv_low"); } private void doTestFLVMed(String source) throws Exception { doTestCommon(source, "flv_med"); } private void doTestFLVHigh(String source) throws Exception { doTestCommon(source, "flv_high"); } private void doTestX264Low(String source) throws Exception { doTestCommon(source, "x264_low"); } private void doTestX264Med(String source) throws Exception { doTestCommon(source, "x264_med"); } private void doTestX264High(String source) throws Exception { doTestCommon(source, "x264_high"); } private void doTestCommon(String source, String profileFile) throws Exception { Properties props = new Properties(); props.load(ResourceManager.getResourceStream("encodingprofiles/" + profileFile + ".properties")); IVideoEncodingProfile profile = new PropertiesVideoEncodingProfile(props); System.out.println(String.format("\tProfile: %s", profile.getName())); dest = File.createTempFile(profile.getName(), ".flv", parentDir); File src = new File("resources/" + source); doPre(src, dest); long timeBefore = System.currentTimeMillis(); VideoExportTask.encodeVideo(src, dest, profile, 320, 240); long timeAfter = System.currentTimeMillis(); System.out.println(String.format("Took %f seconds", (timeAfter-timeBefore)/1000.0)); doPost(dest); } @Test public void testCircleOfLife_Boy() throws Exception { test("CircleOfLife/boy.mov"); } @Test public void testCircleOfLife_Girl() throws Exception { test("CircleOfLife/girl.mov"); } @Test public void testCircleOfLife_Couple() throws Exception { test("CircleOfLife/couple.mov"); } @Test public void testCircleOfLife_CoupleNaked() throws Exception { test("CircleOfLife/couple_naked.mov"); } @Test public void testCircleOfLife_Family() throws Exception { test("CircleOfLife/family.mov"); } @Test public void testCircleOfLife_Man01() throws Exception { test("CircleOfLife/man01.mov"); } @Test public void testCircleOfLife_Man02() throws Exception { test("CircleOfLife/man02.mov"); } @Test public void testCircleOfLife_Man03() throws Exception { test("CircleOfLife/man03.mov"); } @Test public void testCircleOfLife_Man04() throws Exception { test("CircleOfLife/man04.mov"); } @Test public void testCircleOfLife_Woman01() throws Exception { test("CircleOfLife/woman01.mov"); } @Test public void testCircleOfLife_Woman02() throws Exception { test("CircleOfLife/woman02.mov"); } @Test public void testCircleOfLife_Woman03() throws Exception { test("CircleOfLife/woman03.mov"); } @Test public void testCircleOfLife_Woman04() throws Exception { test("CircleOfLife/woman04.mov"); } /** * This test highlights the fact that we only capture one audio stream. */ @Test public void testGUT() throws Exception { test("gut.mov"); } /** * This test has always passed. */ @Test public void testDIVX_MP2() throws Exception { test("v_divx_a_mp2.mov"); } /** * This test has always passed. */ @Test public void testFLV_H263_MP3() throws Exception { test("v_flv_h263_a_mp3.flv"); } /** * This test has always passed. */ @Test public void testM4V_H264_AAC() throws Exception { test("v_h264_a_aac.m4v"); } /** * This test caught the fact that ffmpeg can't convert audio directly from pcmu8 to mp3 * * TODO: sthiel discovered a 2-pass workaround that involved processing only the audio first into pcm16 * * Has always and currently fails. */ @Test public void testMJPEG_PCM8() throws Exception { test("v_mjpeg_a_pcm8.mov"); } /** * This test caught the fact that FLV requires one of "44100, 22050, 11025" as its audio sampling rate. */ @Test public void testH264_ADPCM_IMA_QT_48000HZ() throws Exception { test("v_h264_a_adpcm_ima_qt_48000.mov"); } /** * This movie came with the download of Korsakow V3. It initially failed to encode on OSX but worked on WIN32 * due to the MJPEG encoding not being properly supported. * A rebuild of FFMPEG for OSX (from unmodified source) fixed the issue (original was from FFMPEGX). * * 2009-09-30: Now using x264 we have to make sure the width and height are multiples of 2 for this to work (padding didn't work, had to set the actual size) */ @Test public void testKorsakow3DefaultMovie1() throws Exception { test("man01.mov"); } /** * This movie came with the download of Korsakow V3. * Has always failed. * org.korsakow.ide.encoders.EncoderException: unknown error: FFmpeg version SVN-r195, Copyright (c) 2000-2009 Fabrice Bellard, et al. configuration: --enable-gpl --enable-postproc --enable-swscale --disable-ffplay --disable-ffserver libavutil 49.14. 0 / 49.14. 0 libavcodec 52.11. 0 / 52.11. 0 libavformat 52.25. 0 / 52.25. 0 libavdevice 52. 1. 0 / 52. 1. 0 libswscale 0. 6. 1 / 0. 6. 1 libpostproc 51. 2. 0 / 51. 2. 0 built on Mar 27 2009 09:21:34, gcc: 4.0.1 (Apple Inc. build 5490) [mov,mp4,m4a,3gp,3g2,mj2 @ 0x1002600]edit list not starting at 0, a/v desync might occur, patch welcome Input #0, mov,mp4,m4a,3gp,3g2,mj2, from '/Users/d/work/korsakow/workspace/ide/test/resources/couple_naked.mov': Duration: 00:00:06.91, start: 0.000000, bitrate: 96 kb/s Stream #0.0(eng): Audio: aac, 22050 Hz, mono, s16 Stream #0.1(eng): Video: mjpeg, 720x405, 15.00 tb(r) Stream #0.2(eng): Video: mjpeg, yuvj444p, 720x405 [PAR 72:72 DAR 16:9], 15.00 tb(r) swScaler: Unknown format is not supported as input pixel format Cannot get resampling context */ @Test public void testKorsakow3DefaultMovie2() throws Exception { test("couple_naked.mov"); } /** * 3GP cell phone video. Has never worked. * * FFMPEG doesn't seem to like the 'samr' audio codec. * * @throws Exception */ @Test public void test3GP() throws Exception { test("MOV00006.3gp"); } /** * FFMpeg does not currently support (Has never worked) * http://www.mail-archive.com/libav-user@mplayerhq.hu/msg00201.html * @throws Exception */ @Test public void testAppleProRes422() throws Exception { test("Harkins_FinalColor_ProResHQ2.blank.mov"); } /** * Illustrates that some builds of FFMpeg (notable the windows build) can't handle Data: TMCD streams. * * This is from Forgotten Flags, one of the well known k3 films. * @throws Exception */ @Test public void testForgottenFlagsMovie1() throws Exception { test("TV-sandra_p.mov"); } /** * This is only interesting in that it is from Almost Architecture, one of the well known k3 films. * @throws Exception */ @Test public void testAlmostArchitechtureMovie1() throws Exception { test("opening.mov"); } }