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