diff --git a/Changelog.md b/Changelog.md index ebd9971..2b075a8 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,6 +1,10 @@ # JAVE2 ## Changelog +- **3.0.0** + - Reworked base classes to handle the executable (Thanks to Michael Ressler) + - Reworked the API to have a fluent and more flexible api (Thanks to Michael Ressler) + - Added more supporting methods/classes to video processing/transformations - **2.8.0** - Added -ss option to ScreenExtractor for faster processing - Add loopAttribute to EncodingAttributes, thanks to chrysophylax diff --git a/README.md b/README.md index c4fdf4c..1a4ee5e 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ It includes all binaries for the supported platforms ws.schild jave-all-deps - 2.7.3 + 3.0.0 ``` @@ -60,7 +60,7 @@ Generally if you want to use for one platform or more what you have to do is add ws.schild jave-core - 2.7.3 + 3.0.0 ``` @@ -71,7 +71,7 @@ and then the specific jar(s) for your platform(s) : ws.schild jave-nativebin-linux64 - 2.7.3 + 3.0.0 ``` @@ -80,7 +80,7 @@ and then the specific jar(s) for your platform(s) : ws.schild jave-nativebin-linux-arm64 - 2.7.3 + 3.0.0 ``` @@ -89,7 +89,7 @@ and then the specific jar(s) for your platform(s) : ws.schild jave-nativebin-win64 - 2.7.3 + 3.0.0 ``` @@ -98,7 +98,7 @@ and then the specific jar(s) for your platform(s) : ws.schild jave-nativebin-osx64 - 2.7.3 + 3.0.0 ``` @@ -107,13 +107,13 @@ and then the specific jar(s) for your platform(s) : It includes all binaries for the supported platforms ``` XML -compile group: 'ws.schild', name: 'jave-all-deps', version: '2.7.3' +compile group: 'ws.schild', name: 'jave-all-deps', version: '3.0.0' ``` ### For one platform only (Linux 64Bit in this case) ``` XML -compile group: 'ws.schild', name: 'jave-core', version: '2.7.3' -compile group: 'ws.schild', name: 'jave-nativebin-linux64', version: '2.7.3' +compile group: 'ws.schild', name: 'jave-core', version: '3.0.0' +compile group: 'ws.schild', name: 'jave-nativebin-linux64', version: '3.0.0' ``` ### Main Components of Jave2 diff --git a/jave-all-deps/nbactions.xml b/jave-all-deps/nbactions.xml index 784adec..1b4047e 100644 --- a/jave-all-deps/nbactions.xml +++ b/jave-all-deps/nbactions.xml @@ -1,10 +1,10 @@ - - CUSTOM-Deploy - Deploy - - deploy - - - + + CUSTOM-Deploy + Deploy + + deploy + + + diff --git a/jave-all-deps/pom.xml b/jave-all-deps/pom.xml index d54979a..da49391 100644 --- a/jave-all-deps/pom.xml +++ b/jave-all-deps/pom.xml @@ -1,12 +1,14 @@ - - 4.0.0 - ws.schild - jar - 2.8.0-SNAPSHOT - jave-all-deps - Jave all native dependencies package - The JAVE (Java Audio Video Encoder) library is Java wrapper on the + + + 4.0.0 + ws.schild + jar + 3.0.0 + jave-all-deps + Jave all native dependencies package + The JAVE (Java Audio Video Encoder) library is Java wrapper on the ffmpeg project. Developers can take take advantage of JAVE2 to transcode audio and video files from a format to another. In example you can transcode an AVI file to a MPEG one, you can change a DivX video stream into a @@ -14,229 +16,224 @@ a Ogg Vorbis one, you can separate and transcode audio and video tracks, you can resize videos, changing their sizes and proportions and so on. Many other formats, containers and operations are supported by JAVE2. - https://github.com/a-schild/jave2 - - - GPL-v3.0 - http://www.gnu.org/licenses/gpl-3.0.txt - - - - - Andre Schild - andre@schild.ws - Aarboard AG - https://www.aarboard.ch - - - - 1.8 - 1.8 - UTF-8 - 3.8.1 - 3.1.0 - 3.0.0-M1 - 2.5.3 - 3.1.0 - 3.1.0 - 1.6 - 1.6.8 - 1.11.2 - 5.4.2 - + https://github.com/a-schild/jave2 + + + GPL-v3.0 + http://www.gnu.org/licenses/gpl-3.0.txt + + + + + Andre Schild + andre@schild.ws + Aarboard AG + https://www.aarboard.ch + + + + 1.8 + 1.8 + UTF-8 + 3.8.1 + 3.1.0 + 3.0.0-M1 + 2.5.3 + 3.1.0 + 3.1.0 + 1.6 + 1.6.8 + 1.11.2 + 5.4.2 + - - - - org.apache.maven.plugins - maven-compiler-plugin - ${maven-compiler-plugin.version} - - ${project.build.sourceEncoding} - - - - org.apache.maven.plugins - maven-resources-plugin - ${maven-resources-plugin.version} - - ${project.build.sourceEncoding} - - - - maven-deploy-plugin - ${maven-deploy-plugin.version} - - - default-deploy - deploy - - deploy - - - - - - org.apache.maven.plugins - maven-release-plugin - ${maven-release-plugin.version} - - true - false - forked-path - -Dgpg.passphrase=${gpg.passphrase} - - - - org.apache.maven.scm - maven-scm-provider-gitexe - ${maven-scm-provider-gitexe.version} - - - - - org.sonatype.plugins - nexus-staging-maven-plugin - ${nexus-staging-maven-plugin.version} - true - - ossrh - https://oss.sonatype.org/ - true - - - - org.apache.maven.plugins - maven-source-plugin - ${maven-source-plugin.version} - - - attach-sources - - jar-no-fork - - - - - - org.apache.maven.plugins - maven-javadoc-plugin - ${maven-javadoc-plugin.version} - - - attach-javadocs - - jar - - - - - - org.apache.maven.plugins - maven-gpg-plugin - ${maven-gpg-plugin.version} - - - sign-artifacts - verify - - sign - - - - - - + + + + org.apache.maven.plugins + maven-compiler-plugin + ${maven-compiler-plugin.version} + + ${project.build.sourceEncoding} + + + + org.apache.maven.plugins + maven-resources-plugin + ${maven-resources-plugin.version} + + ${project.build.sourceEncoding} + + + + maven-deploy-plugin + ${maven-deploy-plugin.version} + + + default-deploy + deploy + + deploy + + + + + + org.apache.maven.plugins + maven-release-plugin + ${maven-release-plugin.version} + + true + false + forked-path + -Dgpg.passphrase=${gpg.passphrase} + + + + org.apache.maven.scm + maven-scm-provider-gitexe + ${maven-scm-provider-gitexe.version} + + + + + org.sonatype.plugins + nexus-staging-maven-plugin + ${nexus-staging-maven-plugin.version} + true + + ossrh + https://oss.sonatype.org/ + true + + + + org.apache.maven.plugins + maven-source-plugin + ${maven-source-plugin.version} + + + attach-sources + + jar-no-fork + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + ${maven-javadoc-plugin.version} + + + attach-javadocs + + jar + + + + + + org.apache.maven.plugins + maven-gpg-plugin + ${maven-gpg-plugin.version} + + + sign-artifacts + verify + + sign + + + + + + - - - ws.schild - jave-core - 2.8.0-SNAPSHOT - - - ws.schild - jave-nativebin-win32 - 2.8.0-SNAPSHOT - - - ws.schild - jave-nativebin-win64 - 2.8.0-SNAPSHOT - - - ws.schild - jave-nativebin-linux32 - 2.8.0-SNAPSHOT - - - ws.schild - jave-nativebin-linux64 - 2.8.0-SNAPSHOT - - - ws.schild - jave-nativebin-linux-arm64 - 2.8.0-SNAPSHOT - - - ws.schild - jave-nativebin-osx64 - 2.8.0-SNAPSHOT - - - - org.junit.jupiter - junit-jupiter-api - ${junit-jupiter-api.version} - test - - - - - ossrh - https://oss.sonatype.org/content/repositories/snapshots - - - ossrh - https://oss.sonatype.org/service/local/staging/deploy/maven2/ + + + ws.schild + jave-core + 3.0.0 + + + ws.schild + jave-nativebin-win32 + 3.0.0 + + + ws.schild + jave-nativebin-win64 + 3.0.0 + + + ws.schild + jave-nativebin-linux32 + 3.0.0 + + + ws.schild + jave-nativebin-linux64 + 3.0.0 + + + ws.schild + jave-nativebin-osx64 + 3.0.0 + + + + org.junit.jupiter + junit-jupiter-api + ${junit-jupiter-api.version} + test + + + + + ossrh + https://oss.sonatype.org/content/repositories/snapshots + + + ossrh + https://oss.sonatype.org/service/local/staging/deploy/maven2/ - - - - scm:git:git://github.com/a-schild/jave2.git - scm:git:git@github.com:a-schild/jave2.git - https://github.com/a-schild/jave2 - HEAD - - - - - release-sign-artifacts - - - performRelease - true - - - - - - org.apache.maven.plugins - maven-gpg-plugin - ${maven-gpg-plugin.version} - - - sign-artifacts - verify - - sign - - - - - - - - + + + + scm:git:git://github.com/a-schild/jave2.git + scm:git:git@github.com:a-schild/jave2.git + https://github.com/a-schild/jave2 + HEAD + + + + + release-sign-artifacts + + + performRelease + true + + + + + + org.apache.maven.plugins + maven-gpg-plugin + ${maven-gpg-plugin.version} + + + sign-artifacts + verify + + sign + + + + + + + + \ No newline at end of file diff --git a/jave-core-test-java11/pom.xml b/jave-core-test-java11/pom.xml index 6cc98b0..5254a56 100644 --- a/jave-core-test-java11/pom.xml +++ b/jave-core-test-java11/pom.xml @@ -3,7 +3,7 @@ 4.0.0 ws.schild jar - 2.8.0-SNAPSHOT + 3.0.0 jave-core-test-java11 11 @@ -38,32 +38,32 @@ ws.schild jave-core - 2.8.0-SNAPSHOT + 3.0.0 ws.schild jave-nativebin-win32 - 2.8.0-SNAPSHOT + 3.0.0 ws.schild jave-nativebin-win64 - 2.8.0-SNAPSHOT + 3.0.0 ws.schild jave-nativebin-linux32 - 2.8.0-SNAPSHOT + 3.0.0 ws.schild jave-nativebin-linux64 - 2.8.0-SNAPSHOT + 3.0.0 ws.schild jave-nativebin-osx64 - 2.8.0-SNAPSHOT + 3.0.0 diff --git a/jave-core-test-java11/src/test/java/ws/schild/jave/DefaultFFMPEGLocatorTest.java b/jave-core-test-java11/src/test/java/ws/schild/jave/DefaultFFMPEGLocatorTest.java index 400c1a2..3d5d0c8 100644 --- a/jave-core-test-java11/src/test/java/ws/schild/jave/DefaultFFMPEGLocatorTest.java +++ b/jave-core-test-java11/src/test/java/ws/schild/jave/DefaultFFMPEGLocatorTest.java @@ -22,6 +22,8 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import org.junit.jupiter.api.Test; +import ws.schild.jave.process.ffmpeg.DefaultFFMPEGLocator; + /** * @@ -45,7 +47,7 @@ public void testFindExecutable() { dirFolder.delete(); } DefaultFFMPEGLocator locator= new DefaultFFMPEGLocator(); - String exePath= locator.getFFMPEGExecutablePath(); + String exePath= locator.getExecutablePath(); assertNotNull("Native component not found", exePath); } diff --git a/jave-core-test-java11/src/test/java/ws/schild/jave/EncoderTest.java b/jave-core-test-java11/src/test/java/ws/schild/jave/EncoderTest.java index 9ab7daa..0934f22 100644 --- a/jave-core-test-java11/src/test/java/ws/schild/jave/EncoderTest.java +++ b/jave-core-test-java11/src/test/java/ws/schild/jave/EncoderTest.java @@ -27,6 +27,13 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.Test; +import ws.schild.jave.encode.AudioAttributes; +import ws.schild.jave.encode.EncodingAttributes; +import ws.schild.jave.encode.VideoAttributes; +import ws.schild.jave.info.MultimediaInfo; +import ws.schild.jave.info.VideoSize; +import ws.schild.jave.progress.EncoderProgressListener; + /** * * @author a.schild @@ -133,7 +140,7 @@ public void testEncodeVideo1() throws Exception { video.setFrameRate(15); video.setSize(new VideoSize(176, 144)); EncodingAttributes attrs = new EncodingAttributes(); - attrs.setFormat("3gp"); + attrs.setOutputFormat("3gp"); attrs.setAudioAttributes(audio); attrs.setVideoAttributes(video); Encoder encoder = new Encoder(); @@ -165,7 +172,7 @@ public void testEncodeVideo2() throws Exception { video.setFrameRate(15); video.setSize(new VideoSize(176, 144)); EncodingAttributes attrs = new EncodingAttributes(); - attrs.setFormat("3gp"); + attrs.setOutputFormat("3gp"); attrs.setAudioAttributes(audio); attrs.setVideoAttributes(video); Encoder encoder = new Encoder(); @@ -199,7 +206,7 @@ public void testEncodeVideo3() throws Exception { video.setFrameRate(15); video.setSize(new VideoSize(176, 144)); EncodingAttributes attrs = new EncodingAttributes(); - attrs.setFormat("3gp"); + attrs.setOutputFormat("3gp"); attrs.setAudioAttributes(audio); attrs.setVideoAttributes(video); Encoder encoder = new Encoder(); @@ -242,7 +249,7 @@ public void testEncodeVideo4() throws Exception { video.setFrameRate(15); video.setSize(new VideoSize(176, 144)); EncodingAttributes attrs = new EncodingAttributes(); - attrs.setFormat("3gp"); + attrs.setOutputFormat("3gp"); attrs.setAudioAttributes(audio); attrs.setVideoAttributes(video); Encoder encoder = new Encoder(); @@ -285,7 +292,7 @@ public void testEncodeVideo5() throws Exception { video.setFrameRate(15); video.setSize(new VideoSize(400, 300)); EncodingAttributes attrs = new EncodingAttributes(); - attrs.setFormat("flv"); + attrs.setOutputFormat("flv"); attrs.setAudioAttributes(audio); attrs.setVideoAttributes(video); Encoder encoder = new Encoder(); @@ -319,7 +326,7 @@ public void testEncodeAudio09() throws Exception { //Set encoding attributes EncodingAttributes attributes = new EncodingAttributes(); - attributes.setFormat("wav"); + attributes.setOutputFormat("wav"); attributes.setAudioAttributes(audio); Encoder encoder = new Encoder(); PListener listener = new PListener(); @@ -356,7 +363,7 @@ public void testEncodeVideo10() throws Exception { video.setFrameRate(30); video.setSize(new VideoSize(320, 240)); EncodingAttributes attrs = new EncodingAttributes(); - attrs.setFormat("mp4"); + attrs.setOutputFormat("mp4"); attrs.setVideoAttributes(video); attrs.setAudioAttributes(audio); Encoder encoder = new Encoder(); @@ -394,7 +401,7 @@ public void testEncodeVideo11() throws Exception { video.setSize(new VideoSize(176, 144)); video.setQuality(31); EncodingAttributes attrs = new EncodingAttributes(); - attrs.setFormat("3gp"); + attrs.setOutputFormat("3gp"); attrs.setAudioAttributes(audio); attrs.setVideoAttributes(video); Encoder encoder = new Encoder(); @@ -431,7 +438,7 @@ public void testEncodeVideo12() throws Exception { encodingAttr.setAudioAttributes(audioAttr); encodingAttr.setVideoAttributes(videoAttr); - encodingAttr.setFormat("mp4"); + encodingAttr.setOutputFormat("mp4"); Encoder encoder = new Encoder(); encoder.encode(new MultimediaObject(source), target, encodingAttr); @@ -461,7 +468,7 @@ public void testEncodeAudio10() throws Exception { audioAttr.setVolume(1000); audioAttr.setQuality(1000); audioAttr.setSamplingRate(48000); - encodingAttr.setFormat("flac"); + encodingAttr.setOutputFormat("flac"); encodingAttr.setAudioAttributes(audioAttr); Encoder encoder = new Encoder(); @@ -488,7 +495,7 @@ public void testEncodeAudio1() throws Exception { audio.setChannels(2); audio.setSamplingRate(44100); EncodingAttributes attrs = new EncodingAttributes(); - attrs.setFormat("mp3"); + attrs.setOutputFormat("mp3"); attrs.setAudioAttributes(audio); Encoder encoder = new Encoder(); PListener listener = new PListener(); @@ -516,7 +523,7 @@ public void testEncodeAudio2() throws Exception { audio.setChannels(2); audio.setSamplingRate(42100); EncodingAttributes attrs = new EncodingAttributes(); - attrs.setFormat("mp3"); + attrs.setOutputFormat("mp3"); attrs.setAudioAttributes(audio); Encoder encoder = new Encoder(); PListener listener = new PListener(); @@ -558,7 +565,7 @@ public void testEncodeAudio3() throws Exception { audio.setChannels(2); audio.setSamplingRate(44100); EncodingAttributes attrs = new EncodingAttributes(); - attrs.setFormat("mp3"); + attrs.setOutputFormat("mp3"); attrs.setAudioAttributes(audio); encoder.encode(new MultimediaObject(source), target, attrs); assertTrue(target.exists(), "Output file missing"); @@ -585,7 +592,7 @@ public void testEncodeAudio4() throws Exception { AudioAttributes audio = new AudioAttributes(); audio.setCodec("libmp3lame"); EncodingAttributes attrs = new EncodingAttributes(); - attrs.setFormat("mp3"); + attrs.setOutputFormat("mp3"); attrs.setAudioAttributes(audio); encoder.encode(new MultimediaObject(source), target, attrs); assertTrue(target.exists(), "Output file missing"); @@ -613,7 +620,7 @@ public void testEncodeAudio5() throws Exception { AudioAttributes audio = new AudioAttributes(); audio.setCodec("libmp3lame"); EncodingAttributes attrs = new EncodingAttributes(); - attrs.setFormat("mp3"); + attrs.setOutputFormat("mp3"); attrs.setAudioAttributes(audio); attrs.setMapMetaData(true); encoder.encode(new MultimediaObject(source), target, attrs); @@ -644,7 +651,7 @@ public void testAbortEncoder() throws Exception { audio.setChannels(2); audio.setSamplingRate(44100); EncodingAttributes attrs = new EncodingAttributes(); - attrs.setFormat("mp3"); + attrs.setOutputFormat("mp3"); attrs.setAudioAttributes(audio); Runnable task = () -> { diff --git a/jave-core-test-java11/src/test/java/ws/schild/jave/MultimediaObjectTest.java b/jave-core-test-java11/src/test/java/ws/schild/jave/MultimediaObjectTest.java index f14d0ca..9c9c4ae 100644 --- a/jave-core-test-java11/src/test/java/ws/schild/jave/MultimediaObjectTest.java +++ b/jave-core-test-java11/src/test/java/ws/schild/jave/MultimediaObjectTest.java @@ -23,6 +23,8 @@ import static org.junit.jupiter.api.Assertions.assertNull; import org.junit.jupiter.api.Test; +import ws.schild.jave.info.MultimediaInfo; + /** * * @author a.schild diff --git a/jave-core-test/nb-configuration.xml b/jave-core-test/nb-configuration.xml index 8b36389..7909d66 100644 --- a/jave-core-test/nb-configuration.xml +++ b/jave-core-test/nb-configuration.xml @@ -1,18 +1,18 @@ - - - - JDK_10 - + + + + JDK_10 + diff --git a/jave-core-test/pom.xml b/jave-core-test/pom.xml index 5622ecd..44936b0 100644 --- a/jave-core-test/pom.xml +++ b/jave-core-test/pom.xml @@ -1,101 +1,103 @@ - - 4.0.0 - ws.schild - jar - 2.8.0-SNAPSHOT - jave-core-test - - 1.8 - 1.8 - UTF-8 - 3.8.1 - 3.1.0 - 5.4.2 - 1.7.26 - - - - - org.apache.maven.plugins - maven-compiler-plugin - ${maven-compiler-plugin.version} - - ${project.build.sourceEncoding} - - - - org.apache.maven.plugins - maven-resources-plugin - ${maven-resources-plugin.version} - - ${project.build.sourceEncoding} - - - - - - - ws.schild - jave-core - 2.8.0-SNAPSHOT - - - ws.schild - jave-nativebin-win32 - 2.8.0-SNAPSHOT - - - ws.schild - jave-nativebin-win64 - 2.8.0-SNAPSHOT - - - ws.schild - jave-nativebin-linux32 - 2.8.0-SNAPSHOT - - - ws.schild - jave-nativebin-linux64 - 2.8.0-SNAPSHOT - - - ws.schild - jave-nativebin-osx64 - 2.8.0-SNAPSHOT - - - - org.junit.jupiter - junit-jupiter-api - ${junit-jupiter-api.version} - test - - - - org.slf4j - slf4j-simple - ${slf4j-simple.version} - test - - - org.seleniumhq.selenium - selenium-java - test - 2.44.0 - - - com.opera - operadriver - test - 1.5 - - - org.seleniumhq.selenium - selenium-remote-driver - - - - + + + 4.0.0 + ws.schild + jar + 3.0.0 + jave-core-test + + 1.8 + 1.8 + UTF-8 + 3.8.1 + 3.1.0 + 5.4.2 + 1.7.26 + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${maven-compiler-plugin.version} + + ${project.build.sourceEncoding} + + + + org.apache.maven.plugins + maven-resources-plugin + ${maven-resources-plugin.version} + + ${project.build.sourceEncoding} + + + + + + + ws.schild + jave-core + 3.0.0 + + + ws.schild + jave-nativebin-win32 + 3.0.0 + + + ws.schild + jave-nativebin-win64 + 3.0.0 + + + ws.schild + jave-nativebin-linux32 + 3.0.0 + + + ws.schild + jave-nativebin-linux64 + 3.0.0 + + + ws.schild + jave-nativebin-osx64 + 3.0.0 + + + + org.junit.jupiter + junit-jupiter-api + ${junit-jupiter-api.version} + test + + + + org.slf4j + slf4j-simple + ${slf4j-simple.version} + test + + + org.seleniumhq.selenium + selenium-java + test + 2.44.0 + + + com.opera + operadriver + test + 1.5 + + + org.seleniumhq.selenium + selenium-remote-driver + + + + \ No newline at end of file diff --git a/jave-core-test/src/test/java/ws/schild/jave/AMediaTest.java b/jave-core-test/src/test/java/ws/schild/jave/AMediaTest.java index d0ec6f8..d7022d8 100644 --- a/jave-core-test/src/test/java/ws/schild/jave/AMediaTest.java +++ b/jave-core-test/src/test/java/ws/schild/jave/AMediaTest.java @@ -7,59 +7,43 @@ import java.io.File; -/** - * - * @author a.schild - */ +/** @author a.schild */ public abstract class AMediaTest { - private final String resourceSourcePath; - private final String resourceTargetPath; + private final String resourceSourcePath; + private final String resourceTargetPath; - /** - * @param sourcePart - * @param targetPart - */ - public AMediaTest(String sourcePart, String targetPart) { - if (sourcePart == null) - { - resourceSourcePath= "src/test/resources/"; - } - else - { - if (sourcePart.endsWith("/")) - { - resourceSourcePath= "src/test/resources/"+sourcePart; - } - else - { - resourceSourcePath= "src/test/resources/"+sourcePart+"/"; - } - } - new File(resourceSourcePath).mkdirs(); - if (targetPart == null) - { - resourceTargetPath= "target/testoutput/"; - } - else - { - if (targetPart.endsWith("/")) - { - resourceTargetPath= "target/testoutput/"+targetPart; - } - else - { - resourceTargetPath= "target/testoutput/"+targetPart+"/"; - } - } - new File(resourceTargetPath).mkdirs(); + /** + * @param sourcePart + * @param targetPart + */ + public AMediaTest(String sourcePart, String targetPart) { + if (sourcePart == null) { + resourceSourcePath = "src/test/resources/"; + } else { + if (sourcePart.endsWith("/")) { + resourceSourcePath = "src/test/resources/" + sourcePart; + } else { + resourceSourcePath = "src/test/resources/" + sourcePart + "/"; + } } - - public String getResourceSourcePath() { - return resourceSourcePath; + new File(resourceSourcePath).mkdirs(); + if (targetPart == null) { + resourceTargetPath = "target/testoutput/"; + } else { + if (targetPart.endsWith("/")) { + resourceTargetPath = "target/testoutput/" + targetPart; + } else { + resourceTargetPath = "target/testoutput/" + targetPart + "/"; + } } + new File(resourceTargetPath).mkdirs(); + } - public String getResourceTargetPath() { - return resourceTargetPath; - } - + public String getResourceSourcePath() { + return resourceSourcePath; + } + + public String getResourceTargetPath() { + return resourceTargetPath; + } } diff --git a/jave-core-test/src/test/java/ws/schild/jave/ConcatDemuxerTest.java b/jave-core-test/src/test/java/ws/schild/jave/ConcatDemuxerTest.java new file mode 100644 index 0000000..6e9ce32 --- /dev/null +++ b/jave-core-test/src/test/java/ws/schild/jave/ConcatDemuxerTest.java @@ -0,0 +1,42 @@ +package ws.schild.jave; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.File; +import java.net.URL; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +import org.junit.jupiter.api.Test; + +import ws.schild.jave.progress.EchoingProgressListener; +import ws.schild.jave.utils.AutoRemoveableFile; + +public class ConcatDemuxerTest { + + public static VideoProcessor vps; + + @Test + public void thatWeCanMergeFiles() throws Exception { + VideoProcessor vps = new VideoProcessor(); + ClassLoader cLoader = ConcatDemuxerTest.class.getClassLoader(); + + List videos = + Arrays.asList( + "9B8CC2D5-3B24-4DD1-B23D-9B5DAF0E70BE.mp4", + "A0EF94F6-F922-4676-B767-A600F2E87F53.mp4", + "B3111BAF-A516-48EC-99FB-B492EB23155D.mp4") + .stream() + .map(cLoader::getResource) + .map(URL::getFile) + .map(File::new) + .collect(Collectors.toList()); + + try (AutoRemoveableFile destination = + new AutoRemoveableFile(videos.get(0).getParentFile(), "merge.mp4")) { + vps.catClipsTogether(videos, destination, new EchoingProgressListener("Test Merge")); + assertTrue(destination.exists(), "Output file missing"); + } + } +} diff --git a/jave-core-test/src/test/java/ws/schild/jave/ConcatEncoderTest.java b/jave-core-test/src/test/java/ws/schild/jave/ConcatEncoderTest.java index e0ac0c2..5dd8d61 100644 --- a/jave-core-test/src/test/java/ws/schild/jave/ConcatEncoderTest.java +++ b/jave-core-test/src/test/java/ws/schild/jave/ConcatEncoderTest.java @@ -1,8 +1,8 @@ /* * JAVE - A Java Audio/Video Encoder (based on FFMPEG) - * + * * Copyright (C) 2018- Andre Schild - * + * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or @@ -24,80 +24,80 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.Test; -/** - * - * @author a.schild - */ -public class ConcatEncoderTest extends AMediaTest{ - - public ConcatEncoderTest() { - super(null, "ConcatEncoder"); - } +import ws.schild.jave.encode.AudioAttributes; +import ws.schild.jave.encode.EncodingAttributes; +import ws.schild.jave.encode.VideoAttributes; +import ws.schild.jave.info.VideoSize; - /** - * Test of encode method, of class Encoder. - * @throws java.lang.Exception - */ - @Test - public void testConcatVideo1() throws Exception { - System.out.println("concat two identical videos"); - - File source1 = new File(getResourceSourcePath(), "dance1.avi"); - File source2 = new File(getResourceSourcePath(), "dance1.avi"); - File target = new File(getResourceTargetPath(), "testConcatVideo1.3gp"); - if (target.exists()) - { - target.delete(); - } - AudioAttributes audio = new AudioAttributes(); - audio.setCodec("libfaac"); - audio.setBitRate(128000); - audio.setSamplingRate(44100); - audio.setChannels(2); - VideoAttributes video = new VideoAttributes(); - video.setCodec("mpeg4"); - video.setBitRate(160000); - video.setFrameRate(15); - video.setSize(new VideoSize(176, 144)); - EncodingAttributes attrs = new EncodingAttributes(); - attrs.setFormat("3gp"); - attrs.setAudioAttributes(audio); - attrs.setVideoAttributes(video); - Encoder encoder = new Encoder(); - List src= new ArrayList<>(); - src.add(new MultimediaObject(source1)); - src.add(new MultimediaObject(source2)); - encoder.encode(src, target, attrs); - assertTrue( target.exists(), "Output file missing"); - } +/** @author a.schild */ +public class ConcatEncoderTest extends AMediaTest { + + public ConcatEncoderTest() { + super(null, "ConcatEncoder"); + } - @Test - public void testContactAudio01() throws Exception { - System.out.println("concat two wmv files and build wav from it"); - File source1 = new File(getResourceSourcePath(), "testfile3.wmv"); - File source2 = new File(getResourceSourcePath(), "testfile3.wmv"); - File target = new File(getResourceTargetPath(), "testContactAudio01.wav"); - if (target.exists()) - { - target.delete(); - } + /** + * Test of encode method, of class Encoder. + * + * @throws java.lang.Exception + */ + @Test + public void testConcatVideo1() throws Exception { + System.out.println("concat two identical videos"); - //Set Audio Attributes - AudioAttributes audio = new AudioAttributes(); - audio.setCodec("pcm_s16le"); - audio.setChannels(2); - audio.setSamplingRate(44100); + File source1 = new File(getResourceSourcePath(), "dance1.avi"); + File source2 = new File(getResourceSourcePath(), "dance1.avi"); + File target = new File(getResourceTargetPath(), "testConcatVideo1.3gp"); + if (target.exists()) { + target.delete(); + } + AudioAttributes audio = new AudioAttributes(); + audio.setCodec("libfaac"); + audio.setBitRate(128000); + audio.setSamplingRate(44100); + audio.setChannels(2); + VideoAttributes video = new VideoAttributes(); + video.setCodec("mpeg4"); + video.setBitRate(160000); + video.setFrameRate(15); + video.setSize(new VideoSize(176, 144)); + EncodingAttributes attrs = new EncodingAttributes(); + attrs.setOutputFormat("3gp"); + attrs.setAudioAttributes(audio); + attrs.setVideoAttributes(video); + Encoder encoder = new Encoder(); + List src = new ArrayList<>(); + src.add(new MultimediaObject(source1)); + src.add(new MultimediaObject(source2)); + encoder.encode(src, target, attrs); + assertTrue(target.exists(), "Output file missing"); + } - //Set encoding attributes - EncodingAttributes attributes = new EncodingAttributes(); - attributes.setFormat("wav"); - attributes.setAudioAttributes(audio); - Encoder encoder = new Encoder(); - List src= new ArrayList<>(); - src.add(new MultimediaObject(source1)); - src.add(new MultimediaObject(source2)); - encoder.encode(src, target, attributes); - assertTrue(target.exists(), "Output file missing"); + @Test + public void testContactAudio01() throws Exception { + System.out.println("concat two wmv files and build wav from it"); + File source1 = new File(getResourceSourcePath(), "testfile3.wmv"); + File source2 = new File(getResourceSourcePath(), "testfile3.wmv"); + File target = new File(getResourceTargetPath(), "testContactAudio01.wav"); + if (target.exists()) { + target.delete(); } - + + // Set Audio Attributes + AudioAttributes audio = new AudioAttributes(); + audio.setCodec("pcm_s16le"); + audio.setChannels(2); + audio.setSamplingRate(44100); + + // Set encoding attributes + EncodingAttributes attributes = new EncodingAttributes(); + attributes.setOutputFormat("wav"); + attributes.setAudioAttributes(audio); + Encoder encoder = new Encoder(); + List src = new ArrayList<>(); + src.add(new MultimediaObject(source1)); + src.add(new MultimediaObject(source2)); + encoder.encode(src, target, attributes); + assertTrue(target.exists(), "Output file missing"); + } } diff --git a/jave-core-test/src/test/java/ws/schild/jave/ConversionOutputAnalyzerTest.java b/jave-core-test/src/test/java/ws/schild/jave/ConversionOutputAnalyzerTest.java index 1022ce4..698e23a 100644 --- a/jave-core-test/src/test/java/ws/schild/jave/ConversionOutputAnalyzerTest.java +++ b/jave-core-test/src/test/java/ws/schild/jave/ConversionOutputAnalyzerTest.java @@ -1,8 +1,8 @@ /* * JAVE - A Java Audio/Video Encoder (based on FFMPEG) - * + * * Copyright (C) 2018- Andre Schild - * + * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or @@ -26,51 +26,39 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import org.junit.jupiter.api.Test; -/** - * - * @author a.schild - */ +/** @author a.schild */ public class ConversionOutputAnalyzerTest extends AMediaTest { - - public ConversionOutputAnalyzerTest() { - super(null, "ConversionOutputAnalyzer"); - } - /** - * Test of getFile method, of class MultimediaObject. - */ - @Test - public void testAnalyzeNewLine1() { - System.out.println("analyzeNewLine 1"); - File file = new File(getResourceSourcePath(), "testoutput1.txt"); - ConversionOutputAnalyzer oa1= new ConversionOutputAnalyzer(0, null); - - try - { - FileInputStream fis = new FileInputStream(file); - InputStreamReader streamReader = new InputStreamReader(fis, "UTF-8"); - LineReader reader = new LineReader(streamReader); - String sLine = null; - while ((sLine = reader.readLine()) != null) - { - oa1.analyzeNewLine(sLine); - } - String result= oa1.getLastWarning(); - String expResult= null; - assertEquals(expResult, result); - } - catch (IOException ioError) - { - System.out.println("IO error "+ioError.getMessage()); - ioError.printStackTrace(); - throw new AssertionError("IO error "+ioError.getMessage()); - } - catch (EncoderException enError) - { - System.out.println("Encoder error "+enError.getMessage()); - enError.printStackTrace(); - throw new AssertionError("Encoder error "+enError.getMessage()); - } + public ConversionOutputAnalyzerTest() { + super(null, "ConversionOutputAnalyzer"); + } + + /** Test of getFile method, of class MultimediaObject. */ + @Test + public void testAnalyzeNewLine1() { + System.out.println("analyzeNewLine 1"); + File file = new File(getResourceSourcePath(), "testoutput1.txt"); + ConversionOutputAnalyzer oa1 = new ConversionOutputAnalyzer(0, null); + + try { + FileInputStream fis = new FileInputStream(file); + InputStreamReader streamReader = new InputStreamReader(fis, "UTF-8"); + LineReader reader = new LineReader(streamReader); + String sLine = null; + while ((sLine = reader.readLine()) != null) { + oa1.analyzeNewLine(sLine); + } + String result = oa1.getLastWarning(); + String expResult = null; + assertEquals(expResult, result); + } catch (IOException ioError) { + System.out.println("IO error " + ioError.getMessage()); + ioError.printStackTrace(); + throw new AssertionError("IO error " + ioError.getMessage()); + } catch (EncoderException enError) { + System.out.println("Encoder error " + enError.getMessage()); + enError.printStackTrace(); + throw new AssertionError("Encoder error " + enError.getMessage()); } - + } } diff --git a/jave-core-test/src/test/java/ws/schild/jave/DefaultFFMPEGLocatorTest.java b/jave-core-test/src/test/java/ws/schild/jave/DefaultFFMPEGLocatorTest.java index 400c1a2..6a633f4 100644 --- a/jave-core-test/src/test/java/ws/schild/jave/DefaultFFMPEGLocatorTest.java +++ b/jave-core-test/src/test/java/ws/schild/jave/DefaultFFMPEGLocatorTest.java @@ -1,8 +1,8 @@ /* * JAVE - A Java Audio/Video Encoder (based on FFMPEG) - * + * * Copyright (C) 2018- Andre Schild - * + * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or @@ -22,31 +22,25 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import org.junit.jupiter.api.Test; +import ws.schild.jave.process.ffmpeg.DefaultFFMPEGLocator; -/** - * - * @author a.schild - */ +/** @author a.schild */ public class DefaultFFMPEGLocatorTest { - - public DefaultFFMPEGLocatorTest() { - } - @Test - public void testFindExecutable() { - // We first remove any old executables, to make sure the copy/deploy works - File dirFolder = new File(System.getProperty("java.io.tmpdir"), "jave/"); - if (dirFolder.exists() && dirFolder.isDirectory()) - { - for (File f : dirFolder.listFiles()) - { - f.delete(); - } - dirFolder.delete(); - } - DefaultFFMPEGLocator locator= new DefaultFFMPEGLocator(); - String exePath= locator.getFFMPEGExecutablePath(); - assertNotNull("Native component not found", exePath); + public DefaultFFMPEGLocatorTest() {} + + @Test + public void testFindExecutable() { + // We first remove any old executables, to make sure the copy/deploy works + File dirFolder = new File(System.getProperty("java.io.tmpdir"), "jave/"); + if (dirFolder.exists() && dirFolder.isDirectory()) { + for (File f : dirFolder.listFiles()) { + f.delete(); + } + dirFolder.delete(); } - + DefaultFFMPEGLocator locator = new DefaultFFMPEGLocator(); + String exePath = locator.getExecutablePath(); + assertNotNull("Native component not found", exePath); + } } diff --git a/jave-core-test/src/test/java/ws/schild/jave/EncoderTest.java b/jave-core-test/src/test/java/ws/schild/jave/EncoderTest.java index 10c1b96..d37b573 100644 --- a/jave-core-test/src/test/java/ws/schild/jave/EncoderTest.java +++ b/jave-core-test/src/test/java/ws/schild/jave/EncoderTest.java @@ -1,8 +1,8 @@ /* * JAVE - A Java Audio/Video Encoder (based on FFMPEG) - * + * * Copyright (C) 2018- Andre Schild - * + * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or @@ -23,128 +23,131 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.Test; -/** - * - * @author a.schild - */ -public class EncoderTest extends AMediaTest{ - - public EncoderTest() { - super(null, "EncoderTest"); - } +import ws.schild.jave.encode.AudioAttributes; +import ws.schild.jave.encode.EncodingAttributes; - /** - * Test of getAudioDecoders method, of class Encoder. - * @throws java.lang.Exception - */ - @Test - public void testGetAudioDecoders() throws Exception { - System.out.println("getAudioDecoders"); - Encoder instance = new Encoder(); - String[] result = instance.getAudioDecoders(); - assertTrue(result != null && result.length >0, "No audio decoders found"); - } +/** @author a.schild */ +public class EncoderTest extends AMediaTest { - /** - * Test of getAudioEncoders method, of class Encoder. - * @throws java.lang.Exception - */ - @Test - public void testGetAudioEncoders() throws Exception { - System.out.println("getAudioEncoders"); - Encoder instance = new Encoder(); - String[] result = instance.getAudioEncoders(); - assertTrue(result != null && result.length >0, "No audio encoders found"); - } + public EncoderTest() { + super(null, "EncoderTest"); + } - /** - * Test of getVideoDecoders method, of class Encoder. - * @throws java.lang.Exception - */ - @Test - public void testGetVideoDecoders() throws Exception { - System.out.println("getVideoDecoders"); - Encoder instance = new Encoder(); - String[] result = instance.getVideoDecoders(); - assertTrue(result != null && result.length >0, "No video decoders found"); - } + /** + * Test of getAudioDecoders method, of class Encoder. + * + * @throws java.lang.Exception + */ + @Test + public void testGetAudioDecoders() throws Exception { + System.out.println("getAudioDecoders"); + Encoder instance = new Encoder(); + String[] result = instance.getAudioDecoders(); + assertTrue(result != null && result.length > 0, "No audio decoders found"); + } - /** - * Test of getVideoEncoders method, of class Encoder. - * @throws java.lang.Exception - */ - @Test - public void testGetVideoEncoders() throws Exception { - System.out.println("getVideoEncoders"); - Encoder instance = new Encoder(); - String[] result = instance.getVideoEncoders(); - assertTrue(result != null && result.length >0, "No video enecoders found"); - } + /** + * Test of getAudioEncoders method, of class Encoder. + * + * @throws java.lang.Exception + */ + @Test + public void testGetAudioEncoders() throws Exception { + System.out.println("getAudioEncoders"); + Encoder instance = new Encoder(); + String[] result = instance.getAudioEncoders(); + assertTrue(result != null && result.length > 0, "No audio encoders found"); + } - /** - * Test of getSupportedEncodingFormats method, of class Encoder. - * @throws java.lang.Exception - */ - @Test - public void testGetSupportedEncodingFormats() throws Exception { - System.out.println("getSupportedEncodingFormats"); - Encoder instance = new Encoder(); - String[] result = instance.getSupportedEncodingFormats(); - assertTrue(result != null && result.length >0, "No supported encoding formats found"); - } + /** + * Test of getVideoDecoders method, of class Encoder. + * + * @throws java.lang.Exception + */ + @Test + public void testGetVideoDecoders() throws Exception { + System.out.println("getVideoDecoders"); + Encoder instance = new Encoder(); + String[] result = instance.getVideoDecoders(); + assertTrue(result != null && result.length > 0, "No video decoders found"); + } - /** - * Test of getSupportedDecodingFormats method, of class Encoder. - * @throws java.lang.Exception - */ - @Test - public void testGetSupportedDecodingFormats() throws Exception { - System.out.println("getSupportedDecodingFormats"); - Encoder instance = new Encoder(); - String[] result = instance.getSupportedDecodingFormats(); - assertTrue(result != null && result.length >0, "No supported decoding formats found"); - } + /** + * Test of getVideoEncoders method, of class Encoder. + * + * @throws java.lang.Exception + */ + @Test + public void testGetVideoEncoders() throws Exception { + System.out.println("getVideoEncoders"); + Encoder instance = new Encoder(); + String[] result = instance.getVideoEncoders(); + assertTrue(result != null && result.length > 0, "No video enecoders found"); + } - /** - * Test of encode method, of class Encoder. - * @throws java.lang.Exception - */ - @Test - public void testAbortEncoder() throws Exception { - System.out.println("testAbortEncoder"); - File source = new File(getResourceSourcePath(), "testfile3.wmv"); - File target = new File(getResourceTargetPath(), "testAbortEncoder.mp3"); - if (target.exists()) - { - target.delete(); - } - Encoder encoder = new Encoder(); - PListener listener = new PListener(); - AudioAttributes audio = new AudioAttributes(); - audio.setCodec("libmp3lame"); - audio.setBitRate(128000); - audio.setChannels(2); - audio.setSamplingRate(44100); - EncodingAttributes attrs = new EncodingAttributes(); - attrs.setFormat("mp3"); - attrs.setAudioAttributes(audio); + /** + * Test of getSupportedEncodingFormats method, of class Encoder. + * + * @throws java.lang.Exception + */ + @Test + public void testGetSupportedEncodingFormats() throws Exception { + System.out.println("getSupportedEncodingFormats"); + Encoder instance = new Encoder(); + String[] result = instance.getSupportedEncodingFormats(); + assertTrue(result != null && result.length > 0, "No supported encoding formats found"); + } - Runnable task = () -> { - try - { - encoder.encode(new MultimediaObject(source), target, attrs, listener); - assertTrue(target.exists(), "Output file missing"); - } - catch (EncoderException ex) - { - throw new AssertionError("Unexpected exception in encoder", ex); - } - }; - - Thread thread = new Thread(task); - thread.start(); - TimeUnit.MILLISECONDS.sleep(100); - encoder.abortEncoding(); + /** + * Test of getSupportedDecodingFormats method, of class Encoder. + * + * @throws java.lang.Exception + */ + @Test + public void testGetSupportedDecodingFormats() throws Exception { + System.out.println("getSupportedDecodingFormats"); + Encoder instance = new Encoder(); + String[] result = instance.getSupportedDecodingFormats(); + assertTrue(result != null && result.length > 0, "No supported decoding formats found"); + } + + /** + * Test of encode method, of class Encoder. + * + * @throws java.lang.Exception + */ + @Test + public void testAbortEncoder() throws Exception { + System.out.println("testAbortEncoder"); + File source = new File(getResourceSourcePath(), "testfile3.wmv"); + File target = new File(getResourceTargetPath(), "testAbortEncoder.mp3"); + if (target.exists()) { + target.delete(); } - + Encoder encoder = new Encoder(); + PListener listener = new PListener(); + AudioAttributes audio = new AudioAttributes(); + audio.setCodec("libmp3lame"); + audio.setBitRate(128000); + audio.setChannels(2); + audio.setSamplingRate(44100); + EncodingAttributes attrs = new EncodingAttributes(); + attrs.setOutputFormat("mp3"); + attrs.setAudioAttributes(audio); + + Runnable task = + () -> { + try { + encoder.encode(new MultimediaObject(source), target, attrs, listener); + assertTrue(target.exists(), "Output file missing"); + } catch (EncoderException ex) { + throw new AssertionError("Unexpected exception in encoder", ex); + } + }; + + Thread thread = new Thread(task); + thread.start(); + TimeUnit.MILLISECONDS.sleep(100); + encoder.abortEncoding(); + } } diff --git a/jave-core-test/src/test/java/ws/schild/jave/EncodingAttributesTest.java b/jave-core-test/src/test/java/ws/schild/jave/EncodingAttributesTest.java index 9bc6bd1..461376b 100644 --- a/jave-core-test/src/test/java/ws/schild/jave/EncodingAttributesTest.java +++ b/jave-core-test/src/test/java/ws/schild/jave/EncodingAttributesTest.java @@ -1,8 +1,8 @@ /* * JAVE - A Java Audio/Video Encoder (based on FFMPEG) - * + * * Copyright (C) 2018- Andre Schild - * + * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or @@ -22,51 +22,51 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.Test; -/** - * - * @author a.schild - */ -public class EncodingAttributesTest extends AMediaTest{ - - - public EncodingAttributesTest() { - super(null, "EncodingAttributes"); - } +import ws.schild.jave.encode.AudioAttributes; +import ws.schild.jave.encode.EncodingAttributes; +import ws.schild.jave.encode.VideoAttributes; +import ws.schild.jave.info.VideoSize; - /** - * Test of encode method, of class Encoder. - * @throws java.lang.Exception - */ - @Test - public void testEncodeVideo1() throws Exception { - System.out.println("testEncodeVideo1 avi to mp4"); - - File source = new File(getResourceSourcePath(), "dance1.avi"); - File target = new File(getResourceTargetPath(), "testEncodeVideo1.mp4"); - if (target.exists()) - { - target.delete(); - } - AudioAttributes audio = new AudioAttributes(); - audio.setCodec("libfaac"); - audio.setBitRate(128000); - audio.setSamplingRate(44100); - audio.setChannels(2); - VideoAttributes video = new VideoAttributes(); - video.setCodec("mpeg4"); - video.setBitRate(160000); - video.setFrameRate(15); - video.setSize(new VideoSize(176, 144)); - EncodingAttributes attrs = new EncodingAttributes(); - attrs.setFormat("mp4"); - attrs.setAudioAttributes(audio); - attrs.setVideoAttributes(video); - attrs.setDecodingThreads(1); - attrs.setEncodingThreads(1); - //attrs.setFilterThreads(1); - Encoder encoder = new Encoder(); - encoder.encode(new MultimediaObject(source), target, attrs); - assertTrue( target.exists(), "Output file missing"); - } +/** @author a.schild */ +public class EncodingAttributesTest extends AMediaTest { + + public EncodingAttributesTest() { + super(null, "EncodingAttributes"); + } + /** + * Test of encode method, of class Encoder. + * + * @throws java.lang.Exception + */ + @Test + public void testEncodeVideo1() throws Exception { + System.out.println("testEncodeVideo1 avi to mp4"); + + File source = new File(getResourceSourcePath(), "dance1.avi"); + File target = new File(getResourceTargetPath(), "testEncodeVideo1.mp4"); + if (target.exists()) { + target.delete(); + } + AudioAttributes audio = new AudioAttributes(); + audio.setCodec("libfaac"); + audio.setBitRate(128000); + audio.setSamplingRate(44100); + audio.setChannels(2); + VideoAttributes video = new VideoAttributes(); + video.setCodec("mpeg4"); + video.setBitRate(160000); + video.setFrameRate(15); + video.setSize(new VideoSize(176, 144)); + EncodingAttributes attrs = new EncodingAttributes(); + attrs.setOutputFormat("mp4"); + attrs.setAudioAttributes(audio); + attrs.setVideoAttributes(video); + attrs.setDecodingThreads(1); + attrs.setEncodingThreads(1); + // attrs.setFilterThreads(1); + Encoder encoder = new Encoder(); + encoder.encode(new MultimediaObject(source), target, attrs); + assertTrue(target.exists(), "Output file missing"); + } } diff --git a/jave-core-test/src/test/java/ws/schild/jave/FileAudioEncoderTest.java b/jave-core-test/src/test/java/ws/schild/jave/FileAudioEncoderTest.java index 67245a7..162ab66 100644 --- a/jave-core-test/src/test/java/ws/schild/jave/FileAudioEncoderTest.java +++ b/jave-core-test/src/test/java/ws/schild/jave/FileAudioEncoderTest.java @@ -1,8 +1,8 @@ /* * JAVE - A Java Audio/Video Encoder (based on FFMPEG) - * + * * Copyright (C) 2018- Andre Schild - * + * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or @@ -24,186 +24,181 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.Test; -/** - * - * @author a.schild - */ -public class FileAudioEncoderTest extends AMediaTest{ - - public FileAudioEncoderTest() { - super(null, "FileAudioEncoder"); - } +import ws.schild.jave.encode.AudioAttributes; +import ws.schild.jave.encode.EncodingAttributes; + +/** @author a.schild */ +public class FileAudioEncoderTest extends AMediaTest { + + public FileAudioEncoderTest() { + super(null, "FileAudioEncoder"); + } - /** - * Test of encode method, of class Encoder. - * @throws java.lang.Exception - */ - @Test - public void testEncodeAudio10() throws Exception { - System.out.println("testEncodeAudio10"); - - File source = new File(getResourceSourcePath(), "4channels.ogg"); - File target = new File(getResourceTargetPath(), "4channels.flac"); - if (target.exists()) - { - target.delete(); - } - AudioAttributes audioAttr = new AudioAttributes(); - EncodingAttributes encodingAttr = new EncodingAttributes(); + /** + * Test of encode method, of class Encoder. + * + * @throws java.lang.Exception + */ + @Test + public void testEncodeAudio10() throws Exception { + System.out.println("testEncodeAudio10"); - audioAttr.setCodec("flac"); - audioAttr.setBitRate(360000); -// audioAttr.setChannels(4); - audioAttr.setVolume(1000); - audioAttr.setQuality(1000); - audioAttr.setSamplingRate(48000); - encodingAttr.setFormat("flac"); - encodingAttr.setAudioAttributes(audioAttr); - - Encoder encoder = new Encoder(); - encoder.encode(new MultimediaObject(source), target, encodingAttr); - assertTrue(target.exists(), "Output file missing"); + File source = new File(getResourceSourcePath(), "4channels.ogg"); + File target = new File(getResourceTargetPath(), "4channels.flac"); + if (target.exists()) { + target.delete(); } - - /** - * Test of encode method, of class Encoder. - * @throws java.lang.Exception - */ - @Test - public void testEncodeAudio1() throws Exception { - System.out.println("testEncodeAudio1"); - File source = new File(getResourceSourcePath(), "Alesis-Fusion-Clean-Guitar-C3.wav"); - File target = new File(getResourceTargetPath(), "testEncodeAudio1.mp3"); - if (target.exists()) - { - target.delete(); - } - AudioAttributes audio = new AudioAttributes(); - audio.setCodec("libmp3lame"); - audio.setBitRate(128000); - audio.setChannels(2); - audio.setSamplingRate(44100); - EncodingAttributes attrs = new EncodingAttributes(); - attrs.setFormat("mp3"); - attrs.setAudioAttributes(audio); - Encoder encoder = new Encoder(); - PListener listener = new PListener(); - encoder.encode(new MultimediaObject(source), target, attrs, listener); - assertNotNull(listener.getInfo()); - assertTrue(target.exists(), "Output file missing"); + AudioAttributes audioAttr = new AudioAttributes(); + EncodingAttributes encodingAttr = new EncodingAttributes(); + + audioAttr.setCodec("flac"); + audioAttr.setBitRate(360000); + // audioAttr.setChannels(4); + audioAttr.setVolume(1000); + audioAttr.setQuality(1000); + audioAttr.setSamplingRate(48000); + encodingAttr.setOutputFormat("flac"); + encodingAttr.setAudioAttributes(audioAttr); + + Encoder encoder = new Encoder(); + encoder.encode(new MultimediaObject(source), target, encodingAttr); + assertTrue(target.exists(), "Output file missing"); + } + + /** + * Test of encode method, of class Encoder. + * + * @throws java.lang.Exception + */ + @Test + public void testEncodeAudio1() throws Exception { + System.out.println("testEncodeAudio1"); + File source = new File(getResourceSourcePath(), "Alesis-Fusion-Clean-Guitar-C3.wav"); + File target = new File(getResourceTargetPath(), "testEncodeAudio1.mp3"); + if (target.exists()) { + target.delete(); } + AudioAttributes audio = new AudioAttributes(); + audio.setCodec("libmp3lame"); + audio.setBitRate(128000); + audio.setChannels(2); + audio.setSamplingRate(44100); + EncodingAttributes attrs = new EncodingAttributes(); + attrs.setOutputFormat("mp3"); + attrs.setAudioAttributes(audio); + Encoder encoder = new Encoder(); + PListener listener = new PListener(); + encoder.encode(new MultimediaObject(source), target, attrs, listener); + assertNotNull(listener.getInfo()); + assertTrue(target.exists(), "Output file missing"); + } - /** - * Test of encode method, of class Encoder. - * @throws java.lang.Exception - */ - @Test - public void testEncodeAudio2() throws Exception { - System.out.println("testEncodeAudio2"); - File source = new File(getResourceSourcePath(), "Alesis-Fusion-Clean-Guitar-C3.wav"); - File target = new File(getResourceTargetPath(), "testEncodeAudio2.mp3"); - if (target.exists()) - { - target.delete(); - } - AudioAttributes audio = new AudioAttributes(); - audio.setCodec("libmp3lame"); - audio.setBitRate(128000); - audio.setChannels(2); - audio.setSamplingRate(42100); - EncodingAttributes attrs = new EncodingAttributes(); - attrs.setFormat("mp3"); - attrs.setAudioAttributes(audio); - Encoder encoder = new Encoder(); - PListener listener = new PListener(); - String errorMessage= "Exit code of ffmpeg encoding run is 1"; - boolean exceptionThrown= false; - try - { - encoder.encode(new MultimediaObject(source), target, attrs, listener); - } - catch (EncoderException ex) - { - assertEquals(ex.getMessage(), errorMessage, "Not expected error message"); - exceptionThrown= true; - } - assertTrue( exceptionThrown, "No exception occured"); + /** + * Test of encode method, of class Encoder. + * + * @throws java.lang.Exception + */ + @Test + public void testEncodeAudio2() throws Exception { + System.out.println("testEncodeAudio2"); + File source = new File(getResourceSourcePath(), "Alesis-Fusion-Clean-Guitar-C3.wav"); + File target = new File(getResourceTargetPath(), "testEncodeAudio2.mp3"); + if (target.exists()) { + target.delete(); } - - - /** - * Test of encode method, of class Encoder. - * @throws java.lang.Exception - */ - @Test - public void testEncodeAudio3() throws Exception { - System.out.println("testEncodeAudio3"); - File source = new File(getResourceSourcePath(), "testfile3.wmv"); - File target = new File(getResourceTargetPath(), "testEncodeAudio3.mp3"); - if (target.exists()) - { - target.delete(); - } - Encoder encoder = new Encoder(); - AudioAttributes audio = new AudioAttributes(); - audio.setCodec("libmp3lame"); - audio.setBitRate(128000); - audio.setChannels(2); - audio.setSamplingRate(44100); - EncodingAttributes attrs = new EncodingAttributes(); - attrs.setFormat("mp3"); - attrs.setAudioAttributes(audio); - encoder.encode(new MultimediaObject(source), target, attrs); - assertTrue(target.exists(), "Output file missing"); + AudioAttributes audio = new AudioAttributes(); + audio.setCodec("libmp3lame"); + audio.setBitRate(128000); + audio.setChannels(2); + audio.setSamplingRate(42100); + EncodingAttributes attrs = new EncodingAttributes(); + attrs.setOutputFormat("mp3"); + attrs.setAudioAttributes(audio); + Encoder encoder = new Encoder(); + PListener listener = new PListener(); + String errorMessage = "Exit code of ffmpeg encoding run is 1"; + boolean exceptionThrown = false; + try { + encoder.encode(new MultimediaObject(source), target, attrs, listener); + } catch (EncoderException ex) { + assertEquals(ex.getMessage(), errorMessage, "Not expected error message"); + exceptionThrown = true; } + assertTrue(exceptionThrown, "No exception occured"); + } - /** - * Test of encode method, of class Encoder. - * @throws java.lang.Exception - */ - @Test - public void testEncodeAudio4() throws Exception { - System.out.println("testEncodeAudio4"); - File source = new File(getResourceSourcePath(), "buggy.ogg"); - File target = new File(getResourceTargetPath(), "testEncodeAudio4.mp3"); - if (target.exists()) - { - target.delete(); - } - - Encoder encoder = new Encoder(); - AudioAttributes audio = new AudioAttributes(); - audio.setCodec("libmp3lame"); - EncodingAttributes attrs = new EncodingAttributes(); - attrs.setFormat("mp3"); - attrs.setAudioAttributes(audio); - encoder.encode(new MultimediaObject(source), target, attrs); - assertTrue(target.exists(), "Output file missing"); + /** + * Test of encode method, of class Encoder. + * + * @throws java.lang.Exception + */ + @Test + public void testEncodeAudio3() throws Exception { + System.out.println("testEncodeAudio3"); + File source = new File(getResourceSourcePath(), "testfile3.wmv"); + File target = new File(getResourceTargetPath(), "testEncodeAudio3.mp3"); + if (target.exists()) { + target.delete(); } + Encoder encoder = new Encoder(); + AudioAttributes audio = new AudioAttributes(); + audio.setCodec("libmp3lame"); + audio.setBitRate(128000); + audio.setChannels(2); + audio.setSamplingRate(44100); + EncodingAttributes attrs = new EncodingAttributes(); + attrs.setOutputFormat("mp3"); + attrs.setAudioAttributes(audio); + encoder.encode(new MultimediaObject(source), target, attrs); + assertTrue(target.exists(), "Output file missing"); + } + /** + * Test of encode method, of class Encoder. + * + * @throws java.lang.Exception + */ + @Test + public void testEncodeAudio4() throws Exception { + System.out.println("testEncodeAudio4"); + File source = new File(getResourceSourcePath(), "buggy.ogg"); + File target = new File(getResourceTargetPath(), "testEncodeAudio4.mp3"); + if (target.exists()) { + target.delete(); + } + + Encoder encoder = new Encoder(); + AudioAttributes audio = new AudioAttributes(); + audio.setCodec("libmp3lame"); + EncodingAttributes attrs = new EncodingAttributes(); + attrs.setOutputFormat("mp3"); + attrs.setAudioAttributes(audio); + encoder.encode(new MultimediaObject(source), target, attrs); + assertTrue(target.exists(), "Output file missing"); + } + + /** + * Test of encode method, of class Encoder. + * + * @throws java.lang.Exception + */ + @Test + public void testEncodeAudio5() throws Exception { + System.out.println("testEncodeAudio5"); + File source = new File(getResourceSourcePath(), "cj2009-10-05d01t07.ku100_at37.flac"); + File target = new File(getResourceTargetPath(), "testEncodeAudio5.mp3"); + if (target.exists()) { + target.delete(); + } - /** - * Test of encode method, of class Encoder. - * @throws java.lang.Exception - */ - @Test - public void testEncodeAudio5() throws Exception { - System.out.println("testEncodeAudio5"); - File source = new File(getResourceSourcePath(), "cj2009-10-05d01t07.ku100_at37.flac"); - File target = new File(getResourceTargetPath(), "testEncodeAudio5.mp3"); - if (target.exists()) - { - target.delete(); - } - - Encoder encoder = new Encoder(); - AudioAttributes audio = new AudioAttributes(); - audio.setCodec("libmp3lame"); - EncodingAttributes attrs = new EncodingAttributes(); - attrs.setFormat("mp3"); - attrs.setAudioAttributes(audio); - attrs.setMapMetaData(true); - encoder.encode(new MultimediaObject(source), target, attrs); - assertTrue(target.exists(), "Output file missing"); - } + Encoder encoder = new Encoder(); + AudioAttributes audio = new AudioAttributes(); + audio.setCodec("libmp3lame"); + EncodingAttributes attrs = new EncodingAttributes(); + attrs.setOutputFormat("mp3"); + attrs.setAudioAttributes(audio); + attrs.setMapMetaData(true); + encoder.encode(new MultimediaObject(source), target, attrs); + assertTrue(target.exists(), "Output file missing"); + } } diff --git a/jave-core-test/src/test/java/ws/schild/jave/FileVideoEncoderTest.java b/jave-core-test/src/test/java/ws/schild/jave/FileVideoEncoderTest.java index 990805c..82c2fc5 100644 --- a/jave-core-test/src/test/java/ws/schild/jave/FileVideoEncoderTest.java +++ b/jave-core-test/src/test/java/ws/schild/jave/FileVideoEncoderTest.java @@ -1,8 +1,8 @@ /* * JAVE - A Java Audio/Video Encoder (based on FFMPEG) - * + * * Copyright (C) 2018- Andre Schild - * + * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or @@ -24,344 +24,336 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.Test; -/** - * - * @author a.schild - */ -public class FileVideoEncoderTest extends AMediaTest{ - - public FileVideoEncoderTest() { - super(null, "FileVideoEncoder"); +import ws.schild.jave.encode.AudioAttributes; +import ws.schild.jave.encode.EncodingAttributes; +import ws.schild.jave.encode.VideoAttributes; +import ws.schild.jave.info.VideoSize; + +/** @author a.schild */ +public class FileVideoEncoderTest extends AMediaTest { + + public FileVideoEncoderTest() { + super(null, "FileVideoEncoder"); + } + + /** + * Test of encode method, of class Encoder. + * + * @throws java.lang.Exception + */ + @Test + public void testEncodeVideo1() throws Exception { + System.out.println("testEncodeVideo1"); + + File source = new File(getResourceSourcePath(), "dance1.avi"); + File target = new File(getResourceTargetPath(), "testEncodeVideo1.3gp"); + if (target.exists()) { + target.delete(); } + AudioAttributes audio = new AudioAttributes(); + audio.setCodec("libfaac"); + audio.setBitRate(128000); + audio.setSamplingRate(44100); + audio.setChannels(2); + VideoAttributes video = new VideoAttributes(); + video.setCodec("mpeg4"); + video.setBitRate(160000); + video.setFrameRate(15); + video.setSize(new VideoSize(176, 144)); + EncodingAttributes attrs = new EncodingAttributes(); + attrs.setOutputFormat("3gp"); + attrs.setAudioAttributes(audio); + attrs.setVideoAttributes(video); + Encoder encoder = new Encoder(); + encoder.encode(new MultimediaObject(source), target, attrs); + assertTrue(target.exists(), "Output file missing"); + } - /** - * Test of encode method, of class Encoder. - * @throws java.lang.Exception - */ - @Test - public void testEncodeVideo1() throws Exception { - System.out.println("testEncodeVideo1"); - - File source = new File(getResourceSourcePath(), "dance1.avi"); - File target = new File(getResourceTargetPath(), "testEncodeVideo1.3gp"); - if (target.exists()) - { - target.delete(); - } - AudioAttributes audio = new AudioAttributes(); - audio.setCodec("libfaac"); - audio.setBitRate(128000); - audio.setSamplingRate(44100); - audio.setChannels(2); - VideoAttributes video = new VideoAttributes(); - video.setCodec("mpeg4"); - video.setBitRate(160000); - video.setFrameRate(15); - video.setSize(new VideoSize(176, 144)); - EncodingAttributes attrs = new EncodingAttributes(); - attrs.setFormat("3gp"); - attrs.setAudioAttributes(audio); - attrs.setVideoAttributes(video); - Encoder encoder = new Encoder(); - encoder.encode(new MultimediaObject(source), target, attrs); - assertTrue( target.exists(), "Output file missing"); + /** + * Test of encode method, of class Encoder. + * + * @throws java.lang.Exception + */ + @Test + public void testEncodeVideo2() throws Exception { + System.out.println("testEncodeVideo2"); + File source = new File(getResourceSourcePath(), "dance1.avi"); + File target = new File(getResourceTargetPath(), "testEncodeVideo2.3gp"); + if (target.exists()) { + target.delete(); } + AudioAttributes audio = new AudioAttributes(); + audio.setCodec("libfaac"); + audio.setBitRate(64000); + audio.setSamplingRate(6400); + audio.setChannels(2); + VideoAttributes video = new VideoAttributes(); + video.setCodec("mpeg4"); + video.setBitRate(60000); + video.setFrameRate(15); + video.setSize(new VideoSize(160, 120)); + EncodingAttributes attrs = new EncodingAttributes(); + attrs.setOutputFormat("3gp"); + attrs.setAudioAttributes(audio); + attrs.setVideoAttributes(video); + Encoder encoder = new Encoder(); + PListener listener = new PListener(); + encoder.encode(new MultimediaObject(source), target, attrs, listener); + assertNotNull(listener.getInfo()); + assertTrue(target.exists(), "Output file missing"); + } - /** - * Test of encode method, of class Encoder. - * @throws java.lang.Exception - */ - @Test - public void testEncodeVideo2() throws Exception { - System.out.println("testEncodeVideo2"); - File source = new File(getResourceSourcePath(), "dance1.avi"); - File target = new File(getResourceTargetPath(), "testEncodeVideo2.3gp"); - if (target.exists()) - { - target.delete(); - } - AudioAttributes audio = new AudioAttributes(); - audio.setCodec("libfaac"); - audio.setBitRate(64000); - audio.setSamplingRate(6400); - audio.setChannels(2); - VideoAttributes video = new VideoAttributes(); - video.setCodec("mpeg4"); - video.setBitRate(60000); - video.setFrameRate(15); - video.setSize(new VideoSize(160, 120)); - EncodingAttributes attrs = new EncodingAttributes(); - attrs.setFormat("3gp"); - attrs.setAudioAttributes(audio); - attrs.setVideoAttributes(video); - Encoder encoder = new Encoder(); - PListener listener = new PListener(); - encoder.encode(new MultimediaObject(source), target, attrs, listener); - assertNotNull(listener.getInfo()); - assertTrue( target.exists(), "Output file missing"); + /** + * Test of encode method, of class Encoder. + * + * @throws java.lang.Exception + */ + @Test + public void testEncodeVideo3() throws Exception { + System.out.println("testEncodeVideo3"); + File source = new File(getResourceSourcePath(), "AV36_1.AVI"); + File target = new File(getResourceTargetPath(), "testEncodeVideo3.3gp"); + if (target.exists()) { + target.delete(); + } + AudioAttributes audio = new AudioAttributes(); + audio.setCodec("libfaac"); + audio.setBitRate(128000); + audio.setSamplingRate(44100); + audio.setChannels(2); + VideoAttributes video = new VideoAttributes(); + video.setCodec("mpeg4"); + video.setBitRate(160000); + video.setFrameRate(15); + video.setSize(new VideoSize(176, 144)); + EncodingAttributes attrs = new EncodingAttributes(); + attrs.setOutputFormat("3gp"); + attrs.setAudioAttributes(audio); + attrs.setVideoAttributes(video); + Encoder encoder = new Encoder(); + PListener listener = new PListener(); + String errorMessage = "Exit code of ffmpeg encoding run is 1"; + boolean exceptionThrown = false; + try { + encoder.encode(new MultimediaObject(source), target, attrs, listener); + } catch (EncoderException ex) { + assertEquals(ex.getMessage(), errorMessage, "Not expected error message"); + exceptionThrown = true; } + assertTrue(exceptionThrown, "No exception occured"); + } - /** - * Test of encode method, of class Encoder. - * @throws java.lang.Exception - */ - @Test - public void testEncodeVideo3() throws Exception { - System.out.println("testEncodeVideo3"); - File source = new File(getResourceSourcePath(), "AV36_1.AVI"); - File target = new File(getResourceTargetPath(), "testEncodeVideo3.3gp"); - if (target.exists()) - { - target.delete(); - } - AudioAttributes audio = new AudioAttributes(); - audio.setCodec("libfaac"); - audio.setBitRate(128000); - audio.setSamplingRate(44100); - audio.setChannels(2); - VideoAttributes video = new VideoAttributes(); - video.setCodec("mpeg4"); - video.setBitRate(160000); - video.setFrameRate(15); - video.setSize(new VideoSize(176, 144)); - EncodingAttributes attrs = new EncodingAttributes(); - attrs.setFormat("3gp"); - attrs.setAudioAttributes(audio); - attrs.setVideoAttributes(video); - Encoder encoder = new Encoder(); - PListener listener = new PListener(); - String errorMessage= "Exit code of ffmpeg encoding run is 1"; - boolean exceptionThrown= false; - try - { - encoder.encode(new MultimediaObject(source), target, attrs, listener); - } - catch (EncoderException ex) - { - assertEquals(ex.getMessage(), errorMessage, "Not expected error message"); - exceptionThrown= true; - } - assertTrue( exceptionThrown, "No exception occured"); + /** + * Test of encode method, of class Encoder. + * + * @throws java.lang.Exception + */ + @Test + public void testEncodeVideo4() throws Exception { + System.out.println("testEncodeVideo4"); + File source = new File(getResourceSourcePath(), "AV36_1.AVI"); + File target = new File(getResourceTargetPath(), "testEncodeVideo4.3gp"); + if (target.exists()) { + target.delete(); } - - /** - * Test of encode method, of class Encoder. - * @throws java.lang.Exception - */ - @Test - public void testEncodeVideo4() throws Exception { - System.out.println("testEncodeVideo4"); - File source = new File(getResourceSourcePath(), "AV36_1.AVI"); - File target = new File(getResourceTargetPath(), "testEncodeVideo4.3gp"); - if (target.exists()) - { - target.delete(); - } - AudioAttributes audio = new AudioAttributes(); - audio.setCodec("adpcm_ms"); - audio.setBitRate(128000); - audio.setSamplingRate(44100); - audio.setChannels(2); - VideoAttributes video = new VideoAttributes(); - video.setCodec("mpeg4"); - video.setBitRate(160000); - video.setFrameRate(15); - video.setSize(new VideoSize(176, 144)); - EncodingAttributes attrs = new EncodingAttributes(); - attrs.setFormat("3gp"); - attrs.setAudioAttributes(audio); - attrs.setVideoAttributes(video); - Encoder encoder = new Encoder(); - PListener listener = new PListener(); - boolean exceptionThrown= false; - String errorMessage= "Exit code of ffmpeg encoding run is 1"; - try - { - encoder.encode(new MultimediaObject(source), target, attrs, listener); - } - catch (EncoderException ex) - { - assertEquals(ex.getMessage(), errorMessage, "Not expected error message"); - exceptionThrown= true; - } - assertTrue( exceptionThrown, "No exception occured"); + AudioAttributes audio = new AudioAttributes(); + audio.setCodec("adpcm_ms"); + audio.setBitRate(128000); + audio.setSamplingRate(44100); + audio.setChannels(2); + VideoAttributes video = new VideoAttributes(); + video.setCodec("mpeg4"); + video.setBitRate(160000); + video.setFrameRate(15); + video.setSize(new VideoSize(176, 144)); + EncodingAttributes attrs = new EncodingAttributes(); + attrs.setOutputFormat("3gp"); + attrs.setAudioAttributes(audio); + attrs.setVideoAttributes(video); + Encoder encoder = new Encoder(); + PListener listener = new PListener(); + boolean exceptionThrown = false; + String errorMessage = "Exit code of ffmpeg encoding run is 1"; + try { + encoder.encode(new MultimediaObject(source), target, attrs, listener); + } catch (EncoderException ex) { + assertEquals(ex.getMessage(), errorMessage, "Not expected error message"); + exceptionThrown = true; } - - /** - * Test of encode method, of class Encoder. - * @throws java.lang.Exception - */ - @Test - public void testEncodeVideo5() throws Exception { - System.out.println("testEncodeVideo5"); - File source = new File(getResourceSourcePath(), "AV36_1.AVI"); - File target = new File(getResourceTargetPath(), "testEncodeVideo5.flv"); - if (target.exists()) - { - target.delete(); - } - AudioAttributes audio = new AudioAttributes(); - audio.setCodec("libmp3lame"); - audio.setBitRate(64000); - audio.setChannels(1); - audio.setSamplingRate(22050); - VideoAttributes video = new VideoAttributes(); - video.setCodec("flv"); - video.setBitRate(160000); - video.setFrameRate(15); - video.setSize(new VideoSize(400, 300)); - EncodingAttributes attrs = new EncodingAttributes(); - attrs.setFormat("flv"); - attrs.setAudioAttributes(audio); - attrs.setVideoAttributes(video); - Encoder encoder = new Encoder(); - PListener listener = new PListener(); - encoder.encode(new MultimediaObject(source), target, attrs, listener); - assertNotNull(listener.getInfo()); - assertTrue(target.exists(), "Output file missing"); + assertTrue(exceptionThrown, "No exception occured"); + } + + /** + * Test of encode method, of class Encoder. + * + * @throws java.lang.Exception + */ + @Test + public void testEncodeVideo5() throws Exception { + System.out.println("testEncodeVideo5"); + File source = new File(getResourceSourcePath(), "AV36_1.AVI"); + File target = new File(getResourceTargetPath(), "testEncodeVideo5.flv"); + if (target.exists()) { + target.delete(); } + AudioAttributes audio = new AudioAttributes(); + audio.setCodec("libmp3lame"); + audio.setBitRate(64000); + audio.setChannels(1); + audio.setSamplingRate(22050); + VideoAttributes video = new VideoAttributes(); + video.setCodec("flv"); + video.setBitRate(160000); + video.setFrameRate(15); + video.setSize(new VideoSize(400, 300)); + EncodingAttributes attrs = new EncodingAttributes(); + attrs.setOutputFormat("flv"); + attrs.setAudioAttributes(audio); + attrs.setVideoAttributes(video); + Encoder encoder = new Encoder(); + PListener listener = new PListener(); + encoder.encode(new MultimediaObject(source), target, attrs, listener); + assertNotNull(listener.getInfo()); + assertTrue(target.exists(), "Output file missing"); + } - /** - * Test of encode method, of class Encoder. - * @throws java.lang.Exception - */ - @Test - public void testEncodeAudio09() throws Exception { - System.out.println("testEncodeAudio09"); - File source = new File(getResourceSourcePath(), "testfile09.mp3"); - if (source.exists()) - { - File target = new File(getResourceTargetPath(), "testEncodeAudio09.wav"); - if (target.exists()) - { - target.delete(); - } + /** + * Test of encode method, of class Encoder. + * + * @throws java.lang.Exception + */ + @Test + public void testEncodeAudio09() throws Exception { + System.out.println("testEncodeAudio09"); + File source = new File(getResourceSourcePath(), "testfile09.mp3"); + if (source.exists()) { + File target = new File(getResourceTargetPath(), "testEncodeAudio09.wav"); + if (target.exists()) { + target.delete(); + } - //Set Audio Attributes - AudioAttributes audio = new AudioAttributes(); - audio.setCodec("pcm_s16le"); - audio.setChannels(2); - audio.setSamplingRate(44100); + // Set Audio Attributes + AudioAttributes audio = new AudioAttributes(); + audio.setCodec("pcm_s16le"); + audio.setChannels(2); + audio.setSamplingRate(44100); - //Set encoding attributes - EncodingAttributes attributes = new EncodingAttributes(); - attributes.setFormat("wav"); - attributes.setAudioAttributes(audio); - Encoder encoder = new Encoder(); - PListener listener = new PListener(); - encoder.encode(new MultimediaObject(source), target, attributes, listener); - assertNotNull(listener.getInfo()); - assertTrue(target.exists(), "Output file missing"); - } + // Set encoding attributes + EncodingAttributes attributes = new EncodingAttributes(); + attributes.setOutputFormat("wav"); + attributes.setAudioAttributes(audio); + Encoder encoder = new Encoder(); + PListener listener = new PListener(); + encoder.encode(new MultimediaObject(source), target, attributes, listener); + assertNotNull(listener.getInfo()); + assertTrue(target.exists(), "Output file missing"); } - - /** - * Test of encode method, of class Encoder. - * @throws java.lang.Exception - */ - @Test - public void testEncodeVideo10() throws Exception { - System.out.println("testEncodeVideo10"); - File source = new File(getResourceSourcePath(), "private/test10.mpg"); - if (source.exists()) - { - File target = new File(getResourceTargetPath(), "testEncodeVideo10.mp4"); - if (target.exists()) - { - target.delete(); - } + } + + /** + * Test of encode method, of class Encoder. + * + * @throws java.lang.Exception + */ + @Test + public void testEncodeVideo10() throws Exception { + System.out.println("testEncodeVideo10"); + File source = new File(getResourceSourcePath(), "private/test10.mpg"); + if (source.exists()) { + File target = new File(getResourceTargetPath(), "testEncodeVideo10.mp4"); + if (target.exists()) { + target.delete(); + } - AudioAttributes audio = new AudioAttributes(); - audio.setCodec("eac3"); - audio.setBitRate(97000); - audio.setSamplingRate(48000); - audio.setChannels(2); - VideoAttributes video = new VideoAttributes(); - video.setCodec("mpeg4"); - video.setBitRate(1500000); - video.setFrameRate(30); - video.setSize(new VideoSize(320, 240)); - EncodingAttributes attrs = new EncodingAttributes(); - attrs.setFormat("mp4"); - attrs.setVideoAttributes(video); - attrs.setAudioAttributes(audio); - Encoder encoder = new Encoder(); - PListener listener = new PListener(); - encoder.encode(new MultimediaObject(source), target, attrs, listener); - assertNotNull(listener.getInfo()); - assertTrue(target.exists(), "Output file missing"); - } + AudioAttributes audio = new AudioAttributes(); + audio.setCodec("eac3"); + audio.setBitRate(97000); + audio.setSamplingRate(48000); + audio.setChannels(2); + VideoAttributes video = new VideoAttributes(); + video.setCodec("mpeg4"); + video.setBitRate(1500000); + video.setFrameRate(30); + video.setSize(new VideoSize(320, 240)); + EncodingAttributes attrs = new EncodingAttributes(); + attrs.setOutputFormat("mp4"); + attrs.setVideoAttributes(video); + attrs.setAudioAttributes(audio); + Encoder encoder = new Encoder(); + PListener listener = new PListener(); + encoder.encode(new MultimediaObject(source), target, attrs, listener); + assertNotNull(listener.getInfo()); + assertTrue(target.exists(), "Output file missing"); } + } - /** - * Test of encode method, of class Encoder. - * @throws java.lang.Exception - */ - @Test - public void testEncodeVideo11() throws Exception { - System.out.println("testEncodeVideo11"); - - File source = new File(getResourceSourcePath(), "dance1.avi"); - File target = new File(getResourceTargetPath(), "testEncodeVideo11.3gp"); - if (target.exists()) - { - target.delete(); - } - AudioAttributes audio = new AudioAttributes(); - audio.setCodec("libfaac"); - audio.setBitRate(128000); - audio.setSamplingRate(44100); - audio.setChannels(2); - audio.setQuality(31); - VideoAttributes video = new VideoAttributes(); - video.setCodec("mpeg4"); - video.setBitRate(160000); - video.setFrameRate(15); - video.setSize(new VideoSize(176, 144)); - video.setQuality(31); - EncodingAttributes attrs = new EncodingAttributes(); - attrs.setFormat("3gp"); - attrs.setAudioAttributes(audio); - attrs.setVideoAttributes(video); - Encoder encoder = new Encoder(); - encoder.encode(new MultimediaObject(source), target, attrs); - assertTrue( target.exists(), "Output file missing"); + /** + * Test of encode method, of class Encoder. + * + * @throws java.lang.Exception + */ + @Test + public void testEncodeVideo11() throws Exception { + System.out.println("testEncodeVideo11"); + + File source = new File(getResourceSourcePath(), "dance1.avi"); + File target = new File(getResourceTargetPath(), "testEncodeVideo11.3gp"); + if (target.exists()) { + target.delete(); } + AudioAttributes audio = new AudioAttributes(); + audio.setCodec("libfaac"); + audio.setBitRate(128000); + audio.setSamplingRate(44100); + audio.setChannels(2); + audio.setQuality(31); + VideoAttributes video = new VideoAttributes(); + video.setCodec("mpeg4"); + video.setBitRate(160000); + video.setFrameRate(15); + video.setSize(new VideoSize(176, 144)); + video.setQuality(31); + EncodingAttributes attrs = new EncodingAttributes(); + attrs.setOutputFormat("3gp"); + attrs.setAudioAttributes(audio); + attrs.setVideoAttributes(video); + Encoder encoder = new Encoder(); + encoder.encode(new MultimediaObject(source), target, attrs); + assertTrue(target.exists(), "Output file missing"); + } - - /** - * Test of encode method, of class Encoder. - * @throws java.lang.Exception - */ - @Test - public void testEncodeVideo12() throws Exception { - System.out.println("testEncodeVideo12"); - - File source = new File(getResourceSourcePath(), "small.mp4"); - File target = new File(getResourceTargetPath(), "testEncodeVideo12.mp4"); - if (target.exists()) - { - target.delete(); - } - AudioAttributes audioAttr = new AudioAttributes(); - VideoAttributes videoAttr = new VideoAttributes(); - EncodingAttributes encodingAttr = new EncodingAttributes(); + /** + * Test of encode method, of class Encoder. + * + * @throws java.lang.Exception + */ + @Test + public void testEncodeVideo12() throws Exception { + System.out.println("testEncodeVideo12"); - audioAttr.setChannels(2); - audioAttr.setCodec("aac"); - audioAttr.setBitRate(128000); - audioAttr.setSamplingRate(44100); + File source = new File(getResourceSourcePath(), "small.mp4"); + File target = new File(getResourceTargetPath(), "testEncodeVideo12.mp4"); + if (target.exists()) { + target.delete(); + } + AudioAttributes audioAttr = new AudioAttributes(); + VideoAttributes videoAttr = new VideoAttributes(); + EncodingAttributes encodingAttr = new EncodingAttributes(); - videoAttr.setCodec("libx264"); - videoAttr.setBitRate(4000000); + audioAttr.setChannels(2); + audioAttr.setCodec("aac"); + audioAttr.setBitRate(128000); + audioAttr.setSamplingRate(44100); - encodingAttr.setAudioAttributes(audioAttr); - encodingAttr.setVideoAttributes(videoAttr); - encodingAttr.setFormat("mp4"); + videoAttr.setCodec("libx264"); + videoAttr.setBitRate(4000000); - Encoder encoder = new Encoder(); - encoder.encode(new MultimediaObject(source), target, encodingAttr); - assertTrue(target.exists(), "Output file missing"); - } + encodingAttr.setAudioAttributes(audioAttr); + encodingAttr.setVideoAttributes(videoAttr); + encodingAttr.setOutputFormat("mp4"); + Encoder encoder = new Encoder(); + encoder.encode(new MultimediaObject(source), target, encodingAttr); + assertTrue(target.exists(), "Output file missing"); + } } diff --git a/jave-core-test/src/test/java/ws/schild/jave/MultimediaObjectTest.java b/jave-core-test/src/test/java/ws/schild/jave/MultimediaObjectTest.java index 31df4e4..4846767 100644 --- a/jave-core-test/src/test/java/ws/schild/jave/MultimediaObjectTest.java +++ b/jave-core-test/src/test/java/ws/schild/jave/MultimediaObjectTest.java @@ -1,8 +1,8 @@ /* * JAVE - A Java Audio/Video Encoder (based on FFMPEG) - * + * * Copyright (C) 2018- Andre Schild - * + * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or @@ -24,149 +24,148 @@ import static org.junit.jupiter.api.Assertions.assertNull; import org.junit.jupiter.api.Test; -/** - * - * @author a.schild - */ +import ws.schild.jave.info.MultimediaInfo; + +/** @author a.schild */ public class MultimediaObjectTest extends AMediaTest { - - public MultimediaObjectTest() { - super(null, "MultimediaObject"); - } - /** - * Test of getFile method, of class MultimediaObject. - */ - @Test - public void testGetFile() { - System.out.println("getFile"); - File file = new File(getResourceSourcePath(), "dance1.avi"); - MultimediaObject instance = new MultimediaObject(file); - File expResult = file; - File result = instance.getFile(); - assertEquals(expResult, result); - } + public MultimediaObjectTest() { + super(null, "MultimediaObject"); + } - /** - * Test of getInfo method, of class MultimediaObject. - * @throws java.lang.Exception - */ - @Test - public void testGetInfo01() throws Exception { - System.out.println("testGetInfo01"); - File file = new File(getResourceSourcePath(), "dance1.avi"); - MultimediaObject instance = new MultimediaObject(file); - MultimediaInfo result = instance.getInfo(); - - assertEquals("avi", result.getFormat()); - assertEquals(1530, result.getDuration()); - assertNull(result.getAudio()); - assertEquals("rawvideo", result.getVideo().getDecoder()); - assertEquals(320, result.getVideo().getSize().getWidth()); - assertEquals(240, result.getVideo().getSize().getHeight()); - assertEquals(4817000, result.getVideo().getBitRate()); - assertEquals(15.0f, result.getVideo().getFrameRate()); - } - - /** - * Test of getInfo method, of class MultimediaObject. - * @throws java.lang.Exception - */ - @Test - public void testGetInfo02() throws Exception { - System.out.println("testGetInfo02"); - File file = new File(getResourceSourcePath(), "4channels.ogg"); - MultimediaObject instance = new MultimediaObject(file); - MultimediaInfo result = instance.getInfo(); - - assertEquals("ogg", result.getFormat()); - assertEquals(20000, result.getDuration()); - assertNull(result.getVideo()); - assertEquals("vorbis", result.getAudio().getDecoder()); - assertEquals(48000, result.getAudio().getSamplingRate()); - assertEquals(4, result.getAudio().getChannels()); - assertEquals(959000, result.getAudio().getBitRate()); - } - - /** - * Test of getInfo method, of class MultimediaObject. - * @throws java.lang.Exception - */ - @Test - public void testGetInfo03() throws Exception { - System.out.println("testGetInfo03"); - File file = new File(getResourceSourcePath(), "2019V7HR.amr"); - MultimediaObject instance = new MultimediaObject(file); - try - { - instance.getInfo(); - assertEquals(1,1, "Invalid data in header not thrown"); - } - catch (Exception ex) - { - assertEquals(1,1); - } - } - - /** - * Test of getInfo method, of class MultimediaObject. - * @throws java.lang.Exception - */ - @Test - public void testGetInfo04() throws Exception { - System.out.println("testGetInfo04"); - URL source = new URL("https://samples.ffmpeg.org/MPEG1/zelda%20first%20commercial.mpeg"); - MultimediaObject instance = new MultimediaObject(source); - MultimediaInfo result = instance.getInfo(); - assertEquals(result.getFormat(), "mpeg", "Invalid video format"); - assertEquals(result.getDuration(), 29800, "Invalid duration"); - } + /** Test of getFile method, of class MultimediaObject. */ + @Test + public void testGetFile() { + System.out.println("getFile"); + File file = new File(getResourceSourcePath(), "dance1.avi"); + MultimediaObject instance = new MultimediaObject(file); + File expResult = file; + File result = instance.getFile(); + assertEquals(expResult, result); + } + /** + * Test of getInfo method, of class MultimediaObject. + * + * @throws java.lang.Exception + */ + @Test + public void testGetInfo01() throws Exception { + System.out.println("testGetInfo01"); + File file = new File(getResourceSourcePath(), "dance1.avi"); + MultimediaObject instance = new MultimediaObject(file); + MultimediaInfo result = instance.getInfo(); - - /** - * Test of getInfo method, of class MultimediaObject. - * @throws java.lang.Exception - */ -// @Test -// public void testGetInfo05() throws Exception { -// System.out.println("testGetInfo05"); -// File file = new File(getResourceSourcePath(), "PCRecorded.mp4"); -// MultimediaObject instance = new MultimediaObject(file); -// MultimediaInfo result = instance.getInfo(); -// assertEquals("matroska", result.getFormat(), "Invalid video format"); -// assertEquals("vp8", result.getVideo().getDecoder(), "Invalid video decoder format"); -// assertEquals("opus", result.getAudio().getDecoder(), "Invalid audio decoder format"); -// -// } -// - /** - * Test of getInfo method, of class MultimediaObject. - * @throws java.lang.Exception - */ - @Test - public void testGetInfo06() throws Exception { - System.out.println("testGetInfo06"); - File file = new File(getResourceSourcePath(), "size1.mp4"); - MultimediaObject instance = new MultimediaObject(file); - MultimediaInfo result = instance.getInfo(); - assertEquals("mov", result.getFormat(), "Invalid video format"); - assertEquals(640, result.getVideo().getSize().getHeight(), "Video height not as expected"); - assertEquals(360, result.getVideo().getSize().getWidth(), "Video width not as expected"); - } + assertEquals("avi", result.getFormat()); + assertEquals(1530, result.getDuration()); + assertNull(result.getAudio()); + assertEquals("rawvideo", result.getVideo().getDecoder()); + assertEquals(320, result.getVideo().getSize().getWidth()); + assertEquals(240, result.getVideo().getSize().getHeight()); + assertEquals(4817000, result.getVideo().getBitRate()); + assertEquals(15.0f, result.getVideo().getFrameRate()); + } + + /** + * Test of getInfo method, of class MultimediaObject. + * + * @throws java.lang.Exception + */ + @Test + public void testGetInfo02() throws Exception { + System.out.println("testGetInfo02"); + File file = new File(getResourceSourcePath(), "4channels.ogg"); + MultimediaObject instance = new MultimediaObject(file); + MultimediaInfo result = instance.getInfo(); - /** - * Test of getInfo method, of class MultimediaObject. - * @throws java.lang.Exception - */ - @Test - public void testGetInfo07() throws Exception { - System.out.println("testGetInfo07"); - File file = new File(getResourceSourcePath(), "size2.mp4"); - MultimediaObject instance = new MultimediaObject(file); - MultimediaInfo result = instance.getInfo(); - assertEquals("mov", result.getFormat(), "Invalid video format"); - assertEquals(960, result.getVideo().getSize().getHeight(), "Video height not as expected"); - assertEquals(544, result.getVideo().getSize().getWidth(), "Video width not as expected"); + assertEquals("ogg", result.getFormat()); + assertEquals(20000, result.getDuration()); + assertNull(result.getVideo()); + assertEquals("vorbis", result.getAudio().getDecoder()); + assertEquals(48000, result.getAudio().getSamplingRate()); + assertEquals(4, result.getAudio().getChannels()); + assertEquals(959000, result.getAudio().getBitRate()); + } + + /** + * Test of getInfo method, of class MultimediaObject. + * + * @throws java.lang.Exception + */ + @Test + public void testGetInfo03() throws Exception { + System.out.println("testGetInfo03"); + File file = new File(getResourceSourcePath(), "2019V7HR.amr"); + MultimediaObject instance = new MultimediaObject(file); + try { + instance.getInfo(); + assertEquals(1, 1, "Invalid data in header not thrown"); + } catch (Exception ex) { + assertEquals(1, 1); } + } + + /** + * Test of getInfo method, of class MultimediaObject. + * + * @throws java.lang.Exception + */ + @Test + public void testGetInfo04() throws Exception { + System.out.println("testGetInfo04"); + URL source = new URL("https://samples.ffmpeg.org/MPEG1/zelda%20first%20commercial.mpeg"); + MultimediaObject instance = new MultimediaObject(source); + MultimediaInfo result = instance.getInfo(); + assertEquals(result.getFormat(), "mpeg", "Invalid video format"); + assertEquals(result.getDuration(), 29800, "Invalid duration"); + } + + /** + * Test of getInfo method, of class MultimediaObject. + * + * @throws java.lang.Exception + */ + // @Test + // public void testGetInfo05() throws Exception { + // System.out.println("testGetInfo05"); + // File file = new File(getResourceSourcePath(), "PCRecorded.mp4"); + // MultimediaObject instance = new MultimediaObject(file); + // MultimediaInfo result = instance.getInfo(); + // assertEquals("matroska", result.getFormat(), "Invalid video format"); + // assertEquals("vp8", result.getVideo().getDecoder(), "Invalid video decoder format"); + // assertEquals("opus", result.getAudio().getDecoder(), "Invalid audio decoder format"); + // + // } + // + /** + * Test of getInfo method, of class MultimediaObject. + * + * @throws java.lang.Exception + */ + @Test + public void testGetInfo06() throws Exception { + System.out.println("testGetInfo06"); + File file = new File(getResourceSourcePath(), "size1.mp4"); + MultimediaObject instance = new MultimediaObject(file); + MultimediaInfo result = instance.getInfo(); + assertEquals("mov", result.getFormat(), "Invalid video format"); + assertEquals(640, result.getVideo().getSize().getHeight(), "Video height not as expected"); + assertEquals(360, result.getVideo().getSize().getWidth(), "Video width not as expected"); + } + + /** + * Test of getInfo method, of class MultimediaObject. + * + * @throws java.lang.Exception + */ + @Test + public void testGetInfo07() throws Exception { + System.out.println("testGetInfo07"); + File file = new File(getResourceSourcePath(), "size2.mp4"); + MultimediaObject instance = new MultimediaObject(file); + MultimediaInfo result = instance.getInfo(); + assertEquals("mov", result.getFormat(), "Invalid video format"); + assertEquals(960, result.getVideo().getSize().getHeight(), "Video height not as expected"); + assertEquals(544, result.getVideo().getSize().getWidth(), "Video width not as expected"); + } } diff --git a/jave-core-test/src/test/java/ws/schild/jave/PListener.java b/jave-core-test/src/test/java/ws/schild/jave/PListener.java index edce1e9..f2f24a9 100644 --- a/jave-core-test/src/test/java/ws/schild/jave/PListener.java +++ b/jave-core-test/src/test/java/ws/schild/jave/PListener.java @@ -8,50 +8,42 @@ import java.util.LinkedList; import java.util.List; -/** - * - * @author a.schild - */ -public class PListener implements EncoderProgressListener - { - private MultimediaInfo _info= null; - private final List _messages= new LinkedList<>(); - private final List _progress= new LinkedList<>(); - - @Override - public void sourceInfo(MultimediaInfo info) { - _info= info; - } - - @Override - public void progress(int permil) { - _progress.add(permil); - } - - @Override - public void message(String message) { - _messages.add(message); - } - - /** - * @return the _info - */ - public MultimediaInfo getInfo() { - return _info; - } - - /** - * @return the _messages - */ - public List getMessages() { - return _messages; - } - - /** - * @return the _progress - */ - public List getProgress() { - return _progress; - } - +import ws.schild.jave.info.MultimediaInfo; +import ws.schild.jave.progress.EncoderProgressListener; + +/** @author a.schild */ +public class PListener implements EncoderProgressListener { + private MultimediaInfo _info = null; + private final List _messages = new LinkedList<>(); + private final List _progress = new LinkedList<>(); + + @Override + public void sourceInfo(MultimediaInfo info) { + _info = info; + } + + @Override + public void progress(int permil) { + _progress.add(permil); + } + + @Override + public void message(String message) { + _messages.add(message); + } + + /** @return the _info */ + public MultimediaInfo getInfo() { + return _info; + } + + /** @return the _messages */ + public List getMessages() { + return _messages; + } + + /** @return the _progress */ + public List getProgress() { + return _progress; + } } diff --git a/jave-core-test/src/test/java/ws/schild/jave/ScreenExtractorTest.java b/jave-core-test/src/test/java/ws/schild/jave/ScreenExtractorTest.java index 46119fa..4d3a4a0 100644 --- a/jave-core-test/src/test/java/ws/schild/jave/ScreenExtractorTest.java +++ b/jave-core-test/src/test/java/ws/schild/jave/ScreenExtractorTest.java @@ -10,223 +10,236 @@ import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; -/** - * - * @author andre - */ -public class ScreenExtractorTest extends AMediaTest{ +/** @author andre */ +public class ScreenExtractorTest extends AMediaTest { - public ScreenExtractorTest() { - super(null, "ScreenExtractor"); - } + public ScreenExtractorTest() { + super(null, "ScreenExtractor"); + } - /** - * Test of render method, of class ScreenExtractor. - * @throws java.lang.Exception - */ - @Test - public void testRenderImages_01() throws Exception { - System.out.println("render images 01"); - URL source = new URL("https://samples.ffmpeg.org/MPEG1/zelda%20first%20commercial.mpeg"); - File target = new File(getResourceTargetPath(), "extractor01"); - if (target.exists()) - { - for (File f : target.listFiles()) - { - f.delete(); - } - target.delete(); - } - MultimediaObject multimediaObject = new MultimediaObject(source); - int width = 100; - int height = 100; - int seconds = 2; - String fileNamePrefix = "extractor01"; - String extension = "jpg"; - int quality = 0; - ScreenExtractor instance = new ScreenExtractor(); - instance.render(multimediaObject, width, height, seconds, target, fileNamePrefix, extension, quality); - File tFiles[]= target.listFiles(); - assertEquals(instance.getNumberOfScreens(), tFiles.length, "Not correct number of output files, expecting: "+ instance.getNumberOfScreens()+" got: "+tFiles.length); - assertEquals(15, tFiles.length, "Not 15 output files, but "+tFiles.length); + /** + * Test of render method, of class ScreenExtractor. + * + * @throws java.lang.Exception + */ + @Test + public void testRenderImages_01() throws Exception { + System.out.println("render images 01"); + URL source = new URL("https://samples.ffmpeg.org/MPEG1/zelda%20first%20commercial.mpeg"); + File target = new File(getResourceTargetPath(), "extractor01"); + if (target.exists()) { + for (File f : target.listFiles()) { + f.delete(); + } + target.delete(); } + MultimediaObject multimediaObject = new MultimediaObject(source); + int width = 100; + int height = 100; + int seconds = 2; + String fileNamePrefix = "extractor01"; + String extension = "jpg"; + int quality = 0; + ScreenExtractor instance = new ScreenExtractor(); + instance.render( + multimediaObject, width, height, seconds, target, fileNamePrefix, extension, quality); + File tFiles[] = target.listFiles(); + assertEquals( + instance.getNumberOfScreens(), + tFiles.length, + "Not correct number of output files, expecting: " + + instance.getNumberOfScreens() + + " got: " + + tFiles.length); + assertEquals(15, tFiles.length, "Not 15 output files, but " + tFiles.length); + } - /** - * Test of render method, of class ScreenExtractor. - * @throws java.lang.Exception - */ - @Test - public void testRenderImages_02() throws Exception { - System.out.println("render images 02"); - File source = new File(getResourceSourcePath(), "AV36_1.AVI"); - File target = new File(getResourceTargetPath(), "extractor02"); - if (target.exists()) - { - for (File f : target.listFiles()) - { - f.delete(); - } - target.delete(); - } - MultimediaObject multimediaObject = new MultimediaObject(source); - int width = 100; - int height = 100; - int seconds = 2; - String fileNamePrefix = "extractor02"; - String extension = "jpg"; - int quality = 0; - ScreenExtractor instance = new ScreenExtractor(); - instance.render(multimediaObject, width, height, seconds, target, fileNamePrefix, extension, quality); - File tFiles[]= target.listFiles(); - assertEquals(instance.getNumberOfScreens(), tFiles.length, "Not correct number of output files, expecting: "+ instance.getNumberOfScreens()+" got: "+tFiles.length); - assertEquals(16, tFiles.length, "Not 16 output files, but "+tFiles.length); + /** + * Test of render method, of class ScreenExtractor. + * + * @throws java.lang.Exception + */ + @Test + public void testRenderImages_02() throws Exception { + System.out.println("render images 02"); + File source = new File(getResourceSourcePath(), "AV36_1.AVI"); + File target = new File(getResourceTargetPath(), "extractor02"); + if (target.exists()) { + for (File f : target.listFiles()) { + f.delete(); + } + target.delete(); } + MultimediaObject multimediaObject = new MultimediaObject(source); + int width = 100; + int height = 100; + int seconds = 2; + String fileNamePrefix = "extractor02"; + String extension = "jpg"; + int quality = 0; + ScreenExtractor instance = new ScreenExtractor(); + instance.render( + multimediaObject, width, height, seconds, target, fileNamePrefix, extension, quality); + File tFiles[] = target.listFiles(); + assertEquals( + instance.getNumberOfScreens(), + tFiles.length, + "Not correct number of output files, expecting: " + + instance.getNumberOfScreens() + + " got: " + + tFiles.length); + assertEquals(16, tFiles.length, "Not 16 output files, but " + tFiles.length); + } - /** - * Test of render method, of class ScreenExtractor. - * @throws java.lang.Exception - */ - @Test - public void testRenderImages_03() throws Exception { - System.out.println("render images 03"); - File source = new File(getResourceSourcePath(), "zelda first commercial.mpeg"); - File target = new File(getResourceTargetPath(), "extractor03"); - if (target.exists()) - { - for (File f : target.listFiles()) - { - f.delete(); - } - target.delete(); - } - MultimediaObject multimediaObject = new MultimediaObject(source); - int width = 100; - int height = 100; - int seconds = 2; - String fileNamePrefix = "extractor03"; - String extension = "jpg"; - int quality = 0; - ScreenExtractor instance = new ScreenExtractor(); - instance.render(multimediaObject, width, height, seconds, target, fileNamePrefix, extension, quality); - File tFiles[]= target.listFiles(); - assertEquals(instance.getNumberOfScreens(), tFiles.length, "Not correct number of output files, expecting: "+ instance.getNumberOfScreens()+" got: "+tFiles.length); - assertEquals(15, tFiles.length, "Not 15 output files, but "+tFiles.length); + /** + * Test of render method, of class ScreenExtractor. + * + * @throws java.lang.Exception + */ + @Test + public void testRenderImages_03() throws Exception { + System.out.println("render images 03"); + File source = new File(getResourceSourcePath(), "zelda first commercial.mpeg"); + File target = new File(getResourceTargetPath(), "extractor03"); + if (target.exists()) { + for (File f : target.listFiles()) { + f.delete(); + } + target.delete(); } + MultimediaObject multimediaObject = new MultimediaObject(source); + int width = 100; + int height = 100; + int seconds = 2; + String fileNamePrefix = "extractor03"; + String extension = "jpg"; + int quality = 0; + ScreenExtractor instance = new ScreenExtractor(); + instance.render( + multimediaObject, width, height, seconds, target, fileNamePrefix, extension, quality); + File tFiles[] = target.listFiles(); + assertEquals( + instance.getNumberOfScreens(), + tFiles.length, + "Not correct number of output files, expecting: " + + instance.getNumberOfScreens() + + " got: " + + tFiles.length); + assertEquals(15, tFiles.length, "Not 15 output files, but " + tFiles.length); + } - /** - * Test of render method, of class ScreenExtractor. - * @throws java.lang.Exception - */ - @Test - public void testRenderImage_01() throws Exception { - System.out.println("render image 01"); - URL source = new URL("https://samples.ffmpeg.org/MPEG1/zelda%20first%20commercial.mpeg"); - File target = new File(getResourceTargetPath(), "extractor01.jpg"); - if (target.exists()) - { - target.delete(); - } - MultimediaObject multimediaObject = new MultimediaObject(source); - int width = 100; - int height = 100; - int seconds = 2; - int quality = 0; - ScreenExtractor instance = new ScreenExtractor(); - instance.render(multimediaObject, width, height, seconds, target, quality); - assertTrue(target.exists(), "Output file missing"); + /** + * Test of render method, of class ScreenExtractor. + * + * @throws java.lang.Exception + */ + @Test + public void testRenderImage_01() throws Exception { + System.out.println("render image 01"); + URL source = new URL("https://samples.ffmpeg.org/MPEG1/zelda%20first%20commercial.mpeg"); + File target = new File(getResourceTargetPath(), "extractor01.jpg"); + if (target.exists()) { + target.delete(); } + MultimediaObject multimediaObject = new MultimediaObject(source); + int width = 100; + int height = 100; + int seconds = 2; + int quality = 0; + ScreenExtractor instance = new ScreenExtractor(); + instance.render(multimediaObject, width, height, seconds, target, quality); + assertTrue(target.exists(), "Output file missing"); + } - - /** - * Test of render method, of class ScreenExtractor. - * @throws java.lang.Exception - */ - @Test - public void testRenderImage_02() throws Exception { - System.out.println("render image 02"); - URL source = new URL("https://samples.ffmpeg.org/MPEG1/zelda%20first%20commercial.mpeg"); - File target = new File(getResourceTargetPath(), "extractor02.jpg"); - if (target.exists()) - { - target.delete(); - } - MultimediaObject multimediaObject = new MultimediaObject(source); - int width = 100; - int height = 100; - int seconds = 2; - int quality = 0; - ScreenExtractor instance = new ScreenExtractor(); - instance.render(multimediaObject, width, height, seconds, target, quality); - assertTrue(target.exists(), "Output file missing"); + /** + * Test of render method, of class ScreenExtractor. + * + * @throws java.lang.Exception + */ + @Test + public void testRenderImage_02() throws Exception { + System.out.println("render image 02"); + URL source = new URL("https://samples.ffmpeg.org/MPEG1/zelda%20first%20commercial.mpeg"); + File target = new File(getResourceTargetPath(), "extractor02.jpg"); + if (target.exists()) { + target.delete(); } + MultimediaObject multimediaObject = new MultimediaObject(source); + int width = 100; + int height = 100; + int seconds = 2; + int quality = 0; + ScreenExtractor instance = new ScreenExtractor(); + instance.render(multimediaObject, width, height, seconds, target, quality); + assertTrue(target.exists(), "Output file missing"); + } - /** - * Test of render method, of class ScreenExtractor. - * @throws java.lang.Exception - */ - @Test - public void testRenderOneImage00() throws Exception { - System.out.println("render one image 00"); - File source = new File(getResourceSourcePath(), "zelda first commercial.mpeg"); - File target = new File(getResourceTargetPath(), "RenderOneImage00.jpg"); - if (target.exists()) - { - target.delete(); - } - MultimediaObject multimediaObject = new MultimediaObject(source); - int width = -1; - int height = -1; - int millis = 15000; - int quality = 1; - ScreenExtractor instance = new ScreenExtractor(); - instance.renderOneImage(multimediaObject, width, height, millis, target, quality); - assertTrue(target.exists(), "Output file missing"); + /** + * Test of render method, of class ScreenExtractor. + * + * @throws java.lang.Exception + */ + @Test + public void testRenderOneImage00() throws Exception { + System.out.println("render one image 00"); + File source = new File(getResourceSourcePath(), "zelda first commercial.mpeg"); + File target = new File(getResourceTargetPath(), "RenderOneImage00.jpg"); + if (target.exists()) { + target.delete(); } + MultimediaObject multimediaObject = new MultimediaObject(source); + int width = -1; + int height = -1; + int millis = 15000; + int quality = 1; + ScreenExtractor instance = new ScreenExtractor(); + instance.renderOneImage(multimediaObject, width, height, millis, target, quality); + assertTrue(target.exists(), "Output file missing"); + } - - /** - * Test of render method, of class ScreenExtractor. - * @throws java.lang.Exception - */ - @Test - public void testRenderOneImage01() throws Exception { - System.out.println("render one image 01"); - File source = new File(getResourceSourcePath(), "zelda first commercial.mpeg"); - File target = new File(getResourceTargetPath(), "RenderOneImage01.jpg"); - if (target.exists()) - { - target.delete(); - } - MultimediaObject multimediaObject = new MultimediaObject(source); - int width = 60; - int height = 60; - int millis = 15000; - int quality = 1; - ScreenExtractor instance = new ScreenExtractor(); - instance.renderOneImage(multimediaObject, width, height, millis, target, quality); - assertTrue(target.exists(), "Output file missing"); + /** + * Test of render method, of class ScreenExtractor. + * + * @throws java.lang.Exception + */ + @Test + public void testRenderOneImage01() throws Exception { + System.out.println("render one image 01"); + File source = new File(getResourceSourcePath(), "zelda first commercial.mpeg"); + File target = new File(getResourceTargetPath(), "RenderOneImage01.jpg"); + if (target.exists()) { + target.delete(); } + MultimediaObject multimediaObject = new MultimediaObject(source); + int width = 60; + int height = 60; + int millis = 15000; + int quality = 1; + ScreenExtractor instance = new ScreenExtractor(); + instance.renderOneImage(multimediaObject, width, height, millis, target, quality); + assertTrue(target.exists(), "Output file missing"); + } - /** - * Test of render method, of class ScreenExtractor. - * @throws java.lang.Exception - */ - @Test - public void testRenderOneImage03() throws Exception { - System.out.println("render one image 03"); - File source = new File(getResourceSourcePath(), "testfile3.wmv"); - File target = new File(getResourceTargetPath(), "RenderOneImage03.jpg"); - if (target.exists()) - { - target.delete(); - } - MultimediaObject multimediaObject = new MultimediaObject(source); - int width = -1; - int height = -1; - int millis = 56000; - int quality = 1; - ScreenExtractor instance = new ScreenExtractor(); - instance.renderOneImage(multimediaObject, width, height, millis, target, quality, true); - assertTrue(target.exists(), "Output file missing"); + /** + * Test of render method, of class ScreenExtractor. + * + * @throws java.lang.Exception + */ + @Test + public void testRenderOneImage03() throws Exception { + System.out.println("render one image 03"); + File source = new File(getResourceSourcePath(), "testfile3.wmv"); + File target = new File(getResourceTargetPath(), "RenderOneImage03.jpg"); + if (target.exists()) { + target.delete(); } + MultimediaObject multimediaObject = new MultimediaObject(source); + int width = -1; + int height = -1; + int millis = 56000; + int quality = 1; + ScreenExtractor instance = new ScreenExtractor(); + instance.renderOneImage(multimediaObject, width, height, millis, target, quality, true); + assertTrue(target.exists(), "Output file missing"); + } } diff --git a/jave-core-test/src/test/java/ws/schild/jave/URLVideoEncoderTest.java b/jave-core-test/src/test/java/ws/schild/jave/URLVideoEncoderTest.java index c2d1c27..21a76b3 100644 --- a/jave-core-test/src/test/java/ws/schild/jave/URLVideoEncoderTest.java +++ b/jave-core-test/src/test/java/ws/schild/jave/URLVideoEncoderTest.java @@ -1,8 +1,8 @@ /* * JAVE - A Java Audio/Video Encoder (based on FFMPEG) - * + * * Copyright (C) 2018- Andre Schild - * + * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or @@ -25,88 +25,88 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.Test; -/** - * - * @author a.schild - */ -public class URLVideoEncoderTest extends AMediaTest{ - - public URLVideoEncoderTest() { - super(null, "URLVideoEncoder"); - } +import ws.schild.jave.encode.AudioAttributes; +import ws.schild.jave.encode.EncodingAttributes; +import ws.schild.jave.encode.VideoAttributes; - /** - * Test of encode method, of class Encoder. - * @throws java.lang.Exception - */ - @Test - public void testEncodeVideo13() throws Exception { - System.out.println("testEncodeVideo13"); - - URL source = new URL("https://samples.ffmpeg.org/MPEG1/zelda%20first%20commercial.mpeg"); - File target = new File(getResourceTargetPath(), "testEncodeVideo13.mp4"); - if (target.exists()) - { - target.delete(); - } - AudioAttributes audioAttr = new AudioAttributes(); - VideoAttributes videoAttr = new VideoAttributes(); - EncodingAttributes encodingAttr = new EncodingAttributes(); - - audioAttr.setChannels(2); - audioAttr.setCodec("aac"); - audioAttr.setBitRate(128000); - audioAttr.setSamplingRate(44100); - - videoAttr.setCodec("libx264"); - videoAttr.setBitRate(4000000); - - encodingAttr.setAudioAttributes(audioAttr); - encodingAttr.setVideoAttributes(videoAttr); - encodingAttr.setFormat("mp4"); - - PListener listener = new PListener(); - Encoder encoder = new Encoder(); - encoder.encode(new MultimediaObject(source, false), target, encodingAttr, listener); - assertNotNull(listener.getInfo(), "URL should be able to read twice"); - assertTrue(target.exists(), "Output file missing"); +/** @author a.schild */ +public class URLVideoEncoderTest extends AMediaTest { + + public URLVideoEncoderTest() { + super(null, "URLVideoEncoder"); + } + + /** + * Test of encode method, of class Encoder. + * + * @throws java.lang.Exception + */ + @Test + public void testEncodeVideo13() throws Exception { + System.out.println("testEncodeVideo13"); + + URL source = new URL("https://samples.ffmpeg.org/MPEG1/zelda%20first%20commercial.mpeg"); + File target = new File(getResourceTargetPath(), "testEncodeVideo13.mp4"); + if (target.exists()) { + target.delete(); } - - /** - * Test of encode method, of class Encoder. - * @throws java.lang.Exception - */ - @Test - public void testEncodeVideo14() throws Exception { - System.out.println("testEncodeVideo14"); - - URL source = new URL("https://samples.ffmpeg.org/MPEG1/zelda%20first%20commercial.mpeg"); - File target = new File(getResourceTargetPath(), "testEncodeVideo14.mp4"); - if (target.exists()) - { - target.delete(); - } - AudioAttributes audioAttr = new AudioAttributes(); - VideoAttributes videoAttr = new VideoAttributes(); - EncodingAttributes encodingAttr = new EncodingAttributes(); - - audioAttr.setChannels(2); - audioAttr.setCodec("aac"); - audioAttr.setBitRate(128000); - audioAttr.setSamplingRate(44100); - - videoAttr.setCodec("libx264"); - videoAttr.setBitRate(4000000); - - encodingAttr.setAudioAttributes(audioAttr); - encodingAttr.setVideoAttributes(videoAttr); - encodingAttr.setFormat("mp4"); - - Encoder encoder = new Encoder(); - PListener listener = new PListener(); - encoder.encode(new MultimediaObject(source, true), target, encodingAttr, listener); - assertNull(listener.getInfo(), "URL should not be read twice"); - assertTrue(target.exists(), "Output file missing"); + AudioAttributes audioAttr = new AudioAttributes(); + VideoAttributes videoAttr = new VideoAttributes(); + EncodingAttributes encodingAttr = new EncodingAttributes(); + + audioAttr.setChannels(2); + audioAttr.setCodec("aac"); + audioAttr.setBitRate(128000); + audioAttr.setSamplingRate(44100); + + videoAttr.setCodec("libx264"); + videoAttr.setBitRate(4000000); + + encodingAttr.setAudioAttributes(audioAttr); + encodingAttr.setVideoAttributes(videoAttr); + encodingAttr.setOutputFormat("mp4"); + + PListener listener = new PListener(); + Encoder encoder = new Encoder(); + encoder.encode(new MultimediaObject(source, false), target, encodingAttr, listener); + assertNotNull(listener.getInfo(), "URL should be able to read twice"); + assertTrue(target.exists(), "Output file missing"); + } + + /** + * Test of encode method, of class Encoder. + * + * @throws java.lang.Exception + */ + @Test + public void testEncodeVideo14() throws Exception { + System.out.println("testEncodeVideo14"); + + URL source = new URL("https://samples.ffmpeg.org/MPEG1/zelda%20first%20commercial.mpeg"); + File target = new File(getResourceTargetPath(), "testEncodeVideo14.mp4"); + if (target.exists()) { + target.delete(); } - + AudioAttributes audioAttr = new AudioAttributes(); + VideoAttributes videoAttr = new VideoAttributes(); + EncodingAttributes encodingAttr = new EncodingAttributes(); + + audioAttr.setChannels(2); + audioAttr.setCodec("aac"); + audioAttr.setBitRate(128000); + audioAttr.setSamplingRate(44100); + + videoAttr.setCodec("libx264"); + videoAttr.setBitRate(4000000); + + encodingAttr.setAudioAttributes(audioAttr); + encodingAttr.setVideoAttributes(videoAttr); + encodingAttr.setOutputFormat("mp4"); + + Encoder encoder = new Encoder(); + PListener listener = new PListener(); + encoder.encode(new MultimediaObject(source, true), target, encodingAttr, listener); + assertNull(listener.getInfo(), "URL should not be read twice"); + assertTrue(target.exists(), "Output file missing"); + } } diff --git a/jave-core-test/src/test/java/ws/schild/jave/filters/VideoFilterTest.java b/jave-core-test/src/test/java/ws/schild/jave/filters/VideoFilterTest.java index a53c23f..cf53735 100644 --- a/jave-core-test/src/test/java/ws/schild/jave/filters/VideoFilterTest.java +++ b/jave-core-test/src/test/java/ws/schild/jave/filters/VideoFilterTest.java @@ -1,8 +1,8 @@ /* * JAVE - A Java Audio/Video Encoder (based on FFMPEG) - * + * * Copyright (C) 2018- Andre Schild - * + * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or @@ -18,104 +18,314 @@ */ package ws.schild.jave.filters; -import ws.schild.jave.*; -import java.io.File; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.File; +import java.net.URL; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + import org.junit.jupiter.api.Test; -/** - * - * @author a.schild - */ -public class VideoFilterTest extends AMediaTest{ - - public VideoFilterTest() { - super(null, "VideoFilterTest"); +import ws.schild.jave.AMediaTest; +import ws.schild.jave.Encoder; +import ws.schild.jave.MultimediaObject; +import ws.schild.jave.encode.EncodingAttributes; +import ws.schild.jave.encode.VideoAttributes; +import ws.schild.jave.filtergraphs.OverlayWatermark; +import ws.schild.jave.filtergraphs.TrimAndWatermark; +import ws.schild.jave.filtergraphs.TrimFadeAndWatermark; +import ws.schild.jave.filters.helpers.Color; +import ws.schild.jave.filters.helpers.FadeDirection; +import ws.schild.jave.filters.helpers.OverlayLocation; +import ws.schild.jave.info.VideoSize; +import ws.schild.jave.utils.AutoRemoveableFile; + +/** @author a.schild */ +public class VideoFilterTest extends AMediaTest { + + private static ClassLoader cLoader = VideoFilterTest.class.getClassLoader(); + + public VideoFilterTest() { + super(null, "VideoFilterTest"); + } + + @Test + public void testVideoFilter1() throws Exception { + System.out.println("testVideoFilter1"); + + File source = new File(getResourceSourcePath(), "testfile3.wmv"); + File target = new File(getResourceTargetPath(), "testVideoFilter1.mp4"); + if (target.exists()) { + target.delete(); + } + DrawtextFilter vf = + new DrawtextFilter("testVideoFilter1", "30", "30", "Arial", 30.0, new Color("ffffff")); + vf.setShadow(new Color("000000"), 2, 2); + VideoAttributes videoAttributes = new VideoAttributes(); + videoAttributes.addFilter(vf); + EncodingAttributes attrs = new EncodingAttributes(); + attrs.setVideoAttributes(videoAttributes); + Encoder encoder = new Encoder(); + encoder.encode(new MultimediaObject(source), target, attrs); + assertTrue(target.exists(), "Output file missing"); + } + + @Test + public void testVideoFilter2() throws Exception { + System.out.println("testVideoFilter2"); + + File source = new File(getResourceSourcePath(), "testfile3.wmv"); + File target = new File(getResourceTargetPath(), "testVideoFilter2.mp4"); + if (target.exists()) { + target.delete(); + } + DrawtextFilter vf = + new DrawtextFilter("testVideoFilter2", "30", "30", "Arial", 30.0, new Color("ffffff", "44")); + vf.setShadow(new Color("000000", "44"), 2, 2); + VideoAttributes videoAttributes = new VideoAttributes(); + videoAttributes.addFilter(vf); + EncodingAttributes attrs = new EncodingAttributes(); + attrs.setVideoAttributes(videoAttributes); + Encoder encoder = new Encoder(); + encoder.encode(new MultimediaObject(source), target, attrs); + assertTrue(target.exists(), "Output file missing"); + } + + @Test + public void testVideoFilter3() throws Exception { + System.out.println("testVideoFilter3"); + + File source = new File(getResourceSourcePath(), "testfile3.wmv"); + File target = new File(getResourceTargetPath(), "testVideoFilter3.mp4"); + if (target.exists()) { + target.delete(); + } + DrawtextFilter vf = + new DrawtextFilter( + "testVideoFilter3 <[]:=,> End of special chars", + "30", + "30", + "Arial", + 30.0, + new Color("ffffff", "dd")); + vf.setShadow(new Color("000000", "44"), 2, 2); + VideoAttributes videoAttributes = new VideoAttributes(); + videoAttributes.addFilter(vf); + EncodingAttributes attrs = new EncodingAttributes(); + attrs.setVideoAttributes(videoAttributes); + Encoder encoder = new Encoder(); + encoder.encode(new MultimediaObject(source), target, attrs); + assertTrue(target.exists(), "Output file missing"); + } + + @Test + public void testVideoFilter4() throws Exception { + System.out.println("testVideoFilter4"); + + File source = new File(getResourceSourcePath(), "testfile3.wmv"); + File target = new File(getResourceTargetPath(), "testVideoFilter4.mp4"); + if (target.exists()) { + target.delete(); } + DrawtextFilter vf = + new DrawtextFilter( + "testVideoFilter4 center", "(w-text_w)/2", "(h-text_h)/2", "Arial", 30.0, new Color("ffffff", "44")); + vf.setShadow(new Color("000000", "44"), 2, 2); + VideoAttributes videoAttributes = new VideoAttributes(); + videoAttributes.addFilter(vf); + EncodingAttributes attrs = new EncodingAttributes(); + attrs.setVideoAttributes(videoAttributes); + Encoder encoder = new Encoder(); + encoder.encode(new MultimediaObject(source), target, attrs); + assertTrue(target.exists(), "Output file missing"); + } + + @Test + public void testOverlayWatermarkExpression() { + File fooPng = new File("foo.png"); + String fooPath = fooPng.getAbsolutePath(); + OverlayWatermark checkMe = new OverlayWatermark(fooPng, OverlayLocation.BOTTOM_RIGHT, -10, -10); + assertEquals( + "movie='" + + fooPath + + "'[watermark];[0:v][watermark]overlay='main_w-overlay_w-10:main_h-overlay_h-10'", + checkMe.getExpression()); + + checkMe = new OverlayWatermark(fooPng, OverlayLocation.TOP_LEFT, null, null); + assertEquals( + "movie='" + fooPath + "'[watermark];[0:v][watermark]overlay='0:0'", + checkMe.getExpression()); + + checkMe = new OverlayWatermark(fooPng, OverlayLocation.TOP_RIGHT, null, 10); + assertEquals( + "movie='" + fooPath + "'[watermark];[0:v][watermark]overlay='main_w-overlay_w:10'", + checkMe.getExpression()); + } + + @Test + public void thatWeCanOverlayAWatermark() throws Exception { + File sourceVideo = + new File(cLoader.getResource("9B8CC2D5-3B24-4DD1-B23D-9B5DAF0E70BE.mp4").getFile()); + File watermark = new File(cLoader.getResource("watermark.png").getFile()); - @Test - public void testVideoFilter1() throws Exception { - System.out.println("testVideoFilter1"); - - File source = new File(getResourceSourcePath(), "testfile3.wmv "); - File target = new File(getResourceTargetPath(), "testVideoFilter1.mp4"); - if (target.exists()) - { - target.delete(); - } - VideoDrawtext vf= new VideoDrawtext("testVideoFilter1", 30, 30, "Arial", null, 30, new Color("ffffff")); - vf.setShadow(new Color("000000"), 2, 2); - VideoAttributes videoAttributes= new VideoAttributes(); - videoAttributes.addFilter(vf); - EncodingAttributes attrs = new EncodingAttributes(); - attrs.setVideoAttributes(videoAttributes); - Encoder encoder = new Encoder(); - encoder.encode(new MultimediaObject(source), target, attrs); - assertTrue( target.exists(), "Output file missing"); + VideoAttributes vidAttr = new VideoAttributes(); + vidAttr.addFilter(new OverlayWatermark(watermark, OverlayLocation.BOTTOM_RIGHT, -10, -10)); + EncodingAttributes encAttr = new EncodingAttributes().setVideoAttributes(vidAttr); + + try (AutoRemoveableFile target = + new AutoRemoveableFile(sourceVideo.getParentFile(), "overlay.mp4")) { + new Encoder().encode(new MultimediaObject(sourceVideo), target, encAttr); + assertTrue(target.exists(), "Output file missing"); } + } + + @Test + public void thatWeCanFadeAVideo() throws Exception { + File sourceVideo = + new File(cLoader.getResource("9B8CC2D5-3B24-4DD1-B23D-9B5DAF0E70BE.mp4").getFile()); + + VideoAttributes vidAttr = new VideoAttributes(); + vidAttr.addFilter(new FadeFilter(FadeDirection.OUT, 1.0, 1.0)); + EncodingAttributes encAttr = new EncodingAttributes().setVideoAttributes(vidAttr); - @Test - public void testVideoFilter2() throws Exception { - System.out.println("testVideoFilter2"); - - File source = new File(getResourceSourcePath(), "testfile3.wmv "); - File target = new File(getResourceTargetPath(), "testVideoFilter2.mp4"); - if (target.exists()) - { - target.delete(); - } - VideoDrawtext vf= new VideoDrawtext("testVideoFilter2", 30, 30, "Arial", null, 30, new Color("ffffff", "44")); - vf.setShadow(new Color("000000", "44"), 2, 2); - VideoAttributes videoAttributes= new VideoAttributes(); - videoAttributes.addFilter(vf); - EncodingAttributes attrs = new EncodingAttributes(); - attrs.setVideoAttributes(videoAttributes); - Encoder encoder = new Encoder(); - encoder.encode(new MultimediaObject(source), target, attrs); - assertTrue( target.exists(), "Output file missing"); + try (AutoRemoveableFile target = + new AutoRemoveableFile(sourceVideo.getParentFile(), "fade.mp4")) { + new Encoder().encode(new MultimediaObject(sourceVideo), target, encAttr); + assertTrue(target.exists(), "Output file missing"); } + } + + @Test + public void thatTrimAndWatermarkFilterProducesCorrectFiltergraphs() throws Exception { + List videos = + Arrays.asList( + "9B8CC2D5-3B24-4DD1-B23D-9B5DAF0E70BE.mp4", + "A0EF94F6-F922-4676-B767-A600F2E87F53.mp4", + "B3111BAF-A516-48EC-99FB-B492EB23155D.mp4") + .stream() + .map(cLoader::getResource) + .map(URL::getFile) + .map(File::new) + .collect(Collectors.toList()); + + List trimInfo = + videos + .stream() + .map(f -> new TrimAndWatermark.TrimInfo(0.5, 1.0)) + .collect(Collectors.toList()); + File fooPng = new File("foo.png"); + String fooPath = fooPng.getAbsolutePath(); + + assertEquals( + "[0]trim='duration=1.0:start=0.5',setpts='PTS-STARTPTS'[filtered0];" + + "[1]trim='duration=1.0:start=0.5',setpts='PTS-STARTPTS'[filtered1];" + + "[2]trim='duration=1.0:start=0.5',setpts='PTS-STARTPTS'[filtered2];" + + "[filtered0][filtered1][filtered2]concat='n=3'[concatenated];" + + "movie='" + fooPath + "',[concatenated]overlay='main_w-overlay_w-10:main_h-overlay_h-10'", + new TrimAndWatermark(fooPng, trimInfo).getExpression()); + } - @Test - public void testVideoFilter3() throws Exception { - System.out.println("testVideoFilter3"); - - File source = new File(getResourceSourcePath(), "testfile3.wmv "); - File target = new File(getResourceTargetPath(), "testVideoFilter3.mp4"); - if (target.exists()) - { - target.delete(); - } - VideoDrawtext vf= new VideoDrawtext("testVideoFilter3 <[]:=,> End of special chars", 30, 30, "Arial", null, 30, new Color("ffffff", "dd")); - vf.setShadow(new Color("000000", "44"), 2, 2); - VideoAttributes videoAttributes= new VideoAttributes(); - videoAttributes.addFilter(vf); - EncodingAttributes attrs = new EncodingAttributes(); - attrs.setVideoAttributes(videoAttributes); - Encoder encoder = new Encoder(); - encoder.encode(new MultimediaObject(source), target, attrs); - assertTrue( target.exists(), "Output file missing"); + @Test + public void thatWeCanTrimAndWatermarkFiles() throws Exception { + File watermark = + new File(VideoFilterTest.class.getClassLoader().getResource("watermark.png").getFile()); + + List videos = + Arrays.asList( + "9B8CC2D5-3B24-4DD1-B23D-9B5DAF0E70BE.mp4", + "A0EF94F6-F922-4676-B767-A600F2E87F53.mp4", + "B3111BAF-A516-48EC-99FB-B492EB23155D.mp4") + .stream() + .map(cLoader::getResource) + .map(URL::getFile) + .map(File::new) + .collect(Collectors.toList()); + + VideoAttributes vidAttr = new VideoAttributes(); + vidAttr.setComplexFiltergraph( + new TrimAndWatermark( + watermark, + videos + .stream() + .map(v -> new TrimAndWatermark.TrimInfo(0.5, 1.0)) + .collect(Collectors.toList()))); + EncodingAttributes encAttr = new EncodingAttributes().setVideoAttributes(vidAttr); + + try (AutoRemoveableFile target = + new AutoRemoveableFile(videos.get(0).getParentFile(), "overlay.mp4")) { + new Encoder() + .encode( + videos.stream().map(MultimediaObject::new).collect(Collectors.toList()), + target, + encAttr); + assertTrue(target.exists(), "Output file missing"); + } + } + + @Test + public void thatWeCanTrimFadeAndWatermarkFiles() throws Exception { + File watermark = + new File(VideoFilterTest.class.getClassLoader().getResource("watermark.png").getFile()); + + List videos = + Arrays.asList( + "9B8CC2D5-3B24-4DD1-B23D-9B5DAF0E70BE.mp4", + "A0EF94F6-F922-4676-B767-A600F2E87F53.mp4", + "B3111BAF-A516-48EC-99FB-B492EB23155D.mp4") + .stream() + .map(cLoader::getResource) + .map(URL::getFile) + .map(File::new) + .collect(Collectors.toList()); + + VideoAttributes vidAttr = new VideoAttributes(); + vidAttr.setComplexFiltergraph( + new TrimFadeAndWatermark( + watermark, + videos + .stream() + .map(v -> new TrimAndWatermark.TrimInfo(0.5, 1.0)) + .collect(Collectors.toList()))); + EncodingAttributes encAttr = new EncodingAttributes().setVideoAttributes(vidAttr); + + try (AutoRemoveableFile target = + new AutoRemoveableFile(videos.get(0).getParentFile(), "overlay.mp4")) { + new Encoder() + .encode( + videos.stream().map(MultimediaObject::new).collect(Collectors.toList()), + target, + encAttr); + assertTrue(target.exists(), "Output file missing"); } + } + @Test + public void thatWeCanPadAndZoomImages() throws Exception { + File profileSample = + new File(VideoFilterTest.class.getClassLoader().getResource("profileSample.png").getFile()); + VideoSize profileSize = new VideoSize(1532, 1378); + + VideoSize outputSize = new VideoSize(1280, 720); + + VideoAttributes vidAttr = new VideoAttributes(); + vidAttr.setComplexFiltergraph(new FilterGraph(new FilterChain( + new PadFilter(outputSize), + new ZoomPanFilter(8*25, profileSize, outputSize) + ))); + vidAttr.setPixelFormat("yuv420p"); + EncodingAttributes encAttr = new EncodingAttributes().setVideoAttributes(vidAttr); - @Test - public void testVideoFilter4() throws Exception { - System.out.println("testVideoFilter4"); - - File source = new File(getResourceSourcePath(), "testfile3.wmv "); - File target = new File(getResourceTargetPath(), "testVideoFilter4.mp4"); - if (target.exists()) - { - target.delete(); - } - VideoDrawtext vf= new VideoDrawtext("testVideoFilter4 center", -1, -1, "Arial", null, 30, new Color("ffffff", "44")); - vf.setAddArgument("x=(w-text_w)/2:y=(h-text_h)/2"); - vf.setShadow(new Color("000000", "44"), 2, 2); - VideoAttributes videoAttributes= new VideoAttributes(); - videoAttributes.addFilter(vf); - EncodingAttributes attrs = new EncodingAttributes(); - attrs.setVideoAttributes(videoAttributes); - Encoder encoder = new Encoder(); - encoder.encode(new MultimediaObject(source), target, attrs); - assertTrue( target.exists(), "Output file missing"); + try (AutoRemoveableFile target = + new AutoRemoveableFile(profileSample.getParentFile(), "zoompan.mp4")) { + new Encoder() + .encode( + new MultimediaObject(profileSample), + target, + encAttr); + assertTrue(target.exists(), "Output file missing"); } + } } diff --git a/jave-core-test/src/test/resources/9B8CC2D5-3B24-4DD1-B23D-9B5DAF0E70BE.mp4 b/jave-core-test/src/test/resources/9B8CC2D5-3B24-4DD1-B23D-9B5DAF0E70BE.mp4 new file mode 100644 index 0000000..3fdacc2 Binary files /dev/null and b/jave-core-test/src/test/resources/9B8CC2D5-3B24-4DD1-B23D-9B5DAF0E70BE.mp4 differ diff --git a/jave-core-test/src/test/resources/A0EF94F6-F922-4676-B767-A600F2E87F53.mp4 b/jave-core-test/src/test/resources/A0EF94F6-F922-4676-B767-A600F2E87F53.mp4 new file mode 100644 index 0000000..c005e76 Binary files /dev/null and b/jave-core-test/src/test/resources/A0EF94F6-F922-4676-B767-A600F2E87F53.mp4 differ diff --git a/jave-core-test/src/test/resources/B3111BAF-A516-48EC-99FB-B492EB23155D.mp4 b/jave-core-test/src/test/resources/B3111BAF-A516-48EC-99FB-B492EB23155D.mp4 new file mode 100644 index 0000000..96f9125 Binary files /dev/null and b/jave-core-test/src/test/resources/B3111BAF-A516-48EC-99FB-B492EB23155D.mp4 differ diff --git a/jave-core-test/src/test/resources/profileSample.png b/jave-core-test/src/test/resources/profileSample.png new file mode 100644 index 0000000..71ff3e5 Binary files /dev/null and b/jave-core-test/src/test/resources/profileSample.png differ diff --git a/jave-core-test/src/test/resources/watermark.png b/jave-core-test/src/test/resources/watermark.png new file mode 100644 index 0000000..e276a85 Binary files /dev/null and b/jave-core-test/src/test/resources/watermark.png differ diff --git a/jave-core/nb-configuration.xml b/jave-core/nb-configuration.xml index a65c451..e1234de 100644 --- a/jave-core/nb-configuration.xml +++ b/jave-core/nb-configuration.xml @@ -1,18 +1,18 @@ - - - - JDK_1.8 - + + + + JDK_1.8 + diff --git a/jave-core/nbactions.xml b/jave-core/nbactions.xml index 784adec..1b4047e 100644 --- a/jave-core/nbactions.xml +++ b/jave-core/nbactions.xml @@ -1,10 +1,10 @@ - - CUSTOM-Deploy - Deploy - - deploy - - - + + CUSTOM-Deploy + Deploy + + deploy + + + diff --git a/jave-core/pom.xml b/jave-core/pom.xml index 5282e7a..50980b4 100644 --- a/jave-core/pom.xml +++ b/jave-core/pom.xml @@ -1,12 +1,14 @@ - - 4.0.0 - ws.schild - jave-core - jar - 2.8.0-SNAPSHOT - Jave core package - The JAVE (Java Audio Video Encoder) library is Java wrapper on the + + + 4.0.0 + ws.schild + jave-core + jar + 3.0.0 + Jave core package + The JAVE (Java Audio Video Encoder) library is Java wrapper on the ffmpeg project. Developers can take take advantage of JAVE2 to transcode audio and video files from a format to another. In example you can transcode an AVI file to a MPEG one, you can change a DivX video stream into a @@ -14,229 +16,235 @@ a Ogg Vorbis one, you can separate and transcode audio and video tracks, you can resize videos, changing their sizes and proportions and so on. Many other formats, containers and operations are supported by JAVE2. - https://github.com/a-schild/jave2 - - - GPL-v3.0 - http://www.gnu.org/licenses/gpl-3.0.txt - - - - - Andre Schild - andre@schild.ws - Aarboard AG - https://www.aarboard.ch - - - - 1.8 - 1.8 - UTF-8 - 3.8.1 - 3.1.0 - 3.0.0-M1 - 2.5.3 - 3.1.0 - 3.1.0 - 1.6 - 1.6.8 - 1.11.2 - 1.7.26 - 5.4.2 - - - - - org.apache.maven.plugins - maven-compiler-plugin - ${maven-compiler-plugin.version} - - ${project.build.sourceEncoding} - - - - org.apache.maven.plugins - maven-resources-plugin - ${maven-resources-plugin.version} - - ${project.build.sourceEncoding} - - - - org.codehaus.mojo - buildnumber-maven-plugin - 1.4 - - - validate - - create - - - - - true - true - - - - org.codehaus.mojo - templating-maven-plugin - 1.0.0 - - - generate-verion-class - - filter-sources - - - - - - maven-deploy-plugin - ${maven-deploy-plugin.version} - - - default-deploy - deploy - - deploy - - - - - - org.apache.maven.plugins - maven-release-plugin - ${maven-release-plugin.version} - - true - false - forked-path - -Dgpg.passphrase=${gpg.passphrase} - - - - org.apache.maven.scm - maven-scm-provider-gitexe - ${maven-scm-provider-gitexe.version} - - - - - org.sonatype.plugins - nexus-staging-maven-plugin - ${nexus-staging-maven-plugin.version} - true - - ossrh - https://oss.sonatype.org/ - true - - - - org.apache.maven.plugins - maven-source-plugin - ${maven-source-plugin.version} - - - attach-sources - - jar-no-fork - - - - - - org.apache.maven.plugins - maven-javadoc-plugin - ${maven-javadoc-plugin.version} - - - attach-javadocs - - jar - - - - - - org.apache.maven.plugins - maven-gpg-plugin - ${maven-gpg-plugin.version} - - - sign-artifacts - verify - - sign - - - - - - - - - - org.slf4j - slf4j-api - ${slf4j-api.version} - - - - org.junit.jupiter - junit-jupiter-api - ${junit-jupiter-api.version} - test - - - - - ossrh - https://oss.sonatype.org/content/repositories/snapshots - - - ossrh - https://oss.sonatype.org/service/local/staging/deploy/maven2/ + https://github.com/a-schild/jave2 + + + GPL-v3.0 + http://www.gnu.org/licenses/gpl-3.0.txt + + + + + Andre Schild + andre@schild.ws + Aarboard AG + https://www.aarboard.ch + + + Mike Ressler + mressler@diamondkinetics.com + Diamond Kinetics, Inc. + https://diamondkinetics.com + + + + 1.8 + 1.8 + UTF-8 + 3.8.1 + 3.1.0 + 3.0.0-M1 + 2.5.3 + 3.1.0 + 3.1.0 + 1.6 + 1.6.8 + 1.11.2 + 1.7.26 + 5.4.2 + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${maven-compiler-plugin.version} + + ${project.build.sourceEncoding} + + + + org.apache.maven.plugins + maven-resources-plugin + ${maven-resources-plugin.version} + + ${project.build.sourceEncoding} + + + + org.codehaus.mojo + buildnumber-maven-plugin + 1.4 + + + validate + + create + + + + + true + true + + + + org.codehaus.mojo + templating-maven-plugin + 1.0.0 + + + generate-verion-class + + filter-sources + + + + + + maven-deploy-plugin + ${maven-deploy-plugin.version} + + + default-deploy + deploy + + deploy + + + + + + org.apache.maven.plugins + maven-release-plugin + ${maven-release-plugin.version} + + true + false + forked-path + -Dgpg.passphrase=${gpg.passphrase} + + + + org.apache.maven.scm + maven-scm-provider-gitexe + ${maven-scm-provider-gitexe.version} + + + + + org.sonatype.plugins + nexus-staging-maven-plugin + ${nexus-staging-maven-plugin.version} + true + + ossrh + https://oss.sonatype.org/ + true + + + + org.apache.maven.plugins + maven-source-plugin + ${maven-source-plugin.version} + + + attach-sources + + jar-no-fork + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + ${maven-javadoc-plugin.version} + + + attach-javadocs + + jar + + + + + + org.apache.maven.plugins + maven-gpg-plugin + ${maven-gpg-plugin.version} + + + sign-artifacts + verify + + sign + + + + + + + + + + org.slf4j + slf4j-api + ${slf4j-api.version} + + + + org.junit.jupiter + junit-jupiter-api + ${junit-jupiter-api.version} + test + + + + + ossrh + https://oss.sonatype.org/content/repositories/snapshots + + + ossrh + https://oss.sonatype.org/service/local/staging/deploy/maven2/ - - - - scm:git:https://github.com/a-schild/jave2.git - scm:git:https://github.com/a-schild/jave2.git - https://github.com/a-schild/jave2 - HEAD - - - - - release-sign-artifacts - - - performRelease - true - - - - - - org.apache.maven.plugins - maven-gpg-plugin - ${maven-gpg-plugin.version} - - - sign-artifacts - verify - - sign - - - - - - - - + + + + scm:git:https://github.com/a-schild/jave2.git + scm:git:https://github.com/a-schild/jave2.git + https://github.com/a-schild/jave2 + dev-3.0-changes + + + + + release-sign-artifacts + + + performRelease + true + + + + + + org.apache.maven.plugins + maven-gpg-plugin + ${maven-gpg-plugin.version} + + + sign-artifacts + verify + + sign + + + + + + + + \ No newline at end of file diff --git a/jave-core/src/main/java/ws/schild/jave/AudioAttributes.java b/jave-core/src/main/java/ws/schild/jave/AudioAttributes.java deleted file mode 100644 index f02b752..0000000 --- a/jave-core/src/main/java/ws/schild/jave/AudioAttributes.java +++ /dev/null @@ -1,224 +0,0 @@ -/* - * JAVE - A Java Audio/Video Encoder (based on FFMPEG) - * - * Copyright (C) 2008-2009 Carlo Pelliccia (www.sauronsoftware.it) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package ws.schild.jave; - -import java.io.Serializable; - -/** - * Attributes controlling the audio encoding process. - * - * @author Carlo Pelliccia - */ -public class AudioAttributes implements Serializable { - - private static final long serialVersionUID = 2L; - - /** - * This value can be setted in the codec field to perform a direct stream - * copy, without re-encoding of the audio stream. - */ - public static final String DIRECT_STREAM_COPY = "copy"; - - /** - * The codec name for the encoding process. If null or not specified the - * encoder will perform a direct stream copy. - */ - private String codec = null; - - /** - * The bitrate value for the encoding process. If null or not specified a - * default value will be picked. - */ - private Integer bitRate = null; - - /** - * The samplingRate value for the encoding process. If null or not specified - * a default value will be picked. - */ - private Integer samplingRate = null; - - /** - * The channels value (1=mono, 2=stereo) for the encoding process. If null - * or not specified a default value will be picked. - */ - private Integer channels = null; - - /** - * The volume value for the encoding process. If null or not specified a - * default value will be picked. If 256 no volume change will be performed. - */ - private Integer volume = null; - - /** - * The audio quality value for the encoding process. If null or not specified - * the ffmpeg default will be used - */ - private Integer quality = null; - - /** - * Returns the codec name for the encoding process. - * - * @return The codec name for the encoding process. - */ - String getCodec() { - return codec; - } - - /** - * Sets the codec name for the encoding process.If null or not specified - the encoder will perform a direct stream copy. Be sure the supplied codec name is in the list returned by - {@link Encoder#getAudioEncoders()}. - * - * A special value can be picked from - * {@link AudioAttributes#DIRECT_STREAM_COPY}. - * - * @param codec The codec name for the encoding process. - * @return this instance - */ - public AudioAttributes setCodec(String codec) { - this.codec = codec; - return this; - } - - /** - * Returns the bitrate value for the encoding process. - * - * @return The bitrate value for the encoding process. - */ - Integer getBitRate() { - return bitRate; - } - - /** - * Sets the bitrate value for the encoding process. If null or not specified - * a default value will be picked. - * - * @param bitRate The bitrate value for the encoding process. - * @return this instance - */ - public AudioAttributes setBitRate(Integer bitRate) { - this.bitRate = bitRate; - return this; - } - - /** - * Returns the samplingRate value for the encoding process. - * - * @return the samplingRate The samplingRate value for the encoding process. - */ - Integer getSamplingRate() { - return samplingRate; - } - - /** - * Sets the samplingRate value for the encoding process. If null or not - * specified a default value will be picked. - * - * @param samplingRate The samplingRate value for the encoding process. - * @return this instance - */ - public AudioAttributes setSamplingRate(Integer samplingRate) { - this.samplingRate = samplingRate; - return this; - } - - /** - * Returns the channels value (1=mono, 2=stereo, 4=quad) for the encoding process. - * - * @return The channels value (1=mono, 2=stereo, 4=quad) for the encoding process. - */ - Integer getChannels() { - return channels; - } - - /** - * Sets the channels value (1=mono, 2=stereo, 4=quad) for the encoding process. If - * null or not specified a default value will be picked. - * - * @param channels The channels value (1=mono, 2=stereo, 4=quad) for the encoding - * process. - * @return this instance - */ - public AudioAttributes setChannels(Integer channels) { - this.channels = channels; - return this; - } - - /** - * Returns the volume value for the encoding process. - * - * @return The volume value for the encoding process. - */ - Integer getVolume() { - return volume; - } - - /** - * Sets the volume value for the encoding process. If null or not specified - * a default value will be picked. If 256 no volume change will be - * performed. - * - * volume is the "amplitude ratio" or "sound pressure level" ratio - * 2560 is volume=20dB - * The formula is dBnumber=20*lg(amplitude ratio) - * 128 means reducing by 50% - * 512 means doubling the volume - * - * @param volume The volume value for the encoding process. - * @return this instance - */ - public AudioAttributes setVolume(Integer volume) { - this.volume = volume; - return this; - } - - /** - * @return the audio conversion quality - */ - public Integer getQuality() { - return quality; - } - - /** - * The audio quality value for the encoding process. If null or not specified - * the ffmpeg default will be used - * - * The value depends on the choosen codec - * - * For mp3 you can see here: - * https://trac.ffmpeg.org/wiki/Encode/MP3 - * - * Or more general - * https://ffmpeg.org/ffmpeg-codecs.html - * - * @param quality the audio conversion quality to set - * @return this instance - */ - public AudioAttributes setQuality(Integer quality) { - this.quality = quality; - return this; - } - - @Override - public String toString() { - return getClass().getName() + "(codec=" + codec + ", bitRate=" - + bitRate + ", samplingRate=" + samplingRate + ", channels=" - + channels + ", volume=" + volume + ", quality="+quality+ ")"; - } -} diff --git a/jave-core/src/main/java/ws/schild/jave/AudioInfo.java b/jave-core/src/main/java/ws/schild/jave/AudioInfo.java deleted file mode 100644 index a067b58..0000000 --- a/jave-core/src/main/java/ws/schild/jave/AudioInfo.java +++ /dev/null @@ -1,142 +0,0 @@ -/* - * JAVE - A Java Audio/Video Encoder (based on FFMPEG) - * - * Copyright (C) 2008-2009 Carlo Pelliccia (www.sauronsoftware.it) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package ws.schild.jave; - -/** - * Instances of this class report informations about an audio stream that can be - * decoded. - * - * @author Carlo Pelliccia - */ -public class AudioInfo { - - /** - * The audio stream decoder name. - */ - private String decoder; - - /** - * The audio stream sampling rate. If less than 0, this information is not - * available. - */ - private int samplingRate = -1; - - /** - * The audio stream channels number (1=mono, 2=stereo). If less than 0, this - * information is not available. - */ - private int channels = -1; - - /** - * The audio stream (average) bit rate. If less than 0, this information is - * not available. - */ - private int bitRate = -1; - - /** - * Returns the audio stream decoder name. - * - * @return The audio stream decoder name. - */ - public String getDecoder() { - return decoder; - } - - /** - * Sets the audio stream decoder name. - * - * @param format The audio stream format name. - * @return this instance - */ - public AudioInfo setDecoder(String format) { - this.decoder = format; - return this; - } - - /** - * Returns the audio stream sampling rate. If less than 0, this information - * is not available. - * - * @return The audio stream sampling rate. - */ - public int getSamplingRate() { - return samplingRate; - } - - /** - * Sets the audio stream sampling rate. - * - * @param samplingRate The audio stream sampling rate. - * @return this instance - */ - public AudioInfo setSamplingRate(int samplingRate) { - this.samplingRate = samplingRate; - return this; - } - - /** - * Returns the audio stream channels number (1=mono, 2=stereo). If less than - * 0, this information is not available. - * - * @return the channels The audio stream channels number (1=mono, 2=stereo). - */ - public int getChannels() { - return channels; - } - - /** - * Sets the audio stream channels number (1=mono, 2=stereo). - * - * @param channels The audio stream channels number (1=mono, 2=stereo). - * @return this instance - */ - public AudioInfo setChannels(int channels) { - this.channels = channels; - return this; - } - - /** - * Returns the audio stream (average) bit rate. If less than 0, this - * information is not available. - * - * @return The audio stream (average) bit rate. - */ - public int getBitRate() { - return bitRate; - } - - /** - * Sets the audio stream (average) bit rate. - * - * @param bitRate The audio stream (average) bit rate. - * @return this instance - */ - public AudioInfo setBitRate(int bitRate) { - this.bitRate = bitRate; - return this; - } - - @Override - public String toString() { - return getClass().getName() + " (decoder=" + decoder + ", samplingRate=" - + samplingRate + ", channels=" + channels + ", bitRate=" - + bitRate + ")"; - } - -} diff --git a/jave-core/src/main/java/ws/schild/jave/Color.java b/jave-core/src/main/java/ws/schild/jave/Color.java deleted file mode 100644 index 0870e19..0000000 --- a/jave-core/src/main/java/ws/schild/jave/Color.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. - */ -package ws.schild.jave; - -/** - * - * @author andre - */ -public class Color { - private String color; - private String alpha= "ff"; - - /** - * - * @param color color in RRGGBB synatx, like in html/css, but without the leading # character - * - */ - public Color(String color) - { - this.color= color; - } - - /** - * - * @param color color in #RRGGBB synatx, like in html/css, but without the leading # character - * @param alpha Alpha channel, Values from 00 up to FF, 00 means not transparent, FF means fully transparent - * - */ - public Color(String color, String alpha) - { - this.color= color; - this.alpha= alpha; - } - - /** - * @return the color - */ - public String getColor() { - return color; - } - - /** - * @param color the color to set - * @return this instance - */ - public Color setColor(String color) { - this.color = color; - return this; - } - - /** - * @return the alpha - */ - public String getAlpha() { - return alpha; - } - - /** - * @param alpha the alpha to set - * @return this instance - */ - public Color setAlpha(String alpha) { - this.alpha = alpha; - return this; - } - - /** - * - * @return color in command line format - */ - public String getFfmpegColor() - { - return "0x"+color+alpha; - } -} diff --git a/jave-core/src/main/java/ws/schild/jave/ConversionOutputAnalyzer.java b/jave-core/src/main/java/ws/schild/jave/ConversionOutputAnalyzer.java index 297ca55..bbcb892 100644 --- a/jave-core/src/main/java/ws/schild/jave/ConversionOutputAnalyzer.java +++ b/jave-core/src/main/java/ws/schild/jave/ConversionOutputAnalyzer.java @@ -2,7 +2,7 @@ * Copyright 2018 a.schild. * * JAVE - A Java Audio/Video Encoder (based on FFMPEG) - * + * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or @@ -26,231 +26,182 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -/** - * - * @author a.schild - */ +import ws.schild.jave.progress.EncoderProgressListener; + +/** @author a.schild */ public class ConversionOutputAnalyzer { - private static final Logger LOG = LoggerFactory.getLogger(ConversionOutputAnalyzer.class); + private static final Logger LOG = LoggerFactory.getLogger(ConversionOutputAnalyzer.class); - /** - * This regexp is used to parse the ffmpeg output about the ongoing encoding - * process. - */ - private static final Pattern PROGRESS_INFO_PATTERN = Pattern.compile( - "\\s*(\\w+)\\s*=\\s*(\\S+)\\s*", Pattern.CASE_INSENSITIVE); + /** This regexp is used to parse the ffmpeg output about the ongoing encoding process. */ + private static final Pattern PROGRESS_INFO_PATTERN = + Pattern.compile("\\s*(\\w+)\\s*=\\s*(\\S+)\\s*", Pattern.CASE_INSENSITIVE); - private final EncoderProgressListener listener; - - private final long duration; - // Step 0 = Before input stuff - // Step 1 = Input stuff - // Step 2 = Stream Mapping - // Step 3 = Output - // Step 4 = frame=... - private int step = 0; - private int lineNR = 0; - private String lastWarning= null; - private final List unhandledMessages= new LinkedList<>(); - - public ConversionOutputAnalyzer(long duration, EncoderProgressListener listener) { - this.duration= duration; - this.listener= listener; + private final EncoderProgressListener listener; + + private final long duration; + // Step 0 = Before input stuff + // Step 1 = Input stuff + // Step 2 = Stream Mapping + // Step 3 = Output + // Step 4 = frame=... + private int step = 0; + private int lineNR = 0; + private String lastWarning = null; + private final List unhandledMessages = new LinkedList<>(); + + public ConversionOutputAnalyzer(long duration, EncoderProgressListener listener) { + this.duration = duration; + this.listener = listener; + } + + public void analyzeNewLine(String line) throws EncoderException { + lineNR++; + LOG.debug("Input Line ({}): <{}>", lineNR, line); + if (line.startsWith("WARNING: ")) { + if (listener != null) { + listener.message(line); + } } - - public void analyzeNewLine(String line) throws EncoderException - { - lineNR++; - LOG.debug("Input Line ({}): <{}>", lineNR, line); - if (line.startsWith("WARNING: ")) - { - if (listener != null) - { - listener.message(line); + if (line.startsWith("Press [q]")) { + // Abort messages + } else { + switch (step) { + case 0: + { + if (line.startsWith("Input #0")) { + step = 1; + } else { + // wait for Stream mapping: } - } - if (line.startsWith("Press [q]")) - { - // Abort messages - } - else - { - switch (step) - { - case 0: - { - if (line.startsWith("Input #0")) - { - step = 1; - } else - { - // wait for Stream mapping: - } - } - break; - case 1: - { - if (line.startsWith("Stream mapping:")) - { - // streamMappingFound - step = 2; - } - else if (line.startsWith("Output #0")) - { - // outputFound - step = 2; - } else if (!line.startsWith(" ")) - { - LOG.info("Unhandled message in step: {} Line: {} message: <{}>", step, lineNR, line); - unhandledMessages.add(line); - } else - { - // wait for Stream mapping: - } - } - break; - case 2: - { - if (line.startsWith("Output #0")) - { - // outputFound - step = 3; - } else if (line.startsWith("Stream mapping:")) - { - // streamMappingFound - step = 3; - } else if (!line.startsWith(" ")) - { - LOG.info("Unhandled message in step: {} Line: {} message: <{}>", step, lineNR, line); - unhandledMessages.add(line); - } else - { - // wait for Stream mapping: - } - } - break; - case 3: - { - if (line.startsWith(" ")) - { - // output details - } else if (line.startsWith("video:")) - { - step = 4; - } else if (line.startsWith("frame=")) - { - // Progressnotification video - } else if (line.startsWith("size=")) - { - // Progressnotification audio - } else if (line.endsWith("Queue input is backward in time") - || line.contains("Application provided invalid, non monotonically increasing dts to muxer in stream")) - { - // Ignore these non-fatal errors, if they are fatal, the next line(s) - // will trow the full error - if (listener != null) - { - listener.message(line); - } - } - else - { - LOG.info("Unhandled message in step: {} Line: {} message: <{}>", step, lineNR, line); - unhandledMessages.add(line); - } - } + } + break; + case 1: + { + if (line.startsWith("Stream mapping:")) { + // streamMappingFound + step = 2; + } else if (line.startsWith("Output #0")) { + // outputFound + step = 2; + } else if (!line.startsWith(" ")) { + LOG.info("Unhandled message in step: {} Line: {} message: <{}>", step, lineNR, line); + unhandledMessages.add(line); + } else { + // wait for Stream mapping: } - if (line.startsWith("frame=") || line.startsWith("size=")) - { - try - { - line = line.trim(); - if (line.length() > 0) - { - HashMap table = parseProgressInfoLine(line); - if (table == null) - { - if (listener != null) - { - listener.message(line); - } - lastWarning = line; - } else - { - if (listener != null) - { - String time = table.get("time"); - if (time != null) - { - String dParts[] = time.split(":"); - // HH:MM:SS.xx - - Double seconds = Double.parseDouble(dParts[dParts.length - 1]); - if (dParts.length > 1) - { - seconds += Double.parseDouble(dParts[dParts.length - 2]) * 60; - if (dParts.length > 2) - { - seconds += Double.parseDouble(dParts[dParts.length - 3]) * 60 * 60; - } - } + } + break; + case 2: + { + if (line.startsWith("Output #0")) { + // outputFound + step = 3; + } else if (line.startsWith("Stream mapping:")) { + // streamMappingFound + step = 3; + } else if (!line.startsWith(" ")) { + LOG.info("Unhandled message in step: {} Line: {} message: <{}>", step, lineNR, line); + unhandledMessages.add(line); + } else { + // wait for Stream mapping: + } + } + break; + case 3: + { + if (line.startsWith(" ")) { + // output details + } else if (line.startsWith("video:")) { + step = 4; + } else if (line.startsWith("frame=")) { + // Progressnotification video + } else if (line.startsWith("size=")) { + // Progressnotification audio + } else if (line.endsWith("Queue input is backward in time") + || line.contains( + "Application provided invalid, non monotonically increasing dts to muxer in stream")) { + // Ignore these non-fatal errors, if they are fatal, the next line(s) + // will throw the full error + if (listener != null) { + listener.message(line); + } + } else { + LOG.info("Unhandled message in step: {} Line: {} message: <{}>", step, lineNR, line); + unhandledMessages.add(line); + } + } + } + if (line.startsWith("frame=") || line.startsWith("size=")) { + try { + line = line.trim(); + if (line.length() > 0) { + HashMap table = parseProgressInfoLine(line); + if (table == null) { + if (listener != null) { + listener.message(line); + } + lastWarning = line; + } else { + if (listener != null) { + String time = table.get("time"); + if (time != null) { + String dParts[] = time.split(":"); + // HH:MM:SS.xx - int perm = (int) Math.round((seconds * 1000L * 1000L) - / (double) duration); - if (perm > 1000) - { - perm = 1000; - } - listener.progress(perm); - } - } - lastWarning = null; - } + Double seconds = Double.parseDouble(dParts[dParts.length - 1]); + if (dParts.length > 1) { + seconds += Double.parseDouble(dParts[dParts.length - 2]) * 60; + if (dParts.length > 2) { + seconds += Double.parseDouble(dParts[dParts.length - 3]) * 60 * 60; } - } catch (Exception ex) - { - LOG.warn("Error in progress parsing for line: {}", line); + } + + int perm = (int) Math.round((seconds * 1000L * 1000L) / (double) duration); + if (perm > 1000) { + perm = 1000; + } + listener.progress(perm); } + } + lastWarning = null; } + } + } catch (Exception ex) { + LOG.warn("Error in progress parsing for line: {}", line); } + } } - - public String getLastWarning() - { - return lastWarning; - } - - /** - * Private utility. Parse a line and try to match its contents against the - * {@link Encoder#PROGRESS_INFO_PATTERN} pattern. It the line can be parsed, - * it returns a hashtable with progress informations, otherwise it returns - * null. - * - * @param line The line from the ffmpeg output. - * @return A hashtable with the value reported in the line, or null if the - * given line can not be parsed. - */ - private HashMap parseProgressInfoLine(String line) { - HashMap table = null; - Matcher m = PROGRESS_INFO_PATTERN.matcher(line); - while (m.find()) - { - if (table == null) - { - table = new HashMap<>(); - } - String key = m.group(1); - String value = m.group(2); - table.put(key, value); - } - return table; - } + } - /** - * @return the unhandledMessages - */ - public List getUnhandledMessages() { - return unhandledMessages; + public String getLastWarning() { + return lastWarning; + } + + /** + * Private utility. Parse a line and try to match its contents against the {@link + * Encoder#PROGRESS_INFO_PATTERN} pattern. It the line can be parsed, it returns a hashtable with + * progress informations, otherwise it returns null. + * + * @param line The line from the ffmpeg output. + * @return A hashtable with the value reported in the line, or null if the given line can not be + * parsed. + */ + private HashMap parseProgressInfoLine(String line) { + HashMap table = null; + Matcher m = PROGRESS_INFO_PATTERN.matcher(line); + while (m.find()) { + if (table == null) { + table = new HashMap<>(); + } + String key = m.group(1); + String value = m.group(2); + table.put(key, value); } - + return table; + } + + /** @return the unhandledMessages */ + public List getUnhandledMessages() { + return unhandledMessages; + } } diff --git a/jave-core/src/main/java/ws/schild/jave/DefaultFFMPEGLocator.java b/jave-core/src/main/java/ws/schild/jave/DefaultFFMPEGLocator.java deleted file mode 100644 index 334cf60..0000000 --- a/jave-core/src/main/java/ws/schild/jave/DefaultFFMPEGLocator.java +++ /dev/null @@ -1,193 +0,0 @@ -/* - * JAVE - A Java Audio/Video Encoder (based on FFMPEG) - * - * Copyright (C) 2008-2009 Carlo Pelliccia (www.sauronsoftware.it) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package ws.schild.jave; - -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.nio.file.StandardCopyOption; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * The default ffmpeg executable locator, which exports on disk the ffmpeg - * executable bundled with the library distributions. It should work both for - * windows and many linux distributions. If it doesn't, try compiling your own - * ffmpeg executable and plug it in JAVE with a custom {@link FFMPEGLocator}. - * - * @author Carlo Pelliccia - */ -public class DefaultFFMPEGLocator extends FFMPEGLocator { - - private static final Logger LOG = LoggerFactory.getLogger(DefaultFFMPEGLocator.class); - - /** - * The ffmpeg executable file path. - */ - private final String path; - - /** - * It builds the default FFMPEGLocator, exporting the ffmpeg executable on a - * temp file. - */ - public DefaultFFMPEGLocator() { - String os = System.getProperty("os.name").toLowerCase(); - boolean isWindows = os.contains("windows"); - boolean isMac = os.contains("mac"); - LOG.debug("Os name is <{}> isWindows: {} isMac: {}", os, isWindows, isMac); - - // Dir Folder - File dirFolder = new File(System.getProperty("java.io.tmpdir"), "jave/"); - if (!dirFolder.exists()) - { - LOG.debug("Creating jave temp folder to place executables in <{}>", dirFolder.getAbsolutePath()); - dirFolder.mkdirs(); - } - else - { - LOG.debug("Jave temp folder exists in <{}>", dirFolder.getAbsolutePath()); - } - - // -----------------ffmpeg executable export on disk.----------------------------- - String suffix = isWindows ? ".exe" : (isMac ? "-osx" : ""); - String arch = System.getProperty("os.arch"); - - //File - File ffmpegFile = new File(dirFolder, "ffmpeg-" + arch +"-"+Version.getVersion()+ suffix); - LOG.debug("Executable path: {}", ffmpegFile.getAbsolutePath()); - - //Check the version of existing .exe file - if (ffmpegFile.exists()) - { - // OK, already present - LOG.debug("Executable exists in <{}>", ffmpegFile.getAbsolutePath()); - } - else - { - LOG.debug("Need to copy executable to <{}>", ffmpegFile.getAbsolutePath()); - copyFile("ffmpeg-" + arch + suffix, ffmpegFile); - } - - // Need a chmod? - if (!isWindows) - { - try - { - Runtime.getRuntime().exec(new String[] - { - "/bin/chmod", "755", ffmpegFile.getAbsolutePath() - }); - } catch (IOException e) - { - LOG.error("Error setting executable via chmod", e); - } - } - - // Everything seems okay - path = ffmpegFile.getAbsolutePath(); - LOG.debug("ffmpeg executable found: {}", path); - } - - @Override - public String getFFMPEGExecutablePath() { - return path; - } - - /** - * Copies a file bundled in the package to the supplied destination. - * - * @param path The name of the bundled file. - * @param dest The destination. - * @throws RuntimeException If an unexpected error occurs. - */ - private void copyFile(String path, File dest) { - String resourceName= "nativebin/" + path; - try - { - LOG.debug("Copy from resource <{}> to target <{}>", resourceName, dest.getAbsolutePath()); - InputStream is= getClass().getResourceAsStream(resourceName); - if (is == null) - { - // Use this for Java 9+ only if required - resourceName= "ws/schild/jave/nativebin/" + path; - LOG.debug("Alternative copy from SystemResourceAsStream <{}> to target <{}>", resourceName, dest.getAbsolutePath()); - is= ClassLoader.getSystemResourceAsStream(resourceName); - } - if (is != null) - { - if (copy(is, dest.getAbsolutePath())) - { - if (dest.exists()) - { - LOG.debug("Target <{}> exists", dest.getAbsolutePath()); - } - else - { - LOG.error("Target <{}> does not exist", dest.getAbsolutePath()); - } - } - else - { - LOG.error("Copy resource to target <{}> failed", dest.getAbsolutePath()); - } - try - { - is.close(); - } - catch (IOException ioex) - { - LOG.warn("Error in closing input stream", ioex); - } - } - else - { - LOG.error("Could not find ffmpeg platform executable in resources for <{}>", resourceName); - } - } - catch (NullPointerException ex) - { - LOG.error("Could not find ffmpeg executable for {} is the correct platform jar included?", resourceName); - throw ex; - } - } - - /** - * Copy a file from source to destination. - * - * @param source The name of the bundled file. - * @param destination the destination - * @return True if succeeded , False if not - */ - private boolean copy(InputStream source, String destination) { - boolean success = true; - - try - { - Files.copy(source, Paths.get(destination), StandardCopyOption.REPLACE_EXISTING); - } catch (IOException ex) - { - LOG.error("Cannot write file " + destination, ex); - success = false; - } - - return success; - } -} diff --git a/jave-core/src/main/java/ws/schild/jave/Encoder.java b/jave-core/src/main/java/ws/schild/jave/Encoder.java index 0ce6c8f..b7b2f0f 100644 --- a/jave-core/src/main/java/ws/schild/jave/Encoder.java +++ b/jave-core/src/main/java/ws/schild/jave/Encoder.java @@ -1,8 +1,8 @@ /* * JAVE - A Java Audio/Video Encoder (based on FFMPEG) - * + * * Copyright (C) 2008-2009 Carlo Pelliccia (www.sauronsoftware.it) - * + * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or @@ -18,18 +18,40 @@ */ package ws.schild.jave; - import java.io.File; import java.io.IOException; import java.io.InputStreamReader; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; import java.util.List; import java.util.StringTokenizer; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.stream.Stream; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import ws.schild.jave.encode.ArgType; +import ws.schild.jave.encode.AudioAttributes; +import ws.schild.jave.encode.EncodingArgument; +import ws.schild.jave.encode.EncodingAttributes; +import ws.schild.jave.encode.PredicateArgument; +import ws.schild.jave.encode.SimpleArgument; +import ws.schild.jave.encode.ValueArgument; +import ws.schild.jave.encode.VideoAttributes; +import ws.schild.jave.encode.enums.VsyncMethod; +import ws.schild.jave.encode.enums.X264_PROFILE; +import ws.schild.jave.filters.FilterGraph; +import ws.schild.jave.info.MultimediaInfo; +import ws.schild.jave.info.VideoSize; +import ws.schild.jave.process.ProcessLocator; +import ws.schild.jave.process.ProcessWrapper; +import ws.schild.jave.process.ffmpeg.DefaultFFMPEGLocator; +import ws.schild.jave.progress.EncoderProgressListener; +import ws.schild.jave.utils.RBufferedReader; + /** * Main class of the package. Instances can encode audio and video streams. * @@ -37,694 +59,542 @@ */ public class Encoder { - private static final Logger LOG = LoggerFactory.getLogger(Encoder.class); - - /** - * This regexp is used to parse the ffmpeg output about the supported - * formats. - */ - private static final Pattern FORMAT_PATTERN = Pattern - .compile("^\\s*([D ])([E ])\\s+([\\w,]+)\\s+.+$"); - - /** - * This regexp is used to parse the ffmpeg output about the included - * encoders/decoders. - */ - private static final Pattern ENCODER_DECODER_PATTERN = Pattern.compile( - "^\\s*([AVS]).{5}\\s(\\S+).(.+)$", Pattern.CASE_INSENSITIVE); - - /** - * This regexp is used to parse the ffmpeg output about the success of an - * encoding operation. - */ - private static final Pattern SUCCESS_PATTERN = Pattern.compile( - "^\\s*video\\:\\S+\\s+audio\\:\\S+\\s+subtitle\\:\\S+\\s+global headers\\:\\S+.*$", - Pattern.CASE_INSENSITIVE); - - /** - * The locator of the ffmpeg executable used by this encoder. - */ - private final FFMPEGLocator locator; - - /** - * The executor used to do the conversion - * Is saved here, so we can abort the conversion process - * - */ - private FFMPEGExecutor ffmpeg; - - /** - * List of unhandled messages from ffmpeng run - */ - private List unhandledMessages= null; - - /** - * It builds an encoder using a {@link DefaultFFMPEGLocator} instance to - * locate the ffmpeg executable to use. - */ - public Encoder() { - this.locator = new DefaultFFMPEGLocator(); - } + private static final Logger LOG = LoggerFactory.getLogger(Encoder.class); - /** - * It builds an encoder with a custom {@link FFMPEGLocator}. - * - * @param locator The locator picking up the ffmpeg executable used by the - * encoder. - */ - public Encoder(FFMPEGLocator locator) { - this.locator = locator; - } + /** This regexp is used to parse the ffmpeg output about the supported formats. */ + private static final Pattern FORMAT_PATTERN = + Pattern.compile("^\\s*([D ])([E ])\\s+([\\w,]+)\\s+.+$"); - /** - * Returns a list with the names of all the audio decoders bundled with the - * ffmpeg distribution in use. An audio stream can be decoded only if a - * decoder for its format is available. - * - * @return A list with the names of all the included audio decoders. - * @throws EncoderException If a problem occurs calling the underlying - * ffmpeg executable. - */ - public String[] getAudioDecoders() throws EncoderException { - return getCoders(false, true); - } + /** This regexp is used to parse the ffmpeg output about the included encoders/decoders. */ + private static final Pattern ENCODER_DECODER_PATTERN = + Pattern.compile("^\\s*([AVS]).{5}\\s(\\S+).(.+)$", Pattern.CASE_INSENSITIVE); - /** - * Returns a list with the names of all the audio encoders bundled with the - * ffmpeg distribution in use. An audio stream can be encoded using one of - * these encoders. - * - * @return A list with the names of all the included audio encoders. - * @throws EncoderException If a problem occurs calling the underlying - * ffmpeg executable. - */ - public String[] getAudioEncoders() throws EncoderException { - return getCoders(true, true); - } + /** This regexp is used to parse the ffmpeg output about the success of an encoding operation. */ + private static final Pattern SUCCESS_PATTERN = + Pattern.compile( + "^\\s*video\\:\\S+\\s+audio\\:\\S+\\s+subtitle\\:\\S+\\s+global headers\\:\\S+.*$", + Pattern.CASE_INSENSITIVE); - /** - * Returns a list with the names of all the coders bundled with the ffmpeg - * distribution in use. - * - * @param encoder Do search encoders, else decoders - * @param audio Do search for audio encodes, else video - * @return A list with the names of all the included encoders - * @throws EncoderException If a problem occurs calling the underlying - * ffmpeg executable. - */ - protected String[] getCoders(boolean encoder, boolean audio) throws EncoderException { - ArrayList res = new ArrayList<>(); - FFMPEGExecutor localFFMPEG = locator.createExecutor(); - localFFMPEG.addArgument(encoder ? "-encoders" : "-decoders"); - try - { - localFFMPEG.execute(); - RBufferedReader reader = - new RBufferedReader(new InputStreamReader(localFFMPEG - .getInputStream())); - String line; - String format = audio ? "A" : "V"; - boolean headerFound = false; - boolean evaluateLine = false; - while ((line = reader.readLine()) != null) - { - if (line.trim().length() == 0) - { - continue; - } - if (headerFound) - { - if (evaluateLine) - { - Matcher matcher = ENCODER_DECODER_PATTERN.matcher(line); - if (matcher.matches()) - { - //String encoderFlag = matcher.group(2); - String audioVideoFlag = matcher.group(1); - if (format.equals(audioVideoFlag)) - { - String name = matcher.group(2); - res.add(name); - } - } else - { - break; - } - } else - { - evaluateLine = line.trim().equals("------"); - } - } else if (line.trim().equals(encoder ? "Encoders:" : "Decoders:")) - { - headerFound = true; - } - } - } catch (IOException e) - { - throw new EncoderException(e); - } finally - { - localFFMPEG.destroy(); + /** The locator of the ffmpeg executable used by this encoder. */ + private final ProcessLocator locator; + + /** + * The executor used to do the conversion Is saved here, so we can abort the conversion process + */ + private ProcessWrapper ffmpeg; + + /** List of unhandled messages from ffmpeng run */ + private List unhandledMessages = null; + + /** + * It builds an encoder using a {@link DefaultFFMPEGLocator} instance to locate the ffmpeg + * executable to use. + */ + public Encoder() { + this.locator = new DefaultFFMPEGLocator(); + } + + /** + * It builds an encoder with a custom {@link ws.schild.jave.process.ffmpeg.FFMPEGProcess}. + * + * @param locator The locator picking up the ffmpeg executable used by the encoder. + */ + public Encoder(ProcessLocator locator) { + this.locator = locator; + } + + /** + * Returns a list with the names of all the audio decoders bundled with the ffmpeg distribution in + * use. An audio stream can be decoded only if a decoder for its format is available. + * + * @return A list with the names of all the included audio decoders. + * @throws EncoderException If a problem occurs calling the underlying ffmpeg executable. + */ + public String[] getAudioDecoders() throws EncoderException { + return getCoders(false, true); + } + + /** + * Returns a list with the names of all the audio encoders bundled with the ffmpeg distribution in + * use. An audio stream can be encoded using one of these encoders. + * + * @return A list with the names of all the included audio encoders. + * @throws EncoderException If a problem occurs calling the underlying ffmpeg executable. + */ + public String[] getAudioEncoders() throws EncoderException { + return getCoders(true, true); + } + + /** + * Returns a list with the names of all the coders bundled with the ffmpeg distribution in use. + * + * @param encoder Do search encoders, else decoders + * @param audio Do search for audio encodes, else video + * @return A list with the names of all the included encoders + * @throws EncoderException If a problem occurs calling the underlying ffmpeg executable. + */ + protected String[] getCoders(boolean encoder, boolean audio) throws EncoderException { + ArrayList res = new ArrayList<>(); + ProcessWrapper localFFMPEG = locator.createExecutor(); + localFFMPEG.addArgument(encoder ? "-encoders" : "-decoders"); + try { + localFFMPEG.execute(); + RBufferedReader reader = + new RBufferedReader(new InputStreamReader(localFFMPEG.getInputStream())); + String line; + String format = audio ? "A" : "V"; + boolean headerFound = false; + boolean evaluateLine = false; + while ((line = reader.readLine()) != null) { + if (line.trim().length() == 0) { + continue; } - int size = res.size(); - String[] ret = new String[size]; - for (int i = 0; i < size; i++) - { - ret[i] = res.get(i); + if (headerFound) { + if (evaluateLine) { + Matcher matcher = ENCODER_DECODER_PATTERN.matcher(line); + if (matcher.matches()) { + // String encoderFlag = matcher.group(2); + String audioVideoFlag = matcher.group(1); + if (format.equals(audioVideoFlag)) { + String name = matcher.group(2); + res.add(name); + } + } else { + break; + } + } else { + evaluateLine = line.trim().equals("------"); + } + } else if (line.trim().equals(encoder ? "Encoders:" : "Decoders:")) { + headerFound = true; } - return ret; + } + } catch (IOException e) { + throw new EncoderException(e); + } finally { + localFFMPEG.destroy(); } - - /** - * Returns a list with the names of all the video decoders bundled with the - * ffmpeg distribution in use. A video stream can be decoded only if a - * decoder for its format is available. - * - * @return A list with the names of all the included video decoders. - * @throws EncoderException If a problem occurs calling the underlying - * ffmpeg executable. - */ - public String[] getVideoDecoders() throws EncoderException { - return getCoders(false, false); + int size = res.size(); + String[] ret = new String[size]; + for (int i = 0; i < size; i++) { + ret[i] = res.get(i); } + return ret; + } - /** - * Returns a list with the names of all the video encoders bundled with the - * ffmpeg distribution in use. A video stream can be encoded using one of - * these encoders. - * - * @return A list with the names of all the included video encoders. - * @throws EncoderException If a problem occurs calling the underlying - * ffmpeg executable. - */ - public String[] getVideoEncoders() throws EncoderException { - return getCoders(true, false); - } + /** + * Returns a list with the names of all the video decoders bundled with the ffmpeg distribution in + * use. A video stream can be decoded only if a decoder for its format is available. + * + * @return A list with the names of all the included video decoders. + * @throws EncoderException If a problem occurs calling the underlying ffmpeg executable. + */ + public String[] getVideoDecoders() throws EncoderException { + return getCoders(false, false); + } - /** - * Returns a list with the names of all the file formats supported at - * encoding time by the underlying ffmpeg distribution. A multimedia file - * could be encoded and generated only if the specified format is in this - * list. - * - * @return A list with the names of all the supported file formats at - * encoding time. - * @throws EncoderException If a problem occurs calling the underlying - * ffmpeg executable. - */ - public String[] getSupportedEncodingFormats() throws EncoderException { - return getSupportedCodingFormats(true); - } + /** + * Returns a list with the names of all the video encoders bundled with the ffmpeg distribution in + * use. A video stream can be encoded using one of these encoders. + * + * @return A list with the names of all the included video encoders. + * @throws EncoderException If a problem occurs calling the underlying ffmpeg executable. + */ + public String[] getVideoEncoders() throws EncoderException { + return getCoders(true, false); + } - /** - * Returns a list with the names of all the file formats supported at - * en/de-coding time by the underlying ffmpeg distribution.A multimedia file - * could be encoded and generated only if the specified format is in this - * list. - * - * @param encoding True for encoding job, false to decode a file - * @return A list with the names of all the supported file formats at - * encoding time. - * @throws EncoderException If a problem occurs calling the underlying - * ffmpeg executable. - */ - protected String[] getSupportedCodingFormats(boolean encoding) throws EncoderException { - ArrayList res = new ArrayList<>(); - FFMPEGExecutor localFFMPEG = locator.createExecutor(); - localFFMPEG.addArgument("-formats"); - try - { - localFFMPEG.execute(); - RBufferedReader reader = - new RBufferedReader(new InputStreamReader(localFFMPEG - .getInputStream())); - String line; - String ed = encoding ? "E" : "D"; - boolean headerFound = false; - boolean evaluateLine = false; - while ((line = reader.readLine()) != null) - { - if (line.trim().length() == 0) - { - continue; - } - if (headerFound) - { - if (evaluateLine) - { - Matcher matcher = FORMAT_PATTERN.matcher(line); - if (matcher.matches()) - { - String encoderFlag = matcher.group(encoding ? 2 : 1); - if (ed.equals(encoderFlag)) - { - String aux = matcher.group(3); - StringTokenizer st = new StringTokenizer(aux, ","); - while (st.hasMoreTokens()) - { - String token = st.nextToken().trim(); - if (!res.contains(token)) - { - res.add(token); - } - } - } - } else - { - break; - } - } else - { - evaluateLine = line.trim().equals("--"); - } - } else if (line.trim().equals("File formats:")) - { - headerFound = true; + /** + * Returns a list with the names of all the file formats supported at encoding time by the + * underlying ffmpeg distribution. A multimedia file could be encoded and generated only if the + * specified format is in this list. + * + * @return A list with the names of all the supported file formats at encoding time. + * @throws EncoderException If a problem occurs calling the underlying ffmpeg executable. + */ + public String[] getSupportedEncodingFormats() throws EncoderException { + return getSupportedCodingFormats(true); + } + + /** + * Returns a list with the names of all the file formats supported at en/de-coding time by the + * underlying ffmpeg distribution.A multimedia file could be encoded and generated only if the + * specified format is in this list. + * + * @param encoding True for encoding job, false to decode a file + * @return A list with the names of all the supported file formats at encoding time. + * @throws EncoderException If a problem occurs calling the underlying ffmpeg executable. + */ + /* + * TODO: Refactor this out to a parsing utility. This will enable us to support multiple ffmpeg + * versions if the structure changes. + */ + protected String[] getSupportedCodingFormats(boolean encoding) throws EncoderException { + ArrayList res = new ArrayList<>(); + + try (ProcessWrapper localFFMPEG = locator.createExecutor()) { + localFFMPEG.addArgument("-formats"); + + localFFMPEG.execute(); + RBufferedReader reader = + new RBufferedReader(new InputStreamReader(localFFMPEG.getInputStream())); + String line; + String ed = encoding ? "E" : "D"; + boolean headerFound = false; + boolean evaluateLine = false; + while ((line = reader.readLine()) != null) { + if (line.trim().length() == 0) { + continue; + } + if (headerFound) { + if (evaluateLine) { + Matcher matcher = FORMAT_PATTERN.matcher(line); + if (matcher.matches()) { + String encoderFlag = matcher.group(encoding ? 2 : 1); + if (ed.equals(encoderFlag)) { + String aux = matcher.group(3); + StringTokenizer st = new StringTokenizer(aux, ","); + while (st.hasMoreTokens()) { + String token = st.nextToken().trim(); + if (!res.contains(token)) { + res.add(token); + } } + } + } else { + break; } - } catch (IOException e) - { - throw new EncoderException(e); - } finally - { - localFFMPEG.destroy(); - } - int size = res.size(); - String[] ret = new String[size]; - for (int i = 0; i < size; i++) - { - ret[i] = res.get(i); + } else { + evaluateLine = line.trim().equals("--"); + } + } else if (line.trim().equals("File formats:")) { + headerFound = true; } - return ret; + } + } catch (IOException e) { + throw new EncoderException(e); } - /** - * Returns a list with the names of all the file formats supported at - * decoding time by the underlying ffmpeg distribution. A multimedia file - * could be open and decoded only if its format is in this list. - * - * @return A list with the names of all the supported file formats at - * decoding time. - * @throws EncoderException If a problem occurs calling the underlying - * ffmpeg executable. - */ - public String[] getSupportedDecodingFormats() throws EncoderException { - return getSupportedCodingFormats(false); + int size = res.size(); + String[] ret = new String[size]; + for (int i = 0; i < size; i++) { + ret[i] = res.get(i); } + return ret; + } - /** - * Re-encode a multimedia file(s). - * - * This method is not reentrant, instead create multiple object instances - * - * @param multimediaObject The source multimedia file. It cannot be null. Be - * sure this file can be decoded (see null null null null - * {@link Encoder#getSupportedDecodingFormats()}, - * {@link Encoder#getAudioDecoders()} and - * {@link Encoder#getVideoDecoders()}). - * When passing multiple sources, make sure that they are compatible in the - * way that ffmpeg can concat them. We don't use the complex filter at the moment - * Perhaps you will need to first transcode/resize them - * https://trac.ffmpeg.org/wiki/Concatenate @see "Concat protocol" - * - * @param target The target multimedia re-encoded file. It cannot be null. - * If this file already exists, it will be overwrited. - * @param attributes A set of attributes for the encoding process. - * @throws IllegalArgumentException If both audio and video parameters are - * null. - * @throws InputFormatException If the source multimedia file cannot be - * decoded. - * @throws EncoderException If a problems occurs during the encoding - * process. - */ - public void encode(MultimediaObject multimediaObject, File target, EncodingAttributes attributes) - throws IllegalArgumentException, InputFormatException, - EncoderException { - encode(multimediaObject, target, attributes, null); - } + /** + * Returns a list with the names of all the file formats supported at decoding time by the + * underlying ffmpeg distribution. A multimedia file could be open and decoded only if its format + * is in this list. + * + * @return A list with the names of all the supported file formats at decoding time. + * @throws EncoderException If a problem occurs calling the underlying ffmpeg executable. + */ + public String[] getSupportedDecodingFormats() throws EncoderException { + return getSupportedCodingFormats(false); + } - public void encode(List multimediaObjects, File target, EncodingAttributes attributes) - throws IllegalArgumentException, InputFormatException, - EncoderException { - encode(multimediaObjects, target, attributes, null); - } - - /** - * Re-encode a multimedia file. - * - * This method is not reentrant, instead create multiple object instances - * - * @param multimediaObject The source multimedia file. It cannot be null. Be - * sure this file can be decoded (see null null null null {@link Encoder#getSupportedDecodingFormats()}, - * {@link Encoder#getAudioDecoders()} and - * {@link Encoder#getVideoDecoders()}). - * @param target The target multimedia re-encoded file. It cannot be null. - * If this file already exists, it will be overwrited. - * @param attributes A set of attributes for the encoding process. - * @param listener An optional progress listener for the encoding process. - * It can be null. - * @throws IllegalArgumentException If both audio and video parameters are - * null. - * @throws InputFormatException If the source multimedia file cannot be - * decoded. - * @throws EncoderException If a problems occurs during the encoding - * process. - */ - public void encode(MultimediaObject multimediaObject, File target, EncodingAttributes attributes, - EncoderProgressListener listener) throws IllegalArgumentException, - InputFormatException, EncoderException { - List src= new ArrayList<>(); - src.add(multimediaObject); - encode(src, target, attributes, listener); - } - - /** - * Re-encode a multimedia file(s). - * - * This method is not reentrant, instead create multiple object instances - * - * @param multimediaObjects The source multimedia files. It cannot be null. Be - * sure this file can be decoded (see null null null null {@link Encoder#getSupportedDecodingFormats()}, - * {@link Encoder#getAudioDecoders()} and* {@link Encoder#getVideoDecoders()}) - * When passing multiple sources, make sure that they are compatible in the - * way that ffmpeg can concat them. We don't use the complex filter at the moment - * Perhaps you will need to first transcode/resize them - * https://trac.ffmpeg.org/wiki/Concatenate @see "Concat protocol" - * - * @param target The target multimedia re-encoded file. It cannot be null. - * If this file already exists, it will be overwrited. - * @param attributes A set of attributes for the encoding process. - * @param listener An optional progress listener for the encoding process. - * It can be null. - * @throws IllegalArgumentException If both audio and video parameters are - * null. - * @throws InputFormatException If the source multimedia file cannot be - * decoded. - * @throws EncoderException If a problems occurs during the encoding - * process. - */ - public void encode(List multimediaObjects, File target, EncodingAttributes attributes, - EncoderProgressListener listener) throws IllegalArgumentException, - InputFormatException, EncoderException { - - String formatAttribute = attributes.getFormat(); - Float offsetAttribute = attributes.getOffset(); - Float durationAttribute = attributes.getDuration(); - boolean loopAttribute = attributes.getLoop(); - AudioAttributes audioAttributes = attributes.getAudioAttributes(); - VideoAttributes videoAttributes = attributes.getVideoAttributes(); - if (audioAttributes == null && videoAttributes == null) - { - throw new IllegalArgumentException( - "Both audio and video attributes are null"); - } - target = target.getAbsoluteFile(); - target.getParentFile().mkdirs(); - ffmpeg = locator.createExecutor(); - // Set global options - if (attributes.getFilterThreads() != -1) - { - ffmpeg.addArgument("--filter_thread"); - ffmpeg.addArgument(Integer.toString(attributes.getFilterThreads())); - } - if (offsetAttribute != null) - { - ffmpeg.addArgument("-ss"); - ffmpeg.addArgument(String.valueOf(offsetAttribute.floatValue())); - } - // Set input options, must be before -i argument - if (attributes.getDecodingThreads()!= -1) - { - ffmpeg.addArgument("-threads"); - ffmpeg.addArgument(Integer.toString(attributes.getDecodingThreads())); - } - if (loopAttribute && durationAttribute != null) - { - ffmpeg.addArgument("-loop"); - ffmpeg.addArgument("1"); - } - - ffmpeg.addArgument("-i"); - if (multimediaObjects.size() == 1) - { - // Simple case with one inpit source - if ( multimediaObjects.get(0).isURL() ) - { - ffmpeg.addArgument(multimediaObjects.get(0).getURL().toString()); - } - else - { - ffmpeg.addArgument(multimediaObjects.get(0).getFile().getAbsolutePath()); - } - } - else - { - StringBuilder inFiles= new StringBuilder(); - inFiles.append("concat:"); - boolean isFirst= true; - for (MultimediaObject in : multimediaObjects) - { - if (isFirst) - { - isFirst= false; - } - else - { - inFiles.append("|"); - } - if (in.isURL()) - { - inFiles.append(in.getURL().toString()); - } - else - { - inFiles.append(in.getFile().getAbsolutePath()); - } - } - ffmpeg.addArgument(inFiles.toString()); - } - if (durationAttribute != null) - { - ffmpeg.addArgument("-t"); - ffmpeg.addArgument(String.valueOf(durationAttribute.floatValue())); - } - if (videoAttributes == null) - { - ffmpeg.addArgument("-vn"); - } else - { - String codec = videoAttributes.getCodec(); - if (codec != null) - { - ffmpeg.addArgument("-vcodec"); - ffmpeg.addArgument(codec); - } - String tag = videoAttributes.getTag(); - if (tag != null) - { - ffmpeg.addArgument("-vtag"); - ffmpeg.addArgument(tag); - } - Integer bitRate = videoAttributes.getBitRate(); - if (bitRate != null) - { - ffmpeg.addArgument("-vb"); - ffmpeg.addArgument(String.valueOf(bitRate.intValue())); - } - Integer frameRate = videoAttributes.getFrameRate(); - if (frameRate != null) - { - ffmpeg.addArgument("-r"); - ffmpeg.addArgument(String.valueOf(frameRate.intValue())); - } - VideoSize size = videoAttributes.getSize(); - if (size != null) - { - ffmpeg.addArgument("-s"); - ffmpeg.addArgument(String.valueOf(size.getWidth()) + "x" - + String.valueOf(size.getHeight())); - } + /** + * Re-encode a multimedia file(s). + * + *

This method is not reentrant, instead create multiple object instances + * + * @param multimediaObject The source multimedia file. It cannot be null. Be sure this file can be + * decoded (see null null null null {@link Encoder#getSupportedDecodingFormats()}, {@link + * Encoder#getAudioDecoders()} and {@link Encoder#getVideoDecoders()}). When passing multiple + * sources, make sure that they are compatible in the way that ffmpeg can concat them. We + * don't use the complex filter at the moment Perhaps you will need to first transcode/resize + * them https://trac.ffmpeg.org/wiki/Concatenate @see "Concat protocol" + * @param target The target multimedia re-encoded file. It cannot be null. If this file already + * exists, it will be overwrited. + * @param attributes A set of attributes for the encoding process. + * @throws IllegalArgumentException If both audio and video parameters are null. + * @throws InputFormatException If the source multimedia file cannot be decoded. + * @throws EncoderException If a problems occurs during the encoding process. + */ + public void encode(MultimediaObject multimediaObject, File target, EncodingAttributes attributes) + throws IllegalArgumentException, InputFormatException, EncoderException { + encode(multimediaObject, target, attributes, null); + } - if (videoAttributes.isFaststart()) - { - ffmpeg.addArgument("-movflags"); - ffmpeg.addArgument("faststart"); - } + public void encode( + List multimediaObjects, File target, EncodingAttributes attributes) + throws IllegalArgumentException, InputFormatException, EncoderException { + encode(multimediaObjects, target, attributes, null); + } - if (videoAttributes.getX264Profile() != null) - { - ffmpeg.addArgument("-profile:v"); - ffmpeg.addArgument(videoAttributes.getX264Profile().getModeName()); - } + /** + * Re-encode a multimedia file. + * + *

This method is not reentrant, instead create multiple object instances + * + * @param multimediaObject The source multimedia file. It cannot be null. Be sure this file can be + * decoded (see null null null null {@link Encoder#getSupportedDecodingFormats()}, {@link + * Encoder#getAudioDecoders()} and {@link Encoder#getVideoDecoders()}). + * @param target The target multimedia re-encoded file. It cannot be null. If this file already + * exists, it will be overwrited. + * @param attributes A set of attributes for the encoding process. + * @param listener An optional progress listener for the encoding process. It can be null. + * @throws IllegalArgumentException If both audio and video parameters are null. + * @throws InputFormatException If the source multimedia file cannot be decoded. + * @throws EncoderException If a problems occurs during the encoding process. + */ + public void encode( + MultimediaObject multimediaObject, + File target, + EncodingAttributes attributes, + EncoderProgressListener listener) + throws IllegalArgumentException, InputFormatException, EncoderException { + List src = new ArrayList<>(); + src.add(multimediaObject); + encode(src, target, attributes, listener); + } - if (videoAttributes.getVideoFilters().size() > 0) - { - for (VideoFilter videoFilter : videoAttributes.getVideoFilters()) - { - ffmpeg.addArgument("-vf"); - ffmpeg.addArgument(videoFilter.getExpression()); - } - } + private static List globalOptions = + new ArrayList(Arrays.asList( + new ValueArgument(ArgType.GLOBAL, "--filter_thread", + ea -> ea.getFilterThreads().map(Object::toString)), + new ValueArgument(ArgType.GLOBAL, "-ss", ea -> ea.getOffset().map(Object::toString)), + new ValueArgument(ArgType.INFILE, "-threads", + ea -> ea.getDecodingThreads().map(Object::toString)), + new PredicateArgument(ArgType.INFILE, "-loop", "1", + ea -> ea.getLoop() && ea.getDuration().isPresent()), + new ValueArgument(ArgType.INFILE, "-f", ea -> ea.getInputFormat()), + new ValueArgument(ArgType.INFILE, "-safe", ea -> ea.getSafe().map(Object::toString)), + new ValueArgument(ArgType.OUTFILE, "-t", ea -> ea.getDuration().map(Object::toString)), + // Video Options + new PredicateArgument(ArgType.OUTFILE, "-vn", ea -> !ea.getVideoAttributes().isPresent()), + new ValueArgument(ArgType.OUTFILE, "-vcodec", + ea -> ea.getVideoAttributes().flatMap(VideoAttributes::getCodec)), + new ValueArgument(ArgType.OUTFILE, "-vtag", + ea -> ea.getVideoAttributes().flatMap(VideoAttributes::getTag)), + new ValueArgument(ArgType.OUTFILE, "-vb", + ea -> ea.getVideoAttributes() + .flatMap(VideoAttributes::getBitRate) + .map(Object::toString)), + new ValueArgument(ArgType.OUTFILE, "-r", + ea -> ea.getVideoAttributes() + .flatMap(VideoAttributes::getFrameRate) + .map(Object::toString)), + new ValueArgument(ArgType.OUTFILE, "-s", + ea -> ea.getVideoAttributes() + .flatMap(VideoAttributes::getSize) + .map(VideoSize::asEncoderArgument)), + new PredicateArgument(ArgType.OUTFILE, "-movflags", "faststart", + ea -> ea.getVideoAttributes().isPresent()), + new ValueArgument(ArgType.OUTFILE, "-profile:v", + ea -> ea.getVideoAttributes() + .flatMap(VideoAttributes::getX264Profile) + .map(X264_PROFILE::getModeName)), + new SimpleArgument(ArgType.OUTFILE, + ea -> ea.getVideoAttributes() + .map(VideoAttributes::getVideoFilters) + .map(Collection::stream) + .map(s -> s.flatMap(vf -> Stream.of("-vf", vf.getExpression()))) + .orElseGet(Stream::empty)), + new ValueArgument(ArgType.OUTFILE, "-filter_complex", + ea -> ea.getVideoAttributes() + .flatMap(VideoAttributes::getComplexFiltergraph) + .map(FilterGraph::getExpression)), + new ValueArgument(ArgType.OUTFILE, "-qscale:v", + ea -> ea.getVideoAttributes() + .flatMap(VideoAttributes::getQuality) + .map(Object::toString)), + // Audio Options + new PredicateArgument(ArgType.OUTFILE, "-an", ea -> !ea.getAudioAttributes().isPresent()), + new ValueArgument(ArgType.OUTFILE, "-acodec", + ea -> ea.getAudioAttributes().flatMap(AudioAttributes::getCodec)), + new ValueArgument(ArgType.OUTFILE, "-ab", + ea -> ea.getAudioAttributes() + .flatMap(AudioAttributes::getBitRate) + .map(Object::toString)), + new ValueArgument(ArgType.OUTFILE, "-ac", + ea -> ea.getAudioAttributes() + .flatMap(AudioAttributes::getChannels) + .map(Object::toString)), + new ValueArgument(ArgType.OUTFILE, "-ar", + ea -> ea.getAudioAttributes() + .flatMap(AudioAttributes::getSamplingRate) + .map(Object::toString)), + new ValueArgument(ArgType.OUTFILE, "-vol", + ea -> ea.getAudioAttributes() + .flatMap(AudioAttributes::getVolume) + .map(Object::toString)), + new ValueArgument(ArgType.OUTFILE, "-qscale:a", + ea -> ea.getAudioAttributes() + .flatMap(AudioAttributes::getQuality) + .map(Object::toString)), + new ValueArgument(ArgType.OUTFILE, "-f", ea -> ea.getOutputFormat()), + new ValueArgument(ArgType.OUTFILE, "-threads", + ea -> ea.getEncodingThreads().map(Object::toString)), + new PredicateArgument(ArgType.OUTFILE, "-map_metadata", "0", + ea -> ea.isMapMetaData()), + new ValueArgument(ArgType.OUTFILE, "-pix_fmt", + ea -> ea.getVideoAttributes().flatMap(VideoAttributes::getPixelFormat)), + new ValueArgument(ArgType.OUTFILE, "-vsync", + ea -> ea.getVideoAttributes().flatMap(VideoAttributes::getVsync).map(VsyncMethod::getMethodName)) + ) + ); - Integer quality = videoAttributes.getQuality(); - if (quality != null) - { - ffmpeg.addArgument("-qscale:v"); - ffmpeg.addArgument(String.valueOf(quality.intValue())); - } - } - if (audioAttributes == null) - { - ffmpeg.addArgument("-an"); - } else - { - String codec = audioAttributes.getCodec(); - if (codec != null) - { - ffmpeg.addArgument("-acodec"); - ffmpeg.addArgument(codec); - } - Integer bitRate = audioAttributes.getBitRate(); - if (bitRate != null) - { - ffmpeg.addArgument("-ab"); - ffmpeg.addArgument(String.valueOf(bitRate.intValue())); - } - Integer channels = audioAttributes.getChannels(); - if (channels != null) - { - ffmpeg.addArgument("-ac"); - ffmpeg.addArgument(String.valueOf(channels.intValue())); - } - Integer samplingRate = audioAttributes.getSamplingRate(); - if (samplingRate != null) - { - ffmpeg.addArgument("-ar"); - ffmpeg.addArgument(String.valueOf(samplingRate.intValue())); - } - Integer volume = audioAttributes.getVolume(); - if (volume != null) - { - ffmpeg.addArgument("-vol"); - ffmpeg.addArgument(String.valueOf(volume.intValue())); - } - Integer quality = audioAttributes.getQuality(); - if (quality != null) - { - ffmpeg.addArgument("-qscale:a"); - ffmpeg.addArgument(String.valueOf(quality.intValue())); - } - } - if (formatAttribute != null) - { - ffmpeg.addArgument("-f"); - ffmpeg.addArgument(formatAttribute); - } - // Set output options - if (attributes.getEncodingThreads()!= -1) - { - ffmpeg.addArgument("-threads"); - ffmpeg.addArgument(Integer.toString(attributes.getEncodingThreads())); - } - - ffmpeg.addArgument("-y"); - ffmpeg.addArgument(target.getAbsolutePath()); - - if (attributes.isMapMetaData()) - { // Copy over meta data if possible - ffmpeg.addArgument("-map_metadata"); - ffmpeg.addArgument("0"); - } - -// ffmpeg.addArgument("-loglevel"); -// ffmpeg.addArgument("warning"); // Only report errors - - try - { - ffmpeg.execute(); - } catch (IOException e) - { - throw new EncoderException(e); + public static void addOptionAtIndex(EncodingArgument arg, Integer index) { + globalOptions.add(index, arg); + } + + /** + * Re-encode a multimedia file(s). + * + *

This method is not reentrant, instead create multiple object instances + * + * @param multimediaObjects The source multimedia files. It cannot be null. Be sure this file can + * be decoded (see null null null null {@link Encoder#getSupportedDecodingFormats()}, {@link + * Encoder#getAudioDecoders()} and* {@link Encoder#getVideoDecoders()}) When passing multiple + * sources, make sure that they are compatible in the way that ffmpeg can concat them. We + * don't use the complex filter at the moment Perhaps you will need to first transcode/resize + * them https://trac.ffmpeg.org/wiki/Concatenate @see "Concat protocol" + * @param target The target multimedia re-encoded file. It cannot be null. If this file already + * exists, it will be overwrited. + * @param attributes A set of attributes for the encoding process. + * @param listener An optional progress listener for the encoding process. It can be null. + * @throws IllegalArgumentException If both audio and video parameters are null. + * @throws InputFormatException If the source multimedia file cannot be decoded. + * @throws EncoderException If a problems occurs during the encoding process. + */ + public void encode( + List multimediaObjects, + File target, + EncodingAttributes attributes, + EncoderProgressListener listener) + throws IllegalArgumentException, InputFormatException, EncoderException { + attributes.validate(); + + target = target.getAbsoluteFile(); + target.getParentFile().mkdirs(); + ffmpeg = locator.createExecutor(); + + // Set global options + globalOptions + .stream() + .filter(ea -> ArgType.GLOBAL.equals(ea.getArgType())) + .flatMap(eArg -> eArg.getArguments(attributes)) + .forEach(ffmpeg::addArgument); + + // Set input options, must be before -i argument + globalOptions + .stream() + .filter(ea -> ArgType.INFILE.equals(ea.getArgType())) + .flatMap(eArg -> eArg.getArguments(attributes)) + .forEach(ffmpeg::addArgument); + + multimediaObjects + .stream() + .map(Object::toString) + .flatMap(mmo -> Stream.of("-i", mmo)) + .forEach(ffmpeg::addArgument); + + // Set output options. Must be after the -i and before the outfile target + globalOptions + .stream() + .filter(ea -> ArgType.OUTFILE.equals(ea.getArgType())) + .flatMap(eArg -> eArg.getArguments(attributes)) + .forEach(ffmpeg::addArgument); + + ffmpeg.addArgument("-y"); + ffmpeg.addArgument(target.getAbsolutePath()); + + try { + ffmpeg.execute(); + } catch (IOException e) { + throw new EncoderException(e); + } + + try { + String lastWarning = null; + long duration = 0; + MultimediaInfo info = null; + /* + * TODO: This is an awkward way of determining duration of input videos. This calls a separate + * FFMPEG process to getInfo when the output of running FFMPEG just above will list the info + * of the input videos as "Input #0" -> "Input #N". Capture _that_ output instead of calling + * *back* into FFMPEG. Furthermore, expressing the percentage of the transcoding job as a + * simple "what percentage of the input duration have we output" feels too naive given all of + * the interesting video filters that can be applied. It feels like the user would know the + * duration of the output video as: + * 1. The duration of the input video (as we have expressed here) + * 2. The sum of the durations of the input videos + * 3. A particular duration calculated with the context of all the inputs/encoding attributes. + * So, if the calling method tells this method the expected duration, then we can express + * progress as a percentage. I would like to make #1 and #2 very simple to do, however. + * Perhaps a method that would take the input MultimediaInfo objects that are generated from + * this FFMPEG invocation, the EncodingAttributes, and would output a duration. Then we could + * have named methods that would calculate durations as in #1 and #2. + */ + if (multimediaObjects.size() == 1 + && (!multimediaObjects.get(0).isURL() || !multimediaObjects.get(0).isReadURLOnce())) { + info = multimediaObjects.get(0).getInfo(); + } + + Float offsetAttribute = attributes.getOffset().orElse(null); + Float durationAttribute = attributes.getDuration().orElse(null); + if (durationAttribute != null) { + duration = (long) Math.round((durationAttribute * 1000L)); + } else { + if (info != null) { + duration = info.getDuration(); + if (offsetAttribute != null) { + duration -= (long) Math.round((offsetAttribute * 1000L)); + } } - try - { - String lastWarning = null; - long duration= 0; - RBufferedReader reader = new RBufferedReader( - new InputStreamReader(ffmpeg.getErrorStream())); - MultimediaInfo info = null; - if (multimediaObjects.size() == 1 && (!multimediaObjects.get(0).isURL() || !multimediaObjects.get(0).isReadURLOnce()) ) - { - info= multimediaObjects.get(0).getInfo(); - } - if (durationAttribute != null) - { - duration = (long) Math - .round((durationAttribute * 1000L)); - } else - { - if (info != null) - { - duration = info.getDuration(); - if (offsetAttribute != null) - { - duration -= (long) Math - .round((offsetAttribute * 1000L)); - } - } - } - if (listener != null) - { - listener.sourceInfo(info); - } - String line; - ConversionOutputAnalyzer outputAnalyzer= new ConversionOutputAnalyzer(duration, listener); - while ((line = reader.readLine()) != null) - { - outputAnalyzer.analyzeNewLine(line); - } - if (outputAnalyzer.getLastWarning() != null) - { - if (!SUCCESS_PATTERN.matcher(lastWarning).matches()) - { - throw new EncoderException("No match for: " + SUCCESS_PATTERN + " in " + lastWarning); - } - } - unhandledMessages= outputAnalyzer.getUnhandledMessages(); - int exitCode= ffmpeg.getProcessExitCode(); - if (exitCode != 0) - { - LOG.error("Process exit code: {} to {}", exitCode, target.getName()); - throw new EncoderException("Exit code of ffmpeg encoding run is "+exitCode); - } - } catch (IOException e) - { - throw new EncoderException(e); - } finally - { - ffmpeg.destroy(); - ffmpeg= null; + } + + if (listener != null) { + listener.sourceInfo(info); + } + String line; + ConversionOutputAnalyzer outputAnalyzer = new ConversionOutputAnalyzer(duration, listener); + RBufferedReader reader = new RBufferedReader(new InputStreamReader(ffmpeg.getErrorStream())); + while ((line = reader.readLine()) != null) { + outputAnalyzer.analyzeNewLine(line); + } + if (outputAnalyzer.getLastWarning() != null) { + if (!SUCCESS_PATTERN.matcher(lastWarning).matches()) { + throw new EncoderException("No match for: " + SUCCESS_PATTERN + " in " + lastWarning); } + } + /* + * TODO: This is not thread safe. This needs to be a resulting value from the call to the + * Encoder. We can create a separate EncoderResult, but not a stateful variable. + */ + unhandledMessages = outputAnalyzer.getUnhandledMessages(); + int exitCode = ffmpeg.getProcessExitCode(); + if (exitCode != 0) { + LOG.error("Process exit code: {} to {}", exitCode, target.getName()); + throw new EncoderException("Exit code of ffmpeg encoding run is " + exitCode); + } + } catch (IOException e) { + throw new EncoderException(e); + } finally { + if (ffmpeg != null) { + ffmpeg.destroy(); + } + ffmpeg = null; } + } - /** - * Return the list of unhandled output messages of the ffmpeng encoder run - * - * @return the unhandledMessages list of unhandled messages, can be null or empty - */ - public List getUnhandledMessages() { - return unhandledMessages; - } - - /** - * Force the encoding process to stop - */ - public void abortEncoding() - { - if (ffmpeg != null) - { - ffmpeg.destroy(); - ffmpeg= null; - } + /** + * Return the list of unhandled output messages of the ffmpeng encoder run + * + * @return the unhandledMessages list of unhandled messages, can be null or empty + */ + public List getUnhandledMessages() { + return unhandledMessages; + } + + /** Force the encoding process to stop */ + public void abortEncoding() { + if (ffmpeg != null) { + ffmpeg.destroy(); + ffmpeg = null; } + } } diff --git a/jave-core/src/main/java/ws/schild/jave/EncoderException.java b/jave-core/src/main/java/ws/schild/jave/EncoderException.java index a9441fd..8b35237 100644 --- a/jave-core/src/main/java/ws/schild/jave/EncoderException.java +++ b/jave-core/src/main/java/ws/schild/jave/EncoderException.java @@ -1,8 +1,8 @@ /* * JAVE - A Java Audio/Video Encoder (based on FFMPEG) - * + * * Copyright (C) 2008-2009 Carlo Pelliccia (www.sauronsoftware.it) - * + * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or @@ -25,26 +25,21 @@ */ public class EncoderException extends Exception { - private static final long serialVersionUID = 1L; - - EncoderException() { - super(); - } + private static final long serialVersionUID = 1L; - EncoderException(String message) { - super(message); - } - - EncoderException(int step, int lineNumber, String message) { - super("In step: "+step+" Error in line "+lineNumber+" : <"+message+">"); - } + EncoderException() { + super(); + } - EncoderException(Throwable cause) { - super(cause); - } + EncoderException(String message) { + super(message); + } - EncoderException(String message, Throwable cause) { - super(message, cause); - } + EncoderException(Throwable cause) { + super(cause); + } + EncoderException(String message, Throwable cause) { + super(message, cause); + } } diff --git a/jave-core/src/main/java/ws/schild/jave/EncodingAttributes.java b/jave-core/src/main/java/ws/schild/jave/EncodingAttributes.java deleted file mode 100644 index 88c59c1..0000000 --- a/jave-core/src/main/java/ws/schild/jave/EncodingAttributes.java +++ /dev/null @@ -1,309 +0,0 @@ -/* - * JAVE - A Java Audio/Video Encoder (based on FFMPEG) - * - * Copyright (C) 2008-2009 Carlo Pelliccia (www.sauronsoftware.it) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package ws.schild.jave; - -import java.io.Serializable; - -/** - * Attributes controlling the encoding process. - * - * @author Carlo Pelliccia - */ -public class EncodingAttributes implements Serializable { - - private static final long serialVersionUID = 1L; - - /** - * The format name for the encoded target multimedia file. Be sure this - * format is supported (see {@link Encoder#getSupportedEncodingFormats()}. - */ - private String format = null; - - /** - * The start offset time (seconds). If null or not specified no start offset - * will be applied. - */ - private Float offset = null; - - /** - * The duration (seconds) of the re-encoded stream. If null or not specified - * the source stream, starting from the offset, will be completely - * re-encoded in the target stream. - */ - private Float duration = null; - - /** - * The attributes for the encoding of the audio stream in the target - * multimedia file. If null of not specified no audio stream will be - * encoded. It cannot be null if also the video field is null. - */ - private AudioAttributes audioAttributes = null; - - /** - * The attributes for the encoding of the video stream in the target - * multimedia file. If null of not specified no video stream will be - * encoded. It cannot be null if also the audio field is null. - */ - private VideoAttributes videoAttributes = null; - - /** - * Should we try to copy over the meta data? - */ - private boolean mapMetaData= false; - - /** - * Maximum number of cores/cpus to use for conversion - * -1 means use default of ffmpeg - */ - private int filterThreads= -1; - /** - * Number of threads to use for decoding (if supported by codec) - */ - private int decodingThreads= -1; - /** - * Number of threads to use for encoding (if supported by codec) - */ - private int encodingThreads= -1; - - /** - * Should the input be treated as a loop - */ - private boolean loop = false; - - /** - * Returns the format name for the encoded target multimedia file. - * - * @return The format name for the encoded target multimedia file. - */ - public String getFormat() { - return format; - } - - /** - * Sets the format name for the encoded target multimedia file. Be sure this - * format is supported (see {@link Encoder#getSupportedEncodingFormats()}. - * - * @param format The format name for the encoded target multimedia file. - * @return this instance - */ - public EncodingAttributes setFormat(String format) { - this.format = format; - return this; - } - - /** - * Returns the start offset time (seconds). - * - * @return The start offset time (seconds). - */ - public Float getOffset() { - return offset; - } - - /** - * Sets the start offset time (seconds). If null or not specified no start - * offset will be applied. - * - * @param offset The start offset time (seconds). - * @return this instance - */ - public EncodingAttributes setOffset(Float offset) { - this.offset = offset; - return this; - } - - /** - * Returns the duration (seconds) of the re-encoded stream. - * - * @return The duration (seconds) of the re-encoded stream. - */ - public Float getDuration() { - return duration; - } - - /** - * Sets the duration (seconds) of the re-encoded stream. If null or not - * specified the source stream, starting from the offset, will be completely - * re-encoded in the target stream. - * - * @param duration The duration (seconds) of the re-encoded stream. - * @return this instance - */ - public EncodingAttributes setDuration(Float duration) { - this.duration = duration; - return this; - } - - /* - * Returns if the input is to be considered for looping. - * @return if the input will be looped. - */ - public boolean getLoop() { - return loop; - } - - /** - * Sets if the inputs will be looped or not. - * - * @param loop if the input should be looped. - * @return this instance - */ - public EncodingAttributes setLoop(boolean loop) { - this.loop = loop; - return this; - } - - /** - * Returns the attributes for the encoding of the audio stream in the target - * multimedia file. - * - * @return The attributes for the encoding of the audio stream in the target - * multimedia file. - */ - public AudioAttributes getAudioAttributes() { - return audioAttributes; - } - - /** - * Sets the attributes for the encoding of the audio stream in the target - * multimedia file. If null of not specified no audio stream will be - * encoded. It cannot be null if also the video field is null. - * - * @param audioAttributes The attributes for the encoding of the audio - * stream in the target multimedia file. - * @return this instance - */ - public EncodingAttributes setAudioAttributes(AudioAttributes audioAttributes) { - this.audioAttributes = audioAttributes; - return this; - } - - /** - * Returns the attributes for the encoding of the video stream in the target - * multimedia file. - * - * @return The attributes for the encoding of the video stream in the target - * multimedia file. - */ - public VideoAttributes getVideoAttributes() { - return videoAttributes; - } - - /** - * Sets the attributes for the encoding of the video stream in the target - * multimedia file. If null of not specified no video stream will be - * encoded. It cannot be null if also the audio field is null. - * - * @param videoAttributes The attributes for the encoding of the video - * stream in the target multimedia file. - * @return this instance - */ - public EncodingAttributes setVideoAttributes(VideoAttributes videoAttributes) { - this.videoAttributes = videoAttributes; - return this; - } - - @Override - public String toString() { - return getClass().getName() + "(format=" + format + ", offset=" - + offset + ", duration=" + duration + ",loop=" + loop + ", audioAttributes=" - + audioAttributes + ", videoAttributes=" + videoAttributes - + ")"; - } - - /** - * @return the mapMetaData - */ - public boolean isMapMetaData() { - return mapMetaData; - } - - /** - * Copy over meta data from original file to new output if possible - * - * @param mapMetaData the mapMetaData to set - * @return this instance - */ - public EncodingAttributes setMapMetaData(boolean mapMetaData) { - this.mapMetaData = mapMetaData; - return this; - } - - /** - * @return Maximum number of cores/cpus to use for filtering - * -1 means use default of ffmpeg - * - */ - public int getFilterThreads() { - return filterThreads; - } - - /** - * ffmpeg uses multiple cores for filtering - * - * @param filterThreads Maximum number of cores/cpus to use - * -1 means use default of ffmpeg - * @return this instance - */ - public EncodingAttributes setFilterThreads(int filterThreads) { - this.filterThreads = filterThreads; - return this; - } - - /** - * Number of threads to use for decoding (if supported by codec) - * -1 means use default of ffmpeg - * @return the decodingThreads - */ - public int getDecodingThreads() { - return decodingThreads; - } - - /** - * Number of threads to use for decoding (if supported by codec) - * -1 means use default of ffmpeg - * @param decodingThreads the decodingThreads to set - * @return this instance - */ - public EncodingAttributes setDecodingThreads(int decodingThreads) { - this.decodingThreads = decodingThreads; - return this; - } - - /** - * Number of threads to use for encoding (if supported by codec) - * -1 means use default of ffmpeg - * @return the encodingThreads - */ - public int getEncodingThreads() { - return encodingThreads; - } - - /** - * Number of threads to use for encoding (if supported by codec) - * -1 means use default of ffmpeg - * @param encodingThreads the encodingThreads to set - * @return this instance - */ - public EncodingAttributes setEncodingThreads(int encodingThreads) { - this.encodingThreads = encodingThreads; - return this; - } - -} diff --git a/jave-core/src/main/java/ws/schild/jave/FFMPEGExecutor.java b/jave-core/src/main/java/ws/schild/jave/FFMPEGExecutor.java deleted file mode 100644 index 5979a4e..0000000 --- a/jave-core/src/main/java/ws/schild/jave/FFMPEGExecutor.java +++ /dev/null @@ -1,243 +0,0 @@ -/* - * JAVE - A Java Audio/Video Encoder (based on FFMPEG) - * - * Copyright (C) 2008-2009 Carlo Pelliccia (www.sauronsoftware.it) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package ws.schild.jave; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.ArrayList; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * An ffmpeg process wrapper. - * - * @author Carlo Pelliccia - * @deprecated As of 3.0.0. Use {@link ws.schild.jave.process.ProcessWrapper} instead. - */ -@Deprecated -public class FFMPEGExecutor { - - private static final Logger LOG = LoggerFactory.getLogger(FFMPEGExecutor.class); - - /** - * The path of the ffmpeg executable. - */ - private final String ffmpegExecutablePath; - - /** - * Arguments for the executable. - */ - private final ArrayList args = new ArrayList<>(); - - /** - * The process representing the ffmpeg execution. - */ - private Process ffmpeg = null; - - /** - * A process killer to kill the ffmpeg process with a shutdown hook, useful - * if the jvm execution is shutted down during an ongoing encoding process. - */ - private ProcessKiller ffmpegKiller = null; - - /** - * A stream reading from the ffmpeg process standard output channel. - */ - private InputStream inputStream = null; - - /** - * A stream writing in the ffmpeg process standard input channel. - */ - private OutputStream outputStream = null; - - /** - * A stream reading from the ffmpeg process standard error channel. - */ - private InputStream errorStream = null; - - /** - * It build the executor. - * - * @param ffmpegExecutablePath The path of the ffmpeg executable. - */ - public FFMPEGExecutor(String ffmpegExecutablePath) { - this.ffmpegExecutablePath = ffmpegExecutablePath; - } - - /** - * Adds an argument to the ffmpeg executable call. - * - * @param arg The argument. - */ - public void addArgument(String arg) { - args.add(arg); - } - - /** - * Executes the ffmpeg process with the previous given arguments. - * - * @param destroyOnRuntimeShutdown destroy process if the runtime VM is shutdown - * @param openIOStreams Open IO streams for input/output and errorout, - * should be false when destroyOnRuntimeShutdown is false too - * @throws IOException If the process call fails. - */ - public void execute(boolean destroyOnRuntimeShutdown, boolean openIOStreams) throws IOException { - int argsSize = args.size(); - String[] cmd = new String[argsSize + 2]; - cmd[0] = ffmpegExecutablePath; - for (int i = 0; i < argsSize; i++) - { - cmd[i + 1] = args.get(i); - } - cmd[argsSize + 1] = "-hide_banner"; // Don't show banner - if (LOG.isDebugEnabled()) - { - StringBuilder sb = new StringBuilder(); - for (String c : cmd) - { - sb.append(c); - sb.append(' '); - } - LOG.debug("About to execute {}", sb.toString()); - } - Runtime runtime = Runtime.getRuntime(); - ffmpeg = runtime.exec(cmd); - if (destroyOnRuntimeShutdown) - { - ffmpegKiller = new ProcessKiller(ffmpeg); - runtime.addShutdownHook(ffmpegKiller); - } - if (openIOStreams) - { - inputStream = ffmpeg.getInputStream(); - outputStream = ffmpeg.getOutputStream(); - errorStream = ffmpeg.getErrorStream(); - } - } - - /** - * Executes the ffmpeg process with the previous given arguments. - * Default to kill processes when the JVM terminates, and the various - * IOStreams are opened as required - * - * @throws IOException If the process call fails. - */ - public void execute() throws IOException { - execute(true, true); - } - - /** - * Returns a stream reading from the ffmpeg process standard output channel. - * - * @return A stream reading from the ffmpeg process standard output channel. - */ - public InputStream getInputStream() { - return inputStream; - } - - /** - * Returns a stream writing in the ffmpeg process standard input channel. - * - * @return A stream writing in the ffmpeg process standard input channel. - */ - public OutputStream getOutputStream() { - return outputStream; - } - - /** - * Returns a stream reading from the ffmpeg process standard error channel. - * - * @return A stream reading from the ffmpeg process standard error channel. - */ - public InputStream getErrorStream() { - return errorStream; - } - - /** - * If there's a ffmpeg execution in progress, it kills it. - */ - public void destroy() { - if (inputStream != null) - { - try - { - inputStream.close(); - } catch (Throwable t) - { - LOG.warn("Error closing input stream", t); - } - inputStream = null; - } - if (outputStream != null) - { - try - { - outputStream.close(); - } catch (Throwable t) - { - LOG.warn("Error closing output stream", t); - } - outputStream = null; - } - if (errorStream != null) - { - try - { - errorStream.close(); - } catch (Throwable t) - { - LOG.warn("Error closing error stream", t); - } - errorStream = null; - } - if (ffmpeg != null) - { - ffmpeg.destroy(); - ffmpeg = null; - } - if (ffmpegKiller != null) - { - Runtime runtime = Runtime.getRuntime(); - runtime.removeShutdownHook(ffmpegKiller); - ffmpegKiller = null; - } - } - - /** - * Return the exit code of the ffmpeg process - * If the process is not yet terminated, it waits for the termination - * of the process - * - * @return process exit code - */ - public int getProcessExitCode() - { - // Make sure it's terminated - try - { - ffmpeg.waitFor(); - } - catch (InterruptedException ex) - { - LOG.warn("Interrupted during waiting on process, forced shutdown?", ex); - } - return ffmpeg.exitValue(); - } -} diff --git a/jave-core/src/main/java/ws/schild/jave/FFMPEGLocator.java b/jave-core/src/main/java/ws/schild/jave/FFMPEGLocator.java deleted file mode 100644 index 924357c..0000000 --- a/jave-core/src/main/java/ws/schild/jave/FFMPEGLocator.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * JAVE - A Java Audio/Video Encoder (based on FFMPEG) - * - * Copyright (C) 2008-2009 Carlo Pelliccia (www.sauronsoftware.it) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package ws.schild.jave; - -/** - * Abstract class whose derived concrete instances are used by {@link Encoder} - * to locate the ffmpeg executable path. - * - * @author Carlo Pelliccia - * @see Encoder - * @deprecated As of 3.0.0. Use {@link ws.schild.jave.process.ProcessLocator} instead. In 3.0.0, this will become an interface with a default method for createExecutor. - */ -@Deprecated -public abstract class FFMPEGLocator { - - /** - * This method should return the path of a ffmpeg executable suitable for - * the current machine. - * - * @return The path of the ffmpeg executable. - * @deprecated As of 3.0.0, this will become getExecutablePath. See {@link ws.schild.jave.process.ProcessLocator#getExecutablePath}. - */ - @Deprecated - protected abstract String getFFMPEGExecutablePath(); - - /** - * It returns a brand new {@link FFMPEGExecutor}, ready to be used in a - * ffmpeg call. - * - * @return A newly instanced {@link FFMPEGExecutor}, using this locator to - * call the ffmpeg executable. - */ - public FFMPEGExecutor createExecutor() { - return new FFMPEGExecutor(getFFMPEGExecutablePath()); - } - -} diff --git a/jave-core/src/main/java/ws/schild/jave/InputFormatException.java b/jave-core/src/main/java/ws/schild/jave/InputFormatException.java index 88556be..1c858fe 100644 --- a/jave-core/src/main/java/ws/schild/jave/InputFormatException.java +++ b/jave-core/src/main/java/ws/schild/jave/InputFormatException.java @@ -1,8 +1,8 @@ /* * JAVE - A Java Audio/Video Encoder (based on FFMPEG) - * + * * Copyright (C) 2008-2009 Carlo Pelliccia (www.sauronsoftware.it) - * + * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or @@ -25,14 +25,13 @@ */ public class InputFormatException extends EncoderException { - private static final long serialVersionUID = 1L; - - InputFormatException() { - super(); - } + private static final long serialVersionUID = 1L; - InputFormatException(String message) { - super(message); - } + InputFormatException() { + super(); + } + InputFormatException(String message) { + super(message); + } } diff --git a/jave-core/src/main/java/ws/schild/jave/MultimediaInfo.java b/jave-core/src/main/java/ws/schild/jave/MultimediaInfo.java deleted file mode 100644 index 82717f1..0000000 --- a/jave-core/src/main/java/ws/schild/jave/MultimediaInfo.java +++ /dev/null @@ -1,141 +0,0 @@ -/* - * JAVE - A Java Audio/Video Encoder (based on FFMPEG) - * - * Copyright (C) 2008-2009 Carlo Pelliccia (www.sauronsoftware.it) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package ws.schild.jave; - -/** - * Instances of this class report informations about a decoded multimedia file. - * - * @author Carlo Pelliccia - */ -public class MultimediaInfo { - - /** - * The multimedia file format name. - */ - private String format = null; - - /** - * The stream duration in millis. If less than 0 this information is not - * available. - */ - private long duration = -1; - - /** - * A set of audio-specific informations. If null, there's no audio stream in - * the multimedia file. - */ - private AudioInfo audio = null; - - /** - * A set of video-specific informations. If null, there's no video stream in - * the multimedia file. - */ - private VideoInfo video = null; - - /** - * Returns the multimedia file format name. - * - * @return The multimedia file format name. - */ - public String getFormat() { - return format; - } - - /** - * Sets the multimedia file format name. - * - * @param format The multimedia file format name. - * @return this instance - */ - public MultimediaInfo setFormat(String format) { - this.format = format; - return this; - } - - /** - * Returns the stream duration in millis. If less than 0 this information is - * not available. - * - * @return The stream duration in millis. If less than 0 this information is - * not available. - */ - public long getDuration() { - return duration; - } - - /** - * Sets the stream duration in millis. - * - * @param duration The stream duration in millis. - * @return this instance - */ - public MultimediaInfo setDuration(long duration) { - this.duration = duration; - return this; - } - - /** - * Returns a set of audio-specific informations. If null, there's no audio - * stream in the multimedia file. - * - * @return A set of audio-specific informations. - */ - public AudioInfo getAudio() { - return audio; - } - - /** - * Sets a set of audio-specific informations. - * - * @param audio A set of audio-specific informations. - * @return this instance - */ - public MultimediaInfo setAudio(AudioInfo audio) { - this.audio = audio; - return this; - } - - /** - * Returns a set of video-specific informations. If null, there's no video - * stream in the multimedia file. - * - * @return A set of audio-specific informations. - */ - public VideoInfo getVideo() { - return video; - } - - /** - * Sets a set of video-specific informations. - * - * @param video A set of video-specific informations. - * @return this instance - */ - public MultimediaInfo setVideo(VideoInfo video) { - this.video = video; - return this; - } - - @Override - public String toString() { - return getClass().getName() + " (format=" + format + ", duration=" - + duration + ", video=" + video + ", audio=" + audio + ")"; - } - -} diff --git a/jave-core/src/main/java/ws/schild/jave/MultimediaObject.java b/jave-core/src/main/java/ws/schild/jave/MultimediaObject.java index f63df96..97d57b1 100644 --- a/jave-core/src/main/java/ws/schild/jave/MultimediaObject.java +++ b/jave-core/src/main/java/ws/schild/jave/MultimediaObject.java @@ -10,434 +10,371 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import ws.schild.jave.info.AudioInfo; +import ws.schild.jave.info.MultimediaInfo; +import ws.schild.jave.info.VideoInfo; +import ws.schild.jave.info.VideoSize; +import ws.schild.jave.process.ProcessLocator; +import ws.schild.jave.process.ProcessWrapper; +import ws.schild.jave.process.ffmpeg.DefaultFFMPEGLocator; +import ws.schild.jave.utils.RBufferedReader; + +/* + * TODO: Rip out parsing logic. This shouldn't be in a POJO object. This is meant to be a data + * holder. + * + * Also TODO: Do away with the distinction between URL and File altogether. Shouldn't this just be + * a String anyway? We don't ever need the distinction between the two, correct? + */ public class MultimediaObject { - /** - * @param readURLOnce the readURLOnce to set - */ - public void setReadURLOnce(boolean readURLOnce) { - this.readURLOnce = readURLOnce; - } + /** @param readURLOnce the readURLOnce to set */ + public void setReadURLOnce(boolean readURLOnce) { + this.readURLOnce = readURLOnce; + } - private static final Logger LOG = LoggerFactory.getLogger(MultimediaObject.class); - /** - * This regexp is used to parse the ffmpeg output about the size of a video - * stream. - */ - private static final Pattern SIZE_PATTERN = Pattern.compile( - "(\\d+)x(\\d+)", Pattern.CASE_INSENSITIVE); - /** - * This regexp is used to parse the ffmpeg output about the frame rate value - * of a video stream. - */ - private static final Pattern FRAME_RATE_PATTERN = Pattern.compile( - "([\\d.]+)\\s+(?:fps|tbr)", Pattern.CASE_INSENSITIVE); - /** - * This regexp is used to parse the ffmpeg output about the bit rate value - * of a stream. - */ - private static final Pattern BIT_RATE_PATTERN = Pattern.compile( - "(\\d+)\\s+kb/s", Pattern.CASE_INSENSITIVE); - /** - * This regexp is used to parse the ffmpeg output about the sampling rate of - * an audio stream. - */ - private static final Pattern SAMPLING_RATE_PATTERN = Pattern.compile( - "(\\d+)\\s+Hz", Pattern.CASE_INSENSITIVE); - /** - * This regexp is used to parse the ffmpeg output about the channels number - * of an audio stream. - */ - private static final Pattern CHANNELS_PATTERN = Pattern.compile( - "(mono|stereo|quad)", Pattern.CASE_INSENSITIVE); + private static final Logger LOG = LoggerFactory.getLogger(MultimediaObject.class); + /** This regexp is used to parse the ffmpeg output about the size of a video stream. */ + private static final Pattern SIZE_PATTERN = + Pattern.compile("(\\d+)x(\\d+)", Pattern.CASE_INSENSITIVE); + /** + * This regexp is used to parse the ffmpeg output about the frame rate value of a video stream. + */ + private static final Pattern FRAME_RATE_PATTERN = + Pattern.compile("([\\d.]+)\\s+(?:fps|tbr)", Pattern.CASE_INSENSITIVE); + /** This regexp is used to parse the ffmpeg output about the bit rate value of a stream. */ + private static final Pattern BIT_RATE_PATTERN = + Pattern.compile("(\\d+)\\s+kb/s", Pattern.CASE_INSENSITIVE); + /** This regexp is used to parse the ffmpeg output about the sampling rate of an audio stream. */ + private static final Pattern SAMPLING_RATE_PATTERN = + Pattern.compile("(\\d+)\\s+Hz", Pattern.CASE_INSENSITIVE); + /** + * This regexp is used to parse the ffmpeg output about the channels number of an audio stream. + */ + private static final Pattern CHANNELS_PATTERN = + Pattern.compile("(mono|stereo|quad)", Pattern.CASE_INSENSITIVE); - /** - * The locator of the ffmpeg executable used by this extractor. - */ - private final FFMPEGLocator locator; + /** The locator of the ffmpeg executable used by this extractor. */ + private final ProcessLocator locator; - private File inputFile; - private URL inputURL; - /** - * When true, we try to not read the source more than once - * One of the side effects is, that no progressbar is available. - * - */ - private boolean readURLOnce= false; + private File inputFile; + private URL inputURL; + /** + * When true, we try to not read the source more than once One of the side effects is, that no + * progressbar is available. + */ + private boolean readURLOnce = false; - /** - * It builds an extractor using a {@link DefaultFFMPEGLocator} instance to - * locate the ffmpeg executable to use. - * - * @param input Input file for creating MultimediaObject - */ - public MultimediaObject(File input) { - this.locator = new DefaultFFMPEGLocator(); - this.inputFile = input; - } + /** + * It builds an extractor using a {@link DefaultFFMPEGLocator} instance to locate the ffmpeg + * executable to use. + * + * @param input Input file for creating MultimediaObject + */ + public MultimediaObject(File input) { + this.locator = new DefaultFFMPEGLocator(); + this.inputFile = input; + } - /** - * It builds an extractor using a {@link DefaultFFMPEGLocator} instance to - * locate the ffmpeg executable to use. - * - * @param input Input URL for creating MultimediaObject - */ - public MultimediaObject(URL input) { - this.locator = new DefaultFFMPEGLocator(); - this.inputURL = input; - } - - /** - * It builds an extractor using a {@link DefaultFFMPEGLocator} instance to - * locate the ffmpeg executable to use. - * - * @param input Input URL for creating MultimediaObject - * @param readURLOnce When true, we try to not read the source more than once - * One of the side effects is, that no progressbar is available. - */ - public MultimediaObject(URL input, boolean readURLOnce) { - this.locator = new DefaultFFMPEGLocator(); - this.inputURL = input; - this.readURLOnce= readURLOnce; - } + /** + * It builds an extractor using a {@link DefaultFFMPEGLocator} instance to locate the ffmpeg + * executable to use. + * + * @param input Input URL for creating MultimediaObject + */ + public MultimediaObject(URL input) { + this.locator = new DefaultFFMPEGLocator(); + this.inputURL = input; + } - /** - * - * @return file - */ - public File getFile() { - return this.inputFile; - } - - public URL getURL() { - return this.inputURL; - } + /** + * It builds an extractor using a {@link DefaultFFMPEGLocator} instance to locate the ffmpeg + * executable to use. + * + * @param input Input URL for creating MultimediaObject + * @param readURLOnce When true, we try to not read the source more than once One of the side + * effects is, that no progressbar is available. + */ + public MultimediaObject(URL input, boolean readURLOnce) { + this.locator = new DefaultFFMPEGLocator(); + this.inputURL = input; + this.readURLOnce = readURLOnce; + } - public void setFile(File file) { - this.inputFile = file; - } + /** @return file */ + public File getFile() { + return this.inputFile; + } - public void setUR(URL input) { - this.inputURL = input; - } - - /** - * Check if we have a file or an URL - * - * @return true if this object references an URL - */ - public boolean isURL() - { - return inputURL != null; - } - - /** - * It builds an extractor with a custom {@link FFMPEGLocator}. - * - * @param input Input file for creating MultimediaObject - * @param locator The locator picking up the ffmpeg executable used by the - * extractor. - */ - public MultimediaObject(File input, FFMPEGLocator locator) { - this.locator = locator; - this.inputFile = input; + public URL getURL() { + return this.inputURL; + } + + public void setFile(File file) { + this.inputFile = file; + } + + public void setUR(URL input) { + this.inputURL = input; + } + + /** + * Check if we have a file or an URL + * + * @return true if this object references an URL + */ + public boolean isURL() { + return inputURL != null; + } + + /** + * It builds an extractor with a custom {@link ws.schild.jave.process.ffmpeg.FFMPEGProcess}. + * + * @param input Input file for creating MultimediaObject + * @param locator The locator picking up the ffmpeg executable used by the extractor. + */ + public MultimediaObject(File input, ProcessLocator locator) { + this.locator = locator; + this.inputFile = input; + } + + /** + * Returns a set informations about a multimedia file, if its format is supported for decoding. + * + * @return A set of informations about the file and its contents. + * @throws InputFormatException If the format of the source file cannot be recognized and decoded. + * @throws EncoderException If a problem occurs calling the underlying ffmpeg executable. + */ + public MultimediaInfo getInfo() throws InputFormatException, EncoderException { + if (isURL() || inputFile.canRead()) { + ProcessWrapper ffmpeg = locator.createExecutor(); + ffmpeg.addArgument("-i"); + ffmpeg.addArgument(toString()); + + try { + ffmpeg.execute(); + } catch (IOException e) { + throw new EncoderException(e); + } + try { + RBufferedReader reader = + new RBufferedReader(new InputStreamReader(ffmpeg.getErrorStream())); + if (isURL()) { + return parseMultimediaInfo(inputURL.toString(), reader); + } else { + return parseMultimediaInfo(inputFile.getAbsolutePath(), reader); + } + } finally { + ffmpeg.destroy(); + } + } else { + throw new EncoderException("Input file not found <" + inputFile.getAbsolutePath() + ">"); } + } - /** - * Returns a set informations about a multimedia file, if its format is - * supported for decoding. - * - * @return A set of informations about the file and its contents. - * @throws InputFormatException If the format of the source file cannot be - * recognized and decoded. - * @throws EncoderException If a problem occurs calling the underlying - * ffmpeg executable. - */ - public MultimediaInfo getInfo() throws InputFormatException, - EncoderException { - if (isURL() || inputFile.canRead()) - { - FFMPEGExecutor ffmpeg = locator.createExecutor(); - ffmpeg.addArgument("-i"); - if (isURL()) - { - ffmpeg.addArgument(inputURL.toString()); - } - else - { - ffmpeg.addArgument(inputFile.getAbsolutePath()); - } - try - { - ffmpeg.execute(); - } catch (IOException e) + /** + * Private utility. It parses the ffmpeg output, extracting informations about a source multimedia + * file. + * + * @param source The source multimedia object. + * @param reader The ffmpeg output channel. + * @return A set of informations about the source multimedia file and its contents. + * @throws InputFormatException If the format of the source file cannot be recognized and decoded. + * @throws EncoderException If a problem occurs calling the underlying ffmpeg executable. + */ + /* + * TODO: Refactor all parsing logic to a versioned parsing utility so we can detect FFMPEG version + * programmatically/support multiple runtime versions and consolidate parsing in one location. + */ + private MultimediaInfo parseMultimediaInfo(String source, RBufferedReader reader) + throws InputFormatException, EncoderException { + Pattern p1 = Pattern.compile("^\\s*Input #0, (\\w+).+$\\s*", Pattern.CASE_INSENSITIVE); + Pattern p21 = Pattern.compile("^\\s*Duration:.*$", Pattern.CASE_INSENSITIVE); + Pattern p22 = + Pattern.compile( + "^\\s*Duration: (\\d\\d):(\\d\\d):(\\d\\d)\\.(\\d\\d).*$", Pattern.CASE_INSENSITIVE); + Pattern p3 = + Pattern.compile( + "^\\s*Stream #\\S+: ((?:Audio)|(?:Video)|(?:Data)): (.*)\\s*$", + Pattern.CASE_INSENSITIVE); + @SuppressWarnings("unused") + Pattern p4 = Pattern.compile("^\\s*Metadata:", Pattern.CASE_INSENSITIVE); + MultimediaInfo info = null; + try { + int step = 0; + while (true) { + String line = reader.readLine(); + LOG.debug("Output line: {}", line); + if (line == null) { + break; + } + switch (step) { + case 0: { - throw new EncoderException(e); + String token = source + ": "; + if (line.startsWith(token)) { + String message = line.substring(token.length()); + throw new InputFormatException(message); + } + Matcher m = p1.matcher(line); + if (m.matches()) { + String format = m.group(1); + info = new MultimediaInfo(); + info.setFormat(format); + step++; + } + break; } - try + case 1: { - RBufferedReader reader = new RBufferedReader(new InputStreamReader(ffmpeg - .getErrorStream())); - if (isURL()) - { - return parseMultimediaInfo(inputURL.toString(), reader); + Matcher m1 = p21.matcher(line); + Matcher m2 = p22.matcher(line); + if (m1.matches()) { + if (m2.matches()) { + long hours = Integer.parseInt(m2.group(1)); + long minutes = Integer.parseInt(m2.group(2)); + long seconds = Integer.parseInt(m2.group(3)); + long dec = Integer.parseInt(m2.group(4)); + long duration = + (dec * 10L) + + (seconds * 1000L) + + (minutes * 60L * 1000L) + + (hours * 60L * 60L * 1000L); + info.setDuration(duration); + step++; + } else { + LOG.warn("Invalid duration found {}", line); + step++; + // step = 3; } - else - { - return parseMultimediaInfo(inputFile.getAbsolutePath(), reader); - } - } finally - { - ffmpeg.destroy(); + } else { + // step = 3; + } + break; } - } else - { - throw new EncoderException("Input file not found <" + inputFile.getAbsolutePath() + ">"); - } - } - - /** - * Private utility. It parses the ffmpeg output, extracting informations - * about a source multimedia file. - * - * @param source The source multimedia object. - * @param reader The ffmpeg output channel. - * @return A set of informations about the source multimedia file and its - * contents. - * @throws InputFormatException If the format of the source file cannot be - * recognized and decoded. - * @throws EncoderException If a problem occurs calling the underlying - * ffmpeg executable. - */ - private MultimediaInfo parseMultimediaInfo(String source, - RBufferedReader reader) throws InputFormatException, - EncoderException { - Pattern p1 = Pattern.compile("^\\s*Input #0, (\\w+).+$\\s*", - Pattern.CASE_INSENSITIVE); - Pattern p21 = Pattern.compile( - "^\\s*Duration:.*$", - Pattern.CASE_INSENSITIVE); - Pattern p22 = Pattern.compile( - "^\\s*Duration: (\\d\\d):(\\d\\d):(\\d\\d)\\.(\\d\\d).*$", - Pattern.CASE_INSENSITIVE); - Pattern p3 = Pattern.compile( - "^\\s*Stream #\\S+: ((?:Audio)|(?:Video)|(?:Data)): (.*)\\s*$", - Pattern.CASE_INSENSITIVE); - @SuppressWarnings("unused") - Pattern p4 = Pattern.compile( - "^\\s*Metadata:", - Pattern.CASE_INSENSITIVE); - MultimediaInfo info = null; - try - { - int step = 0; - while (true) + case 2: { - String line = reader.readLine(); - LOG.debug("Output line: {}", line); - if (line == null) - { - break; - } - switch (step) - { - case 0: - { - String token = source + ": "; - if (line.startsWith(token)) - { - String message = line.substring(token.length()); - throw new InputFormatException(message); - } - Matcher m = p1.matcher(line); - if (m.matches()) - { - String format = m.group(1); - info = new MultimediaInfo(); - info.setFormat(format); - step++; - } - break; - } - case 1: - { - Matcher m1 = p21.matcher(line); - Matcher m2 = p22.matcher(line); - if (m1.matches()) - { - if (m2.matches()) - { - long hours = Integer.parseInt(m2.group(1)); - long minutes = Integer.parseInt(m2.group(2)); - long seconds = Integer.parseInt(m2.group(3)); - long dec = Integer.parseInt(m2.group(4)); - long duration = (dec * 10L) + (seconds * 1000L) - + (minutes * 60L * 1000L) - + (hours * 60L * 60L * 1000L); - info.setDuration(duration); - step++; - } else - { - LOG.warn("Invalid duration found {}", line); - step++; - // step = 3; - } + Matcher m = p3.matcher(line); + if (m.matches()) { + String type = m.group(1); + String specs = m.group(2); + if ("Video".equalsIgnoreCase(type)) { + VideoInfo video = new VideoInfo(); + StringTokenizer st = new StringTokenizer(specs, ","); + for (int i = 0; st.hasMoreTokens(); i++) { + String token = st.nextToken().trim(); + if (i == 0) { + video.setDecoder(token); + } else { + boolean parsed = false; + // Video size. + Matcher m2 = SIZE_PATTERN.matcher(token); + if (!parsed && m2.find()) { + int width = Integer.parseInt(m2.group(1)); + int height = Integer.parseInt(m2.group(2)); + video.setSize(new VideoSize(width, height)); + parsed = true; + } + // Frame rate. + m2 = FRAME_RATE_PATTERN.matcher(token); + if (!parsed && m2.find()) { + try { + float frameRate = Float.parseFloat(m2.group(1)); + video.setFrameRate(frameRate); + } catch (NumberFormatException e) { + LOG.info("Invalid frame rate value: " + m2.group(1), e); } - else - { - // step = 3; - } - break; + parsed = true; + } + // Bit rate. + m2 = BIT_RATE_PATTERN.matcher(token); + if (!parsed && m2.find()) { + int bitRate = Integer.parseInt(m2.group(1)); + video.setBitRate(bitRate * 1000); + parsed = true; + } } - case 2: - { - Matcher m = p3.matcher(line); - if (m.matches()) - { - String type = m.group(1); - String specs = m.group(2); - if ("Video".equalsIgnoreCase(type)) - { - VideoInfo video = new VideoInfo(); - StringTokenizer st = new StringTokenizer(specs, ","); - for (int i = 0; st.hasMoreTokens(); i++) - { - String token = st.nextToken().trim(); - if (i == 0) - { - video.setDecoder(token); - } else - { - boolean parsed = false; - // Video size. - Matcher m2 = SIZE_PATTERN.matcher(token); - if (!parsed && m2.find()) - { - int width = Integer.parseInt(m2 - .group(1)); - int height = Integer.parseInt(m2 - .group(2)); - video.setSize(new VideoSize(width, - height)); - parsed = true; - } - // Frame rate. - m2 = FRAME_RATE_PATTERN.matcher(token); - if (!parsed && m2.find()) - { - try - { - float frameRate = Float - .parseFloat(m2.group(1)); - video.setFrameRate(frameRate); - } catch (NumberFormatException e) - { - LOG.info("Invalid frame rate value: " + m2.group(1), e); - } - parsed = true; - } - // Bit rate. - m2 = BIT_RATE_PATTERN.matcher(token); - if (!parsed && m2.find()) - { - int bitRate = Integer.parseInt(m2 - .group(1)); - video.setBitRate(bitRate*1000); - parsed = true; - } - } - } - info.setVideo(video); - } else if ("Audio".equalsIgnoreCase(type)) - { - AudioInfo audio = new AudioInfo(); - StringTokenizer st = new StringTokenizer(specs, ","); - for (int i = 0; st.hasMoreTokens(); i++) - { - String token = st.nextToken().trim(); - if (i == 0) - { - audio.setDecoder(token); - } else - { - boolean parsed = false; - // Sampling rate. - Matcher m2 = SAMPLING_RATE_PATTERN - .matcher(token); - if (!parsed && m2.find()) - { - int samplingRate = Integer.parseInt(m2 - .group(1)); - audio.setSamplingRate(samplingRate); - parsed = true; - } - // Channels. - m2 = CHANNELS_PATTERN.matcher(token); - if (!parsed && m2.find()) - { - String ms = m2.group(1); - if ("mono".equalsIgnoreCase(ms)) - { - audio.setChannels(1); - } else if ("stereo" - .equalsIgnoreCase(ms)) - { - audio.setChannels(2); - } else if ("quad" - .equalsIgnoreCase(ms)) - { - audio.setChannels(4); - } - parsed = true; - } - // Bit rate. - m2 = BIT_RATE_PATTERN.matcher(token); - if (!parsed && m2.find()) - { - int bitRate = Integer.parseInt(m2 - .group(1)); - audio.setBitRate(bitRate*1000); - parsed = true; - } - } - } - info.setAudio(audio); - } - } else // if (m4.matches()) - { - // Stay on level 2 + } + info.setVideo(video); + } else if ("Audio".equalsIgnoreCase(type)) { + AudioInfo audio = new AudioInfo(); + StringTokenizer st = new StringTokenizer(specs, ","); + for (int i = 0; st.hasMoreTokens(); i++) { + String token = st.nextToken().trim(); + if (i == 0) { + audio.setDecoder(token); + } else { + boolean parsed = false; + // Sampling rate. + Matcher m2 = SAMPLING_RATE_PATTERN.matcher(token); + if (!parsed && m2.find()) { + int samplingRate = Integer.parseInt(m2.group(1)); + audio.setSamplingRate(samplingRate); + parsed = true; + } + // Channels. + m2 = CHANNELS_PATTERN.matcher(token); + if (!parsed && m2.find()) { + String ms = m2.group(1); + if ("mono".equalsIgnoreCase(ms)) { + audio.setChannels(1); + } else if ("stereo".equalsIgnoreCase(ms)) { + audio.setChannels(2); + } else if ("quad".equalsIgnoreCase(ms)) { + audio.setChannels(4); } - /* - else - { - step = 3; - } - */ break; + parsed = true; + } + // Bit rate. + m2 = BIT_RATE_PATTERN.matcher(token); + if (!parsed && m2.find()) { + int bitRate = Integer.parseInt(m2.group(1)); + audio.setBitRate(bitRate * 1000); + parsed = true; + } } - default: - break; - } - if (line.startsWith("frame=")) - { - reader.reinsertLine(line); - break; + } + info.setAudio(audio); } + } else // if (m4.matches()) + { + // Stay on level 2 + } + /* + else + { + step = 3; + } + */ break; } - } catch (IOException e) - { - throw new EncoderException(e); + default: + break; } - if (info == null) - { - throw new InputFormatException(); + if (line.startsWith("frame=")) { + reader.reinsertLine(line); + break; } - return info; + } + } catch (IOException e) { + throw new EncoderException(e); } + if (info == null) { + throw new InputFormatException(); + } + return info; + } + + /** @return the readURLOnce */ + public boolean isReadURLOnce() { + return readURLOnce; + } - /** - * @return the readURLOnce - */ - public boolean isReadURLOnce() { - return readURLOnce; + @Override + public String toString() { + if (isURL()) { + return getURL().toString(); + } else { + return getFile().getAbsolutePath(); } + } } diff --git a/jave-core/src/main/java/ws/schild/jave/ProcessKiller.java b/jave-core/src/main/java/ws/schild/jave/ProcessKiller.java deleted file mode 100644 index a8e5bcb..0000000 --- a/jave-core/src/main/java/ws/schild/jave/ProcessKiller.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * JAVE - A Java Audio/Video Encoder (based on FFMPEG) - * - * Copyright (C) 2008-2009 Carlo Pelliccia (www.sauronsoftware.it) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package ws.schild.jave; - -/** - * A package-private utility to add a shutdown hook to kill ongoing encoding - * processes at the jvm shutdown. - * - * @author Carlo Pelliccia - * @deprecated As of 3.0.0. Use {@link ws.schild.jave.process.ProcessKiller} instead. - */ -@Deprecated -class ProcessKiller extends Thread { - - /** - * The process to kill. - */ - private final Process process; - - /** - * Builds the killer. - * - * @param process The process to kill. - */ - public ProcessKiller(Process process) { - this.process = process; - } - - /** - * It kills the supplied process. - */ - @Override - public void run() { - process.destroy(); - } - -} diff --git a/jave-core/src/main/java/ws/schild/jave/RBufferedReader.java b/jave-core/src/main/java/ws/schild/jave/RBufferedReader.java deleted file mode 100644 index 9643935..0000000 --- a/jave-core/src/main/java/ws/schild/jave/RBufferedReader.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * JAVE - A Java Audio/Video Encoder (based on FFMPEG) - * - * Copyright (C) 2008-2009 Carlo Pelliccia (www.sauronsoftware.it) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package ws.schild.jave; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.Reader; -import java.util.ArrayList; - -/** - * A package-private utility extending java.io.BufferedReader. If a line read - * with {@link RBufferedReader#readLine()} is not useful for the calling code, - * it can be re-inserted in the stream. The same line will be returned again at - * the next readLine() call. - * - * @author Carlo Pelliccia - */ -class RBufferedReader extends BufferedReader { - - /** - * Re-inserted lines buffer. - */ - private final ArrayList lines = new ArrayList<>(); - - /** - * It builds the reader. - * - * @param in The underlying reader. - */ - public RBufferedReader(Reader in) { - super(in); - } - - /** - * It returns the next line in the stream. - */ - @Override - public String readLine() throws IOException { - if (lines.size() > 0) - { - return lines.remove(0); - } else - { - return super.readLine(); - } - } - - /** - * Reinserts a line in the stream. The line will be returned at the next - * {@link RBufferedReader#readLine()} call. - * - * @param line The line. - */ - public void reinsertLine(String line) { - lines.add(0, line); - } - -} diff --git a/jave-core/src/main/java/ws/schild/jave/ScreenExtractor.java b/jave-core/src/main/java/ws/schild/jave/ScreenExtractor.java index d5558ab..4683141 100644 --- a/jave-core/src/main/java/ws/schild/jave/ScreenExtractor.java +++ b/jave-core/src/main/java/ws/schild/jave/ScreenExtractor.java @@ -1,332 +1,305 @@ package ws.schild.jave; - import java.io.File; import java.io.IOException; import java.io.InputStreamReader; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import ws.schild.jave.info.MultimediaInfo; +import ws.schild.jave.process.ProcessLocator; +import ws.schild.jave.process.ProcessWrapper; +import ws.schild.jave.process.ffmpeg.DefaultFFMPEGLocator; +import ws.schild.jave.utils.RBufferedReader; +import ws.schild.jave.utils.Utils; + public class ScreenExtractor { - private static final Logger LOG = LoggerFactory.getLogger(ScreenExtractor.class); + private static final Logger LOG = LoggerFactory.getLogger(ScreenExtractor.class); - /** - * The locator of the ffmpeg executable used by this extractor. - */ - private final FFMPEGLocator locator; - private int numberOfScreens; + /** The locator of the ffmpeg executable used by this extractor. */ + private final ProcessLocator locator; - /** - * It builds an extractor using a {@link DefaultFFMPEGLocator} instance to - * locate the ffmpeg executable to use. - */ - public ScreenExtractor() { - this.locator = new DefaultFFMPEGLocator(); - } + private int numberOfScreens; - /** - * - * @return The number of screens found - */ - public int getNumberOfScreens() { - return numberOfScreens; - } + /** + * It builds an extractor using a {@link ws.schild.jave.process.ffmpeg.FFMPEGProcess} instance to + * locate the ffmpeg executable to use. + */ + public ScreenExtractor() { + this.locator = new DefaultFFMPEGLocator(); + } - /** - * It builds an extractor with a custom {@link FFMPEGLocator}. - * - * @param locator The locator picking up the ffmpeg executable used by the - * extractor. - */ - public ScreenExtractor(FFMPEGLocator locator) { - this.locator = locator; - } + /** @return The number of screens found */ + public int getNumberOfScreens() { + return numberOfScreens; + } - /** - * Generates screenshots from source video. - * - * @param multimediaObject Source MultimediaObject @see MultimediaObject - * @param width Output width, pass -1 to use video width and height - * @param height Output height (Ignored when width = -1) - * @param seconds Interval in seconds between screens - * @param outputDir Destination of output images - * @param fileNamePrefix Name all thumbnails will start with - * @param extension Image extension for output (jpg, png, etc) - * @param quality The range is between 1-31 with 31 being the worst quality - * @throws InputFormatException If the source multimedia file cannot be - * decoded. - * @throws EncoderException If a problems occurs during the encoding - * process. - */ - public void render(MultimediaObject multimediaObject, int width, int height, int seconds, File outputDir, - String fileNamePrefix, String extension, int quality) - throws InputFormatException, EncoderException { - String inputSource = multimediaObject.isURL() ? multimediaObject.getURL().toString() : multimediaObject.getFile().getAbsolutePath(); - try - { - if (!outputDir.exists()) - { - if (!outputDir.mkdirs()) - { - LOG.debug("Failed to create destination folder"); - throw new SecurityException(); - } - } - if (!multimediaObject.isURL() && !multimediaObject.getFile().canRead()) - { - LOG.debug("Failed to open input file"); - throw new SecurityException(); - } - } catch (SecurityException e) - { - LOG.debug("Access denied checking destination folder", e); - } - - MultimediaInfo multimediaInfo = multimediaObject.getInfo(); - numberOfScreens = Math.round(((float)multimediaInfo.getDuration()) / 1000.0f / seconds); - - FFMPEGExecutor ffmpeg = this.locator.createExecutor(); - ffmpeg.addArgument("-i"); - ffmpeg.addArgument(inputSource); - ffmpeg.addArgument("-f"); - ffmpeg.addArgument("image2"); - ffmpeg.addArgument("-vf"); - ffmpeg.addArgument(String.format("fps=fps=1/%s", String.valueOf(seconds))); - if (width != -1) - { - ffmpeg.addArgument("-s"); - ffmpeg.addArgument(String.format("%sx%s", String.valueOf(width), String.valueOf(height))); - } - ffmpeg.addArgument("-qscale"); - ffmpeg.addArgument(String.valueOf(quality)); - ffmpeg.addArgument(String.format("%s%s%s-%%04d.%s", - outputDir.getAbsolutePath(), File.separator, fileNamePrefix, extension)); + /** + * It builds an extractor with a custom {@link ws.schild.jave.process.ffmpeg.FFMPEGProcess}. + * + * @param locator The locator picking up the ffmpeg executable used by the extractor. + */ + public ScreenExtractor(ProcessLocator locator) { + this.locator = locator; + } - try - { - ffmpeg.execute(); - } catch (IOException e) - { - throw new EncoderException(e); - } - try - { - RBufferedReader reader = new RBufferedReader( - new InputStreamReader(ffmpeg.getErrorStream())); - int lineNR = 0; - String line; - while ((line = reader.readLine()) != null) - { - lineNR++; - LOG.debug("Input Line ({}): {}", lineNR, line); - // TODO: Implement additional input stream parsing - } - } catch (IOException e) - { - throw new EncoderException(e); - } finally - { - ffmpeg.destroy(); + /** + * Generates screenshots from source video. + * + * @param multimediaObject Source MultimediaObject @see MultimediaObject + * @param width Output width, pass -1 to use video width and height + * @param height Output height (Ignored when width = -1) + * @param seconds Interval in seconds between screens + * @param outputDir Destination of output images + * @param fileNamePrefix Name all thumbnails will start with + * @param extension Image extension for output (jpg, png, etc) + * @param quality The range is between 1-31 with 31 being the worst quality + * @throws InputFormatException If the source multimedia file cannot be decoded. + * @throws EncoderException If a problems occurs during the encoding process. + */ + public void render( + MultimediaObject multimediaObject, + int width, + int height, + int seconds, + File outputDir, + String fileNamePrefix, + String extension, + int quality) + throws InputFormatException, EncoderException { + String inputSource = multimediaObject.toString(); + try { + if (!outputDir.exists()) { + if (!outputDir.mkdirs()) { + LOG.debug("Failed to create destination folder"); + throw new SecurityException(); } + } + if (!multimediaObject.isURL() && !multimediaObject.getFile().canRead()) { + LOG.debug("Failed to open input file"); + throw new SecurityException(); + } + } catch (SecurityException e) { + LOG.debug("Access denied checking destination folder", e); } - /** - * Generate a single screenshot from source video. - * - * @param multimediaObject Source MultimediaObject @see MultimediaObject - * @param width Output width, pass -1 to use video width and height - * @param height Output height (Ignored when width = -1) - * @param seconds Interval in seconds between screens - * @param outputDir Destination folder of output image - * @param quality The range is between 1-31 with 31 being the worst quality - * @throws InputFormatException If the source multimedia file cannot be - * decoded. - * @throws EncoderException If a problems occurs during the encoding - * process. - */ - public void render(MultimediaObject multimediaObject, int width, int height, int seconds, File outputDir, int quality) - throws EncoderException { - String inputSource = multimediaObject.isURL() ? multimediaObject.getURL().toString() : multimediaObject.getFile().getAbsolutePath(); - outputDir = outputDir.getAbsoluteFile(); - outputDir.getParentFile().mkdirs(); - try - { - if (!multimediaObject.isURL() && !multimediaObject.getFile().canRead()) - { - LOG.debug("Failed to open input file"); - throw new SecurityException(); - } - } catch (SecurityException e) - { - LOG.debug("Access denied checking destination folder", e); - } - - MultimediaInfo multimediaInfo = multimediaObject.getInfo(); - int duration = (int) (multimediaInfo.getDuration() / 1000); - numberOfScreens = seconds <= duration ? 1 : 0; + MultimediaInfo multimediaInfo = multimediaObject.getInfo(); + numberOfScreens = Math.round(((float) multimediaInfo.getDuration()) / 1000.0f / seconds); - FFMPEGExecutor ffmpeg = this.locator.createExecutor(); - ffmpeg.addArgument("-i"); - ffmpeg.addArgument(inputSource); - ffmpeg.addArgument("-f"); - ffmpeg.addArgument("image2"); - ffmpeg.addArgument("-vframes"); - ffmpeg.addArgument("1"); - ffmpeg.addArgument("-ss"); - ffmpeg.addArgument(Utils.buildTimeDuration(seconds*1000)); - if (width != -1) - { - ffmpeg.addArgument("-s"); - ffmpeg.addArgument(String.format("%sx%s", String.valueOf(width), String.valueOf(height))); - } - ffmpeg.addArgument("-qscale"); - ffmpeg.addArgument(String.valueOf(quality)); - ffmpeg.addArgument(outputDir.getAbsolutePath()); + ProcessWrapper ffmpeg = this.locator.createExecutor(); + ffmpeg.addArgument("-i"); + ffmpeg.addArgument(inputSource); + ffmpeg.addArgument("-f"); + ffmpeg.addArgument("image2"); + ffmpeg.addArgument("-vf"); + ffmpeg.addArgument(String.format("fps=fps=1/%s", String.valueOf(seconds))); + if (width != -1) { + ffmpeg.addArgument("-s"); + ffmpeg.addArgument(String.format("%sx%s", String.valueOf(width), String.valueOf(height))); + } + ffmpeg.addArgument("-qscale"); + ffmpeg.addArgument(String.valueOf(quality)); + ffmpeg.addArgument( + String.format( + "%s%s%s-%%04d.%s", + outputDir.getAbsolutePath(), File.separator, fileNamePrefix, extension)); - try - { - ffmpeg.execute(); - } catch (IOException e) - { - throw new EncoderException(e); - } - try - { - RBufferedReader reader = new RBufferedReader( - new InputStreamReader(ffmpeg.getErrorStream())); - int lineNR = 0; - String line; - while ((line = reader.readLine()) != null) - { - lineNR++; - LOG.debug("Input Line ({}): {}", lineNR, line); - // TODO: Implement additional input stream parsing - } - } catch (IOException e) - { - throw new EncoderException(e); - } finally - { - ffmpeg.destroy(); - } + try { + ffmpeg.execute(); + } catch (IOException e) { + throw new EncoderException(e); + } + try { + RBufferedReader reader = new RBufferedReader(new InputStreamReader(ffmpeg.getErrorStream())); + int lineNR = 0; + String line; + while ((line = reader.readLine()) != null) { + lineNR++; + LOG.debug("Input Line ({}): {}", lineNR, line); + // TODO: Implement additional input stream parsing + } + } catch (IOException e) { + throw new EncoderException(e); + } finally { + ffmpeg.destroy(); + } + } + /** + * Generate a single screenshot from source video. + * + * @param multimediaObject Source MultimediaObject @see MultimediaObject + * @param width Output width, pass -1 to use video width and height + * @param height Output height (Ignored when width = -1) + * @param seconds Interval in seconds between screens + * @param outputDir Destination folder of output image + * @param quality The range is between 1-31 with 31 being the worst quality + * @throws InputFormatException If the source multimedia file cannot be decoded. + * @throws EncoderException If a problems occurs during the encoding process. + */ + public void render( + MultimediaObject multimediaObject, + int width, + int height, + int seconds, + File outputDir, + int quality) + throws EncoderException { + String inputSource = multimediaObject.toString(); + outputDir = outputDir.getAbsoluteFile(); + outputDir.getParentFile().mkdirs(); + try { + if (!multimediaObject.isURL() && !multimediaObject.getFile().canRead()) { + LOG.debug("Failed to open input file"); + throw new SecurityException(); + } + } catch (SecurityException e) { + LOG.debug("Access denied checking destination folder", e); } - /** - * Generate exactly one screenshot from source video - * - * @param multimediaObject Source MultimediaObject @see MultimediaObject - * @param width Output width, pass -1 to use video width and height - * @param height Output height (Ignored when width = -1) - * @param millis At which second in the video should the screenshot be made - * @param outputFile Outputfile - * @param quality The range is between 1-31 with 31 being the worst quality - * @throws InputFormatException If the source multimedia file cannot be - * decoded. - * @throws EncoderException If a problems occurs during the encoding - * process. - */ - public void renderOneImage(MultimediaObject multimediaObject, - int width, int height, - long millis, - File outputFile, - int quality) - throws InputFormatException, EncoderException { - renderOneImage(multimediaObject, width, height, millis, outputFile, quality, true); + MultimediaInfo multimediaInfo = multimediaObject.getInfo(); + int duration = (int) (multimediaInfo.getDuration() / 1000); + numberOfScreens = seconds <= duration ? 1 : 0; + + ProcessWrapper ffmpeg = this.locator.createExecutor(); + ffmpeg.addArgument("-i"); + ffmpeg.addArgument(inputSource); + ffmpeg.addArgument("-f"); + ffmpeg.addArgument("image2"); + ffmpeg.addArgument("-vframes"); + ffmpeg.addArgument("1"); + ffmpeg.addArgument("-ss"); + ffmpeg.addArgument(Utils.buildTimeDuration(seconds * 1000)); + if (width != -1) { + ffmpeg.addArgument("-s"); + ffmpeg.addArgument(String.format("%sx%s", String.valueOf(width), String.valueOf(height))); } + ffmpeg.addArgument("-qscale"); + ffmpeg.addArgument(String.valueOf(quality)); + ffmpeg.addArgument(outputDir.getAbsolutePath()); - /** - * Generate exactly one screenshot from source video using given seeking mode. - * - * @param multimediaObject Source MultimediaObject @see MultimediaObject - * @param width Output width, pass -1 to use video width and height - * @param height Output height (Ignored when width = -1) - * @param millis At which second in the video should the screenshot be made - * @param outputFile Outputfile - * @param quality The range is between 1-31 with 31 being the worst quality - * @param keyframesSeeking If True, it forces FFmpeg to parse an input file using keyframes, which is very fast. - * If False, input will be parsed frame by frame. See FFmpeg Wiki: Seeking - * @throws InputFormatException If the source multimedia file cannot be - * decoded. - * @throws EncoderException If a problems occurs during the encoding - * process. - */ - public void renderOneImage(MultimediaObject multimediaObject, - int width, int height, - long millis, - File outputFile, - int quality, - boolean keyframesSeeking) - throws InputFormatException, EncoderException { - String inputSource = multimediaObject.isURL() ? multimediaObject.getURL().toString() : multimediaObject.getFile().getAbsolutePath(); - try - { - if (outputFile.exists()) - { - outputFile.delete(); - } - if (!multimediaObject.isURL() && !multimediaObject.getFile().canRead()) - { - LOG.debug("Failed to open input file"); - throw new SecurityException(); - } - } catch (SecurityException e) - { - LOG.debug("Access denied checking destination folder", e); - } + try { + ffmpeg.execute(); + } catch (IOException e) { + throw new EncoderException(e); + } + try { + RBufferedReader reader = new RBufferedReader(new InputStreamReader(ffmpeg.getErrorStream())); + int lineNR = 0; + String line; + while ((line = reader.readLine()) != null) { + lineNR++; + LOG.debug("Input Line ({}): {}", lineNR, line); + // TODO: Implement additional input stream parsing + } + } catch (IOException e) { + throw new EncoderException(e); + } finally { + ffmpeg.destroy(); + } + } - FFMPEGExecutor ffmpeg = this.locator.createExecutor(); - if (keyframesSeeking) - { - ffmpeg.addArgument("-ss"); - ffmpeg.addArgument(Utils.buildTimeDuration(millis)); - } - ffmpeg.addArgument("-i"); - ffmpeg.addArgument(inputSource); - if (!keyframesSeeking) - { - ffmpeg.addArgument("-ss"); - ffmpeg.addArgument(Utils.buildTimeDuration(millis)); - } - ffmpeg.addArgument("-vframes"); - ffmpeg.addArgument("1"); - if (width != -1) - { - ffmpeg.addArgument("-s"); - ffmpeg.addArgument(String.format("%sx%s", String.valueOf(width), String.valueOf(height))); - } + /** + * Generate exactly one screenshot from source video + * + * @param multimediaObject Source MultimediaObject @see MultimediaObject + * @param width Output width, pass -1 to use video width and height + * @param height Output height (Ignored when width = -1) + * @param millis At which second in the video should the screenshot be made + * @param outputFile Outputfile + * @param quality The range is between 1-31 with 31 being the worst quality + * @throws InputFormatException If the source multimedia file cannot be decoded. + * @throws EncoderException If a problems occurs during the encoding process. + */ + public void renderOneImage( + MultimediaObject multimediaObject, + int width, + int height, + long millis, + File outputFile, + int quality) + throws InputFormatException, EncoderException { + renderOneImage(multimediaObject, width, height, millis, outputFile, quality, true); + } - ffmpeg.addArgument("-qscale"); - ffmpeg.addArgument(String.valueOf(quality)); - ffmpeg.addArgument(outputFile.getAbsolutePath()); + /** + * Generate exactly one screenshot from source video using given seeking mode. + * + * @param multimediaObject Source MultimediaObject @see MultimediaObject + * @param width Output width, pass -1 to use video width and height + * @param height Output height (Ignored when width = -1) + * @param millis At which second in the video should the screenshot be made + * @param outputFile Outputfile + * @param quality The range is between 1-31 with 31 being the worst quality + * @param keyframesSeeking If True, it forces FFmpeg to parse an input file using keyframes, which + * is very fast. If False, input will be parsed frame by frame. See FFmpeg Wiki: Seeking + * @throws InputFormatException If the source multimedia file cannot be decoded. + * @throws EncoderException If a problems occurs during the encoding process. + */ + public void renderOneImage( + MultimediaObject multimediaObject, + int width, + int height, + long millis, + File outputFile, + int quality, + boolean keyframesSeeking) + throws InputFormatException, EncoderException { + String inputSource = multimediaObject.toString(); + try { + if (outputFile.exists()) { + outputFile.delete(); + } + if (!multimediaObject.isURL() && !multimediaObject.getFile().canRead()) { + LOG.debug("Failed to open input file"); + throw new SecurityException(); + } + } catch (SecurityException e) { + LOG.debug("Access denied checking destination folder", e); + } - try - { - ffmpeg.execute(); - } catch (IOException e) - { - throw new EncoderException(e); - } - try - { - RBufferedReader reader = new RBufferedReader( - new InputStreamReader(ffmpeg.getErrorStream())); - int lineNR = 0; - String line; - while ((line = reader.readLine()) != null) - { - lineNR++; - LOG.debug("Input Line ({}): {}", lineNR, line); - // TODO: Implement additional input stream parsing - } - } catch (IOException e) - { - throw new EncoderException(e); - } finally - { - ffmpeg.destroy(); - } + ProcessWrapper ffmpeg = this.locator.createExecutor(); + if (keyframesSeeking) { + ffmpeg.addArgument("-ss"); + ffmpeg.addArgument(Utils.buildTimeDuration(millis)); + } + ffmpeg.addArgument("-i"); + ffmpeg.addArgument(inputSource); + if (!keyframesSeeking) { + ffmpeg.addArgument("-ss"); + ffmpeg.addArgument(Utils.buildTimeDuration(millis)); } + ffmpeg.addArgument("-vframes"); + ffmpeg.addArgument("1"); + if (width != -1) { + ffmpeg.addArgument("-s"); + ffmpeg.addArgument(String.format("%sx%s", String.valueOf(width), String.valueOf(height))); + } + + ffmpeg.addArgument("-qscale"); + ffmpeg.addArgument(String.valueOf(quality)); + ffmpeg.addArgument(outputFile.getAbsolutePath()); + try { + ffmpeg.execute(); + } catch (IOException e) { + throw new EncoderException(e); + } + try { + RBufferedReader reader = new RBufferedReader(new InputStreamReader(ffmpeg.getErrorStream())); + int lineNR = 0; + String line; + while ((line = reader.readLine()) != null) { + lineNR++; + LOG.debug("Input Line ({}): {}", lineNR, line); + // TODO: Implement additional input stream parsing + } + } catch (IOException e) { + throw new EncoderException(e); + } finally { + ffmpeg.destroy(); + } + } } diff --git a/jave-core/src/main/java/ws/schild/jave/Utils.java b/jave-core/src/main/java/ws/schild/jave/Utils.java deleted file mode 100644 index 64eecd9..0000000 --- a/jave-core/src/main/java/ws/schild/jave/Utils.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. - */ -package ws.schild.jave; - -import java.text.DecimalFormat; - -/** - * - * @author a.schild - */ -public class Utils { - - /*** - * https://www.ffmpeg.org/ffmpeg-utils.html#time-duration-syntax - * - * Build a time/duration string based on the milisenconds passed in milis - * [-][HH:]MM:SS[.m...] or [-]S+[.m...] - * - * @param milis number of miliseconds, can be negative too - * @return String to be used for specifying positions in the video/audio file - */ - public static String buildTimeDuration(long milis) - { - DecimalFormat df2= new DecimalFormat("00"); - DecimalFormat df3= new DecimalFormat("000"); - long milisPart= Math.abs(milis) % 1000; - long seconds= Math.abs(milis) / 1000; - long secondsPart= seconds % 60; - long minutes= seconds / 60; - long minutesPart= minutes % 60; - long hours= minutes / 60; - StringBuilder retVal= new StringBuilder(); - if (milis < 0) - { - retVal.append("-"); - } - if (hours != 0) - { - retVal.append(df2.format(hours)).append(":"); - } - if (minutesPart != 0 || hours != 0) - { - retVal.append(df2.format(minutesPart)).append(":"); - } - retVal.append(df2.format(secondsPart)); - if (milisPart != 0) - { - retVal.append(".").append(df3.format(milisPart)); - } - return retVal.toString(); - } - - /** - * Escape all special characters []=;, to be safe to use in command line - * @param argumentIn input argument to escape - * @return escaped string - */ - public static String escapeArgument(String argumentIn) - { - String retVal= argumentIn.replace("[", "\\["); - retVal= retVal.replace("]", "\\]"); - retVal= retVal.replace("=", "\\="); - retVal= retVal.replace(":", "\\:"); - retVal= retVal.replace(",", "\\,"); - return retVal; - } -} diff --git a/jave-core/src/main/java/ws/schild/jave/VideoAttributes.java b/jave-core/src/main/java/ws/schild/jave/VideoAttributes.java deleted file mode 100644 index 8f14e98..0000000 --- a/jave-core/src/main/java/ws/schild/jave/VideoAttributes.java +++ /dev/null @@ -1,282 +0,0 @@ -/* - * JAVE - A Java Audio/Video Encoder (based on FFMPEG) - * - * Copyright (C) 2008-2009 Carlo Pelliccia (www.sauronsoftware.it) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package ws.schild.jave; - -import java.io.Serializable; -import java.util.ArrayList; - -/** - * Attributes controlling the video encoding process. - * - * @author Carlo Pelliccia - */ -public class VideoAttributes implements Serializable { - - private static final long serialVersionUID = 2L; - /** - * This value can be setted in the codec field to perform a direct stream - * copy, without re-encoding of the audio stream. - */ - public static final String DIRECT_STREAM_COPY = "copy"; - /** - * The codec name for the encoding process. If null or not specified the - * encoder will perform a direct stream copy. - */ - private String codec = null; - /** - * The the forced tag/fourcc value for the video stream. - */ - private String tag = null; - /** - * The bitrate value for the encoding process. If null or not specified a - * default value will be picked. - */ - private Integer bitRate = null; - /** - * The frame rate value for the encoding process. If null or not specified a - * default value will be picked. - */ - private Integer frameRate = null; - /** - * The video size for the encoding process. If null or not specified the - * source video size will not be modified. - */ - private VideoSize size = null; - - /** - * The audio quality value for the encoding process. If null or not specified - * the ffmpeg default will be used - */ - private Integer quality = null; - - - private final ArrayList videoFilters = new ArrayList<>(); - /** - * Encode the video with faststart mode, default OFF - * - * - * The mov/mp4/ismv muxer supports fragmentation. Normally, a MOV/MP4 file - * has all the metadata about all packets stored in one location (written at - * the end of the file, it can be moved to the start for better playback by - * adding faststart to the movflags, or using the qt-faststart tool). A - * fragmented file consists of a number of fragments, where packets and - * metadata about these packets are stored together. Writing a fragmented - * file has the advantage that the file is decodable even if the writing is - * interrupted (while a normal MOV/MP4 is undecodable if it is not properly - * finished), and it requires less memory when writing very long files - * (since writing normal MOV/MP4 files stores info about every single packet - * in memory until the file is closed). The downside is that it is less - * compatible with other applications. - */ - private boolean faststart = false; - - public enum X264_PROFILE { - BASELINE("baseline"), MAIN("main"), HIGH("high"), - HIGH10("high10"), HIGH422("high422"), HIGH444("high444"); - private final String modeName; - - private X264_PROFILE(String modeName) { - this.modeName = modeName; - } - - public String getModeName() { - return modeName; - } - }; - - private X264_PROFILE x264Profile = null; - - /** - * Returns the codec name for the encoding process. - * - * @return The codec name for the encoding process. - */ - String getCodec() { - return codec; - } - - /** - * Sets the codec name for the encoding process. If null or not specified - * the encoder will perform a direct stream copy. - * - * Be sure the supplied codec name is in the list returned by - * {@link Encoder#getVideoEncoders()}. - * - * A special value can be picked from - * {@link VideoAttributes#DIRECT_STREAM_COPY}. - * - * @param codec The codec name for the encoding process. - * @return this instance - */ - public VideoAttributes setCodec(String codec) { - this.codec = codec; - return this; - } - - /** - * Returns the the forced tag/fourcc value for the video stream. - * - * @return The the forced tag/fourcc value for the video stream. - */ - String getTag() { - return tag; - } - - /** - * Sets the forced tag/fourcc value for the video stream. - * - * @param tag The the forced tag/fourcc value for the video stream. - * @return this instance - */ - public VideoAttributes setTag(String tag) { - this.tag = tag; - return this; - } - - /** - * Returns the bitrate value for the encoding process. - * - * @return The bitrate value for the encoding process. - */ - Integer getBitRate() { - return bitRate; - } - - /** - * Sets the bitrate value for the encoding process. If null or not specified - * a default value will be picked. - * - * @param bitRate The bitrate value for the encoding process. - * @return this instance - */ - public VideoAttributes setBitRate(Integer bitRate) { - this.bitRate = bitRate; - return this; - } - - /** - * Returns the frame rate value for the encoding process. - * - * @return The frame rate value for the encoding process. - */ - Integer getFrameRate() { - return frameRate; - } - - /** - * Sets the frame rate value for the encoding process. If null or not - * specified a default value will be picked. - * - * @param frameRate The frame rate value for the encoding process. - * @return this instance - */ - public VideoAttributes setFrameRate(Integer frameRate) { - this.frameRate = frameRate; - return this; - } - - /** - * Returns the video size for the encoding process. - * - * @return The video size for the encoding process. - */ - VideoSize getSize() { - return size; - } - - /** - * Sets the video size for the encoding process. If null or not specified - * the source video size will not be modified. - * - * @param size he video size for the encoding process. - * @return this instance - */ - public VideoAttributes setSize(VideoSize size) { - this.size = size; - return this; - } - - /** - * @return the faststart - */ - public boolean isFaststart() { - return faststart; - } - - public void addFilter(VideoFilter videoFilter) { - this.videoFilters.add(videoFilter); - } - - public ArrayList getVideoFilters() { - return this.videoFilters; - } - - /** - * @param faststart the faststart to set - * @return this instance - */ - public VideoAttributes setFaststart(boolean faststart) { - this.faststart = faststart; - return this; - } - - /** - * @return the quality - */ - public Integer getQuality() { - return quality; - } - - /** - * The video quality value for the encoding process. If null or not specified - * the ffmpeg default will be used - * - * @param quality the quality to set - * @return this instance - */ - public VideoAttributes setQuality(Integer quality) { - this.quality = quality; - return this; - } - - @Override - public String toString() { - return getClass().getName() + "(codec=" + codec - + ", bitRate=" + bitRate + ", frameRate=" + frameRate - + ", size=" + size +", faststart=" + faststart - + ", quality="+quality+ ")"; - } - - /** - * @return the x264Profile - */ - public X264_PROFILE getX264Profile() { - return x264Profile; - } - - /** - * @param x264Profile the x264Profile to set - * @return this instance - */ - public VideoAttributes setX264Profile(X264_PROFILE x264Profile) { - this.x264Profile = x264Profile; - return this; - } - -} diff --git a/jave-core/src/main/java/ws/schild/jave/VideoFilter.java b/jave-core/src/main/java/ws/schild/jave/VideoFilter.java deleted file mode 100644 index f561674..0000000 --- a/jave-core/src/main/java/ws/schild/jave/VideoFilter.java +++ /dev/null @@ -1,32 +0,0 @@ -package ws.schild.jave; - -/** - * Created with IntelliJ IDEA. User: jgiotta Date: 8/31/13 Time: 10:56 AM To - * change this template use File | Settings | File Templates. - */ -public class VideoFilter { - - private String expression; - - public VideoFilter() { - this.expression = ""; - } - - public VideoFilter(String expression) { - this.expression = expression; - } - - public String getExpression() { - return this.expression; - } - - public VideoFilter setExpression(String expression) { - this.expression = expression; - return this; - } - - @Override - public String toString() { - return this.expression; - } -} diff --git a/jave-core/src/main/java/ws/schild/jave/VideoInfo.java b/jave-core/src/main/java/ws/schild/jave/VideoInfo.java deleted file mode 100644 index cb14410..0000000 --- a/jave-core/src/main/java/ws/schild/jave/VideoInfo.java +++ /dev/null @@ -1,138 +0,0 @@ -/* - * JAVE - A Java Audio/Video Encoder (based on FFMPEG) - * - * Copyright (C) 2008-2009 Carlo Pelliccia (www.sauronsoftware.it) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package ws.schild.jave; - -/** - * Instances of this class report informations about a video stream that can be - * decoded. - * - * @author Carlo Pelliccia - */ -public class VideoInfo { - - /** - * The video stream decoder name. - */ - private String decoder; - - /** - * The video size. If null this information is not available. - */ - private VideoSize size = null; - - /** - * The video stream (average) bit rate. If less than 0, this information is - * not available. - */ - private int bitRate = -1; - - /** - * The video frame rate. If less than 0 this information is not available. - */ - private float frameRate = -1; - - /** - * Returns the video stream decoder name. - * - * @return The video stream decoder name. - */ - public String getDecoder() { - return decoder; - } - - /** - * Sets the video stream decoder name. - * - * @param codec The video stream decoder name. - * @return this instance - */ - public VideoInfo setDecoder(String codec) { - this.decoder = codec; - return this; - } - - /** - * Returns the video size. If null this information is not available. - * - * @return the size The video size. - */ - public VideoSize getSize() { - return size; - } - - /** - * Sets the video size. - * - * @param size The video size. - * @return this instance - */ - public VideoInfo setSize(VideoSize size) { - this.size = size; - return this; - } - - /** - * Returns the video frame rate. If less than 0 this information is not - * available. - * - * @return The video frame rate. - */ - public float getFrameRate() { - return frameRate; - } - - /** - * Sets the video frame rate. - * - * @param frameRate The video frame rate. - * @return this instance - */ - public VideoInfo setFrameRate(float frameRate) { - this.frameRate = frameRate; - return this; - } - - /** - * Returns the video stream (average) bit rate. If less than 0, this - * information is not available. - * - * @return The video stream (average) bit rate. - */ - public int getBitRate() { - return bitRate; - } - - /** - * Sets the video stream (average) bit rate. - * - * @param bitRate The video stream (average) bit rate. - * @return this instance - */ - public VideoInfo setBitRate(int bitRate) { - this.bitRate = bitRate; - return this; - } - - @Override - public String toString() { - return getClass().getName() + " (decoder=" + decoder + ", size=" + size - + ", bitRate=" + bitRate + ", frameRate=" + frameRate + ")"; - } - -} diff --git a/jave-core/src/main/java/ws/schild/jave/VideoProcessor.java b/jave-core/src/main/java/ws/schild/jave/VideoProcessor.java new file mode 100644 index 0000000..c83a0a3 --- /dev/null +++ b/jave-core/src/main/java/ws/schild/jave/VideoProcessor.java @@ -0,0 +1,109 @@ +package ws.schild.jave; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.PrintStream; +import java.util.List; +import java.util.stream.Collectors; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import ws.schild.jave.encode.EncodingAttributes; +import ws.schild.jave.encode.VideoAttributes; +import ws.schild.jave.progress.EncoderProgressAdapter; +import ws.schild.jave.progress.VideoProgressListener; +import ws.schild.jave.process.ProcessLocator; +import ws.schild.jave.process.ffmpeg.DefaultFFMPEGLocator; +import ws.schild.jave.utils.AutoRemoveableFile; + +/** + * A high-level class meant to perform higher level operations on video files. Will use the Encoder + * heavily, but presents a simpler interface for someone new to JAVE. For real customization, use of + * Encoder is encouraged. + * + * @author mressler + */ +public class VideoProcessor { + + private static final Logger logger = LoggerFactory.getLogger(VideoProcessor.class); + + private static boolean enabled = false; + + private Encoder encoder; + private ProcessLocator locator; + + public VideoProcessor() { + try { + locator = new DefaultFFMPEGLocator(); + encoder = new Encoder(locator); + enabled = true; + } catch (IllegalStateException ise) { + logger.error("Error while starting the VideoService", ise); + } + } + + public boolean isEnabled() { + return enabled; + } + + /** + * Concatenate input video files to a destination file.Destination file and parent directory must + * be writeable. + * + * @see FFMPEG documentation for + * concatenate + * @param videos The list of videos on the local filesystem that are readable by this process that + * will be concatenated together + * @param destination The target file to write to. The target file must be unique to this process + * and writeable. + * @param progress Track progress of processing + * @throws FileNotFoundException If the destination cannot be created + * @throws EncoderException error in encoding + * @throws InputFormatException error in input arguments + * @throws IllegalArgumentException thrown when parameters don't match + */ + public void catClipsTogether(List videos, File destination, VideoProgressListener progress) + throws FileNotFoundException, IllegalArgumentException, InputFormatException, + EncoderException { + assert (enabled); + progress.onBegin(); + + try (AutoRemoveableFile mergeFile = prepareMergeInstructions(videos, destination)) { + MultimediaObject toMerge = fromFile(mergeFile); + + EncodingAttributes attributes = new EncodingAttributes(); + attributes.setInputFormat("concat"); + attributes.setSafe(0); + + VideoAttributes videoAttributes = new VideoAttributes(); + videoAttributes.setCodec("copy"); + attributes.setVideoAttributes(videoAttributes); + + encoder.encode(toMerge, destination, attributes, new EncoderProgressAdapter(progress)); + } + + progress.onComplete(); + } + + private MultimediaObject fromFile(File source) { + return new MultimediaObject(source, locator); + } + + private AutoRemoveableFile prepareMergeInstructions(List videos, File destination) + throws FileNotFoundException { + // Create the merge instruction in the same directory as the destination + AutoRemoveableFile mergeFile = + new AutoRemoveableFile(destination.getParentFile(), destination.getName() + ".merge.txt"); + + try (PrintStream fout = new PrintStream(mergeFile)) { + fout.println( + videos + .stream() + .map(File::getAbsolutePath) + .collect(Collectors.joining("'\nfile '", "file '", "'"))); + } + + return mergeFile; + } +} diff --git a/jave-core/src/main/java/ws/schild/jave/VideoSize.java b/jave-core/src/main/java/ws/schild/jave/VideoSize.java deleted file mode 100644 index e3a149e..0000000 --- a/jave-core/src/main/java/ws/schild/jave/VideoSize.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * JAVE - A Java Audio/Video Encoder (based on FFMPEG) - * - * Copyright (C) 2008-2009 Carlo Pelliccia (www.sauronsoftware.it) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package ws.schild.jave; - -import java.io.Serializable; - -/** - * Instances of this class report informations about videos size. - * - * @author Carlo Pelliccia - */ -public class VideoSize implements Serializable { - - private static final long serialVersionUID = 1L; - - /** - * The video width. - */ - private final int width; - - /** - * The video height. - */ - private final int height; - - /** - * It builds the bean. - * - * @param width The video width. - * @param height The video height. - */ - public VideoSize(int width, int height) { - this.width = width; - this.height = height; - } - - /** - * Returns the video width. - * - * @return The video width. - */ - public int getWidth() { - return width; - } - - /** - * Returns the video height. - * - * @return The video height. - */ - public int getHeight() { - return height; - } - - @Override - public String toString() { - return getClass().getName() + " (width=" + width + ", height=" + height - + ")"; - } - -} diff --git a/jave-core/src/main/java/ws/schild/jave/encode/ArgType.java b/jave-core/src/main/java/ws/schild/jave/encode/ArgType.java new file mode 100644 index 0000000..de3e95b --- /dev/null +++ b/jave-core/src/main/java/ws/schild/jave/encode/ArgType.java @@ -0,0 +1,12 @@ +package ws.schild.jave.encode; + +/** + * The type of arguments you can provide to ffmpeg. The naming comes from `ffmpeg -help` + * + * @author mressler + */ +public enum ArgType { + GLOBAL, + INFILE, + OUTFILE; +} diff --git a/jave-core/src/main/java/ws/schild/jave/encode/AudioAttributes.java b/jave-core/src/main/java/ws/schild/jave/encode/AudioAttributes.java new file mode 100644 index 0000000..01786ee --- /dev/null +++ b/jave-core/src/main/java/ws/schild/jave/encode/AudioAttributes.java @@ -0,0 +1,229 @@ +/* + * JAVE - A Java Audio/Video Encoder (based on FFMPEG) + * + * Copyright (C) 2008-2009 Carlo Pelliccia (www.sauronsoftware.it) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package ws.schild.jave.encode; + +import java.io.Serializable; +import java.util.Optional; + +import ws.schild.jave.Encoder; + +/** + * Attributes controlling the audio encoding process. + * + * @author Carlo Pelliccia + */ +public class AudioAttributes implements Serializable { + + private static final long serialVersionUID = 2L; + + /** + * This value can be setted in the codec field to perform a direct stream copy, without + * re-encoding of the audio stream. + */ + public static final String DIRECT_STREAM_COPY = "copy"; + + /** + * The codec name for the encoding process. If null or not specified the encoder will perform a + * direct stream copy. + */ + private String codec = null; + + /** + * The bitrate value for the encoding process. If null or not specified a default value will be + * picked. + */ + private Integer bitRate = null; + + /** + * The samplingRate value for the encoding process. If null or not specified a default value will + * be picked. + */ + private Integer samplingRate = null; + + /** + * The channels value (1=mono, 2=stereo) for the encoding process. If null or not specified a + * default value will be picked. + */ + private Integer channels = null; + + /** + * The volume value for the encoding process. If null or not specified a default value will be + * picked. If 256 no volume change will be performed. + */ + private Integer volume = null; + + /** + * The audio quality value for the encoding process. If null or not specified the ffmpeg default + * will be used + */ + private Integer quality = null; + + /** + * Returns the codec name for the encoding process. + * + * @return The codec name for the encoding process. + */ + public Optional getCodec() { + return Optional.ofNullable(codec); + } + + /** + * Sets the codec name for the encoding process.If null or not specified the encoder will perform + * a direct stream copy. Be sure the supplied codec name is in the list returned by {@link + * Encoder#getAudioEncoders()}. + * + *

A special value can be picked from {@link AudioAttributes#DIRECT_STREAM_COPY}. + * + * @param codec The codec name for the encoding process. + * @return this instance + */ + public AudioAttributes setCodec(String codec) { + this.codec = codec; + return this; + } + + /** + * Returns the bitrate value for the encoding process. + * + * @return The bitrate value for the encoding process. + */ + public Optional getBitRate() { + return Optional.ofNullable(bitRate); + } + + /** + * Sets the bitrate value for the encoding process. If null or not specified a default value will + * be picked. + * + * @param bitRate The bitrate value for the encoding process. + * @return this instance + */ + public AudioAttributes setBitRate(Integer bitRate) { + this.bitRate = bitRate; + return this; + } + + /** + * Returns the samplingRate value for the encoding process. + * + * @return the samplingRate The samplingRate value for the encoding process. + */ + public Optional getSamplingRate() { + return Optional.ofNullable(samplingRate); + } + + /** + * Sets the samplingRate value for the encoding process. If null or not specified a default value + * will be picked. + * + * @param samplingRate The samplingRate value for the encoding process. + * @return this instance + */ + public AudioAttributes setSamplingRate(Integer samplingRate) { + this.samplingRate = samplingRate; + return this; + } + + /** + * Returns the channels value (1=mono, 2=stereo, 4=quad) for the encoding process. + * + * @return The channels value (1=mono, 2=stereo, 4=quad) for the encoding process. + */ + public Optional getChannels() { + return Optional.ofNullable(channels); + } + + /** + * Sets the channels value (1=mono, 2=stereo, 4=quad) for the encoding process. If null or not + * specified a default value will be picked. + * + * @param channels The channels value (1=mono, 2=stereo, 4=quad) for the encoding process. + * @return this instance + */ + public AudioAttributes setChannels(Integer channels) { + this.channels = channels; + return this; + } + + /** + * Returns the volume value for the encoding process. + * + * @return The volume value for the encoding process. + */ + public Optional getVolume() { + return Optional.ofNullable(volume); + } + + /** + * Sets the volume value for the encoding process. If null or not specified a default value will + * be picked. If 256 no volume change will be performed. + * + *

volume is the "amplitude ratio" or "sound pressure level" ratio 2560 is volume=20dB The + * formula is dBnumber=20*lg(amplitude ratio) 128 means reducing by 50% 512 means doubling the + * volume + * + * @param volume The volume value for the encoding process. + * @return this instance + */ + public AudioAttributes setVolume(Integer volume) { + this.volume = volume; + return this; + } + + /** @return the audio conversion quality */ + public Optional getQuality() { + return Optional.ofNullable(quality); + } + + /** + * The audio quality value for the encoding process. If null or not specified the ffmpeg default + * will be used + * + *

The value depends on the choosen codec + * + *

For mp3 you can see here: https://trac.ffmpeg.org/wiki/Encode/MP3 + * + *

Or more general https://ffmpeg.org/ffmpeg-codecs.html + * + * @param quality the audio conversion quality to set + * @return this instance + */ + public AudioAttributes setQuality(Integer quality) { + this.quality = quality; + return this; + } + + @Override + public String toString() { + return getClass().getName() + + "(codec=" + + codec + + ", bitRate=" + + bitRate + + ", samplingRate=" + + samplingRate + + ", channels=" + + channels + + ", volume=" + + volume + + ", quality=" + + quality + + ")"; + } +} diff --git a/jave-core/src/main/java/ws/schild/jave/encode/EncodingArgument.java b/jave-core/src/main/java/ws/schild/jave/encode/EncodingArgument.java new file mode 100644 index 0000000..bc1fadf --- /dev/null +++ b/jave-core/src/main/java/ws/schild/jave/encode/EncodingArgument.java @@ -0,0 +1,26 @@ +package ws.schild.jave.encode; + +import java.util.stream.Stream; + +/** + * An EncodingArgument is a placeholder for a future argument to FFMPEG. It uses the + * EncodingAttributes object to determine context and provides a Stream<String> of arguments + * back to the caller to be used as arguments. + * + * @author mressler + */ +public interface EncodingArgument { + + /** + * Gets the Stream of arguments given the EncodingAttributes as context. Implementers must take + * care to return a new Stream on each successive call as doing otherwise will result in the + * stream already being operated on exceptions. + * + * @param context The EncodingAttributes specified by the user. Use this in your closure to + * generate the arguments you'd like to pass to ffmpeg. + * @return A stream of arguments to pass to ffmpeg. + */ + public Stream getArguments(EncodingAttributes context); + + public ArgType getArgType(); +} diff --git a/jave-core/src/main/java/ws/schild/jave/encode/EncodingAttributes.java b/jave-core/src/main/java/ws/schild/jave/encode/EncodingAttributes.java new file mode 100644 index 0000000..db8463b --- /dev/null +++ b/jave-core/src/main/java/ws/schild/jave/encode/EncodingAttributes.java @@ -0,0 +1,392 @@ +/* + * JAVE - A Java Audio/Video Encoder (based on FFMPEG) + * + * Copyright (C) 2008-2009 Carlo Pelliccia (www.sauronsoftware.it) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package ws.schild.jave.encode; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +import ws.schild.jave.Encoder; + +/** + * Attributes controlling the encoding process. + * + * @author Carlo Pelliccia + */ +public class EncodingAttributes implements Serializable { + + private static final long serialVersionUID = 2473587816471032706L; + + /** The format name for the incoming multimedia file. */ + private String inputFormat = null; + + /** + * The format name for the encoded target multimedia file. Be sure this format is supported (see + * {@link Encoder#getSupportedEncodingFormats()}. + */ + private String outputFormat = null; + + /** The start offset time (seconds). If null or not specified no start offset will be applied. */ + private Float offset = null; + + /** + * The duration (seconds) of the re-encoded stream. If null or not specified the source stream, + * starting from the offset, will be completely re-encoded in the target stream. + */ + private Float duration = null; + + /** + * The attributes for the encoding of the audio stream in the target multimedia file. If null of + * not specified no audio stream will be encoded. It cannot be null if also the video field is + * null. + */ + private AudioAttributes audioAttributes = null; + + /** + * The attributes for the encoding of the video stream in the target multimedia file. If null of + * not specified no video stream will be encoded. It cannot be null if also the audio field is + * null. + */ + private VideoAttributes videoAttributes = null; + + /** Should we try to copy over the meta data? */ + private boolean mapMetaData = false; + + /** + * Maximum number of cores/cpus to use for conversion.
+ * Not set means we use ffmpeg's default. + */ + private Integer filterThreads; + /** Number of threads to use for decoding (if supported by codec) */ + private Integer decodingThreads = null; + /** Number of threads to use for encoding (if supported by codec) */ + private Integer encodingThreads = null; + + /** Should the input be treated as a loop */ + private boolean loop = false; + + /** + * Are the file paths considered "safe" + * + * @see FFMPEG Documentation + */ + private Integer safe = null; + + /** + * Additional context for custom encoder options. Add context here and retrieve/use it by adding + * an EncodingArgument to your Encoder class via {@link + * ws.schild.jave.Encoder#addOptionAtIndex(EncodingArgument, Integer)} + */ + private HashMap extraContext = new HashMap<>(); + + /** + * Returns any additional user supplied context. Meant to be used in conjunction with {@link + * ws.schild.jave.Encoder#addOptionAtIndex(EncodingArgument, Integer)} + * + * @return extra context + */ + public Map getExtraContext() { + return extraContext; + } + + /** + * Adds all key/value pairs from context to the extraContext private variable.Meant to be used in + * conjunction with {@link ws.schild.jave.Encoder#addOptionAtIndex(EncodingArgument, Integer)}.Add + * context here and retrieve the context via an EncodingArgument. + * + * @param context extra context + * @return the EncodingAttributes + */ + public EncodingAttributes setExtraContext(Map context) { + extraContext.putAll(context); + return this; + } + + /** + * Returns the format name for the incoming multimedia file. + * + * @return The format name for the incoming multimedia file. + */ + public Optional getInputFormat() { + return Optional.ofNullable(inputFormat); + } + + /** + * Sets the format name for the source multimedia file. + * + * @param inputFormat the format name for the incoming multimedia file. + * @return this instance + */ + public EncodingAttributes setInputFormat(String inputFormat) { + this.inputFormat = inputFormat; + return this; + } + + /** + * Returns the format name for the encoded target multimedia file. + * + * @return The format name for the encoded target multimedia file. + */ + public Optional getOutputFormat() { + return Optional.ofNullable(outputFormat); + } + + /** + * Sets the format name for the encoded target multimedia file. Be sure this format is supported + * (see {@link Encoder#getSupportedEncodingFormats()}. + * + * @param format The format name for the encoded target multimedia file. + * @return this instance + */ + public EncodingAttributes setOutputFormat(String format) { + this.outputFormat = format; + return this; + } + + /** + * Returns the start offset time (seconds). + * + * @return The start offset time (seconds). + */ + public Optional getOffset() { + return Optional.ofNullable(offset); + } + + /** + * Sets the start offset time (seconds). If null or not specified no start offset will be applied. + * + * @param offset The start offset time (seconds). + * @return this instance + */ + public EncodingAttributes setOffset(Float offset) { + this.offset = offset; + return this; + } + + /** + * Returns the duration (seconds) of the re-encoded stream. + * + * @return The duration (seconds) of the re-encoded stream. + */ + public Optional getDuration() { + return Optional.ofNullable(duration); + } + + /** + * Sets the duration (seconds) of the re-encoded stream. If null or not specified the source + * stream, starting from the offset, will be completely re-encoded in the target stream. + * + * @param duration The duration (seconds) of the re-encoded stream. + * @return this instance + */ + public EncodingAttributes setDuration(Float duration) { + this.duration = duration; + return this; + } + + /** + * Returns if the input is to be considered for looping. + * + * @return if the input will be looped. + */ + public boolean getLoop() { + return loop; + } + + /** + * Sets if the inputs will be looped or not. + * + * @param loop if the input should be looped. + * @return this instance + */ + public EncodingAttributes setLoop(boolean loop) { + this.loop = loop; + return this; + } + + /** + * Returns whether or not the encoder will consider file paths "safe". + * + * @return Whether or not the encoder will consider file paths "safe". + * @see FFMPEG Documentation + */ + public Optional getSafe() { + return Optional.ofNullable(safe); + } + + /** + * Are the file paths considered "safe": A file path is considered safe if it does not contain a + * protocol specification and is relative and all components only contain characters from the + * portable character set (letters, digits, period, underscore and hyphen) and have no period at + * the beginning of a component. + * + * @param safe 0 for not safe; 1 for safe; is equivalent to 1 if the format was automatically + * probed and 0 otherwise. 1 is the default + * @return The EncodingAttributes + * @see FFMPEG Documentation + */ + public EncodingAttributes setSafe(Integer safe) { + this.safe = safe; + return this; + } + + /** + * Returns the attributes for the encoding of the audio stream in the target multimedia file. + * + * @return The attributes for the encoding of the audio stream in the target multimedia file. + */ + public Optional getAudioAttributes() { + return Optional.ofNullable(audioAttributes); + } + + /** + * Sets the attributes for the encoding of the audio stream in the target multimedia file. If null + * of not specified no audio stream will be encoded. It cannot be null if also the video field is + * null. + * + * @param audioAttributes The attributes for the encoding of the audio stream in the target + * multimedia file. + * @return this instance + */ + public EncodingAttributes setAudioAttributes(AudioAttributes audioAttributes) { + this.audioAttributes = audioAttributes; + return this; + } + + /** + * Returns the attributes for the encoding of the video stream in the target multimedia file. + * + * @return The attributes for the encoding of the video stream in the target multimedia file. + */ + public Optional getVideoAttributes() { + return Optional.ofNullable(videoAttributes); + } + + /** + * Sets the attributes for the encoding of the video stream in the target multimedia file. If null + * of not specified no video stream will be encoded. It cannot be null if also the audio field is + * null. + * + * @param videoAttributes The attributes for the encoding of the video stream in the target + * multimedia file. + * @return this instance + */ + public EncodingAttributes setVideoAttributes(VideoAttributes videoAttributes) { + this.videoAttributes = videoAttributes; + return this; + } + + @Override + public String toString() { + return getClass().getName() + + "(format=" + + outputFormat + + ", offset=" + + offset + + ", duration=" + + duration + + ",loop=" + + loop + + ", audioAttributes=" + + audioAttributes + + ", videoAttributes=" + + videoAttributes + + ")"; + } + + /** @return the mapMetaData */ + public boolean isMapMetaData() { + return mapMetaData; + } + + /** + * Copy over meta data from original file to new output if possible + * + * @param mapMetaData the mapMetaData to set + * @return this instance + */ + public EncodingAttributes setMapMetaData(boolean mapMetaData) { + this.mapMetaData = mapMetaData; + return this; + } + + /** @return Maximum number of cores/cpus to use for filtering -1 means use default of ffmpeg */ + public Optional getFilterThreads() { + return Optional.ofNullable(filterThreads); + } + + /** + * ffmpeg uses multiple cores for filtering + * + * @param filterThreads Maximum number of cores/cpus to use -1 means use default of ffmpeg + * @return this instance + */ + public EncodingAttributes setFilterThreads(int filterThreads) { + this.filterThreads = filterThreads; + return this; + } + + /** + * Number of threads to use for decoding (if supported by codec) -1 means use default of ffmpeg + * + * @return the decodingThreads + */ + public Optional getDecodingThreads() { + return Optional.ofNullable(decodingThreads); + } + + /** + * Number of threads to use for decoding (if supported by codec) -1 means use default of ffmpeg + * + * @param decodingThreads the decodingThreads to set + * @return this instance + */ + public EncodingAttributes setDecodingThreads(int decodingThreads) { + this.decodingThreads = decodingThreads; + return this; + } + + /** + * Number of threads to use for encoding (if supported by codec) No value (Optional.empty()) means + * use default of ffmpeg + * + * @return the encodingThreads + */ + public Optional getEncodingThreads() { + return Optional.ofNullable(encodingThreads); + } + + /** + * Number of threads to use for encoding (if supported by codec) null means use default of ffmpeg + * + * @param encodingThreads the encodingThreads to set + * @return this instance + */ + public EncodingAttributes setEncodingThreads(Integer encodingThreads) { + this.encodingThreads = encodingThreads; + return this; + } + + public void validate() { + if (audioAttributes == null && videoAttributes == null) { + throw new IllegalArgumentException("Both audio and video attributes are null"); + } + } +} diff --git a/jave-core/src/main/java/ws/schild/jave/encode/PredicateArgument.java b/jave-core/src/main/java/ws/schild/jave/encode/PredicateArgument.java new file mode 100644 index 0000000..d729731 --- /dev/null +++ b/jave-core/src/main/java/ws/schild/jave/encode/PredicateArgument.java @@ -0,0 +1,49 @@ +package ws.schild.jave.encode; + +import java.util.function.Predicate; +import java.util.function.Supplier; +import java.util.stream.Stream; + +/** + * A PredicateArgument is an EncodingArgument that adds its arguments based on the provided + * predicate + * + * @author mressler + */ +public class PredicateArgument implements EncodingArgument { + + private ArgType argumentType; + private Supplier> arguments; + private Predicate predicate; + + public PredicateArgument( + ArgType argType, String argument, Predicate predicate) { + this.argumentType = argType; + this.arguments = () -> Stream.of(argument); + this.predicate = predicate; + } + + public PredicateArgument( + ArgType argType, + String argument1, + String argument2, + Predicate predicate) { + this.argumentType = argType; + this.arguments = () -> Stream.of(argument1, argument2); + this.predicate = predicate; + } + + @Override + public Stream getArguments(EncodingAttributes context) { + if (predicate.test(context)) { + return arguments.get(); + } else { + return Stream.empty(); + } + } + + @Override + public ArgType getArgType() { + return argumentType; + } +} diff --git a/jave-core/src/main/java/ws/schild/jave/encode/SimpleArgument.java b/jave-core/src/main/java/ws/schild/jave/encode/SimpleArgument.java new file mode 100644 index 0000000..3c31e6f --- /dev/null +++ b/jave-core/src/main/java/ws/schild/jave/encode/SimpleArgument.java @@ -0,0 +1,32 @@ +package ws.schild.jave.encode; + +import java.util.function.Function; +import java.util.stream.Stream; + +/** + * A SimpleArgument is an EncodingArgument that provides all of its components, The argument type + * and a Function from EncodingAttributes to a Stream<String> (arguments to ffmpeg) + * + * @author mressler + */ +public class SimpleArgument implements EncodingArgument { + + private ArgType argumentType; + private Function> getArguments; + + public SimpleArgument( + ArgType argumentType, Function> getArguments) { + this.argumentType = argumentType; + this.getArguments = getArguments; + } + + @Override + public Stream getArguments(EncodingAttributes context) { + return getArguments.apply(context); + } + + @Override + public ArgType getArgType() { + return argumentType; + } +} diff --git a/jave-core/src/main/java/ws/schild/jave/encode/ValueArgument.java b/jave-core/src/main/java/ws/schild/jave/encode/ValueArgument.java new file mode 100644 index 0000000..c64ccc1 --- /dev/null +++ b/jave-core/src/main/java/ws/schild/jave/encode/ValueArgument.java @@ -0,0 +1,49 @@ +package ws.schild.jave.encode; + +import java.util.Optional; +import java.util.function.Function; +import java.util.stream.Stream; + +/** + * A ValueArgument is an EncodingArgument that is optionally present based on the presence of the + * provided valueGetter. + * + * @author mressler + */ +public class ValueArgument implements EncodingArgument { + + private ArgType argumentType; + private String argumentName; + private Function> valueGetter; + + public ValueArgument( + ArgType argType, + String argumentName, + Function> valueGetter) { + this.argumentType = argType; + this.argumentName = argumentName; + this.valueGetter = valueGetter; + } + + protected Boolean isPresent(EncodingAttributes context) { + return getValue(context).isPresent(); + } + + @Override + public Stream getArguments(EncodingAttributes context) { + return getValue(context).map(value -> Stream.of(getName(), value)).orElseGet(Stream::empty); + } + + private String getName() { + return argumentName; + } + + private Optional getValue(EncodingAttributes context) { + return valueGetter.apply(context); + } + + @Override + public ArgType getArgType() { + return argumentType; + } +} diff --git a/jave-core/src/main/java/ws/schild/jave/encode/VideoAttributes.java b/jave-core/src/main/java/ws/schild/jave/encode/VideoAttributes.java new file mode 100644 index 0000000..b9fb064 --- /dev/null +++ b/jave-core/src/main/java/ws/schild/jave/encode/VideoAttributes.java @@ -0,0 +1,301 @@ +/* + * JAVE - A Java Audio/Video Encoder (based on FFMPEG) + * + * Copyright (C) 2008-2009 Carlo Pelliccia (www.sauronsoftware.it) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package ws.schild.jave.encode; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Optional; + +import ws.schild.jave.Encoder; +import ws.schild.jave.encode.enums.VsyncMethod; +import ws.schild.jave.encode.enums.X264_PROFILE; +import ws.schild.jave.filters.FilterGraph; +import ws.schild.jave.filters.VideoFilter; +import ws.schild.jave.info.VideoSize; + +/** + * Attributes controlling the video encoding process. + * + * @author Carlo Pelliccia + */ +public class VideoAttributes implements Serializable { + + private static final long serialVersionUID = 2L; + /** + * This value can be set in the codec field to perform a direct stream copy, without + * re-encoding of the audio stream. + */ + public static final String DIRECT_STREAM_COPY = "copy"; + /** + * The codec name for the encoding process. If null or not specified the encoder will perform a + * direct stream copy. + */ + private String codec = null; + /** The the forced tag/fourcc value for the video stream. */ + private String tag = null; + /** + * The bitrate value for the encoding process. If null or not specified a default value will be + * picked. + */ + private Integer bitRate = null; + /** + * The frame rate value for the encoding process. If null or not specified a default value will be + * picked. + */ + private Integer frameRate = null; + /** + * The video size for the encoding process. If null or not specified the source video size will + * not be modified. + */ + private VideoSize size = null; + + /** + * The audio quality value for the encoding process. If null or not specified the ffmpeg default + * will be used + */ + private Integer quality = null; + private String pixelFormat = null; + private VsyncMethod vsync = null; + + private FilterGraph complexFiltergraph = null; + private final ArrayList videoFilters = new ArrayList<>(); + /** + * Encode the video with faststart mode, default OFF + * + *

The mov/mp4/ismv muxer supports fragmentation. Normally, a MOV/MP4 file has all the metadata + * about all packets stored in one location (written at the end of the file, it can be moved to + * the start for better playback by adding faststart to the movflags, or using the qt-faststart + * tool). A fragmented file consists of a number of fragments, where packets and metadata about + * these packets are stored together. Writing a fragmented file has the advantage that the file is + * decodable even if the writing is interrupted (while a normal MOV/MP4 is undecodable if it is + * not properly finished), and it requires less memory when writing very long files (since writing + * normal MOV/MP4 files stores info about every single packet in memory until the file is closed). + * The downside is that it is less compatible with other applications. + */ + private boolean faststart = false; + + private X264_PROFILE x264Profile = null; + + /** + * Returns the codec name for the encoding process. + * + * @return The codec name for the encoding process. + */ + public Optional getCodec() { + return Optional.ofNullable(codec); + } + + /** + * Sets the codec name for the encoding process. If null or not specified the encoder will perform + * a direct stream copy. + * + *

Be sure the supplied codec name is in the list returned by {@link + * Encoder#getVideoEncoders()}. + * + *

A special value can be picked from {@link VideoAttributes#DIRECT_STREAM_COPY}. + * + * @param codec The codec name for the encoding process. + * @return this instance + */ + public VideoAttributes setCodec(String codec) { + this.codec = codec; + return this; + } + + /** + * Returns the the forced tag/fourcc value for the video stream. + * + * @return The the forced tag/fourcc value for the video stream. + */ + public Optional getTag() { + return Optional.ofNullable(tag); + } + + /** + * Sets the forced tag/fourcc value for the video stream. + * + * @param tag The the forced tag/fourcc value for the video stream. + * @return this instance + */ + public VideoAttributes setTag(String tag) { + this.tag = tag; + return this; + } + + /** + * Returns the bitrate value for the encoding process. + * + * @return The bitrate value for the encoding process. + */ + public Optional getBitRate() { + return Optional.ofNullable(bitRate); + } + + /** + * Sets the bitrate value for the encoding process. If null or not specified a default value will + * be picked. + * + * @param bitRate The bitrate value for the encoding process. + * @return this instance + */ + public VideoAttributes setBitRate(Integer bitRate) { + this.bitRate = bitRate; + return this; + } + + /** + * Returns the frame rate value for the encoding process. + * + * @return The frame rate value for the encoding process. + */ + public Optional getFrameRate() { + return Optional.ofNullable(frameRate); + } + + /** + * Sets the frame rate value for the encoding process. If null or not specified a default value + * will be picked. + * + * @param frameRate The frame rate value for the encoding process. + * @return this instance + */ + public VideoAttributes setFrameRate(Integer frameRate) { + this.frameRate = frameRate; + return this; + } + + /** + * Returns the video size for the encoding process. + * + * @return The video size for the encoding process. + */ + public Optional getSize() { + return Optional.ofNullable(size); + } + + /** + * Sets the video size for the encoding process. If null or not specified the source video size + * will not be modified. + * + * @param size he video size for the encoding process. + * @return this instance + */ + public VideoAttributes setSize(VideoSize size) { + this.size = size; + return this; + } + + /** @return the faststart */ + public boolean isFaststart() { + return faststart; + } + + public Optional getComplexFiltergraph() { + return Optional.ofNullable(complexFiltergraph); + } + + public VideoAttributes setComplexFiltergraph(FilterGraph complexFiltergraph) { + this.complexFiltergraph = complexFiltergraph; + return this; + } + + public void addFilter(VideoFilter videoFilter) { + this.videoFilters.add(videoFilter); + } + + public ArrayList getVideoFilters() { + return this.videoFilters; + } + + /** + * @param faststart the faststart to set + * @return this instance + */ + public VideoAttributes setFaststart(boolean faststart) { + this.faststart = faststart; + return this; + } + + /** @return the quality */ + public Optional getQuality() { + return Optional.ofNullable(quality); + } + + /** + * The video quality value for the encoding process. If null or not specified the ffmpeg default + * will be used + * + * @param quality the quality to set + * @return this instance + */ + public VideoAttributes setQuality(Integer quality) { + this.quality = quality; + return this; + } + + public Optional getPixelFormat() { + return Optional.ofNullable(pixelFormat); + } + + public VideoAttributes setPixelFormat(String pixelFormat) { + this.pixelFormat = pixelFormat; + return this; + } + + public Optional getVsync() { + return Optional.ofNullable(vsync); + } + + public VideoAttributes setVsync(VsyncMethod vsync) { + this.vsync = vsync; + return this; + } + + @Override + public String toString() { + return getClass().getName() + + "(codec=" + + codec + + ", bitRate=" + + bitRate + + ", frameRate=" + + frameRate + + ", size=" + + size + + ", faststart=" + + faststart + + ", quality=" + + quality + + ")"; + } + + /** @return the x264Profile */ + public Optional getX264Profile() { + return Optional.ofNullable(x264Profile); + } + + /** + * @param x264Profile the x264Profile to set + * @return this instance + */ + public VideoAttributes setX264Profile(X264_PROFILE x264Profile) { + this.x264Profile = x264Profile; + return this; + } +} diff --git a/jave-core/src/main/java/ws/schild/jave/encode/enums/VsyncMethod.java b/jave-core/src/main/java/ws/schild/jave/encode/enums/VsyncMethod.java new file mode 100644 index 0000000..e1be02d --- /dev/null +++ b/jave-core/src/main/java/ws/schild/jave/encode/enums/VsyncMethod.java @@ -0,0 +1,38 @@ +package ws.schild.jave.encode.enums; + +/** + * Add VSYNC methods described in the FFMPEG Documentation. + * + */ +public enum VsyncMethod { + /** + * Each frame is passed with its timestamp from the demuxer to the muxer. + */ + PASSTHROUGH("passthrough"), + /** + * Frames will be duplicated and dropped to achieve exactly the requested constant frame rate. + */ + CFR("cfr"), + /** + * Frames are passed through with their timestamp or dropped so as to prevent 2 frames from having the same timestamp. + */ + VFR("vfr"), + /** + * As passthrough but destroys all timestamps, making the muxer generate fresh timestamps based on frame-rate. + */ + DROP("drop"), + /** + * Chooses between CFR and VFR depending on muxer capabilities. This is the default method. + */ + AUTO("auto"); + + private String methodName; + + private VsyncMethod(String parameter) { + methodName = parameter; + } + + public String getMethodName() { + return methodName; + } +} diff --git a/jave-core/src/main/java/ws/schild/jave/encode/enums/X264_PROFILE.java b/jave-core/src/main/java/ws/schild/jave/encode/enums/X264_PROFILE.java new file mode 100644 index 0000000..cbd3cdc --- /dev/null +++ b/jave-core/src/main/java/ws/schild/jave/encode/enums/X264_PROFILE.java @@ -0,0 +1,20 @@ +package ws.schild.jave.encode.enums; + +public enum X264_PROFILE { + BASELINE("baseline"), + MAIN("main"), + HIGH("high"), + HIGH10("high10"), + HIGH422("high422"), + HIGH444("high444"); + + private final String modeName; + + private X264_PROFILE(String modeName) { + this.modeName = modeName; + } + + public String getModeName() { + return modeName; + } +}; \ No newline at end of file diff --git a/jave-core/src/main/java/ws/schild/jave/filtergraphs/FilterAndWatermark.java b/jave-core/src/main/java/ws/schild/jave/filtergraphs/FilterAndWatermark.java new file mode 100644 index 0000000..6f402ba --- /dev/null +++ b/jave-core/src/main/java/ws/schild/jave/filtergraphs/FilterAndWatermark.java @@ -0,0 +1,57 @@ +package ws.schild.jave.filtergraphs; + +import java.io.File; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import ws.schild.jave.filters.ConcatFilter; +import ws.schild.jave.filters.FilterChain; +import ws.schild.jave.filters.FilterGraph; +import ws.schild.jave.filters.MovieFilter; +import ws.schild.jave.filters.OverlayFilter; +import ws.schild.jave.filters.helpers.OverlayLocation; + +/** + * An abstract filtergraph that will run a filter on multiple input videos then concatenate and + * watermark the result. + * + *

Implementors are expected to provide the filter chains to this abstract class via the init() + * method. + */ +public abstract class FilterAndWatermark extends FilterGraph { + + public FilterAndWatermark(File watermark, List inputFilterChains) { + super(); + + // Apply the provided filterchain for each input video + IntStream.range(0, inputFilterChains.size()) + .mapToObj(i -> prepFilterChain(inputFilterChains.get(i), i)) + .forEach(this::addChain); + + // Concatenate all input videos + addChain( + new FilterChain( + new ConcatFilter( + IntStream.range(0, inputFilterChains.size()) + .mapToObj(this::labelForOutput) + .collect(Collectors.toList())) + .addOutputLabel("concatenated"))); + + // Finally overlay the watermark + addChain( + new FilterChain( + new MovieFilter(watermark), // Movie output is the second input to the overlay filter + new OverlayFilter("concatenated", OverlayLocation.BOTTOM_RIGHT, -10, -10))); + } + + private FilterChain prepFilterChain(FilterChain chain, Integer i) { + return chain + .setInputLabel(i.toString()) + .setOutputLabel(labelForOutput(i)); + } + + private String labelForOutput(Integer i) { + return "filtered" + i; + } +} diff --git a/jave-core/src/main/java/ws/schild/jave/filtergraphs/OverlayWatermark.java b/jave-core/src/main/java/ws/schild/jave/filtergraphs/OverlayWatermark.java new file mode 100644 index 0000000..fd0b9ff --- /dev/null +++ b/jave-core/src/main/java/ws/schild/jave/filtergraphs/OverlayWatermark.java @@ -0,0 +1,34 @@ +package ws.schild.jave.filtergraphs; + +import java.io.File; + +import ws.schild.jave.filters.FilterChain; +import ws.schild.jave.filters.FilterGraph; +import ws.schild.jave.filters.MovieFilter; +import ws.schild.jave.filters.OverlayFilter; +import ws.schild.jave.filters.helpers.OverlayLocation; + +/** + * Overlay an image over an input video. Input video must be specified using a -i option to ffmpeg + * + * @author mressler + */ +public class OverlayWatermark extends FilterGraph { + + /** + * Create an overlay filtergraph that will overlay a watermark image on the video. + * + * @param watermark The location of the watermark image + * @param location The location on the video that the watermark should be overlaid + * @param offsetX The offset from the location that the watermark should be offset. Positive + * values move the image right. Negative values move it left. + * @param offsetY The offset from the location that the watermark should be offset. Positive + * values move the image down. Negative values move it up. + */ + public OverlayWatermark( + File watermark, OverlayLocation location, Integer offsetX, Integer offsetY) { + super( + new FilterChain(new MovieFilter(watermark, "watermark")), + new FilterChain(new OverlayFilter("0:v", "watermark", location, offsetX, offsetY))); + } +} diff --git a/jave-core/src/main/java/ws/schild/jave/filtergraphs/TrimAndWatermark.java b/jave-core/src/main/java/ws/schild/jave/filtergraphs/TrimAndWatermark.java new file mode 100644 index 0000000..f530799 --- /dev/null +++ b/jave-core/src/main/java/ws/schild/jave/filtergraphs/TrimAndWatermark.java @@ -0,0 +1,41 @@ +package ws.schild.jave.filtergraphs; + +import java.io.File; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import ws.schild.jave.filters.FilterChain; +import ws.schild.jave.filters.SetPtsFilter; +import ws.schild.jave.filters.TrimFilter; + +/** Trim and watermark any number of input videos. */ +public class TrimAndWatermark extends FilterAndWatermark { + + public TrimAndWatermark(File watermark, List trimInfo) { + super( + watermark, + IntStream.range(0, trimInfo.size()) + .mapToObj(i -> filterChainForTrimInfo(trimInfo.get(i))) + .collect(Collectors.toList())); + } + + public TrimAndWatermark(File watermark, Double trimStart, Double trimDuration) { + this(watermark, Arrays.asList(new TrimInfo(trimStart, trimDuration))); + } + + public static class TrimInfo { + public Double trimStart; + public Double trimDuration; + + public TrimInfo(Double trimStart, Double trimDuration) { + this.trimStart = trimStart; + this.trimDuration = trimDuration; + } + } + + public static FilterChain filterChainForTrimInfo(TrimInfo info) { + return new FilterChain(new TrimFilter(info.trimStart, info.trimDuration), new SetPtsFilter()); + } +} diff --git a/jave-core/src/main/java/ws/schild/jave/filtergraphs/TrimFadeAndWatermark.java b/jave-core/src/main/java/ws/schild/jave/filtergraphs/TrimFadeAndWatermark.java new file mode 100644 index 0000000..07cbbab --- /dev/null +++ b/jave-core/src/main/java/ws/schild/jave/filtergraphs/TrimFadeAndWatermark.java @@ -0,0 +1,52 @@ +package ws.schild.jave.filtergraphs; + +import java.io.File; +import java.util.EnumSet; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import ws.schild.jave.filtergraphs.TrimAndWatermark.TrimInfo; +import ws.schild.jave.filters.FadeFilter; +import ws.schild.jave.filters.FilterChain; +import ws.schild.jave.filters.helpers.FadeDirection; + +public class TrimFadeAndWatermark extends FilterAndWatermark { + + public TrimFadeAndWatermark(File watermark, List trimInfo) { + super( + watermark, + IntStream.range(0, trimInfo.size()) + .mapToObj(i -> filterChainForTrimInfo(trimInfo.get(i), fadesFromIndex(i, trimInfo.size()), 0.1)) + .collect(Collectors.toList())); + } + + public static EnumSet fadesFromIndex(Integer i, Integer size) { + EnumSet toFade = EnumSet.noneOf(FadeDirection.class); + + if (i != 0) { + toFade.add(FadeDirection.IN); + } + + if (i < size) { + toFade.add(FadeDirection.OUT); + } + + return toFade; + } + + public static FilterChain filterChainForTrimInfo(TrimInfo info, EnumSet fades, Double fadeDuration) { + FilterChain toReturn = TrimAndWatermark.filterChainForTrimInfo(info); + + if (fades.contains(FadeDirection.IN)) { + toReturn.prependFilter(new FadeFilter(FadeDirection.IN, 0.0, fadeDuration)); + } + + if (fades.contains(FadeDirection.OUT)) { + toReturn.addFilter(new FadeFilter(FadeDirection.OUT, info.trimDuration - fadeDuration, fadeDuration)); + } + + return toReturn; + } + +} diff --git a/jave-core/src/main/java/ws/schild/jave/filters/ColorFilter.java b/jave-core/src/main/java/ws/schild/jave/filters/ColorFilter.java new file mode 100644 index 0000000..f3b9007 --- /dev/null +++ b/jave-core/src/main/java/ws/schild/jave/filters/ColorFilter.java @@ -0,0 +1,33 @@ +package ws.schild.jave.filters; + +import ws.schild.jave.filters.helpers.Color; +import ws.schild.jave.info.VideoSize; + +/** + * A color filter as described by the + * FFMPEG Documentation. + */ +public class ColorFilter extends Filter { + + /** + * DIY constructor - add the arguments you need. + */ + public ColorFilter() { + super("color"); + } + + /** + * Simple constructor - make a solid color screen for some amount of time. + * + * @param c The color to use. + * @param s The size of the output video + * @param durationSeconds The number of seconds to output the video for + */ + public ColorFilter(Color c, VideoSize s, Double durationSeconds) { + super("color"); + addNamedArgument("c", c.toString()); + addNamedArgument("size", s.toString()); + addNamedArgument("d", durationSeconds.toString()); + } +} diff --git a/jave-core/src/main/java/ws/schild/jave/filters/ConcatFilter.java b/jave-core/src/main/java/ws/schild/jave/filters/ConcatFilter.java new file mode 100644 index 0000000..3cdef27 --- /dev/null +++ b/jave-core/src/main/java/ws/schild/jave/filters/ConcatFilter.java @@ -0,0 +1,16 @@ +package ws.schild.jave.filters; + +import java.util.List; + +public class ConcatFilter extends Filter { + + /** + * Apply the concatenate filter to the associated input labels + * @param inputLabels The list of labels to be used as inputs to this concat filter. + */ + public ConcatFilter(List inputLabels) { + super("concat"); + inputLabels.stream().forEach(this::addInputLabel); + addNamedArgument("n", Integer.toString(inputLabels.size())); + } +} diff --git a/jave-core/src/main/java/ws/schild/jave/filters/DrawtextFilter.java b/jave-core/src/main/java/ws/schild/jave/filters/DrawtextFilter.java new file mode 100644 index 0000000..9787afe --- /dev/null +++ b/jave-core/src/main/java/ws/schild/jave/filters/DrawtextFilter.java @@ -0,0 +1,150 @@ +package ws.schild.jave.filters; + +import java.io.File; + +import ws.schild.jave.filters.helpers.Color; +import ws.schild.jave.utils.Utils; + +/** + * Add text to a video. An implementation of the drawtext filter from the FFMPEG Documentation. + * + *

https://write.corbpie.com/how-to-do-a-text-watermark-in-ffmpeg/ -vf "drawtext=text='a + * watermark':x=10:y=H-th-10:fontfile=/pathto/font.ttf:fontsize=10:fontcolor=white:shadowcolor=black:shadowx=2:shadowy=2" + */ +public class DrawtextFilter extends Filter { + + /** + * @param text The text string to be drawn. The text must be a sequence of UTF-8 encoded + * characters. This parameter is mandatory if no file is specified with the parameter + * textfile. + * @param posX X Position of watermark text. The expressions which specify the offsets where text + * will be drawn within the video frame. They are relative to the top/left border of the + * output image. The default value of x and y is "0". + * @param posY Y Position of watermark text. The expressions which specify the offsets where text + * will be drawn within the video frame. They are relative to the top/left border of the + * output image. The default value of x and y is "0". + */ + public DrawtextFilter( + String text, + String posX, + String posY) { + super("drawtext"); + addNamedArgument("text", Utils.escapeArgument(text)); + addNamedArgument("x", posX.toString()); + addNamedArgument("y", posY.toString()); + } + + /** + * @param text The text string to be drawn. The text must be a sequence of UTF-8 encoded + * characters. This parameter is mandatory if no file is specified with the parameter + * textfile. + * @param posX X Position of watermark text. The expressions which specify the offsets where text + * will be drawn within the video frame. They are relative to the top/left border of the + * output image. The default value of x and y is "0". + * @param posY Y Position of watermark text. The expressions which specify the offsets where text + * will be drawn within the video frame. They are relative to the top/left border of the + * output image. The default value of x and y is "0". + * @param fontName The font family to be used for drawing text. By default Sans. + * @param fontSize The font size to be used for drawing text. The default value of fontsize is 16. + * @param fontColor The color to be used for drawing fonts. The default value of fontcolor is + * "black". + */ + public DrawtextFilter( + String text, + String posX, + String posY, + String fontName, + Double fontSize, + Color fontColor) + throws IllegalArgumentException { + this(text, posX, posY); + + addNamedArgument("font", fontName); + addNamedArgument("fontsize", fontSize.toString()); + addNamedArgument("fontcolor", fontColor.toString()); + } + + /** + * @param text The text string to be drawn. The text must be a sequence of UTF-8 encoded + * characters. This parameter is mandatory if no file is specified with the parameter + * textfile. + * @param posX X Position of watermark text. The expressions which specify the offsets where text + * will be drawn within the video frame. They are relative to the top/left border of the + * output image. The default value of x and y is "0". + * @param posY Y Position of watermark text. The expressions which specify the offsets where text + * will be drawn within the video frame. They are relative to the top/left border of the + * output image. The default value of x and y is "0". + * @param fontFile The font file to be used for drawing text. The path must be included. This + * parameter is mandatory if the fontconfig support is disabled. + * @param fontSize The font size to be used for drawing text. The default value of fontsize is 16. + * @param fontColor The color to be used for drawing fonts. The default value of fontcolor is + * "black". + */ + public DrawtextFilter( + String text, + String posX, + String posY, + File fontFile, + Double fontSize, + Color fontColor) + throws IllegalArgumentException { + this(text, posX, posY); + + addNamedArgument("fontfile", fontFile.getAbsolutePath()); + addNamedArgument("fontsize", fontSize.toString()); + addNamedArgument("fontcolor", fontColor.toString()); + } + + /** + * @param shadowColor Color of shadow + * @param shadowX X Position of shadow, relative to text + * @param shadowY Y Position of shadow, relative to text + * @return this instance + */ + public DrawtextFilter setShadow(Color shadowColor, Integer shadowX, Integer shadowY) { + addNamedArgument("shadowcolor", shadowColor.toString()); + addNamedArgument("shadowx", shadowX.toString()); + addNamedArgument("shadowy", shadowY.toString()); + return this; + } + + /** + * Used to draw a box around text using the background color. + * + * @param borderWidth Set the width of the border to be drawn around the box using boxcolor. The + * default value of boxborderw is 0. + * @param color The color to be used for drawing box around text. The default value of boxcolor is + * "white". + * @return this instance. + */ + public DrawtextFilter setBox(Integer borderWidth, Color color) { + addNamedArgument("box", "1"); + addNamedArgument("boxcolor", color.toString()); + addNamedArgument("boxborderw", borderWidth.toString()); + return this; + } + + /** + * @param borderWidth Set the width of the border to be drawn around the text using bordercolor. + * The default value of borderw is 0. + * @param color Set the color to be used for drawing border around text. The default value of + * bordercolor is "black". + * @return this instance + */ + public DrawtextFilter setBorder(Integer borderWidth, Color color) { + addNamedArgument("bordercolor", color.toString()); + addNamedArgument("borderw", borderWidth.toString()); + return this; + } + + /** + * @param lineSpacing Set the line spacing in pixels of the border to be drawn around the box + * using box. The default value of line_spacing is 0. + * @return this instance + */ + public DrawtextFilter setLineSpacing(Integer lineSpacing) { + addNamedArgument("line_spacing", lineSpacing.toString()); + return this; + } +} diff --git a/jave-core/src/main/java/ws/schild/jave/filters/FadeFilter.java b/jave-core/src/main/java/ws/schild/jave/filters/FadeFilter.java new file mode 100644 index 0000000..b3d03b8 --- /dev/null +++ b/jave-core/src/main/java/ws/schild/jave/filters/FadeFilter.java @@ -0,0 +1,27 @@ +package ws.schild.jave.filters; + +import ws.schild.jave.filters.helpers.FadeDirection; + +/** + * An implementation of the fade filter as found in the FFMPEG Documentation. + */ +public class FadeFilter extends Filter { + + public FadeFilter() { + super("fade"); + } + + /** + * Standard usage - fase in or out at some time for some duration. + * @param dir In or Out. + * @param startTimeSeconds When to start the fade. + * @param durationSeconds How long to fade in or out. + */ + public FadeFilter(FadeDirection dir, Double startTimeSeconds, Double durationSeconds) { + super("fade"); + addNamedArgument("type", dir.toString()); + addNamedArgument("start_time", startTimeSeconds.toString()); + addNamedArgument("duration", durationSeconds.toString()); + } + +} diff --git a/jave-core/src/main/java/ws/schild/jave/filters/Filter.java b/jave-core/src/main/java/ws/schild/jave/filters/Filter.java new file mode 100644 index 0000000..480acaa --- /dev/null +++ b/jave-core/src/main/java/ws/schild/jave/filters/Filter.java @@ -0,0 +1,116 @@ +package ws.schild.jave.filters; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * A filter as described by + * FFMPEG Documentation. + * + *

A filter has an ordered list of input labels, a name, arguments, and an ordered list of output + * labels. Arguments can be either ordered or named. If both are present, ordered must be emitted + * first. + * + *

Currently + * filtergraph escaping is not the responsibility of this class. All arguments must be + * pre-escaped by the time they get to this class. + * + *

It is intended that this class is not used directly. Instead, implementers will add a specific + * implementation of the filter they are implementing. A complete list can be found in FFMPEG Documentation. + * However, the class is not abstract to not prohibit direct use.

+ * + * @author mressler + */ +public class Filter implements VideoFilter { + + private List inputLinkLabels; + private String name; + private List orderedArguments; + private Map namedArguments; + private List outputLinkLabels; + + /** + * Create a filter with the specified name with no input/output labels or arguments. + * + * @param name The name of the filter. + */ + public Filter(String name) { + inputLinkLabels = new ArrayList<>(); + this.name = name; + orderedArguments = new ArrayList<>(); + namedArguments = new HashMap<>(); + outputLinkLabels = new ArrayList<>(); + } + + /** + * Add an input label to the list of input labels for this filter + * + * @param label The name of the input label(s) + * @return this Filter for builder pattern magic + */ + public Filter addInputLabel(String... label) { + inputLinkLabels.addAll(Arrays.asList(label)); + return this; + } + + /** + * Add an ordered argument to the list of arguments for this filter + * + * @param arg Any number of ordered arguments + * @return this Filter for builder pattern magic + */ + public Filter addOrderedArgument(String... arg) { + orderedArguments.addAll(Arrays.asList(arg)); + return this; + } + + /** + * Add a named argument to the set of named arguments for this filter + * + * @param name The name of the argument + * @param value The value for the argument + * @return this Filter for builder pattern magic + */ + public Filter addNamedArgument(String name, String value) { + namedArguments.put(name, value); + return this; + } + + /** + * Add an output label to the list of output labels for this filter + * + * @param label The name of the input label + * @return this Filter for builder pattern magic + */ + public Filter addOutputLabel(String... label) { + outputLinkLabels.addAll(Arrays.asList(label)); + return this; + } + + @Override + public String getExpression() { + return formatLinkLabels(inputLinkLabels) + + name + + formatArguments() + + formatLinkLabels(outputLinkLabels); + } + + private static String formatLinkLabels(List labels) { + return labels.stream().map(labelName -> "[" + labelName + "]").collect(Collectors.joining()); + } + + private String formatArguments() { + return ((orderedArguments.size() + namedArguments.size() > 0) ? "='" : "") + + Stream.concat( + orderedArguments.stream(), + namedArguments.entrySet().stream().map(e -> e.getKey() + "=" + e.getValue())) + .collect(Collectors.joining(":")) + + ((orderedArguments.size() + namedArguments.size() > 0) ? "'" : ""); + } +} diff --git a/jave-core/src/main/java/ws/schild/jave/filters/FilterChain.java b/jave-core/src/main/java/ws/schild/jave/filters/FilterChain.java new file mode 100644 index 0000000..6d27581 --- /dev/null +++ b/jave-core/src/main/java/ws/schild/jave/filters/FilterChain.java @@ -0,0 +1,75 @@ +package ws.schild.jave.filters; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +/** + * A filterchain as described by FFMPEG Documentation. + * + *

A filterchain is a comma separated series of filters. + * + * @author mressler + */ +public class FilterChain implements VideoFilter { + + private List filters; + + /** Create an empty filterchain. */ + public FilterChain() { + filters = new ArrayList<>(); + } + /** + * Create a filterchain with the specified filters + * + * @param filters The ordered list of filters in this chain + */ + public FilterChain(Filter... filters) { + this.filters = new ArrayList<>(Arrays.asList(filters)); + } + + /** + * Add one Filter to this filterchain + * + * @param filter The Filter to add to this chain. + * @return this FilterChain for builder pattern magic + */ + public FilterChain addFilter(Filter filter) { + filters.add(filter); + return this; + } + + public FilterChain prependFilter(Filter filter) { + filters.add(0, filter); + return this; + } + + /** + * Adds an input label to the first filter in this chain. + * @param label The label to use for the input label for the first filter in this chain + * @return this FilterChain for builder pattern magic + * @throws IndexOutOfBoundsException if there are no filters in this chain. + */ + public FilterChain setInputLabel(String label) { + filters.get(0).addInputLabel(label); + return this; + } + + /** + * Adds an output label to the first filter in this chain. + * @param label The label to use for the output label for the last filter in this chain + * @return this FilterChain for builder pattern magic + * @throws IndexOutOfBoundsException if there are no filters in this chain. + */ + public FilterChain setOutputLabel(String label) { + filters.get(filters.size() - 1).addOutputLabel(label); + return this; + } + + @Override + public String getExpression() { + return filters.stream().map(VideoFilter::getExpression).collect(Collectors.joining(",")); + } +} diff --git a/jave-core/src/main/java/ws/schild/jave/filters/FilterGraph.java b/jave-core/src/main/java/ws/schild/jave/filters/FilterGraph.java new file mode 100644 index 0000000..53d6bf3 --- /dev/null +++ b/jave-core/src/main/java/ws/schild/jave/filters/FilterGraph.java @@ -0,0 +1,75 @@ +package ws.schild.jave.filters; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +/** + * A filtergraph as described by FFMPEG Documentation. + * + *

A filtergraph can optionally start with sws_flags for scaling of outputs and is then composed + * of a semi-colon separated series of filterchains. + * + * @author mressler + */ +public class FilterGraph implements VideoFilter { + + private Optional swsFlags; + private List chains; + + /** Create an empty filtergraph. */ + public FilterGraph() { + swsFlags = Optional.empty(); + chains = new ArrayList<>(); + } + /** + * Create a filtergraph with a specified list of filterchains. + * + * @param chains The list of filterchains to be used in this filtergraph. + */ + public FilterGraph(FilterChain... chains) { + this(); + this.chains = new ArrayList<>(Arrays.asList(chains)); + } + /** + * Create a filtergraph with a specified list of filterchains and specified sws_flags. + * + * @param chains The list of filterchains to be used in this filtergraph. + * @param swsFlags The sws_flags parameter to pass to libavfilter scale filters. + */ + public FilterGraph(String swsFlags, FilterChain... chains) { + this(chains); + this.swsFlags = Optional.of(swsFlags); + } + + /** + * Add a filterchain to this filtergraph. + * + * @param chain The filterchain to add to this filtergraph. + * @return this FilterGraph for builder pattern magic + */ + public FilterGraph addChain(FilterChain chain) { + chains.add(chain); + return this; + } + + /** + * set the sws_flags to pass to libavfilter scale filters. + * + * @param swsFlags The flags that will; be passed to libavfilter scale filters. + * @return this FilterGraph for builder pattern magic + */ + public FilterGraph setSwsFlags(String swsFlags) { + this.swsFlags = Optional.of(swsFlags); + return this; + } + + @Override + public String getExpression() { + return swsFlags.map(s -> "sws_flags=" + s + ";").orElse("") + + chains.stream().map(VideoFilter::getExpression).collect(Collectors.joining(";")); + } +} diff --git a/jave-core/src/main/java/ws/schild/jave/filters/MovieFilter.java b/jave-core/src/main/java/ws/schild/jave/filters/MovieFilter.java new file mode 100644 index 0000000..ed7a371 --- /dev/null +++ b/jave-core/src/main/java/ws/schild/jave/filters/MovieFilter.java @@ -0,0 +1,20 @@ +package ws.schild.jave.filters; + +import java.io.File; + +public class MovieFilter extends Filter { + + /** + * A simple instantiation of the movie filter. + * @param source The source image to be used for this movie filter. + */ + public MovieFilter(File source) { + super("movie"); + addOrderedArgument(source.getAbsolutePath()); + } + + public MovieFilter(File source, String outputLabel) { + this(source); + addOutputLabel(outputLabel); + } +} diff --git a/jave-core/src/main/java/ws/schild/jave/filters/OverlayFilter.java b/jave-core/src/main/java/ws/schild/jave/filters/OverlayFilter.java new file mode 100644 index 0000000..e8798b5 --- /dev/null +++ b/jave-core/src/main/java/ws/schild/jave/filters/OverlayFilter.java @@ -0,0 +1,51 @@ +package ws.schild.jave.filters; + +import java.util.Optional; + +import ws.schild.jave.filters.helpers.OverlayLocation; + +/** + * An implementation of the overlay filter as specified by FFMPEG Documentation + * + * @author mressler + */ +public class OverlayFilter extends Filter { + + public OverlayFilter() { + super("overlay"); + } + + /** + * Overlay video onto {@code baseInputLabel} at {@code location}. Offsets specify x/y + * offsets from the four locations. This constructor has an implicit unmatched input pad that + * needs to be filled by a previous filter in the chain. + * + * @param baseInputLabel The location to overlay video onto. + * @param location One of the four corners. + * @param offsetX An offset from one of the four corners. + * @param offsetY An offset from one of the four corners. + */ + public OverlayFilter( + String baseInputLabel, + OverlayLocation location, + Integer offsetX, + Integer offsetY) + { + super("overlay"); + addInputLabel(baseInputLabel); + addOrderedArgument( + location.getX(Optional.ofNullable(offsetX)), location.getY(Optional.ofNullable(offsetY))); + } + + public OverlayFilter( + String baseInputLabel, + String overlayInputLabel, + OverlayLocation location, + Integer offsetX, + Integer offsetY) + { + this(baseInputLabel, location, offsetX, offsetY); + addInputLabel(overlayInputLabel); + } +} diff --git a/jave-core/src/main/java/ws/schild/jave/filters/PadFilter.java b/jave-core/src/main/java/ws/schild/jave/filters/PadFilter.java new file mode 100644 index 0000000..c7beca8 --- /dev/null +++ b/jave-core/src/main/java/ws/schild/jave/filters/PadFilter.java @@ -0,0 +1,24 @@ +package ws.schild.jave.filters; + +import ws.schild.jave.info.VideoSize; + +public class PadFilter extends Filter { + + public PadFilter() { + super("pad"); + } + + /** + * Uses the pad filter to pad the + * source image to the same aspect ratio as {@code aspectRatio}. + * + * @param aspectRatio A {@link VideoSize} that represents the desired resulting aspect ratio. + */ + public PadFilter(VideoSize aspectRatio) { + super("pad"); + addNamedArgument("w", "ih*" + aspectRatio.aspectRatioExpression()); + addNamedArgument("h", "ih"); + addNamedArgument("x", "(ow-iw)/2"); + addNamedArgument("y", "(oh-ih)/2"); + } +} diff --git a/jave-core/src/main/java/ws/schild/jave/filters/ScaleFilter.java b/jave-core/src/main/java/ws/schild/jave/filters/ScaleFilter.java new file mode 100644 index 0000000..c2ca9fc --- /dev/null +++ b/jave-core/src/main/java/ws/schild/jave/filters/ScaleFilter.java @@ -0,0 +1,27 @@ +package ws.schild.jave.filters; + +import ws.schild.jave.filters.helpers.ForceOriginalAspectRatio; +import ws.schild.jave.info.VideoSize; + +/** + * An implementation of the scale filter as found in the FFMPEG Documentation. + */ +public class ScaleFilter extends Filter { + + public ScaleFilter() { + super("scale"); + } + + /** + * Scale the video to a particular size and maintain aspect ratio. + * @param toSize What size should the video be scaled to? + * @param foar Should the video be increased or decreased to size? + */ + public ScaleFilter(VideoSize toSize, ForceOriginalAspectRatio foar) { + super("scale"); + addNamedArgument("w", toSize.getWidth().toString()); + addNamedArgument("h", toSize.getHeight().toString()); + addNamedArgument("force_original_aspect_ratio", foar.getCommandLine()); + } +} diff --git a/jave-core/src/main/java/ws/schild/jave/filters/SetPtsFilter.java b/jave-core/src/main/java/ws/schild/jave/filters/SetPtsFilter.java new file mode 100644 index 0000000..4b7305d --- /dev/null +++ b/jave-core/src/main/java/ws/schild/jave/filters/SetPtsFilter.java @@ -0,0 +1,16 @@ +package ws.schild.jave.filters; + +/** + * An implementation of the setpts filter as specified by FFMPEG Documentation. + * + * @author mressler + */ +public class SetPtsFilter extends Filter { + + /** Create a setpts filter that resets the presentation timestamp to zero */ + public SetPtsFilter() { + super("setpts"); + addOrderedArgument("PTS-STARTPTS"); + } +} diff --git a/jave-core/src/main/java/ws/schild/jave/filters/TrimFilter.java b/jave-core/src/main/java/ws/schild/jave/filters/TrimFilter.java new file mode 100644 index 0000000..99ffa1d --- /dev/null +++ b/jave-core/src/main/java/ws/schild/jave/filters/TrimFilter.java @@ -0,0 +1,28 @@ +package ws.schild.jave.filters; + +/** + * An implementation of the overlay filter as specified by FFMPEG Documentation + * + *

Important implementation note: Most common usage of the trim filter requires a setpts filter + * applied immediately after in the filter chain. + * + * @author mressler + */ +public class TrimFilter extends Filter { + + public TrimFilter() { + super("trim"); + } + + public TrimFilter(Double start, Double duration) { + super("trim"); + addNamedArgument("start", start.toString()); + addNamedArgument("duration", duration.toString()); + } + + public TrimFilter(String inputLabel, Double start, Double duration) { + this(start, duration); + addInputLabel(inputLabel); + } +} diff --git a/jave-core/src/main/java/ws/schild/jave/filters/VideoDrawtext.java b/jave-core/src/main/java/ws/schild/jave/filters/VideoDrawtext.java deleted file mode 100644 index ad6a861..0000000 --- a/jave-core/src/main/java/ws/schild/jave/filters/VideoDrawtext.java +++ /dev/null @@ -1,468 +0,0 @@ -/* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. - */ -package ws.schild.jave.filters; - -import java.io.File; -import ws.schild.jave.Color; -import ws.schild.jave.Utils; -import ws.schild.jave.VideoFilter; - -/** - * Add a text watermark in the video file - * - * https://write.corbpie.com/how-to-do-a-text-watermark-in-ffmpeg/ - * -vf "drawtext=text='a watermark':x=10:y=H-th-10:fontfile=/pathto/font.ttf:fontsize=10:fontcolor=white:shadowcolor=black:shadowx=2:shadowy=2" - * - * - * @author andre - */ -public class VideoDrawtext extends VideoFilter { - - private String watermarkText= null; - private int posX= -1; - private int posY= -1; - - private String fontName= "Arial"; - private File fontFile= null; - private float fontSize= 10; - private Color fontColor= null; - - private int lineSpacing= 0; - - private Color shadowColor= null; - private int shadowX= 2; - private int shadowY= 2; - - private int boxBorderWidth= 0; - private Color boxColor= null; - - private int borderWidth= 0; - private Color borderColor= null; - private String addArgument= null; - - /** - * @param watermarkText Text to be used as watermark - * @param fontColor Color of font - */ - public VideoDrawtext( - String watermarkText, - Color fontColor - ) throws IllegalArgumentException - { - this.watermarkText= watermarkText; - this.fontColor= fontColor; - } - - /** - * @param watermarkText Text to be used as watermark - * @param posX X Position of watermark text (From the left) ignored if posX & posY are both -1 - * @param posY Y Position of watermark text (From the top) ignored if posX & posY are both -1 - * @param fontName Use this font (Can be null, but then we need a fontFile) - * @param fontFile Truetype font file (Only required when fontName is NULL) - * @param fontSize Font size - * @param fontColor Color of font - */ - public VideoDrawtext( - String watermarkText, - int posX, - int posY, - String fontName, - File fontFile, - float fontSize, - Color fontColor - ) throws IllegalArgumentException - { - this.watermarkText= watermarkText; - this.posX= posX; - this.posY= posY; - this.fontName= fontName; - this.fontFile = fontFile; - this.fontSize= fontSize; - this.fontColor= fontColor; - } - - - /** - * - * @param shadowColor Color of shadow - * @param shadowX X Position of shadow, relative to text - * @param shadowY Y Position of shadow, relative to text - * @return this instance - */ - public VideoDrawtext setShadow( - Color shadowColor, - int shadowX, - int shadowY) - { - this.shadowColor= shadowColor; - this.shadowX= shadowX; - this.shadowY= shadowY; - return this; - } - - /** - * @return the watermarkText - */ - public String getWatermarkText() { - return watermarkText; - } - - /** - * @param watermarkText the watermarkText to set - * @return this instance - */ - public VideoDrawtext setWatermarkText(String watermarkText) { - this.watermarkText = watermarkText; - return this; - } - - /** - * @return the posX - */ - public int getPosX() { - return posX; - } - - /** - * ignored if posX & posY are both -1 - * - * @param posX the posX to set - * @return this instance - */ - public VideoDrawtext setPosX(int posX) { - this.posX = posX; - return this; - } - - /** - * ignored if posX & posY are both -1 - * - * @return the posY - */ - public int getPosY() { - return posY; - } - - /** - * @param posY the posY to set - * @return this instance - */ - public VideoDrawtext setPosY(int posY) { - this.posY = posY; - return this; - } - - /** - * @return the fontName - */ - public String getFontName() { - return fontName; - } - - /** - * @param fontName the fontName to set - * @return this instance - */ - public VideoDrawtext setFontName(String fontName) { - this.fontName = fontName; - return this; - } - - /** - * @return the fontFile - */ - public File getFontFile() { - return fontFile; - } - - /** - * @param fontFile the fontFile to set - * @return this instance - */ - public VideoDrawtext setFontFile(File fontFile) { - this.fontFile = fontFile; - return this; - } - - /** - * @return the fontSize - */ - public float getFontSize() { - return fontSize; - } - - /** - * @param fontSize the fontSize to set - * @return this instance - */ - public VideoDrawtext setFontSize(float fontSize) { - this.fontSize = fontSize; - return this; - } - - /** - * @return the fontColor - */ - public Color getFontColor() { - return fontColor; - } - - /** - * @param fontColor the fontColor to set - * @return this instance - */ - public VideoDrawtext setFontColor(Color fontColor) { - this.fontColor = fontColor; - return this; - } - - /** - * @return the shadowColor - */ - public Color getShadowColor() { - return shadowColor; - } - - /** - * @param shadowColor the shadowColor to set - * @return this instance - */ - public VideoDrawtext setShadowColor(Color shadowColor) { - this.shadowColor = shadowColor; - return this; - } - - /** - * @return the shadowX - */ - public int getShadowX() { - return shadowX; - } - - /** - * @param shadowX the shadowX to set - * @return this instance - */ - public VideoDrawtext setShadowX(int shadowX) { - this.shadowX = shadowX; - return this; - } - - /** - * @return the shadowY - */ - public int getShadowY() { - return shadowY; - } - - /** - * @param shadowY the shadowY to set - * @return this instance - */ - public VideoDrawtext setShadowY(int shadowY) { - this.shadowY = shadowY; - return this; - } - - public VideoDrawtext setBox(int borderWidth, Color color) - { - this.setBoxBorderWidth(borderWidth); - this.setBoxColor(color); - return this; - } - - /** - * @return the boxBorderWidth - */ - public int getBoxBorderWidth() { - return boxBorderWidth; - } - - /** - * @param boxBorderWidth the boxBorderWidth to set - * @return this instance - */ - public VideoDrawtext setBoxBorderWidth(int boxBorderWidth) { - this.boxBorderWidth = boxBorderWidth; - return this; - } - - /** - * @return the boxColor - */ - public Color getBoxColor() { - return boxColor; - } - - /** - * @param boxColor the boxColor to set - * @return this instance - */ - public VideoDrawtext setBoxColor(Color boxColor) { - this.boxColor = boxColor; - return this; - } - - - /** - * @return the borderWidth - */ - public int getBorderWidth() { - return borderWidth; - } - - /** - * @param borderWidth the borderWidth to set - * @return this instance - */ - public VideoDrawtext setBorderWidth(int borderWidth) { - this.borderWidth = borderWidth; - return this; - } - - /** - * @return the borderColor - */ - public Color getBorderColor() { - return borderColor; - } - - /** - * @param borderColor the borderColor to set - * @return this instance - */ - public VideoDrawtext setBorderColor(Color borderColor) { - this.borderColor = borderColor; - return this; - } - - /** - * @return the lineSpacing - */ - public int getLineSpacing() { - return lineSpacing; - } - - /** - * @param lineSpacing the lineSpacing to set - * @return this instance - */ - public VideoDrawtext setLineSpacing(int lineSpacing) { - this.lineSpacing = lineSpacing; - return this; - } - - /** - * @return the addArgument - */ - public String getAddArgument() { - return addArgument; - } - - /** - * Add an additional argument to the command line - * https://superuser.com/questions/939357/position-text-on-bottom-right-corner - * - * Bottom right - * x=w-tw:y=h-th - * Bottom right with 10 pixel padding - * x=w-tw-10:y=h-th-10 - * Top right - * x=w-tw - * Top right with 10 pixel padding - * x=w-tw-10:y=10 - * Top left - * x=0:y=0 - * Top left with 10 pixel padding - * x=10:y=10 - * Bottom left - * y=h-th - * Bottom left with 10 pixel padding - * x=10:h-th-10 - * centered - * x=(w-text_w)/2:y=(h-text_h)/2 - * - * Can be used to speicfy other positions like "x=(w-text_w)/2:y=(h-text_h)/2" - * for centered text water mark - * - * @param addArgument the addArgument to set - * @return this instance - */ - public VideoDrawtext setAddArgument(String addArgument) { - this.addArgument = addArgument; - return this; - } - - @Override - public String getExpression() - { - StringBuilder sb= new StringBuilder(); - sb.append("drawtext=text='"); - sb.append(Utils.escapeArgument(watermarkText)); - if (posX != -1 && posY != -1) - { - sb.append("':x="); - sb.append(Integer.toString(posX)); - sb.append("':y="); - sb.append(Integer.toString(posY)); - } - if (fontName != null) - { - sb.append(":font="); - sb.append(fontName); - } - else if (fontFile != null) - { - sb.append(":fontfile="); - sb.append(fontFile.getAbsoluteFile()); - } - else - { - throw new IllegalArgumentException("Need either fontName or fontFile"); - } - sb.append(":fontsize="); - sb.append(Float.toString(fontSize)); - sb.append(":fontcolor="); - sb.append(fontColor.getFfmpegColor()); - - if (lineSpacing != 0) - { - sb.append(":line_spacing:").append(Integer.toString(lineSpacing)); - } - - if (shadowColor != null) - { - sb.append(":shadowcolor"); - sb.append(shadowColor.getFfmpegColor()); - sb.append(":shadowx="); - sb.append(Integer.toString(shadowX)); - sb.append(":shadowy="); - sb.append(Integer.toString(shadowY)); - } - if (boxColor != null) - { - sb.append(":box=1:boxcolor"); - sb.append(boxColor.getFfmpegColor()); - sb.append(":boxborderw="); - sb.append(Integer.toString(boxBorderWidth)); - } - if (borderWidth != 0) - { - sb.append(":bordercolor"); - sb.append(borderColor.getFfmpegColor()); - sb.append(":borderw="); - sb.append(Integer.toString(boxBorderWidth)); - } - if (addArgument != null) - { - sb.append(":"); - sb.append(addArgument); - } - - return sb.toString(); - } - -} diff --git a/jave-core/src/main/java/ws/schild/jave/filters/VideoFilter.java b/jave-core/src/main/java/ws/schild/jave/filters/VideoFilter.java new file mode 100644 index 0000000..a4c49c7 --- /dev/null +++ b/jave-core/src/main/java/ws/schild/jave/filters/VideoFilter.java @@ -0,0 +1,13 @@ +package ws.schild.jave.filters; + +/** @author jgiotta */ +public interface VideoFilter { + + /** + * The expression to be used in the video filter argument to ffmpeg + * + * @return A string that will be placed in the -vf or -filter_complex option to ffmpeg. + */ + public String getExpression(); + +} diff --git a/jave-core/src/main/java/ws/schild/jave/filters/ZoomPanFilter.java b/jave-core/src/main/java/ws/schild/jave/filters/ZoomPanFilter.java new file mode 100644 index 0000000..51dab55 --- /dev/null +++ b/jave-core/src/main/java/ws/schild/jave/filters/ZoomPanFilter.java @@ -0,0 +1,63 @@ +package ws.schild.jave.filters; + +import ws.schild.jave.info.VideoSize; + +public class ZoomPanFilter extends Filter { + + public ZoomPanFilter() { + super("zoompan"); + } + + /** + * A "top to bottom" zoom and pan of an image/video using the zoompan filter. + * + *

This instance of zoompan will animate gently using a sigmoid function. The first third of + * the video is focused on the beginning of the image, the last third is focused on the bottom + * part of the image, and the middle section will animate gracefully between the two. + * + *

It is expected that the input video is of the same aspect ratio as the output video. If + * zooming in to a video of a different size, try using the {@link PadFilter} is used to pad the + * source image/video to the same aspect ratio as {@code ouptputSize} + * + * @param durationFrames The number of frames to emit for this zoompan filter. Default FPS is 25. + * @param inputSize The size of the original image/video this filter is zooming around in. + * @param outputSize The size of the resulting video after the zoompan filter is applied. + */ + public ZoomPanFilter(Integer durationFrames, VideoSize inputSize, VideoSize outputSize) { + super("zoompan"); + addNamedArgument("d", durationFrames.toString()); + addNamedArgument("s", outputSize.asEncoderArgument()); + addNamedArgument( + "zoom", outputSize.aspectRatioExpression() + "/" + inputSize.aspectRatioExpression()); + addNamedArgument("x", "(" + inputSize.getWidth() + "*zoom-" + inputSize.getWidth() + ")/2"); + addNamedArgument("y", "1/(1+exp(-20*(on/(25*4)-0.5)))*(ih-ih/zoom)"); + } + + /** + * A simple usage of the zoompan + * filter. + * + * @param durationFrames The number of frames to emit for this zoompan filter. Default FPS is 25. + * @param outputSize The size of the resulting video after the zoompan filter is applied. + * @param zoomExpression An + * expression that represents the current zoom level. + * @param xExpression An + * expression that represents the current x location. + * @param yExpression An + * expression that represents the current y location. + */ + public ZoomPanFilter( + Integer durationFrames, + VideoSize outputSize, + String zoomExpression, + String xExpression, + String yExpression) { + super("zoompan"); + addNamedArgument("d", durationFrames.toString()); + addNamedArgument("s", outputSize.asEncoderArgument()); + addNamedArgument("zoom", zoomExpression); + addNamedArgument("x", xExpression); + addNamedArgument("y", yExpression); + } +} diff --git a/jave-core/src/main/java/ws/schild/jave/filters/helpers/Color.java b/jave-core/src/main/java/ws/schild/jave/filters/helpers/Color.java new file mode 100644 index 0000000..406c4f7 --- /dev/null +++ b/jave-core/src/main/java/ws/schild/jave/filters/helpers/Color.java @@ -0,0 +1,214 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package ws.schild.jave.filters.helpers; + +/** + * A color object, as FFMPEG + * documentation spells out. + */ +public class Color { + private String color; + private String alpha = "ff"; + + public static final Color AliceBlue = new Color("0xF0F8FF"); + public static final Color AntiqueWhite = new Color("0xFAEBD7"); + public static final Color Aqua = new Color("0x00FFFF"); + public static final Color Aquamarine = new Color("0x7FFFD4"); + public static final Color Azure = new Color("0xF0FFFF"); + public static final Color Beige = new Color("0xF5F5DC"); + public static final Color Bisque = new Color("0xFFE4C4"); + public static final Color Black = new Color("0x000000"); + public static final Color BlanchedAlmond = new Color("0xFFEBCD"); + public static final Color Blue = new Color("0x0000FF"); + public static final Color BlueViolet = new Color("0x8A2BE2"); + public static final Color Brown = new Color("0xA52A2A"); + public static final Color BurlyWood = new Color("0xDEB887"); + public static final Color CadetBlue = new Color("0x5F9EA0"); + public static final Color Chartreuse = new Color("0x7FFF00"); + public static final Color Chocolate = new Color("0xD2691E"); + public static final Color Coral = new Color("0xFF7F50"); + public static final Color CornflowerBlue = new Color("0x6495ED"); + public static final Color Cornsilk = new Color("0xFFF8DC"); + public static final Color Crimson = new Color("0xDC143C"); + public static final Color Cyan = new Color("0x00FFFF"); + public static final Color DarkBlue = new Color("0x00008B"); + public static final Color DarkCyan = new Color("0x008B8B"); + public static final Color DarkGoldenRod = new Color("0xB8860B"); + public static final Color DarkGray = new Color("0xA9A9A9"); + public static final Color DarkGreen = new Color("0x006400"); + public static final Color DarkKhaki = new Color("0xBDB76B"); + public static final Color DarkMagenta = new Color("0x8B008B"); + public static final Color DarkOliveGreen = new Color("0x556B2F"); + public static final Color Darkorange = new Color("0xFF8C00"); + public static final Color DarkOrchid = new Color("0x9932CC"); + public static final Color DarkRed = new Color("0x8B0000"); + public static final Color DarkSalmon = new Color("0xE9967A"); + public static final Color DarkSeaGreen = new Color("0x8FBC8F"); + public static final Color DarkSlateBlue = new Color("0x483D8B"); + public static final Color DarkSlateGray = new Color("0x2F4F4F"); + public static final Color DarkTurquoise = new Color("0x00CED1"); + public static final Color DarkViolet = new Color("0x9400D3"); + public static final Color DeepPink = new Color("0xFF1493"); + public static final Color DeepSkyBlue = new Color("0x00BFFF"); + public static final Color DimGray = new Color("0x696969"); + public static final Color DodgerBlue = new Color("0x1E90FF"); + public static final Color FireBrick = new Color("0xB22222"); + public static final Color FloralWhite = new Color("0xFFFAF0"); + public static final Color ForestGreen = new Color("0x228B22"); + public static final Color Fuchsia = new Color("0xFF00FF"); + public static final Color Gainsboro = new Color("0xDCDCDC"); + public static final Color GhostWhite = new Color("0xF8F8FF"); + public static final Color Gold = new Color("0xFFD700"); + public static final Color GoldenRod = new Color("0xDAA520"); + public static final Color Gray = new Color("0x808080"); + public static final Color Green = new Color("0x008000"); + public static final Color GreenYellow = new Color("0xADFF2F"); + public static final Color HoneyDew = new Color("0xF0FFF0"); + public static final Color HotPink = new Color("0xFF69B4"); + public static final Color IndianRed = new Color("0xCD5C5C"); + public static final Color Indigo = new Color("0x4B0082"); + public static final Color Ivory = new Color("0xFFFFF0"); + public static final Color Khaki = new Color("0xF0E68C"); + public static final Color Lavender = new Color("0xE6E6FA"); + public static final Color LavenderBlush = new Color("0xFFF0F5"); + public static final Color LawnGreen = new Color("0x7CFC00"); + public static final Color LemonChiffon = new Color("0xFFFACD"); + public static final Color LightBlue = new Color("0xADD8E6"); + public static final Color LightCoral = new Color("0xF08080"); + public static final Color LightCyan = new Color("0xE0FFFF"); + public static final Color LightGoldenRodYellow = new Color("0xFAFAD2"); + public static final Color LightGreen = new Color("0x90EE90"); + public static final Color LightGrey = new Color("0xD3D3D3"); + public static final Color LightPink = new Color("0xFFB6C1"); + public static final Color LightSalmon = new Color("0xFFA07A"); + public static final Color LightSeaGreen = new Color("0x20B2AA"); + public static final Color LightSkyBlue = new Color("0x87CEFA"); + public static final Color LightSlateGray = new Color("0x778899"); + public static final Color LightSteelBlue = new Color("0xB0C4DE"); + public static final Color LightYellow = new Color("0xFFFFE0"); + public static final Color Lime = new Color("0x00FF00"); + public static final Color LimeGreen = new Color("0x32CD32"); + public static final Color Linen = new Color("0xFAF0E6"); + public static final Color Magenta = new Color("0xFF00FF"); + public static final Color Maroon = new Color("0x800000"); + public static final Color MediumAquaMarine = new Color("0x66CDAA"); + public static final Color MediumBlue = new Color("0x0000CD"); + public static final Color MediumOrchid = new Color("0xBA55D3"); + public static final Color MediumPurple = new Color("0x9370D8"); + public static final Color MediumSeaGreen = new Color("0x3CB371"); + public static final Color MediumSlateBlue = new Color("0x7B68EE"); + public static final Color MediumSpringGreen = new Color("0x00FA9A"); + public static final Color MediumTurquoise = new Color("0x48D1CC"); + public static final Color MediumVioletRed = new Color("0xC71585"); + public static final Color MidnightBlue = new Color("0x191970"); + public static final Color MintCream = new Color("0xF5FFFA"); + public static final Color MistyRose = new Color("0xFFE4E1"); + public static final Color Moccasin = new Color("0xFFE4B5"); + public static final Color NavajoWhite = new Color("0xFFDEAD"); + public static final Color Navy = new Color("0x000080"); + public static final Color OldLace = new Color("0xFDF5E6"); + public static final Color Olive = new Color("0x808000"); + public static final Color OliveDrab = new Color("0x6B8E23"); + public static final Color Orange = new Color("0xFFA500"); + public static final Color OrangeRed = new Color("0xFF4500"); + public static final Color Orchid = new Color("0xDA70D6"); + public static final Color PaleGoldenRod = new Color("0xEEE8AA"); + public static final Color PaleGreen = new Color("0x98FB98"); + public static final Color PaleTurquoise = new Color("0xAFEEEE"); + public static final Color PaleVioletRed = new Color("0xD87093"); + public static final Color PapayaWhip = new Color("0xFFEFD5"); + public static final Color PeachPuff = new Color("0xFFDAB9"); + public static final Color Peru = new Color("0xCD853F"); + public static final Color Pink = new Color("0xFFC0CB"); + public static final Color Plum = new Color("0xDDA0DD"); + public static final Color PowderBlue = new Color("0xB0E0E6"); + public static final Color Purple = new Color("0x800080"); + public static final Color Red = new Color("0xFF0000"); + public static final Color RosyBrown = new Color("0xBC8F8F"); + public static final Color RoyalBlue = new Color("0x4169E1"); + public static final Color SaddleBrown = new Color("0x8B4513"); + public static final Color Salmon = new Color("0xFA8072"); + public static final Color SandyBrown = new Color("0xF4A460"); + public static final Color SeaGreen = new Color("0x2E8B57"); + public static final Color SeaShell = new Color("0xFFF5EE"); + public static final Color Sienna = new Color("0xA0522D"); + public static final Color Silver = new Color("0xC0C0C0"); + public static final Color SkyBlue = new Color("0x87CEEB"); + public static final Color SlateBlue = new Color("0x6A5ACD"); + public static final Color SlateGray = new Color("0x708090"); + public static final Color Snow = new Color("0xFFFAFA"); + public static final Color SpringGreen = new Color("0x00FF7F"); + public static final Color SteelBlue = new Color("0x4682B4"); + public static final Color Tan = new Color("0xD2B48C"); + public static final Color Teal = new Color("0x008080"); + public static final Color Thistle = new Color("0xD8BFD8"); + public static final Color Tomato = new Color("0xFF6347"); + public static final Color Turquoise = new Color("0x40E0D0"); + public static final Color Violet = new Color("0xEE82EE"); + public static final Color Wheat = new Color("0xF5DEB3"); + public static final Color White = new Color("0xFFFFFF"); + public static final Color WhiteSmoke = new Color("0xF5F5F5"); + public static final Color Yellow = new Color("0xFFFF00"); + public static final Color YellowGreen = new Color("0x9ACD32"); + + /** @param color color in RRGGBB syntax, like in html/css, but without the leading # character */ + public Color(String color) { + this.color = color; + } + + /** + * @param color color in #RRGGBB syntax, like in html/css, but without the leading # character + * @param alpha Alpha channel, Values from 00 up to FF, 00 means not transparent, FF means fully + * transparent + */ + public Color(String color, String alpha) { + this.color = color; + this.alpha = alpha; + } + + /** @return the color */ + public String getColor() { + return color; + } + + /** + * @param color the color to set + * @return this instance + */ + public Color setColor(String color) { + this.color = color; + return this; + } + + /** @return the alpha */ + public String getAlpha() { + return alpha; + } + + /** + * @param alpha the alpha to set + * @return this instance + */ + public Color setAlpha(String alpha) { + this.alpha = alpha; + return this; + } + + /** + * @return color in command line format + * */ + public String toString() { + return getFfmpegColor(); + } + + /** + * Deprecated in favor of toString + * @return The specified color in command line format. + */ + public String getFfmpegColor() { + return "0x" + color + alpha; + } +} diff --git a/jave-core/src/main/java/ws/schild/jave/filters/helpers/FadeDirection.java b/jave-core/src/main/java/ws/schild/jave/filters/helpers/FadeDirection.java new file mode 100644 index 0000000..9f9f8a3 --- /dev/null +++ b/jave-core/src/main/java/ws/schild/jave/filters/helpers/FadeDirection.java @@ -0,0 +1,16 @@ +package ws.schild.jave.filters.helpers; + +public enum FadeDirection { + IN("in"), + OUT("out"); + + private String friendlyName; + + private FadeDirection(String friendlyName) { + this.friendlyName = friendlyName; + } + + public String toString() { + return friendlyName; + } +} diff --git a/jave-core/src/main/java/ws/schild/jave/filters/helpers/ForceOriginalAspectRatio.java b/jave-core/src/main/java/ws/schild/jave/filters/helpers/ForceOriginalAspectRatio.java new file mode 100644 index 0000000..b3700ff --- /dev/null +++ b/jave-core/src/main/java/ws/schild/jave/filters/helpers/ForceOriginalAspectRatio.java @@ -0,0 +1,20 @@ +package ws.schild.jave.filters.helpers; + +public enum ForceOriginalAspectRatio { + /** + * Scale the video as specified and disable this feature. + */ + DISABLE, + /** + * The output video dimensions will automatically be decreased if needed. + */ + DECREASE, + /** + * The output video dimensions will automatically be increased if needed. + */ + INCREASE; + + public String getCommandLine() { + return name().toLowerCase(); + } +} diff --git a/jave-core/src/main/java/ws/schild/jave/filters/helpers/OverlayLocation.java b/jave-core/src/main/java/ws/schild/jave/filters/helpers/OverlayLocation.java new file mode 100644 index 0000000..92f6120 --- /dev/null +++ b/jave-core/src/main/java/ws/schild/jave/filters/helpers/OverlayLocation.java @@ -0,0 +1,41 @@ +package ws.schild.jave.filters.helpers; + +import java.util.Optional; + +/** + * Use this class to specify the starting location of your overlay. This accounts for video and + * overlay dimensions and still allows for relative offset. + * + * @author mressler + */ +public enum OverlayLocation { + TOP_LEFT(null, null), + TOP_RIGHT("main_w-overlay_w", null), + BOTTOM_RIGHT("main_w-overlay_w", "main_h-overlay_h"), + BOTTOM_LEFT(null, "main_h-overlay_h"); + + private Optional x; + private Optional y; + + private OverlayLocation(String x, String y) { + this.x = Optional.ofNullable(x); + this.y = Optional.ofNullable(y); + } + + public String getExpression(Optional offsetX, Optional offsetY) { + return getX(offsetX) + ":" + getY(offsetY); + } + + private static String resolveExpression(Optional location, Optional offset) { + Optional offsetValue = offset.map(Object::toString); + return location.map(loc -> loc.concat(offsetValue.orElse(""))).orElse(offsetValue.orElse("0")); + } + + public String getX(Optional offset) { + return resolveExpression(x, offset); + } + + public String getY(Optional offset) { + return resolveExpression(y, offset); + } +} diff --git a/jave-core/src/main/java/ws/schild/jave/info/AudioInfo.java b/jave-core/src/main/java/ws/schild/jave/info/AudioInfo.java new file mode 100644 index 0000000..1434c96 --- /dev/null +++ b/jave-core/src/main/java/ws/schild/jave/info/AudioInfo.java @@ -0,0 +1,137 @@ +/* + * JAVE - A Java Audio/Video Encoder (based on FFMPEG) + * + * Copyright (C) 2008-2009 Carlo Pelliccia (www.sauronsoftware.it) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package ws.schild.jave.info; + +/** + * Instances of this class report informations about an audio stream that can be decoded. + * + * @author Carlo Pelliccia + */ +public class AudioInfo { + + /** The audio stream decoder name. */ + private String decoder; + + /** The audio stream sampling rate. If less than 0, this information is not available. */ + private int samplingRate = -1; + + /** + * The audio stream channels number (1=mono, 2=stereo). If less than 0, this information is not + * available. + */ + private int channels = -1; + + /** The audio stream (average) bit rate. If less than 0, this information is not available. */ + private int bitRate = -1; + + /** + * Returns the audio stream decoder name. + * + * @return The audio stream decoder name. + */ + public String getDecoder() { + return decoder; + } + + /** + * Sets the audio stream decoder name. + * + * @param format The audio stream format name. + * @return this instance + */ + public AudioInfo setDecoder(String format) { + this.decoder = format; + return this; + } + + /** + * Returns the audio stream sampling rate. If less than 0, this information is not available. + * + * @return The audio stream sampling rate. + */ + public int getSamplingRate() { + return samplingRate; + } + + /** + * Sets the audio stream sampling rate. + * + * @param samplingRate The audio stream sampling rate. + * @return this instance + */ + public AudioInfo setSamplingRate(int samplingRate) { + this.samplingRate = samplingRate; + return this; + } + + /** + * Returns the audio stream channels number (1=mono, 2=stereo). If less than 0, this information + * is not available. + * + * @return the channels The audio stream channels number (1=mono, 2=stereo). + */ + public int getChannels() { + return channels; + } + + /** + * Sets the audio stream channels number (1=mono, 2=stereo). + * + * @param channels The audio stream channels number (1=mono, 2=stereo). + * @return this instance + */ + public AudioInfo setChannels(int channels) { + this.channels = channels; + return this; + } + + /** + * Returns the audio stream (average) bit rate. If less than 0, this information is not available. + * + * @return The audio stream (average) bit rate. + */ + public int getBitRate() { + return bitRate; + } + + /** + * Sets the audio stream (average) bit rate. + * + * @param bitRate The audio stream (average) bit rate. + * @return this instance + */ + public AudioInfo setBitRate(int bitRate) { + this.bitRate = bitRate; + return this; + } + + @Override + public String toString() { + return getClass().getName() + + " (decoder=" + + decoder + + ", samplingRate=" + + samplingRate + + ", channels=" + + channels + + ", bitRate=" + + bitRate + + ")"; + } +} diff --git a/jave-core/src/main/java/ws/schild/jave/info/MultimediaInfo.java b/jave-core/src/main/java/ws/schild/jave/info/MultimediaInfo.java new file mode 100644 index 0000000..4f47033 --- /dev/null +++ b/jave-core/src/main/java/ws/schild/jave/info/MultimediaInfo.java @@ -0,0 +1,139 @@ +/* + * JAVE - A Java Audio/Video Encoder (based on FFMPEG) + * + * Copyright (C) 2008-2009 Carlo Pelliccia (www.sauronsoftware.it) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package ws.schild.jave.info; + +/** + * Instances of this class report informations about a decoded multimedia file. + * + * @author Carlo Pelliccia + */ +public class MultimediaInfo { + + /** The multimedia file format name. */ + private String format = null; + + /** The stream duration in millis. If less than 0 this information is not available. */ + private long duration = -1; + + /** + * A set of audio-specific informations. If null, there's no audio stream in the multimedia file. + */ + private AudioInfo audio = null; + + /** + * A set of video-specific informations. If null, there's no video stream in the multimedia file. + */ + private VideoInfo video = null; + + /** + * Returns the multimedia file format name. + * + * @return The multimedia file format name. + */ + public String getFormat() { + return format; + } + + /** + * Sets the multimedia file format name. + * + * @param format The multimedia file format name. + * @return this instance + */ + public MultimediaInfo setFormat(String format) { + this.format = format; + return this; + } + + /** + * Returns the stream duration in millis. If less than 0 this information is not available. + * + * @return The stream duration in millis. If less than 0 this information is not available. + */ + public long getDuration() { + return duration; + } + + /** + * Sets the stream duration in millis. + * + * @param duration The stream duration in millis. + * @return this instance + */ + public MultimediaInfo setDuration(long duration) { + this.duration = duration; + return this; + } + + /** + * Returns a set of audio-specific informations. If null, there's no audio stream in the + * multimedia file. + * + * @return A set of audio-specific informations. + */ + public AudioInfo getAudio() { + return audio; + } + + /** + * Sets a set of audio-specific informations. + * + * @param audio A set of audio-specific informations. + * @return this instance + */ + public MultimediaInfo setAudio(AudioInfo audio) { + this.audio = audio; + return this; + } + + /** + * Returns a set of video-specific informations. If null, there's no video stream in the + * multimedia file. + * + * @return A set of audio-specific informations. + */ + public VideoInfo getVideo() { + return video; + } + + /** + * Sets a set of video-specific informations. + * + * @param video A set of video-specific informations. + * @return this instance + */ + public MultimediaInfo setVideo(VideoInfo video) { + this.video = video; + return this; + } + + @Override + public String toString() { + return getClass().getName() + + " (format=" + + format + + ", duration=" + + duration + + ", video=" + + video + + ", audio=" + + audio + + ")"; + } +} diff --git a/jave-core/src/main/java/ws/schild/jave/info/VideoInfo.java b/jave-core/src/main/java/ws/schild/jave/info/VideoInfo.java new file mode 100644 index 0000000..66e8e52 --- /dev/null +++ b/jave-core/src/main/java/ws/schild/jave/info/VideoInfo.java @@ -0,0 +1,133 @@ +/* + * JAVE - A Java Audio/Video Encoder (based on FFMPEG) + * + * Copyright (C) 2008-2009 Carlo Pelliccia (www.sauronsoftware.it) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package ws.schild.jave.info; + +/** + * Instances of this class report informations about a video stream that can be decoded. + * + * @author Carlo Pelliccia + */ +public class VideoInfo { + + /** The video stream decoder name. */ + private String decoder; + + /** The video size. If null this information is not available. */ + private VideoSize size = null; + + /** The video stream (average) bit rate. If less than 0, this information is not available. */ + private int bitRate = -1; + + /** The video frame rate. If less than 0 this information is not available. */ + private float frameRate = -1; + + /** + * Returns the video stream decoder name. + * + * @return The video stream decoder name. + */ + public String getDecoder() { + return decoder; + } + + /** + * Sets the video stream decoder name. + * + * @param codec The video stream decoder name. + * @return this instance + */ + public VideoInfo setDecoder(String codec) { + this.decoder = codec; + return this; + } + + /** + * Returns the video size. If null this information is not available. + * + * @return the size The video size. + */ + public VideoSize getSize() { + return size; + } + + /** + * Sets the video size. + * + * @param size The video size. + * @return this instance + */ + public VideoInfo setSize(VideoSize size) { + this.size = size; + return this; + } + + /** + * Returns the video frame rate. If less than 0 this information is not available. + * + * @return The video frame rate. + */ + public float getFrameRate() { + return frameRate; + } + + /** + * Sets the video frame rate. + * + * @param frameRate The video frame rate. + * @return this instance + */ + public VideoInfo setFrameRate(float frameRate) { + this.frameRate = frameRate; + return this; + } + + /** + * Returns the video stream (average) bit rate. If less than 0, this information is not available. + * + * @return The video stream (average) bit rate. + */ + public int getBitRate() { + return bitRate; + } + + /** + * Sets the video stream (average) bit rate. + * + * @param bitRate The video stream (average) bit rate. + * @return this instance + */ + public VideoInfo setBitRate(int bitRate) { + this.bitRate = bitRate; + return this; + } + + @Override + public String toString() { + return getClass().getName() + + " (decoder=" + + decoder + + ", size=" + + size + + ", bitRate=" + + bitRate + + ", frameRate=" + + frameRate + + ")"; + } +} diff --git a/jave-core/src/main/java/ws/schild/jave/info/VideoSize.java b/jave-core/src/main/java/ws/schild/jave/info/VideoSize.java new file mode 100644 index 0000000..4008399 --- /dev/null +++ b/jave-core/src/main/java/ws/schild/jave/info/VideoSize.java @@ -0,0 +1,133 @@ +/* + * JAVE - A Java Audio/Video Encoder (based on FFMPEG) + * + * Copyright (C) 2008-2009 Carlo Pelliccia (www.sauronsoftware.it) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package ws.schild.jave.info; + +import java.io.Serializable; + +/** + * Instances of this class report informations about videos size. + * + * @author Carlo Pelliccia + */ +public class VideoSize implements Serializable { + + private static final long serialVersionUID = 1L; + + /** The video width. */ + private final Integer width; + + /** The video height. */ + private final Integer height; + + /** + * It builds the bean. + * + * @param width The video width. + * @param height The video height. + */ + public VideoSize(int width, int height) { + this.width = width; + this.height = height; + } + + public static final VideoSize ntsc = new VideoSize(720, 480); + public static final VideoSize pal = new VideoSize(720, 576); + public static final VideoSize qntsc = new VideoSize(352, 240); + public static final VideoSize qpal = new VideoSize(352, 288); + public static final VideoSize sntsc = new VideoSize(640, 480); + public static final VideoSize spal = new VideoSize(768, 576); + public static final VideoSize film = new VideoSize(352, 240); + public static final VideoSize ntsc_film = new VideoSize(352, 240); + public static final VideoSize sqcif = new VideoSize(128, 96); + public static final VideoSize qcif = new VideoSize(176, 144); + public static final VideoSize cif = new VideoSize(352, 288); + public static final VideoSize FOUR_cif = new VideoSize(704, 576); + public static final VideoSize SIXTEEN_cif = new VideoSize(1408, 1152); + public static final VideoSize qqvga = new VideoSize(160, 120); + public static final VideoSize qvga = new VideoSize(320, 240); + public static final VideoSize vga = new VideoSize(640, 480); + public static final VideoSize svga = new VideoSize(800, 600); + public static final VideoSize xga = new VideoSize(1024, 768); + public static final VideoSize uxga = new VideoSize(1600, 1200); + public static final VideoSize qxga = new VideoSize(2048, 1536); + public static final VideoSize sxga = new VideoSize(1280, 1024); + public static final VideoSize qsxga = new VideoSize(2560, 2048); + public static final VideoSize hsxga = new VideoSize(5120, 4096); + public static final VideoSize wvga = new VideoSize(852, 480); + public static final VideoSize wxga = new VideoSize(1366, 768); + public static final VideoSize wsxga = new VideoSize(1600, 1024); + public static final VideoSize wuxga = new VideoSize(1920, 1200); + public static final VideoSize woxga = new VideoSize(2560, 1600); + public static final VideoSize wqsxga = new VideoSize(3200, 2048); + public static final VideoSize wquxga = new VideoSize(3840, 2400); + public static final VideoSize whsxga = new VideoSize(6400, 4096); + public static final VideoSize whuxga = new VideoSize(7680, 4800); + public static final VideoSize cga = new VideoSize(320, 200); + public static final VideoSize ega = new VideoSize(640, 350); + public static final VideoSize hd480 = new VideoSize(852, 480); + public static final VideoSize hd720 = new VideoSize(1280, 720); + public static final VideoSize hd1080 = new VideoSize(1920, 1080); + public static final VideoSize TWOk = new VideoSize(2048, 1080); + public static final VideoSize TWOkflat = new VideoSize(1998, 1080); + public static final VideoSize TWOkscope = new VideoSize(2048, 858); + public static final VideoSize FOURk = new VideoSize(4096, 2160); + public static final VideoSize FOURkflat = new VideoSize(3996, 2160); + public static final VideoSize FOURkscope = new VideoSize(4096, 1716); + public static final VideoSize nhd = new VideoSize(640, 360); + public static final VideoSize hqvga = new VideoSize(240, 160); + public static final VideoSize wqvga = new VideoSize(400, 240); + public static final VideoSize fwqvga = new VideoSize(432, 240); + public static final VideoSize hvga = new VideoSize(480, 320); + public static final VideoSize qhd = new VideoSize(960, 540); + public static final VideoSize TWOkdci = new VideoSize(2048, 1080); + public static final VideoSize FOURkdci = new VideoSize(4096, 2160); + public static final VideoSize uhd2160 = new VideoSize(3840, 2160); + public static final VideoSize uhd4320 = new VideoSize(7680, 4320); + + /** + * Returns the video width. + * + * @return The video width. + */ + public Integer getWidth() { + return width; + } + + /** + * Returns the video height. + * + * @return The video height. + */ + public Integer getHeight() { + return height; + } + + @Override + public String toString() { + return getClass().getName() + " (width=" + width + ", height=" + height + ")"; + } + + public String asEncoderArgument() { + return getWidth() + "x" + getHeight(); + } + + public String aspectRatioExpression() { + return "(" + getWidth() + "/" + getHeight() + ")"; + } +} diff --git a/jave-core/src/main/java/ws/schild/jave/process/ProcessKiller.java b/jave-core/src/main/java/ws/schild/jave/process/ProcessKiller.java index b1f3268..ba4a3a5 100644 --- a/jave-core/src/main/java/ws/schild/jave/process/ProcessKiller.java +++ b/jave-core/src/main/java/ws/schild/jave/process/ProcessKiller.java @@ -1,8 +1,8 @@ /* * JAVE - A Java Audio/Video Encoder (based on FFMPEG) - * + * * Copyright (C) 2008-2009 Carlo Pelliccia (www.sauronsoftware.it) - * + * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or @@ -19,33 +19,28 @@ package ws.schild.jave.process; /** - * A package-private utility to add a shutdown hook to kill ongoing encoding - * processes at the jvm shutdown. + * A package-private utility to add a shutdown hook to kill ongoing encoding processes at the jvm + * shutdown. * * @author Carlo Pelliccia */ public class ProcessKiller extends Thread { - /** - * The process to kill. - */ - private final Process process; - - /** - * Builds the killer. - * - * @param process The process to kill. - */ - public ProcessKiller(Process process) { - this.process = process; - } + /** The process to kill. */ + private final Process process; - /** - * It kills the supplied process. - */ - @Override - public void run() { - process.destroy(); - } + /** + * Builds the killer. + * + * @param process The process to kill. + */ + public ProcessKiller(Process process) { + this.process = process; + } + /** It kills the supplied process. */ + @Override + public void run() { + process.destroy(); + } } diff --git a/jave-core/src/main/java/ws/schild/jave/process/ProcessLocator.java b/jave-core/src/main/java/ws/schild/jave/process/ProcessLocator.java index 40801c0..3510df8 100644 --- a/jave-core/src/main/java/ws/schild/jave/process/ProcessLocator.java +++ b/jave-core/src/main/java/ws/schild/jave/process/ProcessLocator.java @@ -1,8 +1,8 @@ /* * JAVE - A Java Audio/Video Encoder (based on FFMPEG) - * + * * Copyright (C) 2008-2009 Carlo Pelliccia (www.sauronsoftware.it) - * + * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or @@ -21,31 +21,28 @@ import ws.schild.jave.Encoder; /** - * Abstract class whose derived concrete instances are used by {@link Encoder} - * to locate the ffmpeg executable path. + * Abstract class whose derived concrete instances are used by {@link Encoder} to locate the ffmpeg + * executable path. * * @author Carlo Pelliccia * @see Encoder */ public interface ProcessLocator { - /** - * This method should return the path of a ffmpeg executable suitable for - * the current machine. - * - * @return The path of the ffmpeg executable. - */ - public String getExecutablePath(); - - /** - * It returns a brand new {@link ProcessWrapper}, ready to be used in a - * ffmpeg call. - * - * @return A newly instanced {@link ProcessWrapper}, using this locator to - * call the ffmpeg executable. - */ - default public ProcessWrapper createExecutor() { - return new ProcessWrapper(getExecutablePath()); - } + /** + * This method should return the path of a ffmpeg executable suitable for the current machine. + * + * @return The path of the ffmpeg executable. + */ + public String getExecutablePath(); + /** + * It returns a brand new {@link ProcessWrapper}, ready to be used in a ffmpeg call. + * + * @return A newly instanced {@link ProcessWrapper}, using this locator to call the ffmpeg + * executable. + */ + public default ProcessWrapper createExecutor() { + return new ProcessWrapper(getExecutablePath()); + } } diff --git a/jave-core/src/main/java/ws/schild/jave/process/ProcessWrapper.java b/jave-core/src/main/java/ws/schild/jave/process/ProcessWrapper.java index dc4a86d..d886402 100644 --- a/jave-core/src/main/java/ws/schild/jave/process/ProcessWrapper.java +++ b/jave-core/src/main/java/ws/schild/jave/process/ProcessWrapper.java @@ -1,8 +1,8 @@ /* * JAVE - A Java Audio/Video Encoder (based on FFMPEG) - * + * * Copyright (C) 2008-2009 Carlo Pelliccia (www.sauronsoftware.it) - * + * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or @@ -22,6 +22,10 @@ import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -30,212 +34,193 @@ * * @author Carlo Pelliccia */ -public class ProcessWrapper { - - private static final Logger LOG = LoggerFactory.getLogger(ProcessWrapper.class); - - /** - * The path of the ffmpeg executable. - */ - private final String ffmpegExecutablePath; - - /** - * Arguments for the executable. - */ - private final ArrayList args = new ArrayList<>(); - - /** - * The process representing the ffmpeg execution. - */ - private Process ffmpeg = null; - - /** - * A process killer to kill the ffmpeg process with a shutdown hook, useful - * if the jvm execution is shutted down during an ongoing encoding process. - */ - private ProcessKiller ffmpegKiller = null; - - /** - * A stream reading from the ffmpeg process standard output channel. - */ - private InputStream inputStream = null; - - /** - * A stream writing in the ffmpeg process standard input channel. - */ - private OutputStream outputStream = null; - - /** - * A stream reading from the ffmpeg process standard error channel. - */ - private InputStream errorStream = null; - - /** - * It build the executor. - * - * @param ffmpegExecutablePath The path of the ffmpeg executable. - */ - public ProcessWrapper(String ffmpegExecutablePath) { - this.ffmpegExecutablePath = ffmpegExecutablePath; +public class ProcessWrapper implements AutoCloseable { + + private static final Logger LOG = LoggerFactory.getLogger(ProcessWrapper.class); + + /** The path of the ffmpeg executable. */ + private final String ffmpegExecutablePath; + + /** Arguments for the executable. */ + private final ArrayList args = new ArrayList<>(); + + /** The process representing the ffmpeg execution. */ + private Process ffmpeg = null; + + /** + * A process killer to kill the ffmpeg process with a shutdown hook, useful if the jvm execution + * is shutted down during an ongoing encoding process. + */ + private ProcessKiller ffmpegKiller = null; + + /** A stream reading from the ffmpeg process standard output channel. */ + private InputStream inputStream = null; + + /** A stream writing in the ffmpeg process standard input channel. */ + private OutputStream outputStream = null; + + /** A stream reading from the ffmpeg process standard error channel. */ + private InputStream errorStream = null; + + /** + * It build the executor. + * + * @param ffmpegExecutablePath The path of the ffmpeg executable. + */ + public ProcessWrapper(String ffmpegExecutablePath) { + this.ffmpegExecutablePath = ffmpegExecutablePath; + } + + /** + * Adds an argument to the ffmpeg executable call. + * + * @param arg The argument. + */ + public void addArgument(String arg) { + args.add(arg); + } + + /** + * Executes the ffmpeg process with the previous given arguments. + * + * @param destroyOnRuntimeShutdown destroy process if the runtime VM is shutdown + * @param openIOStreams Open IO streams for input/output and errorout, should be false when + * destroyOnRuntimeShutdown is false too + * @throws IOException If the process call fails. + */ + public void execute(boolean destroyOnRuntimeShutdown, boolean openIOStreams) throws IOException { + Stream execArgs = Stream.concat(Stream.of(ffmpegExecutablePath), args.stream()); + + execArgs = enhanceArguments(execArgs); + + List execList = execArgs.collect(Collectors.toList()); + + if (LOG.isDebugEnabled()) { + LOG.debug("About to execute {}", execList.stream().collect(Collectors.joining(" "))); } - /** - * Adds an argument to the ffmpeg executable call. - * - * @param arg The argument. - */ - public void addArgument(String arg) { - args.add(arg); + Runtime runtime = Runtime.getRuntime(); + ffmpeg = runtime.exec(execList.toArray(new String[0])); + + if (destroyOnRuntimeShutdown) { + ffmpegKiller = new ProcessKiller(ffmpeg); + runtime.addShutdownHook(ffmpegKiller); } - /** - * Executes the ffmpeg process with the previous given arguments. - * - * @param destroyOnRuntimeShutdown destroy process if the runtime VM is shutdown - * @param openIOStreams Open IO streams for input/output and errorout, - * should be false when destroyOnRuntimeShutdown is false too - * @throws IOException If the process call fails. - */ - public void execute(boolean destroyOnRuntimeShutdown, boolean openIOStreams) throws IOException { - int argsSize = args.size(); - String[] cmd = new String[argsSize + 2]; - cmd[0] = ffmpegExecutablePath; - for (int i = 0; i < argsSize; i++) - { - cmd[i + 1] = args.get(i); - } - cmd[argsSize + 1] = "-hide_banner"; // Don't show banner - if (LOG.isDebugEnabled()) - { - StringBuilder sb = new StringBuilder(); - for (String c : cmd) - { - sb.append(c); - sb.append(' '); - } - LOG.debug("About to execute {}", sb.toString()); - } - Runtime runtime = Runtime.getRuntime(); - ffmpeg = runtime.exec(cmd); - if (destroyOnRuntimeShutdown) - { - ffmpegKiller = new ProcessKiller(ffmpeg); - runtime.addShutdownHook(ffmpegKiller); - } - if (openIOStreams) - { - inputStream = ffmpeg.getInputStream(); - outputStream = ffmpeg.getOutputStream(); - errorStream = ffmpeg.getErrorStream(); - } + if (openIOStreams) { + inputStream = ffmpeg.getInputStream(); + outputStream = ffmpeg.getOutputStream(); + errorStream = ffmpeg.getErrorStream(); } - - /** - * Executes the ffmpeg process with the previous given arguments. - * Default to kill processes when the JVM terminates, and the various - * IOStreams are opened as required - * - * @throws IOException If the process call fails. - */ - public void execute() throws IOException { - execute(true, true); + } + + /** + * Provide an opportunity for subclasses to enhance the argument list before passing off to + * execute. + * + * @param execArgs The current Stream of arguments + * @return A possibly enhanced stream of arguments + */ + protected Stream enhanceArguments(Stream execArgs) { + return execArgs; + } + + /** + * Executes the ffmpeg process with the previous given arguments. Default to kill processes when + * the JVM terminates, and the various IOStreams are opened as required + * + * @throws IOException If the process call fails. + */ + public void execute() throws IOException { + execute(true, true); + } + + /** + * Returns a stream reading from the ffmpeg process standard output channel. + * + * @return A stream reading from the ffmpeg process standard output channel. + */ + public InputStream getInputStream() { + return inputStream; + } + + /** + * Returns a stream writing in the ffmpeg process standard input channel. + * + * @return A stream writing in the ffmpeg process standard input channel. + */ + public OutputStream getOutputStream() { + return outputStream; + } + + /** + * Returns a stream reading from the ffmpeg process standard error channel. + * + * @return A stream reading from the ffmpeg process standard error channel. + */ + public InputStream getErrorStream() { + return errorStream; + } + + /** If there's a ffmpeg execution in progress, it kills it. */ + public void destroy() { + if (inputStream != null) { + try { + inputStream.close(); + } catch (Throwable t) { + LOG.warn("Error closing input stream", t); + } + inputStream = null; } - /** - * Returns a stream reading from the ffmpeg process standard output channel. - * - * @return A stream reading from the ffmpeg process standard output channel. - */ - public InputStream getInputStream() { - return inputStream; + if (outputStream != null) { + try { + outputStream.close(); + } catch (Throwable t) { + LOG.warn("Error closing output stream", t); + } + outputStream = null; } - /** - * Returns a stream writing in the ffmpeg process standard input channel. - * - * @return A stream writing in the ffmpeg process standard input channel. - */ - public OutputStream getOutputStream() { - return outputStream; + if (errorStream != null) { + try { + errorStream.close(); + } catch (Throwable t) { + LOG.warn("Error closing error stream", t); + } + errorStream = null; } - /** - * Returns a stream reading from the ffmpeg process standard error channel. - * - * @return A stream reading from the ffmpeg process standard error channel. - */ - public InputStream getErrorStream() { - return errorStream; + if (ffmpeg != null) { + ffmpeg.destroy(); + ffmpeg = null; } - /** - * If there's a ffmpeg execution in progress, it kills it. - */ - public void destroy() { - if (inputStream != null) - { - try - { - inputStream.close(); - } catch (Throwable t) - { - LOG.warn("Error closing input stream", t); - } - inputStream = null; - } - if (outputStream != null) - { - try - { - outputStream.close(); - } catch (Throwable t) - { - LOG.warn("Error closing output stream", t); - } - outputStream = null; - } - if (errorStream != null) - { - try - { - errorStream.close(); - } catch (Throwable t) - { - LOG.warn("Error closing error stream", t); - } - errorStream = null; - } - if (ffmpeg != null) - { - ffmpeg.destroy(); - ffmpeg = null; - } - if (ffmpegKiller != null) - { - Runtime runtime = Runtime.getRuntime(); - runtime.removeShutdownHook(ffmpegKiller); - ffmpegKiller = null; - } + if (ffmpegKiller != null) { + Runtime runtime = Runtime.getRuntime(); + runtime.removeShutdownHook(ffmpegKiller); + ffmpegKiller = null; } - - /** - * Return the exit code of the ffmpeg process - * If the process is not yet terminated, it waits for the termination - * of the process - * - * @return process exit code - */ - public int getProcessExitCode() - { - // Make sure it's terminated - try - { - ffmpeg.waitFor(); - } - catch (InterruptedException ex) - { - LOG.warn("Interrupted during waiting on process, forced shutdown?", ex); - } - return ffmpeg.exitValue(); + } + + /** + * Return the exit code of the ffmpeg process If the process is not yet terminated, it waits for + * the termination of the process + * + * @return process exit code + */ + public int getProcessExitCode() { + // Make sure it's terminated + try { + ffmpeg.waitFor(); + } catch (InterruptedException ex) { + LOG.warn("Interrupted during waiting on process, forced shutdown?", ex); } + return ffmpeg.exitValue(); + } + + @Override + public void close() { + destroy(); + } } diff --git a/jave-core/src/main/java/ws/schild/jave/process/ffmpeg/DefaultFFMPEGLocator.java b/jave-core/src/main/java/ws/schild/jave/process/ffmpeg/DefaultFFMPEGLocator.java new file mode 100644 index 0000000..5657a48 --- /dev/null +++ b/jave-core/src/main/java/ws/schild/jave/process/ffmpeg/DefaultFFMPEGLocator.java @@ -0,0 +1,173 @@ +/* + * JAVE - A Java Audio/Video Encoder (based on FFMPEG) + * + * Copyright (C) 2008-2009 Carlo Pelliccia (www.sauronsoftware.it) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package ws.schild.jave.process.ffmpeg; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import ws.schild.jave.Version; + +import ws.schild.jave.process.ProcessLocator; +import ws.schild.jave.process.ProcessWrapper; + +/** + * The default ffmpeg executable locator, which exports on disk the ffmpeg executable bundled with + * the library distributions. It should work both for windows and many linux distributions. If it + * doesn't, try compiling your own ffmpeg executable and plug it in JAVE with a custom {@link + * FFMPEGProcess} + * + * @author Carlo Pelliccia + */ +public class DefaultFFMPEGLocator implements ProcessLocator { + + private static final Logger LOG = LoggerFactory.getLogger(DefaultFFMPEGLocator.class); + + /** The ffmpeg executable file path. */ + private final String path; + + /** It builds the default FFMPEGLocator, exporting the ffmpeg executable on a temp file. */ + public DefaultFFMPEGLocator() { + String os = System.getProperty("os.name").toLowerCase(); + boolean isWindows = os.contains("windows"); + boolean isMac = os.contains("mac"); + LOG.debug("Os name is <{}> isWindows: {} isMac: {}", os, isWindows, isMac); + + // Dir Folder + File dirFolder = new File(System.getProperty("java.io.tmpdir"), "jave/"); + if (!dirFolder.exists()) { + LOG.debug( + "Creating jave temp folder to place executables in <{}>", dirFolder.getAbsolutePath()); + dirFolder.mkdirs(); + } else { + LOG.debug("Jave temp folder exists in <{}>", dirFolder.getAbsolutePath()); + } + + // -----------------ffmpeg executable export on disk.----------------------------- + String suffix = isWindows ? ".exe" : (isMac ? "-osx" : ""); + String arch = System.getProperty("os.arch"); + + // File + File ffmpegFile = new File(dirFolder, "ffmpeg-" + arch + "-" + Version.getVersion() + suffix); + LOG.debug("Executable path: {}", ffmpegFile.getAbsolutePath()); + + // Check the version of existing .exe file + if (ffmpegFile.exists()) { + // OK, already present + LOG.debug("Executable exists in <{}>", ffmpegFile.getAbsolutePath()); + } else { + LOG.debug("Need to copy executable to <{}>", ffmpegFile.getAbsolutePath()); + copyFile("ffmpeg-" + arch + suffix, ffmpegFile); + } + + // Need a chmod? + if (!isWindows) { + try { + Runtime.getRuntime().exec(new String[] {"/bin/chmod", "755", ffmpegFile.getAbsolutePath()}); + } catch (IOException e) { + LOG.error("Error setting executable via chmod", e); + } + } + + // Everything seems okay + path = ffmpegFile.getAbsolutePath(); + LOG.debug("ffmpeg executable found: {}", path); + } + + @Override + public String getExecutablePath() { + return path; + } + + /** + * Copies a file bundled in the package to the supplied destination. + * + * @param path The name of the bundled file. + * @param dest The destination. + * @throws RuntimeException If an unexpected error occurs. + */ + private void copyFile(String path, File dest) { + String resourceName = "nativebin/" + path; + try { + LOG.debug("Copy from resource <{}> to target <{}>", resourceName, dest.getAbsolutePath()); + InputStream is = getClass().getResourceAsStream(resourceName); + if (is == null) { + // Use this for Java 9+ only if required + resourceName = "ws/schild/jave/nativebin/" + path; + LOG.debug( + "Alternative copy from SystemResourceAsStream <{}> to target <{}>", + resourceName, + dest.getAbsolutePath()); + is = ClassLoader.getSystemResourceAsStream(resourceName); + } + if (is != null) { + if (copy(is, dest.getAbsolutePath())) { + if (dest.exists()) { + LOG.debug("Target <{}> exists", dest.getAbsolutePath()); + } else { + LOG.error("Target <{}> does not exist", dest.getAbsolutePath()); + } + } else { + LOG.error("Copy resource to target <{}> failed", dest.getAbsolutePath()); + } + try { + is.close(); + } catch (IOException ioex) { + LOG.warn("Error in closing input stream", ioex); + } + } else { + LOG.error("Could not find ffmpeg platform executable in resources for <{}>", resourceName); + } + } catch (NullPointerException ex) { + LOG.error( + "Could not find ffmpeg executable for {} is the correct platform jar included?", + resourceName); + throw ex; + } + } + + /** + * Copy a file from source to destination. + * + * @param source The name of the bundled file. + * @param destination the destination + * @return True if succeeded , False if not + */ + private boolean copy(InputStream source, String destination) { + boolean success = true; + + try { + Files.copy(source, Paths.get(destination), StandardCopyOption.REPLACE_EXISTING); + } catch (IOException ex) { + LOG.error("Cannot write file " + destination, ex); + success = false; + } + + return success; + } + + @Override + public ProcessWrapper createExecutor() { + return new FFMPEGProcess(getExecutablePath()); + } +} diff --git a/jave-core/src/main/java/ws/schild/jave/process/ffmpeg/FFMPEGProcess.java b/jave-core/src/main/java/ws/schild/jave/process/ffmpeg/FFMPEGProcess.java new file mode 100644 index 0000000..2db67ed --- /dev/null +++ b/jave-core/src/main/java/ws/schild/jave/process/ffmpeg/FFMPEGProcess.java @@ -0,0 +1,22 @@ +package ws.schild.jave.process.ffmpeg; + +import java.util.stream.Stream; + +import ws.schild.jave.process.ProcessWrapper; + +/** + * The standard FFMPEGProcess - enhances the ProcessWrapper by always suppressing the FFMPEG banner. + * + * @author mressler + */ +public class FFMPEGProcess extends ProcessWrapper { + + public FFMPEGProcess(String ffmpegExecutablePath) { + super(ffmpegExecutablePath); + } + + @Override + protected Stream enhanceArguments(Stream execArgs) { + return Stream.concat(execArgs, Stream.of("-hide_banner")); + } +} diff --git a/jave-core/src/main/java/ws/schild/jave/progress/EchoingEncoderProgressListener.java b/jave-core/src/main/java/ws/schild/jave/progress/EchoingEncoderProgressListener.java new file mode 100644 index 0000000..1d206cb --- /dev/null +++ b/jave-core/src/main/java/ws/schild/jave/progress/EchoingEncoderProgressListener.java @@ -0,0 +1,46 @@ +package ws.schild.jave.progress; + +import java.io.PrintStream; + +import ws.schild.jave.info.MultimediaInfo; + +/** + * A simple progress listener that will echo progress out to any PrintStream. + * + * @author mressler + */ +public class EchoingEncoderProgressListener implements EncoderProgressListener { + + private PrintStream out; + private String prefix; + + public EchoingEncoderProgressListener() { + out = System.out; + prefix = ""; + } + + public EchoingEncoderProgressListener(String prefix) { + this(); + this.prefix = prefix; + } + + public EchoingEncoderProgressListener(String prefix, PrintStream out) { + this(prefix); + this.out = out; + } + + @Override + public void sourceInfo(MultimediaInfo info) { + out.println(prefix + " source info: " + info); + } + + @Override + public void progress(int permil) { + out.println(prefix + " progress: " + permil); + } + + @Override + public void message(String message) { + out.println(prefix + " message: " + message); + } +} diff --git a/jave-core/src/main/java/ws/schild/jave/progress/EchoingProgressListener.java b/jave-core/src/main/java/ws/schild/jave/progress/EchoingProgressListener.java new file mode 100644 index 0000000..84d6551 --- /dev/null +++ b/jave-core/src/main/java/ws/schild/jave/progress/EchoingProgressListener.java @@ -0,0 +1,54 @@ +package ws.schild.jave.progress; + +import java.io.PrintStream; + +/** + * Simple class to echo progress to Standard out - or any PrintStream. + * + * @author mressler + */ +public class EchoingProgressListener implements VideoProgressListener { + + private String prefix; + private PrintStream out; + + public EchoingProgressListener() { + out = System.out; + prefix = ""; + } + + public EchoingProgressListener(String prefix) { + this(); + this.prefix = prefix; + } + + public EchoingProgressListener(String prefix, PrintStream out) { + this(prefix); + this.out = out; + } + + @Override + public void onBegin() { + out.println(prefix + " Beginning"); + } + + @Override + public void onMessage(String message) { + out.println(prefix + " Message Received: " + message); + } + + @Override + public void onProgress(Double progress) { + out.println(prefix + " Progress Notification: " + progress); + } + + @Override + public void onError(String message) { + out.println(prefix + " Error Encountered: " + message); + } + + @Override + public void onComplete() { + out.println(prefix + " Complete!"); + } +} diff --git a/jave-core/src/main/java/ws/schild/jave/progress/EncoderProgressAdapter.java b/jave-core/src/main/java/ws/schild/jave/progress/EncoderProgressAdapter.java new file mode 100644 index 0000000..e78f4b8 --- /dev/null +++ b/jave-core/src/main/java/ws/schild/jave/progress/EncoderProgressAdapter.java @@ -0,0 +1,29 @@ +package ws.schild.jave.progress; + +import ws.schild.jave.info.MultimediaInfo; + +public class EncoderProgressAdapter implements EncoderProgressListener { + + private VideoProgressListener listener; + + public EncoderProgressAdapter(VideoProgressListener listener) { + this.listener = listener; + } + + @Override + public void sourceInfo(MultimediaInfo info) { + if (info != null) { + listener.onMessage(info.toString()); + } + } + + @Override + public void progress(int permil) { + listener.onProgress(new Double(permil) / 100); + } + + @Override + public void message(String message) { + listener.onMessage(message); + } +} diff --git a/jave-core/src/main/java/ws/schild/jave/EncoderProgressListener.java b/jave-core/src/main/java/ws/schild/jave/progress/EncoderProgressListener.java similarity index 52% rename from jave-core/src/main/java/ws/schild/jave/EncoderProgressListener.java rename to jave-core/src/main/java/ws/schild/jave/progress/EncoderProgressListener.java index f63849e..24e2bb7 100644 --- a/jave-core/src/main/java/ws/schild/jave/EncoderProgressListener.java +++ b/jave-core/src/main/java/ws/schild/jave/progress/EncoderProgressListener.java @@ -1,8 +1,8 @@ /* * JAVE - A Java Audio/Video Encoder (based on FFMPEG) - * + * * Copyright (C) 2008-2009 Carlo Pelliccia (www.sauronsoftware.it) - * + * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or @@ -16,37 +16,37 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package ws.schild.jave; +package ws.schild.jave.progress; + +import ws.schild.jave.info.MultimediaInfo; /** - * Encoding progress listener interface. Instances of implementing classes could - * be used to listen an encoding process. + * Encoding progress listener interface. Instances of implementing classes could be used to listen + * an encoding process. * * @author Carlo Pelliccia */ public interface EncoderProgressListener { - /** - * This method is called before the encoding process starts, reporting - * information about the source stream that will be decoded and re-encoded. - * - * @param info Informations about the source multimedia stream. - */ - public void sourceInfo(MultimediaInfo info); - - /** - * This method is called to notify a progress in the encoding process. - * - * @param permil A permil value representing the encoding process progress. - */ - public void progress(int permil); + /** + * This method is called before the encoding process starts, reporting information about the + * source stream that will be decoded and re-encoded. + * + * @param info Informations about the source multimedia stream. + */ + public void sourceInfo(MultimediaInfo info); - /** - * This method is called every time the encoder need to send a message - * (usually, a warning). - * - * @param message The message sent by the encoder. - */ - public void message(String message); + /** + * This method is called to notify a progress in the encoding process. + * + * @param permil A permil value representing the encoding process progress. + */ + public void progress(int permil); + /** + * This method is called every time the encoder need to send a message (usually, a warning). + * + * @param message The message sent by the encoder. + */ + public void message(String message); } diff --git a/jave-core/src/main/java/ws/schild/jave/progress/VideoProgressListener.java b/jave-core/src/main/java/ws/schild/jave/progress/VideoProgressListener.java new file mode 100644 index 0000000..3ccf5fd --- /dev/null +++ b/jave-core/src/main/java/ws/schild/jave/progress/VideoProgressListener.java @@ -0,0 +1,38 @@ +package ws.schild.jave.progress; + +/** + * A VideoProgressListener is meant to share progress from potentially any number of + * EncoderProgressListeners. Because it would be hard to determine the overall status by just + * tracking successive progress from ffmpeg, an onBbegin and onComplete have been added. + * + * @author mressler + */ +public interface VideoProgressListener { + + /** It has begun! */ + public void onBegin(); + + /** + * Any messages that arise during the activity. + * + * @param message Whatever the process reported out. + */ + public void onMessage(String message); + + /** + * Meaningful progress has been made. + * + * @param progress Current percentage complete. (0-1) + */ + public void onProgress(Double progress); + + /** + * An error has occurred! + * + * @param message The error message reported by the process. + */ + public void onError(String message); + + /** It has ended! */ + public void onComplete(); +} diff --git a/jave-core/src/main/java/ws/schild/jave/utils/AutoRemoveableFile.java b/jave-core/src/main/java/ws/schild/jave/utils/AutoRemoveableFile.java new file mode 100644 index 0000000..ca18407 --- /dev/null +++ b/jave-core/src/main/java/ws/schild/jave/utils/AutoRemoveableFile.java @@ -0,0 +1,36 @@ +package ws.schild.jave.utils; + +import java.io.File; +import java.util.Arrays; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Use this class in a try-with-resources block to automatically delete the referenced file when + * this goes out of scope. + * + * @author mressler + */ +public class AutoRemoveableFile extends File implements AutoCloseable { + + private static Logger logger = LoggerFactory.getLogger(AutoRemoveableFile.class); + + private static final long serialVersionUID = 1270202558229293283L; + + public AutoRemoveableFile(File parent, String child) { + super(parent, child); + } + + @Override + public void close() { + boolean closed = delete(); + if (!closed) { + logger.warn( + "File " + + getAbsolutePath() + + " did not automatically delete itself: " + + Arrays.toString(Thread.currentThread().getStackTrace())); + } + } +} diff --git a/jave-core/src/main/java/ws/schild/jave/utils/RBufferedReader.java b/jave-core/src/main/java/ws/schild/jave/utils/RBufferedReader.java new file mode 100644 index 0000000..6d85a97 --- /dev/null +++ b/jave-core/src/main/java/ws/schild/jave/utils/RBufferedReader.java @@ -0,0 +1,66 @@ +/* + * JAVE - A Java Audio/Video Encoder (based on FFMPEG) + * + * Copyright (C) 2008-2009 Carlo Pelliccia (www.sauronsoftware.it) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package ws.schild.jave.utils; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.Reader; +import java.util.ArrayList; + +/** + * A package-private utility extending java.io.BufferedReader. If a line read with {@link + * RBufferedReader#readLine()} is not useful for the calling code, it can be re-inserted in the + * stream. The same line will be returned again at the next readLine() call. + * + * @author Carlo Pelliccia + */ +public class RBufferedReader extends BufferedReader { + + /** Re-inserted lines buffer. */ + private final ArrayList lines = new ArrayList<>(); + + /** + * It builds the reader. + * + * @param in The underlying reader. + */ + public RBufferedReader(Reader in) { + super(in); + } + + /** It returns the next line in the stream. */ + @Override + public String readLine() throws IOException { + if (lines.size() > 0) { + return lines.remove(0); + } else { + return super.readLine(); + } + } + + /** + * Reinserts a line in the stream. The line will be returned at the next {@link + * RBufferedReader#readLine()} call. + * + * @param line The line. + */ + public void reinsertLine(String line) { + lines.add(0, line); + } +} diff --git a/jave-core/src/main/java/ws/schild/jave/utils/Utils.java b/jave-core/src/main/java/ws/schild/jave/utils/Utils.java new file mode 100644 index 0000000..bf83916 --- /dev/null +++ b/jave-core/src/main/java/ws/schild/jave/utils/Utils.java @@ -0,0 +1,62 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package ws.schild.jave.utils; + +import java.text.DecimalFormat; + +/** @author a.schild */ +public class Utils { + + /** + * * https://www.ffmpeg.org/ffmpeg-utils.html#time-duration-syntax + * + *

Build a time/duration string based on the milisenconds passed in milis [-][HH:]MM:SS[.m...] + * or [-]S+[.m...] + * + * @param milis number of miliseconds, can be negative too + * @return String to be used for specifying positions in the video/audio file + */ + public static String buildTimeDuration(long milis) { + DecimalFormat df2 = new DecimalFormat("00"); + DecimalFormat df3 = new DecimalFormat("000"); + long milisPart = Math.abs(milis) % 1000; + long seconds = Math.abs(milis) / 1000; + long secondsPart = seconds % 60; + long minutes = seconds / 60; + long minutesPart = minutes % 60; + long hours = minutes / 60; + StringBuilder retVal = new StringBuilder(); + if (milis < 0) { + retVal.append("-"); + } + if (hours != 0) { + retVal.append(df2.format(hours)).append(":"); + } + if (minutesPart != 0 || hours != 0) { + retVal.append(df2.format(minutesPart)).append(":"); + } + retVal.append(df2.format(secondsPart)); + if (milisPart != 0) { + retVal.append(".").append(df3.format(milisPart)); + } + return retVal.toString(); + } + + /** + * Escape all special characters []=;, to be safe to use in command line + * + * @param argumentIn input argument to escape + * @return escaped string + */ + public static String escapeArgument(String argumentIn) { + String retVal = argumentIn.replace("[", "\\["); + retVal = retVal.replace("]", "\\]"); + retVal = retVal.replace("=", "\\="); + retVal = retVal.replace(":", "\\:"); + retVal = retVal.replace(",", "\\,"); + return retVal; + } +} diff --git a/jave-core/src/test/java/ws/schild/jave/UtilsTest.java b/jave-core/src/test/java/ws/schild/jave/UtilsTest.java index d2504d1..3b5090e 100644 --- a/jave-core/src/test/java/ws/schild/jave/UtilsTest.java +++ b/jave-core/src/test/java/ws/schild/jave/UtilsTest.java @@ -6,95 +6,94 @@ package ws.schild.jave; import org.junit.jupiter.api.Test; + +import ws.schild.jave.utils.Utils; + import static org.junit.jupiter.api.Assertions.*; -/** - * - * @author a.schild - */ +/** @author a.schild */ public class UtilsTest { - - public UtilsTest() { - } - @Test - public void testBuildTimeDuration00() { - System.out.println("buildTimeDuration00"); - long milis = 0L; - String expResult = "00"; - String result = Utils.buildTimeDuration(milis); - assertEquals(expResult, result); - } - - @Test - public void testBuildTimeDuration01() { - System.out.println("buildTimeDuration01"); - long milis = -1L; - String expResult = "-00.001"; - String result = Utils.buildTimeDuration(milis); - assertEquals(expResult, result); - } + public UtilsTest() {} + + @Test + public void testBuildTimeDuration00() { + System.out.println("buildTimeDuration00"); + long milis = 0L; + String expResult = "00"; + String result = Utils.buildTimeDuration(milis); + assertEquals(expResult, result); + } + + @Test + public void testBuildTimeDuration01() { + System.out.println("buildTimeDuration01"); + long milis = -1L; + String expResult = "-00.001"; + String result = Utils.buildTimeDuration(milis); + assertEquals(expResult, result); + } - @Test - public void testBuildTimeDuration02() { - System.out.println("buildTimeDuration02"); - long milis = 1L; - String expResult = "00.001"; - String result = Utils.buildTimeDuration(milis); - assertEquals(expResult, result); - } + @Test + public void testBuildTimeDuration02() { + System.out.println("buildTimeDuration02"); + long milis = 1L; + String expResult = "00.001"; + String result = Utils.buildTimeDuration(milis); + assertEquals(expResult, result); + } - @Test - public void testBuildTimeDuration03() { - System.out.println("buildTimeDuration03"); - long milis = 1000L; - String expResult = "01"; - String result = Utils.buildTimeDuration(milis); - assertEquals(expResult, result); - } + @Test + public void testBuildTimeDuration03() { + System.out.println("buildTimeDuration03"); + long milis = 1000L; + String expResult = "01"; + String result = Utils.buildTimeDuration(milis); + assertEquals(expResult, result); + } - @Test - public void testBuildTimeDuration04() { - System.out.println("buildTimeDuration04"); - long milis = 60000L; - String expResult = "01:00"; - String result = Utils.buildTimeDuration(milis); - assertEquals(expResult, result); - } + @Test + public void testBuildTimeDuration04() { + System.out.println("buildTimeDuration04"); + long milis = 60000L; + String expResult = "01:00"; + String result = Utils.buildTimeDuration(milis); + assertEquals(expResult, result); + } - @Test - public void testBuildTimeDuration05() { - System.out.println("buildTimeDuration05"); - long milis = 60001L; - String expResult = "01:00.001"; - String result = Utils.buildTimeDuration(milis); - assertEquals(expResult, result); - } + @Test + public void testBuildTimeDuration05() { + System.out.println("buildTimeDuration05"); + long milis = 60001L; + String expResult = "01:00.001"; + String result = Utils.buildTimeDuration(milis); + assertEquals(expResult, result); + } - @Test - public void testBuildTimeDuration06() { - System.out.println("buildTimeDuration06"); - long milis = 3600001L; - String expResult = "01:00:00.001"; - String result = Utils.buildTimeDuration(milis); - assertEquals(expResult, result); - } + @Test + public void testBuildTimeDuration06() { + System.out.println("buildTimeDuration06"); + long milis = 3600001L; + String expResult = "01:00:00.001"; + String result = Utils.buildTimeDuration(milis); + assertEquals(expResult, result); + } - @Test - public void testBuildTimeDuration07() { - System.out.println("buildTimeDuration07"); - long milis = -3600001L; - String expResult = "-01:00:00.001"; - String result = Utils.buildTimeDuration(milis); - assertEquals(expResult, result); - } + @Test + public void testBuildTimeDuration07() { + System.out.println("buildTimeDuration07"); + long milis = -3600001L; + String expResult = "-01:00:00.001"; + String result = Utils.buildTimeDuration(milis); + assertEquals(expResult, result); + } - @Test - public void testBuildTimeDuration08() { - System.out.println("buildTimeDuration08"); - long milis = -72000001L; - String expResult = "-20:00:00.001"; - String result = Utils.buildTimeDuration(milis); - assertEquals(expResult, result); - } + @Test + public void testBuildTimeDuration08() { + System.out.println("buildTimeDuration08"); + long milis = -72000001L; + String expResult = "-20:00:00.001"; + String result = Utils.buildTimeDuration(milis); + assertEquals(expResult, result); + } } diff --git a/jave-example/pom.xml b/jave-example/pom.xml index b21530e..4320573 100644 --- a/jave-example/pom.xml +++ b/jave-example/pom.xml @@ -3,7 +3,7 @@ 4.0.0 ws.schild jar - 2.8.0-SNAPSHOT + 3.0.0 jave-example 12 @@ -66,12 +66,12 @@ ws.schild jave-core - 2.8.0-SNAPSHOT + 3.0.0 ws.schild jave-nativebin-linux32 - 2.8.0-SNAPSHOT + 3.0.0 diff --git a/jave-example/src/main/java/ws/schild/jave/example/DefaultFFMPEGLocatorTest.java b/jave-example/src/main/java/ws/schild/jave/example/DefaultFFMPEGLocatorTest.java index 829c146..e0a0e8c 100644 --- a/jave-example/src/main/java/ws/schild/jave/example/DefaultFFMPEGLocatorTest.java +++ b/jave-example/src/main/java/ws/schild/jave/example/DefaultFFMPEGLocatorTest.java @@ -18,7 +18,7 @@ */ package ws.schild.jave.example; -import ws.schild.jave.DefaultFFMPEGLocator; +import ws.schild.jave.process.ffmpeg.DefaultFFMPEGLocator; /** @@ -30,7 +30,7 @@ public class DefaultFFMPEGLocatorTest { public static void main(String [] args) { // TODO review the generated test code and remove the default call to fail. DefaultFFMPEGLocator locator= new DefaultFFMPEGLocator(); - String exePath= locator.getFFMPEGExecutablePath(); + String exePath= locator.getExecutablePath(); System.out.println("ffmpeg executable found in <"+exePath+">"); } diff --git a/jave-nativebin-arm64/pom.xml b/jave-nativebin-arm64/pom.xml index cd3e59a..dc4e619 100644 --- a/jave-nativebin-arm64/pom.xml +++ b/jave-nativebin-arm64/pom.xml @@ -3,7 +3,7 @@ 4.0.0 ws.schild jar - 2.8.0-SNAPSHOT + 3.0.0 jave-nativebin-linux-arm64 Jave linux arm 64 bit native package The JAVE (Java Audio Video Encoder) library is Java wrapper on the diff --git a/jave-nativebin-linux32/pom.xml b/jave-nativebin-linux32/pom.xml index f98581d..d1d140c 100644 --- a/jave-nativebin-linux32/pom.xml +++ b/jave-nativebin-linux32/pom.xml @@ -3,7 +3,7 @@ 4.0.0 ws.schild jar - 2.8.0-SNAPSHOT + 3.0.0 jave-nativebin-linux32 Jave linux 32 bit native package The JAVE (Java Audio Video Encoder) library is Java wrapper on the diff --git a/jave-nativebin-linux64/pom.xml b/jave-nativebin-linux64/pom.xml index 287967b..2a54a2b 100644 --- a/jave-nativebin-linux64/pom.xml +++ b/jave-nativebin-linux64/pom.xml @@ -3,7 +3,7 @@ 4.0.0 ws.schild jar - 2.8.0-SNAPSHOT + 3.0.0 jave-nativebin-linux64 Jave linux 64 bit native package The JAVE (Java Audio Video Encoder) library is Java wrapper on the diff --git a/jave-nativebin-osx64/pom.xml b/jave-nativebin-osx64/pom.xml index 8d2af90..4cb4ac1 100644 --- a/jave-nativebin-osx64/pom.xml +++ b/jave-nativebin-osx64/pom.xml @@ -3,7 +3,7 @@ 4.0.0 ws.schild jar - 2.8.0-SNAPSHOT + 3.0.0 jave-nativebin-osx64 Jave OSX 64 bit native package The JAVE (Java Audio Video Encoder) library is Java wrapper on the diff --git a/jave-nativebin-win32/pom.xml b/jave-nativebin-win32/pom.xml index 0ba63f6..7d6211f 100644 --- a/jave-nativebin-win32/pom.xml +++ b/jave-nativebin-win32/pom.xml @@ -3,7 +3,7 @@ 4.0.0 ws.schild jar - 2.8.0-SNAPSHOT + 3.0.0 jave-nativebin-win32 Jave windows 32 bit native package The JAVE (Java Audio Video Encoder) library is Java wrapper on the diff --git a/jave-nativebin-win64/pom.xml b/jave-nativebin-win64/pom.xml index eb816d6..a23df1d 100644 --- a/jave-nativebin-win64/pom.xml +++ b/jave-nativebin-win64/pom.xml @@ -3,7 +3,7 @@ 4.0.0 ws.schild jar - 2.8.0-SNAPSHOT + 3.0.0 jave-nativebin-win64 Jave windows 64 bit native package The JAVE (Java Audio Video Encoder) library is Java wrapper on the diff --git a/pom.xml b/pom.xml index 3a0fa69..b56dadc 100644 --- a/pom.xml +++ b/pom.xml @@ -4,14 +4,14 @@ jave-modules pom - 2.8.0-SNAPSHOT + 3.0.0 1.8 1.8 UTF-8 - 2.8.0-SNAPSHOT + 3.0.0 Jave master project Jave master project https://github.com/a-schild/jave2 @@ -50,7 +50,6 @@ jave-nativebin-win32 jave-nativebin-win64 - jave-nativebin-linux-arm64 jave-nativebin-linux32 jave-nativebin-linux64 jave-nativebin-osx64