From e2a1ddfc2220af7e509d968907e888e047a2c6f7 Mon Sep 17 00:00:00 2001 From: kokorin <9crqUVAXd6Q17EY354lkbYeB> Date: Fri, 9 Apr 2021 10:13:08 +0300 Subject: [PATCH 1/7] Improve FFmpeg --- .../github/kokorin/jaffree/ffmpeg/FFmpeg.java | 77 ++++++++++--------- 1 file changed, 42 insertions(+), 35 deletions(-) diff --git a/src/main/java/com/github/kokorin/jaffree/ffmpeg/FFmpeg.java b/src/main/java/com/github/kokorin/jaffree/ffmpeg/FFmpeg.java index 3d2b9093..5d795a09 100644 --- a/src/main/java/com/github/kokorin/jaffree/ffmpeg/FFmpeg.java +++ b/src/main/java/com/github/kokorin/jaffree/ffmpeg/FFmpeg.java @@ -42,23 +42,18 @@ * {@link FFmpeg} provides an ability to start & stop ffmpeg process and keep track of * encoding progress. */ -//TODO add debug statements for all methods public class FFmpeg { private final List inputs = new ArrayList<>(); private final List outputs = new ArrayList<>(); private final List additionalArguments = new ArrayList<>(); - //TODO make it Boolean (non-primitive) private boolean overwriteOutput; private ProgressListener progressListener; private OutputListener outputListener; private String progress; //-filter_threads nb_threads (global) //-debug_ts (global) - private FilterGraph complexFilter; - /** - * A map with 0 or 1 filter per stream type. Type can be "a" (audio), "v" (video) or "" (plain 'filter') - */ + private String complexFilter; private final Map filters = new HashMap<>(); private LogLevel logLevel = LogLevel.INFO; @@ -149,43 +144,61 @@ public FFmpeg addArguments(final String key, final String value) { * applied to one stream. This is the case, for example, when the graph has more than one input * and/or output, or when output stream type is different from input. * - * @param graph complex filter graph + * @param complexFilter complex filter graph * @return this * @see * Filtergraph syntax * @see * Complex filtergraph */ - // TODO overload with String parameter - public FFmpeg setComplexFilter(final FilterGraph graph) { - this.complexFilter = graph; + public FFmpeg setComplexFilter(final FilterGraph complexFilter) { + return setComplexFilter(complexFilter.getValue()); + } + + /** + * Adds complex filter graph to ffmpeg arguments list. + *

+ * Complex filtergraphs are those which cannot be described as simply a linear processing chain + * applied to one stream. This is the case, for example, when the graph has more than one input + * and/or output, or when output stream type is different from input. + * + * @param complexFilter complex filter graph + * @return this + * @see + * Filtergraph syntax + * @see + * Complex filtergraph + */ + public FFmpeg setComplexFilter(final String complexFilter) { + this.complexFilter = complexFilter; return this; } /** * Sets the 'generic' filter value (equivalent to the "-filter" command-line parameter). * - * @param filter a String describing the filter to apply + * @param filter a FilterGraph describing the filter to apply * @return this * @see Simple filtergraphs */ - public FFmpeg setFilter(String filter) { - return setFilter("", filter); + public FFmpeg setFilter(FilterGraph filter) { + return setFilter(filter.getValue()); } /** * Sets the 'generic' filter value (equivalent to the "-filter" command-line parameter). * - * @param filter a FilterGraph describing the filter to apply + * @param filter a String describing the filter to apply * @return this * @see Simple filtergraphs */ - public FFmpeg setFilter(FilterGraph filter) { - return setFilter("", filter.getValue()); + public FFmpeg setFilter(String filter) { + return setFilter((String) null, filter); } /** - * Sets a 'stream specific' filter value (equivalent to the "-av" / "-filter:a" or "-fv" / "-filter:v" command-line parameters). + * Sets a 'stream specific' filter value (equivalent to the "-av" / "-filter:a" or "-fv" / "-filter:v" + * command-line parameters). * * @param streamType the stream type to apply this filter to (StreamType.AUDIO or StreamType.VIDEO) * @param filterGraph a graph describing the filters to apply @@ -193,11 +206,12 @@ public FFmpeg setFilter(FilterGraph filter) { * @see Simple filtergraphs */ public FFmpeg setFilter(StreamType streamType, FilterGraph filterGraph) { - return setFilter(streamType.code(), filterGraph.getValue()); + return setFilter(streamType, filterGraph.getValue()); } /** - * Sets a 'stream specific' filter value (equivalent to the "-av" / "-filter:a" or "-fv" / "-filter:v" command-line parameters). + * Sets a 'stream specific' filter value (equivalent to the "-av" / "-filter:a" or "-fv" / "-filter:v" + * command-line parameters). * * @param streamType the stream type to apply this filter to (StreamType.AUDIO or StreamType.VIDEO) * @param filter a String describing the filter to apply @@ -209,9 +223,11 @@ public FFmpeg setFilter(StreamType streamType, String filter) { } /** - * Sets a 'stream specific' filter value (equivalent to the "-av" / "-filter:a" or "-fv" / "-filter:v" / "-filter" command-line parameters). + * Sets a 'stream specific' filter value (equivalent to the "-av" / "-filter:a" or "-fv" / "-filter:v" / "-filter" + * command-line parameters). * - * @param streamSpecifier a String specifying to which stream this filter must be applied ("a" for audio, "v" "for video, or "" for generic 'filter') + * @param streamSpecifier a String specifying to which stream this filter must be applied ("a" for audio, + * "v" "for video, or "" for generic 'filter') * @param filterGraph a graph describing the filters to apply * @return this * @see Simple filtergraphs @@ -221,29 +237,20 @@ public FFmpeg setFilter(String streamSpecifier, FilterGraph filterGraph) { } /** - * Sets a 'stream specific' filter value (equivalent to the "-av" / "-filter:a" or "-fv" / "-filter:v" / "-filter" command-line parameters). + * Sets a 'stream specific' filter value (equivalent to the "-av" / "-filter:a" or "-fv" / "-filter:v" / "-filter" + * command-line parameters). * - * @param streamSpecifier a String specifying to which stream this filter must be applied ("a" for audio, "v" "for video, or "" for generic 'filter') + * @param streamSpecifier a String specifying to which stream this filter must be applied ("a" for audio, + * "v" "for video, or "" for generic 'filter') * @param filter a String describing the filter to apply * @return this * @see Simple filtergraphs */ public FFmpeg setFilter(String streamSpecifier, String filter) { - // If a previous filter was set, warn that it is replaced by the new one - final String previousFilter = (String) filters.get(streamSpecifier); - if (previousFilter != null) { - if (streamSpecifier.isEmpty()) { - LOGGER.warn("Only one generic filter is supported. Ignoring previous filter '" + previousFilter + "'."); - } else { - LOGGER.error("Only one filter per stream is supported. Ignoring previous filter '" + previousFilter + "' for stream '" + streamSpecifier + "'."); - } - } - // Store the new filter filters.put(streamSpecifier, filter); return this; } - /** * Whether to overwrite output. False by default. *

@@ -467,7 +474,7 @@ protected List buildArguments() { } if (complexFilter != null) { - result.addAll(Arrays.asList("-filter_complex", complexFilter.getValue())); + result.addAll(Arrays.asList("-filter_complex", complexFilter)); } result.addAll(BaseInOut.toArguments("-filter", filters)); From 8b9bc01c935fc11b29f96a7df1d342723714f2bf Mon Sep 17 00:00:00 2001 From: kokorin <9crqUVAXd6Q17EY354lkbYeB> Date: Tue, 13 Apr 2021 08:24:06 +0300 Subject: [PATCH 2/7] Frame static factory methods, remove some todos --- README.md | 2 +- .../kokorin/jaffree/ffmpeg/CaptureInput.java | 1 - .../github/kokorin/jaffree/ffmpeg/FFmpeg.java | 2 - .../jaffree/ffmpeg/FFmpegResultFuture.java | 2 - .../github/kokorin/jaffree/ffmpeg/Frame.java | 53 +++++++++---------- .../jaffree/ffmpeg/NutFrameWriter.java | 1 - .../github/kokorin/jaffree/nut/DataItem.java | 1 - .../jaffree/process/LoggingStdReader.java | 3 -- .../kokorin/jaffree/util/LineIterator.java | 1 - .../kokorin/jaffree/ffmpeg/FrameIOTest.java | 11 ++-- .../java/examples/BouncingBallExample.java | 4 +- src/test/java/examples/MosaicExample.java | 4 +- .../java/examples/ProduceVideoExample.java | 2 +- 13 files changed, 37 insertions(+), 50 deletions(-) diff --git a/README.md b/README.md index 399bc276..ab8eee03 100644 --- a/README.md +++ b/README.md @@ -265,7 +265,7 @@ FrameProducer producer = new FrameProducer() { graphics.setPaint(new Color(frameCounter * 1.0f / 30, 0, 0)); graphics.fillRect(0, 0, 320, 240); long pts = frameCounter * 1000 / 10; // Frame PTS in Stream Timebase - Frame videoFrame = new Frame(0, pts, image); + Frame videoFrame = Frame.createVideoFrame(0, pts, image); frameCounter++; return videoFrame; diff --git a/src/main/java/com/github/kokorin/jaffree/ffmpeg/CaptureInput.java b/src/main/java/com/github/kokorin/jaffree/ffmpeg/CaptureInput.java index 0e99e5f7..885edb9b 100644 --- a/src/main/java/com/github/kokorin/jaffree/ffmpeg/CaptureInput.java +++ b/src/main/java/com/github/kokorin/jaffree/ffmpeg/CaptureInput.java @@ -106,7 +106,6 @@ public CaptureInput setCaptureVideoSize(String size) { */ public abstract CaptureInput setCaptureCursor(boolean captureCursor); - // TODO check static method references public static CaptureInput captureDesktop() { CaptureInput result = null; if (OS.IS_LINUX) { diff --git a/src/main/java/com/github/kokorin/jaffree/ffmpeg/FFmpeg.java b/src/main/java/com/github/kokorin/jaffree/ffmpeg/FFmpeg.java index 5d795a09..46c8b569 100644 --- a/src/main/java/com/github/kokorin/jaffree/ffmpeg/FFmpeg.java +++ b/src/main/java/com/github/kokorin/jaffree/ffmpeg/FFmpeg.java @@ -411,8 +411,6 @@ protected StdReader createStdErrReader(OutputListener outputListen * @return this */ protected StdReader createStdOutReader() { - // TODO ffmpeg normally doesn't write to Std OUT, stdOutReader should throw an error - // if it reads any byte return new LoggingStdReader<>(); } diff --git a/src/main/java/com/github/kokorin/jaffree/ffmpeg/FFmpegResultFuture.java b/src/main/java/com/github/kokorin/jaffree/ffmpeg/FFmpegResultFuture.java index d5e634a7..2b9f259e 100644 --- a/src/main/java/com/github/kokorin/jaffree/ffmpeg/FFmpegResultFuture.java +++ b/src/main/java/com/github/kokorin/jaffree/ffmpeg/FFmpegResultFuture.java @@ -111,12 +111,10 @@ public FFmpegResult get(long timeout, TimeUnit unit) throws InterruptedException return resultFuture.get(timeout, unit); } - // TODO check if required or replace with more suitable method public boolean isCancelled() { return resultFuture.isCancelled(); } - // TODO check if required or replace with more suitable method public boolean isDone() { return resultFuture.isDone(); } diff --git a/src/main/java/com/github/kokorin/jaffree/ffmpeg/Frame.java b/src/main/java/com/github/kokorin/jaffree/ffmpeg/Frame.java index 529f2c9d..b85cddb3 100644 --- a/src/main/java/com/github/kokorin/jaffree/ffmpeg/Frame.java +++ b/src/main/java/com/github/kokorin/jaffree/ffmpeg/Frame.java @@ -32,32 +32,6 @@ public class Frame { private final BufferedImage image; private final int[] samples; - /** - * Creates video {@link Frame}, samples are set to null. - * - * @param streamId stream id (starting with 0) - * @param pts pts in {@link Stream} timebase - * @param image video frame image - * @see Stream#getTimebase() - */ - // TODO make static method - public Frame(final int streamId, final long pts, final BufferedImage image) { - this(streamId, pts, image, null); - } - - /** - * Creates audio {@link Frame}, image is set to null. - * - * @param streamId streamId - * @param pts pts in {@link Stream} timebase - * @param samples audio samples in PCM S32BE format - * @see Stream#getTimebase() - */ - // TODO make static method - public Frame(final int streamId, final long pts, final int[] samples) { - this(streamId, pts, null, samples); - } - /** * Creates {@link Frame}. * @@ -67,7 +41,7 @@ public Frame(final int streamId, final long pts, final int[] samples) { * @param samples audio samples in PCM S32BE format * @see Stream#getTimebase() */ - public Frame(final int streamId, final long pts, final BufferedImage image, + protected Frame(final int streamId, final long pts, final BufferedImage image, final int[] samples) { if (image != null && samples != null) { throw new IllegalArgumentException( @@ -133,4 +107,29 @@ public String toString() { + ", samples?=" + (samples != null) + '}'; } + + /** + * Creates video {@link Frame}, samples are set to null. + * + * @param streamId stream id (starting with 0) + * @param pts pts in {@link Stream} timebase + * @param image video frame image + * @see Stream#getTimebase() + */ + public static Frame createVideoFrame(final int streamId, final long pts, final BufferedImage image) { + return new Frame(streamId, pts, image, null); + } + + /** + * Creates audio {@link Frame}, image is set to null. + * + * @param streamId streamId + * @param pts pts in {@link Stream} timebase + * @param samples audio samples in PCM S32BE format + * @see Stream#getTimebase() + */ + public static Frame createAudioFrame(final int streamId, final long pts, final int[] samples) { + return new Frame(streamId, pts, null, samples); + } + } diff --git a/src/main/java/com/github/kokorin/jaffree/ffmpeg/NutFrameWriter.java b/src/main/java/com/github/kokorin/jaffree/ffmpeg/NutFrameWriter.java index 2c1a6c0d..68575b3a 100644 --- a/src/main/java/com/github/kokorin/jaffree/ffmpeg/NutFrameWriter.java +++ b/src/main/java/com/github/kokorin/jaffree/ffmpeg/NutFrameWriter.java @@ -198,7 +198,6 @@ private void write(final NutWriter writer) throws IOException { case AUDIO: data = new byte[frame.getSamples().length * 4]; - // TODO check number of samples provided ByteBuffer.wrap(data).asIntBuffer().put(frame.getSamples()); break; diff --git a/src/main/java/com/github/kokorin/jaffree/nut/DataItem.java b/src/main/java/com/github/kokorin/jaffree/nut/DataItem.java index 32a8f4a3..b85dbb28 100644 --- a/src/main/java/com/github/kokorin/jaffree/nut/DataItem.java +++ b/src/main/java/com/github/kokorin/jaffree/nut/DataItem.java @@ -60,7 +60,6 @@ public class DataItem { public final String name; public final Object value; - // TODO: introduce type enum? public final String type; /** diff --git a/src/main/java/com/github/kokorin/jaffree/process/LoggingStdReader.java b/src/main/java/com/github/kokorin/jaffree/process/LoggingStdReader.java index 5285e951..a4c74b43 100644 --- a/src/main/java/com/github/kokorin/jaffree/process/LoggingStdReader.java +++ b/src/main/java/com/github/kokorin/jaffree/process/LoggingStdReader.java @@ -40,13 +40,10 @@ public class LoggingStdReader implements StdReader { */ @Override public T read(final InputStream stdOut) { - // TODO use line iterator? BufferedReader reader = new BufferedReader(new InputStreamReader(stdOut)); try { String line; - // TODO log message with the same logging level - // for example if message starts with [DEBUG] output it to debug. while ((line = reader.readLine()) != null) { LOGGER.info(line); } diff --git a/src/main/java/com/github/kokorin/jaffree/util/LineIterator.java b/src/main/java/com/github/kokorin/jaffree/util/LineIterator.java index fd0a4857..e11f3b60 100644 --- a/src/main/java/com/github/kokorin/jaffree/util/LineIterator.java +++ b/src/main/java/com/github/kokorin/jaffree/util/LineIterator.java @@ -27,7 +27,6 @@ /** * Adapts {@link BufferedReader} to line {@link Iterator}. */ -// TODO: move to util package public class LineIterator implements Iterator { private final BufferedReader reader; private String nextLine = null; diff --git a/src/test/java/com/github/kokorin/jaffree/ffmpeg/FrameIOTest.java b/src/test/java/com/github/kokorin/jaffree/ffmpeg/FrameIOTest.java index b5302e65..f3c30b21 100644 --- a/src/test/java/com/github/kokorin/jaffree/ffmpeg/FrameIOTest.java +++ b/src/test/java/com/github/kokorin/jaffree/ffmpeg/FrameIOTest.java @@ -270,10 +270,10 @@ public Frame produce() { } if (videoPts <= audioPts) { - return new Frame(0, videoPts++, flag); + return Frame.createVideoFrame(0, videoPts++, flag); } - return new Frame(1, audioPts++, samples); + return Frame.createAudioFrame(1, audioPts++, samples); } }; @@ -406,10 +406,10 @@ public Frame produce() { } if (videoPts <= audioPts) { - return new Frame(0, videoPts++, redCross); + return Frame.createVideoFrame(0, videoPts++, redCross); } - return new Frame(1, audioPts++, samples); + return Frame.createAudioFrame(1, audioPts++, samples); } }; @@ -504,7 +504,6 @@ private void testNutGenerationAndConsumption(final int duration, final int fps, final AtomicReference progressRef = new AtomicReference<>(); - // TODO: convert outputPath to MP4 with ffmpeg and check how long will it take FFmpeg.atPath(BIN) .addInput(FrameInput.withProducer( new FrameProducer() { @@ -546,7 +545,7 @@ public Frame produce() { lastSecond = currentSecond; } - Frame result = new Frame(0, frame * timebase / fps, image); + Frame result = Frame.createVideoFrame(0, frame * timebase / fps, image); frame++; return result; diff --git a/src/test/java/examples/BouncingBallExample.java b/src/test/java/examples/BouncingBallExample.java index aa9aa084..7f23ff79 100644 --- a/src/test/java/examples/BouncingBallExample.java +++ b/src/test/java/examples/BouncingBallExample.java @@ -125,7 +125,7 @@ public Frame produce() { graphics.setPaint(ballColor); graphics.fillOval(ballCenterX - BALL_RADIUS, ballCenterY - BALL_RADIUS, BALL_RADIUS * 2, BALL_RADIUS * 2); - Frame videoFrame = new Frame(0, nextVideoTimecode, image); + Frame videoFrame = Frame.createVideoFrame(0, nextVideoTimecode, image); if (collisionVideoTimecode <= nextVideoTimecode) { Random random = new Random(); @@ -152,7 +152,7 @@ public Frame produce() { } - Frame audioFrame = new Frame(1, nextAudioTimecode, samples); + Frame audioFrame = Frame.createAudioFrame(1, nextAudioTimecode, samples); nextAudioTimecode += 1000 / FPS; return audioFrame; diff --git a/src/test/java/examples/MosaicExample.java b/src/test/java/examples/MosaicExample.java index 551de0b1..bdbf9db9 100644 --- a/src/test/java/examples/MosaicExample.java +++ b/src/test/java/examples/MosaicExample.java @@ -235,7 +235,7 @@ public boolean imageUpdate(Image img, int infoflags, int x, int y, int width, in return null; } - Frame result = new Frame(0, nextVideoFrameTimecode, mosaic); + Frame result = Frame.createVideoFrame(0, nextVideoFrameTimecode, mosaic); nextVideoFrameTimecode += videoFrameDuration; @@ -273,7 +273,7 @@ private Frame produceAudioFrame() { if (aFrame == null) { return null; } - aFrame = new Frame(1 + minI, aFrame.getPts(), aFrame.getSamples()); + aFrame = Frame.createAudioFrame(1 + minI, aFrame.getPts(), aFrame.getSamples()); if (nextPts != Long.MAX_VALUE) { nextAudioFrameTimecode = 1000L * nextPts / sampleRate; diff --git a/src/test/java/examples/ProduceVideoExample.java b/src/test/java/examples/ProduceVideoExample.java index 22939837..a2aff946 100644 --- a/src/test/java/examples/ProduceVideoExample.java +++ b/src/test/java/examples/ProduceVideoExample.java @@ -54,7 +54,7 @@ public Frame produce() { graphics.setPaint(new Color(frameCounter * 1.0f / 30, 0, 0)); graphics.fillRect(0, 0, 320, 240); long pts = frameCounter * 1000 / 10; // Frame PTS in Stream Timebase - Frame videoFrame = new Frame(0, pts, image); + Frame videoFrame = Frame.createVideoFrame(0, pts, image); frameCounter++; return videoFrame; From 85848f68679b780deb3c66bc05f39d41c5866a94 Mon Sep 17 00:00:00 2001 From: kokorin <9crqUVAXd6Q17EY354lkbYeB> Date: Tue, 13 Apr 2021 09:09:56 +0300 Subject: [PATCH 3/7] Extract TagAware interface --- .../kokorin/jaffree/ffprobe/Chapter.java | 14 +---- .../kokorin/jaffree/ffprobe/Format.java | 20 +----- .../github/kokorin/jaffree/ffprobe/Frame.java | 15 +---- .../kokorin/jaffree/ffprobe/Packet.java | 11 ++-- .../kokorin/jaffree/ffprobe/Program.java | 10 +-- .../kokorin/jaffree/ffprobe/Stream.java | 10 +-- .../kokorin/jaffree/ffprobe/TagAware.java | 62 +++++++++++++++++++ .../ffprobe/data/AbstractProbeData.java | 33 ++++++++-- .../jaffree/ffprobe/data/ProbeData.java | 41 +++++++++++- .../kokorin/jaffree/ffprobe/FFprobeTest.java | 4 ++ 10 files changed, 158 insertions(+), 62 deletions(-) create mode 100644 src/main/java/com/github/kokorin/jaffree/ffprobe/TagAware.java diff --git a/src/main/java/com/github/kokorin/jaffree/ffprobe/Chapter.java b/src/main/java/com/github/kokorin/jaffree/ffprobe/Chapter.java index 580aac79..8543d737 100644 --- a/src/main/java/com/github/kokorin/jaffree/ffprobe/Chapter.java +++ b/src/main/java/com/github/kokorin/jaffree/ffprobe/Chapter.java @@ -23,7 +23,7 @@ /** * Chapter description. */ -public class Chapter { +public class Chapter implements TagAware { private final ProbeData probeData; /** @@ -36,21 +36,13 @@ public Chapter(final ProbeData probeData) { } /** - * Returns data section which holds all the data provided by ffprobe for the current Chapter. - *

- * Use this method if you have to access properties which are not accessible through - * other getters in this class. - * - * @return data section + * {@inheritDoc} */ + @Override public ProbeData getProbeData() { return probeData; } - public String getTag(String name) { - return probeData.getSubDataString("tags", name); - } - /** * Returns Chapter ID. * diff --git a/src/main/java/com/github/kokorin/jaffree/ffprobe/Format.java b/src/main/java/com/github/kokorin/jaffree/ffprobe/Format.java index e3240197..0c792aaf 100644 --- a/src/main/java/com/github/kokorin/jaffree/ffprobe/Format.java +++ b/src/main/java/com/github/kokorin/jaffree/ffprobe/Format.java @@ -22,7 +22,7 @@ /** * Format description. */ -public class Format { +public class Format implements TagAware { private final ProbeData probeData; /** @@ -35,27 +35,13 @@ public Format(final ProbeData probeData) { } /** - * Returns data section which holds all the data provided by ffprobe for current {@link Format}. - *

- * Use this method if you have to access properties which are not accessible through - * other getters in this class. - * - * @return data section + * {@inheritDoc} */ + @Override public ProbeData getProbeData() { return probeData; } - /** - * Return tag value by name - * @param name tag name - * @return tag value - */ - // TODO Type-specific getters: Integer, Long, etc - public String getTag(String name) { - return probeData.getSubDataString("tags", name); - } - /** * @return file name */ diff --git a/src/main/java/com/github/kokorin/jaffree/ffprobe/Frame.java b/src/main/java/com/github/kokorin/jaffree/ffprobe/Frame.java index 332b03d5..8c0a5c98 100644 --- a/src/main/java/com/github/kokorin/jaffree/ffprobe/Frame.java +++ b/src/main/java/com/github/kokorin/jaffree/ffprobe/Frame.java @@ -29,7 +29,7 @@ * * @see FFprobe#setShowFrames(boolean) */ -public class Frame implements FrameSubtitle, PacketFrameSubtitle { +public class Frame implements TagAware, FrameSubtitle, PacketFrameSubtitle { private final ProbeData probeData; /** @@ -42,22 +42,13 @@ public Frame(final ProbeData probeData) { } /** - * Returns data section which holds all the data provided by ffprobe for - * the current {@link Frame}. - *

- * Use this method if you have to access properties which are not accessible through - * other getters in this class. - * - * @return data section + * {@inheritDoc} */ + @Override public ProbeData getProbeData() { return probeData; } - public String getTag(String name) { - return probeData.getSubDataString("tags", name); - } - /** * Returns logging information from the decoder about each frame according to the value * set in loglevel. diff --git a/src/main/java/com/github/kokorin/jaffree/ffprobe/Packet.java b/src/main/java/com/github/kokorin/jaffree/ffprobe/Packet.java index 3708be82..d59e7ce7 100644 --- a/src/main/java/com/github/kokorin/jaffree/ffprobe/Packet.java +++ b/src/main/java/com/github/kokorin/jaffree/ffprobe/Packet.java @@ -23,7 +23,7 @@ import java.util.List; -public class Packet implements PacketFrameSubtitle { +public class Packet implements TagAware, PacketFrameSubtitle { private final ProbeData probeData; @@ -31,6 +31,10 @@ public Packet(ProbeData probeData) { this.probeData = probeData; } + /** + * {@inheritDoc} + */ + @Override public ProbeData getProbeData() { return probeData; } @@ -43,11 +47,6 @@ public Float getPtsTime() { return probeData.getFloat("pts_time"); } - // TODO Does Packet contain any tags? - public String getTag(String name) { - return probeData.getSubDataString("tags", name); - } - public List getSideDataList() { return probeData.getSubDataList("side_data_list", new ProbeDataConverter() { @Override diff --git a/src/main/java/com/github/kokorin/jaffree/ffprobe/Program.java b/src/main/java/com/github/kokorin/jaffree/ffprobe/Program.java index c95bb9e9..a919da76 100644 --- a/src/main/java/com/github/kokorin/jaffree/ffprobe/Program.java +++ b/src/main/java/com/github/kokorin/jaffree/ffprobe/Program.java @@ -23,21 +23,21 @@ import java.util.List; // TODO check what timebase are used for StartPts & EndPts -public class Program { +public class Program implements TagAware { private final ProbeData probeData; public Program(ProbeData probeData) { this.probeData = probeData; } + /** + * {@inheritDoc} + */ + @Override public ProbeData getProbeData() { return probeData; } - public String getTag(String name) { - return probeData.getSubDataString("tags", name); - } - public List getStreams() { return probeData.getSubDataList("streams", new ProbeDataConverter() { @Override diff --git a/src/main/java/com/github/kokorin/jaffree/ffprobe/Stream.java b/src/main/java/com/github/kokorin/jaffree/ffprobe/Stream.java index 1b310417..44f854c1 100644 --- a/src/main/java/com/github/kokorin/jaffree/ffprobe/Stream.java +++ b/src/main/java/com/github/kokorin/jaffree/ffprobe/Stream.java @@ -25,13 +25,17 @@ import java.util.List; import java.util.concurrent.TimeUnit; -public class Stream { +public class Stream implements TagAware { private final ProbeData probeData; public Stream(ProbeData probeData) { this.probeData = probeData; } + /** + * {@inheritDoc} + */ + @Override public ProbeData getProbeData() { return probeData; } @@ -40,10 +44,6 @@ public StreamDisposition getDisposition() { return new StreamDisposition(probeData.getSubData("disposition")); } - public String getTag(String name) { - return probeData.getSubDataString("tags", name); - } - public List getSideDataList() { return probeData.getSubDataList("side_data_list", new ProbeDataConverter() { @Override diff --git a/src/main/java/com/github/kokorin/jaffree/ffprobe/TagAware.java b/src/main/java/com/github/kokorin/jaffree/ffprobe/TagAware.java new file mode 100644 index 00000000..00c8d4b9 --- /dev/null +++ b/src/main/java/com/github/kokorin/jaffree/ffprobe/TagAware.java @@ -0,0 +1,62 @@ +package com.github.kokorin.jaffree.ffprobe; + +import com.github.kokorin.jaffree.ffprobe.data.ProbeData; + +public interface TagAware { + + /** + * Returns data section which holds all the data provided by ffprobe for current {@link Format}. + *

+ * Use this method if you have to access properties which are not accessible through + * other getters in this class. + * + * @return data section + */ + ProbeData getProbeData(); + + /** + * Return tag string value by name + * @param name tag name + * @return tag value + */ + default String getTag(String name) { + return getProbeData().getSubDataString("tags", name); + } + + /** + * Return tag long value by name + * @param name tag name + * @return tag value + */ + default Long getTagLong(String name) { + return getProbeData().getSubDataLong("tags", name); + } + + /** + * Return tag integer value by name + * @param name tag name + * @return tag value + */ + default Double getTagInteger(String name) { + return getProbeData().getSubDataDouble("tags", name); + } + + /** + * Return tag double value by name + * @param name tag name + * @return tag value + */ + default Double getTagDouble(String name) { + return getProbeData().getSubDataDouble("tags", name); + } + + /** + * Return tag float value by name + * @param name tag name + * @return tag value + */ + default Float getTagFloat(String name) { + return getProbeData().getSubDataFloat("tags", name); + } + +} diff --git a/src/main/java/com/github/kokorin/jaffree/ffprobe/data/AbstractProbeData.java b/src/main/java/com/github/kokorin/jaffree/ffprobe/data/AbstractProbeData.java index 556b1187..fe22fd51 100644 --- a/src/main/java/com/github/kokorin/jaffree/ffprobe/data/AbstractProbeData.java +++ b/src/main/java/com/github/kokorin/jaffree/ffprobe/data/AbstractProbeData.java @@ -183,12 +183,37 @@ public Object getSubDataValue(String subDataName, String property) { } @Override - public String getSubDataString(String subDataName, String property) { - ProbeData subData = getSubData(subDataName); - if (subData == null) { + public T getSubDataValue(String subDataName, String property, ValueConverter converter) { + Object value = getSubDataValue(subDataName, property); + if (value == null) { return null; } - return subData.getString(property); + return converter.convert(value); + } + + @Override + public String getSubDataString(String subDataName, String property) { + return getSubDataValue(subDataName, property, STRING_CONVERTER); + } + + @Override + public Long getSubDataLong(String subDataName, String property) { + return getSubDataValue(subDataName, property, LONG_CONVERTER); + } + + @Override + public Integer getSubDataInteger(String subDataName, String property) { + return getSubDataValue(subDataName, property, INTEGER_CONVERTER); + } + + @Override + public Double getSubDataDouble(String subDataName, String property) { + return getSubDataValue(subDataName, property, DOUBLE_CONVERTER); + } + + @Override + public Float getSubDataFloat(String subDataName, String property) { + return getSubDataValue(subDataName, property, FLOAT_CONVERTER); } private static final ValueConverter STRING_CONVERTER = diff --git a/src/main/java/com/github/kokorin/jaffree/ffprobe/data/ProbeData.java b/src/main/java/com/github/kokorin/jaffree/ffprobe/data/ProbeData.java index 5fc82e04..432db445 100644 --- a/src/main/java/com/github/kokorin/jaffree/ffprobe/data/ProbeData.java +++ b/src/main/java/com/github/kokorin/jaffree/ffprobe/data/ProbeData.java @@ -86,16 +86,53 @@ public interface ProbeData { /** * @param subDataName sub-data name - * @param property property name + * @param property property name * @return sub-data property value */ Object getSubDataValue(String subDataName, String property); /** + * Handy method which returns subdata property with specified name converted to T type. + * * @param subDataName sub-data name - * @param property property name + * @param property property name + * @return sub-data property value + */ + T getSubDataValue(String subDataName, String property, ValueConverter converter); + + /** + * @param subDataName sub-data name + * @param property property name * @return sub-data property value */ String getSubDataString(String subDataName, String property); + /** + * @param subDataName sub-data name + * @param property property name + * @return sub-data property value + */ + Long getSubDataLong(String subDataName, String property); + + /** + * @param subDataName sub-data name + * @param property property name + * @return sub-data property value + */ + Integer getSubDataInteger(String subDataName, String property); + + /** + * @param subDataName sub-data name + * @param property property name + * @return sub-data property value + */ + Double getSubDataDouble(String subDataName, String property); + + /** + * @param subDataName sub-data name + * @param property property name + * @return sub-data property value + */ + Float getSubDataFloat(String subDataName, String property); + } diff --git a/src/test/java/com/github/kokorin/jaffree/ffprobe/FFprobeTest.java b/src/test/java/com/github/kokorin/jaffree/ffprobe/FFprobeTest.java index 36438c4e..fc8fa994 100644 --- a/src/test/java/com/github/kokorin/jaffree/ffprobe/FFprobeTest.java +++ b/src/test/java/com/github/kokorin/jaffree/ffprobe/FFprobeTest.java @@ -175,6 +175,10 @@ public void testShowFormat() throws Exception { assertNotNull(format.getBitRate()); assertNotNull(format.getProbeScore()); assertEquals("isom", format.getTag("major_brand")); + assertNotNull(format.getTagInteger("minor_version")); + assertNotNull(format.getTagLong("minor_version")); + assertNotNull(format.getTagDouble("minor_version")); + assertNotNull(format.getTagFloat("minor_version")); } //private String showFormatEntry; From 501c9fc0538408afa7ed216e29c915bff20da942 Mon Sep 17 00:00:00 2001 From: kokorin <9crqUVAXd6Q17EY354lkbYeB> Date: Tue, 13 Apr 2021 09:46:29 +0300 Subject: [PATCH 4/7] Add ProbeData#getBoolean, convert some ffprobe data properties to boolean --- .../github/kokorin/jaffree/ffmpeg/Stream.java | 1 - .../kokorin/jaffree/ffprobe/Format.java | 2 - .../github/kokorin/jaffree/ffprobe/Frame.java | 21 ++++---- .../jaffree/ffprobe/StreamDisposition.java | 49 +++++++++---------- .../ffprobe/data/AbstractProbeData.java | 33 ++++++++++++- .../ffprobe/data/FlatFormatParser.java | 8 +-- .../ffprobe/data/JsonFormatParser.java | 3 ++ .../jaffree/ffprobe/data/ProbeData.java | 2 + .../github/kokorin/jaffree/nut/NutWriter.java | 3 +- .../kokorin/jaffree/ffprobe/FFprobeTest.java | 24 ++++----- 10 files changed, 87 insertions(+), 59 deletions(-) diff --git a/src/main/java/com/github/kokorin/jaffree/ffmpeg/Stream.java b/src/main/java/com/github/kokorin/jaffree/ffmpeg/Stream.java index 15221c8f..f8c76302 100644 --- a/src/main/java/com/github/kokorin/jaffree/ffmpeg/Stream.java +++ b/src/main/java/com/github/kokorin/jaffree/ffmpeg/Stream.java @@ -23,7 +23,6 @@ public class Stream { private Long timebase; private Integer width; private Integer height; - //TODO check if sampleRate can have Integer type private Long sampleRate; private Integer channels; diff --git a/src/main/java/com/github/kokorin/jaffree/ffprobe/Format.java b/src/main/java/com/github/kokorin/jaffree/ffprobe/Format.java index 0c792aaf..e4b11878 100644 --- a/src/main/java/com/github/kokorin/jaffree/ffprobe/Format.java +++ b/src/main/java/com/github/kokorin/jaffree/ffprobe/Format.java @@ -80,7 +80,6 @@ public String getFormatLongName() { /** * @return media start time in seconds */ - // TODO: getter with TimeUnit? public Float getStartTime() { return probeData.getFloat("start_time"); } @@ -88,7 +87,6 @@ public Float getStartTime() { /** * @return media duration in seconds */ - // TODO: getter with TimeUnit? public Float getDuration() { return probeData.getFloat("duration"); } diff --git a/src/main/java/com/github/kokorin/jaffree/ffprobe/Frame.java b/src/main/java/com/github/kokorin/jaffree/ffprobe/Frame.java index 8c0a5c98..c76c14b9 100644 --- a/src/main/java/com/github/kokorin/jaffree/ffprobe/Frame.java +++ b/src/main/java/com/github/kokorin/jaffree/ffprobe/Frame.java @@ -118,11 +118,10 @@ public Integer getStreamIndex() { } /** - * @return 1 -> keyframe, 0-> not + * @return true if key frame */ - // TODO make boolean - public Integer getKeyFrame() { - return probeData.getInteger("key_frame"); + public Boolean getKeyFrame() { + return probeData.getBoolean("key_frame"); } /** @@ -323,21 +322,19 @@ public Long getDisplayPictureNumber() { /** * The content of the picture is interlaced. * - * @return 1 -> interlaced, 0-> not + * @return true if interlaced */ - // TODO make boolean - public Integer getInterlacedFrame() { - return probeData.getInteger("interlaced_frame"); + public Boolean getInterlacedFrame() { + return probeData.getBoolean("interlaced_frame"); } /** * If the content is interlaced, is top field displayed first. * - * @return 1, if top field displayed first + * @return true if top field is displayed first */ - // TODO make boolean - public Integer getTopFieldFirst() { - return probeData.getInteger("top_field_first"); + public Boolean getTopFieldFirst() { + return probeData.getBoolean("top_field_first"); } /** diff --git a/src/main/java/com/github/kokorin/jaffree/ffprobe/StreamDisposition.java b/src/main/java/com/github/kokorin/jaffree/ffprobe/StreamDisposition.java index 6c730610..c8e27a71 100644 --- a/src/main/java/com/github/kokorin/jaffree/ffprobe/StreamDisposition.java +++ b/src/main/java/com/github/kokorin/jaffree/ffprobe/StreamDisposition.java @@ -30,53 +30,52 @@ public ProbeData getProbeData() { return probeData; } - // TODO make boolean? - public Integer getDefault() { - return probeData.getInteger("default"); + public Boolean getDefault() { + return probeData.getBoolean("default"); } - public Integer getDub() { - return probeData.getInteger("dub"); + public Boolean getDub() { + return probeData.getBoolean("dub"); } - public Integer getOriginal() { - return probeData.getInteger("original"); + public Boolean getOriginal() { + return probeData.getBoolean("original"); } - public Integer getComment() { - return probeData.getInteger("comment"); + public Boolean getComment() { + return probeData.getBoolean("comment"); } - public Integer getLyrics() { - return probeData.getInteger("lyrics"); + public Boolean getLyrics() { + return probeData.getBoolean("lyrics"); } - public Integer getKaraoke() { - return probeData.getInteger("karaoke"); + public Boolean getKaraoke() { + return probeData.getBoolean("karaoke"); } - public Integer getForced() { - return probeData.getInteger("forced"); + public Boolean getForced() { + return probeData.getBoolean("forced"); } - public Integer getHearingImpaired() { - return probeData.getInteger("hearing_impaired"); + public Boolean getHearingImpaired() { + return probeData.getBoolean("hearing_impaired"); } - public Integer getVisualImpaired() { - return probeData.getInteger("visual_impaired"); + public Boolean getVisualImpaired() { + return probeData.getBoolean("visual_impaired"); } - public Integer getCleanEffects() { - return probeData.getInteger("clean_effects"); + public Boolean getCleanEffects() { + return probeData.getBoolean("clean_effects"); } - public Integer getAttachedPic() { - return probeData.getInteger("attached_pic"); + public Boolean getAttachedPic() { + return probeData.getBoolean("attached_pic"); } - public Integer getTimedThumbnails() { - return probeData.getInteger("timed_thumbnails"); + public Boolean getTimedThumbnails() { + return probeData.getBoolean("timed_thumbnails"); } diff --git a/src/main/java/com/github/kokorin/jaffree/ffprobe/data/AbstractProbeData.java b/src/main/java/com/github/kokorin/jaffree/ffprobe/data/AbstractProbeData.java index fe22fd51..1b6568f3 100644 --- a/src/main/java/com/github/kokorin/jaffree/ffprobe/data/AbstractProbeData.java +++ b/src/main/java/com/github/kokorin/jaffree/ffprobe/data/AbstractProbeData.java @@ -40,6 +40,17 @@ public String getString(final String key) { return getValue(key, STRING_CONVERTER); } + /** + * Returns boolean value for specified key (using default converter). + * + * @param key key + * @return value + */ + @Override + public Boolean getBoolean(final String key) { + return getValue(key, BOOLEAN_CONVERTER); + } + /** * Returns long value for specified key (using default converter). * @@ -90,7 +101,6 @@ public Double getDouble(final String key) { * @param key key * @return StreamType */ - // TODO: check if it should be here @Override public StreamType getStreamType(final String key) { return getValue(key, STREAM_TYPE_CONVERTER); @@ -227,6 +237,27 @@ public String convert(final Object value) { } }; + private static final ValueConverter BOOLEAN_CONVERTER = + new ValueConverter() { + @Override + public Boolean convert(final Object value) { + if (value == null || value.equals("") || value.equals("N/A")) { + return null; + } + if (value instanceof Number) { + return ((Number) value).intValue() > 0; + } + + try { + return Integer.parseInt(value.toString()) > 0; + } catch (Exception e) { + LOGGER.warn("Failed to parse int number: " + value, e); + } + + return null; + } + }; + private static final ValueConverter LONG_CONVERTER = new ValueConverter() { @Override diff --git a/src/main/java/com/github/kokorin/jaffree/ffprobe/data/FlatFormatParser.java b/src/main/java/com/github/kokorin/jaffree/ffprobe/data/FlatFormatParser.java index 98bc17eb..cd57e98e 100644 --- a/src/main/java/com/github/kokorin/jaffree/ffprobe/data/FlatFormatParser.java +++ b/src/main/java/com/github/kokorin/jaffree/ffprobe/data/FlatFormatParser.java @@ -31,9 +31,11 @@ import java.util.TreeMap; /** - * {@link com.github.kokorin.jaffree.ffprobe.FFprobe} output parser which parses - * ffprobe "flat" output. + * ffprobe flat format output parser. + * + * @deprecated use {@link JsonFormatParser} */ +@Deprecated public class FlatFormatParser implements FormatParser { private static final Logger LOGGER = LoggerFactory.getLogger(FlatFormatParser.class); @@ -95,7 +97,6 @@ public ProbeData parse(final InputStream inputStream) { * @param value value * @return true if parsed and set */ - // TODO: refactor me protected boolean setKeyValue(final TreeMap data, final String key, final String value) { String[] pathStr = key.split("\\."); List path = new ArrayList<>(); @@ -108,7 +109,6 @@ protected boolean setKeyValue(final TreeMap data, final String k if (i + 2 < pathStr.length) { step = SectionPath.parse(pathStr[i], pathStr[i + 1], pathStr[i + 2]); if (step != null) { - // TODO checkstyle? inc = 1 + 1 + 1; } } diff --git a/src/main/java/com/github/kokorin/jaffree/ffprobe/data/JsonFormatParser.java b/src/main/java/com/github/kokorin/jaffree/ffprobe/data/JsonFormatParser.java index 5767402e..21587819 100644 --- a/src/main/java/com/github/kokorin/jaffree/ffprobe/data/JsonFormatParser.java +++ b/src/main/java/com/github/kokorin/jaffree/ffprobe/data/JsonFormatParser.java @@ -27,6 +27,9 @@ import java.util.ArrayList; import java.util.List; +/** + * ffprobe json format output parser. + */ public class JsonFormatParser implements FormatParser { @Override public String getFormatName() { diff --git a/src/main/java/com/github/kokorin/jaffree/ffprobe/data/ProbeData.java b/src/main/java/com/github/kokorin/jaffree/ffprobe/data/ProbeData.java index 432db445..d3661276 100644 --- a/src/main/java/com/github/kokorin/jaffree/ffprobe/data/ProbeData.java +++ b/src/main/java/com/github/kokorin/jaffree/ffprobe/data/ProbeData.java @@ -34,6 +34,8 @@ public interface ProbeData { String getString(String key); + Boolean getBoolean(String key); + Long getLong(String key); Integer getInteger(String key); diff --git a/src/main/java/com/github/kokorin/jaffree/nut/NutWriter.java b/src/main/java/com/github/kokorin/jaffree/nut/NutWriter.java index c4d8c0b7..1ca4820f 100644 --- a/src/main/java/com/github/kokorin/jaffree/nut/NutWriter.java +++ b/src/main/java/com/github/kokorin/jaffree/nut/NutWriter.java @@ -308,7 +308,6 @@ private void writeFrameInternal(NutFrame frame) throws IOException { initialize(); // EOR frames by specification use TS of the previous frame in the same stream. - // TODO: do we need this check? if (!frame.eor) { Rational maxTs = Rational.ZERO; for (int i = 0; i < mainHeader.timeBases.length; i++) { @@ -461,7 +460,7 @@ private void writeFrameInternal(NutFrame frame) throws IOException { output.writeCrc32(); } - // TODO: elision headers? + // elision headers? output.writeBytes(frame.data); lastPts[frame.streamId] = frame.pts; diff --git a/src/test/java/com/github/kokorin/jaffree/ffprobe/FFprobeTest.java b/src/test/java/com/github/kokorin/jaffree/ffprobe/FFprobeTest.java index fc8fa994..e76b36ad 100644 --- a/src/test/java/com/github/kokorin/jaffree/ffprobe/FFprobeTest.java +++ b/src/test/java/com/github/kokorin/jaffree/ffprobe/FFprobeTest.java @@ -340,18 +340,18 @@ public void testShowStreams() throws Exception { StreamDisposition disposition = audioStream.getDisposition(); assertNotNull(disposition); - assertEquals((Integer) 1, disposition.getDefault()); - assertEquals((Integer) 0, disposition.getDub()); - assertEquals((Integer) 0, disposition.getOriginal()); - assertEquals((Integer) 0, disposition.getComment()); - assertEquals((Integer) 0, disposition.getLyrics()); - assertEquals((Integer) 0, disposition.getKaraoke()); - assertEquals((Integer) 0, disposition.getForced()); - assertEquals((Integer) 0, disposition.getHearingImpaired()); - assertEquals((Integer) 0, disposition.getVisualImpaired()); - assertEquals((Integer) 0, disposition.getCleanEffects()); - assertEquals((Integer) 0, disposition.getAttachedPic()); - assertEquals((Integer) 0, disposition.getTimedThumbnails()); + assertEquals(Boolean.TRUE, disposition.getDefault()); + assertEquals(Boolean.FALSE, disposition.getDub()); + assertEquals(Boolean.FALSE, disposition.getOriginal()); + assertEquals(Boolean.FALSE, disposition.getComment()); + assertEquals(Boolean.FALSE, disposition.getLyrics()); + assertEquals(Boolean.FALSE, disposition.getKaraoke()); + assertEquals(Boolean.FALSE, disposition.getForced()); + assertEquals(Boolean.FALSE, disposition.getHearingImpaired()); + assertEquals(Boolean.FALSE, disposition.getVisualImpaired()); + assertEquals(Boolean.FALSE, disposition.getCleanEffects()); + assertEquals(Boolean.FALSE, disposition.getAttachedPic()); + assertEquals(Boolean.FALSE, disposition.getTimedThumbnails()); } @Test From 31b01d69e9f622a880ecc6cc4297abc52635395e Mon Sep 17 00:00:00 2001 From: kokorin <9crqUVAXd6Q17EY354lkbYeB> Date: Tue, 13 Apr 2021 09:55:45 +0300 Subject: [PATCH 5/7] Fix test & JavaDoc --- .../java/com/github/kokorin/jaffree/ffprobe/TagAware.java | 4 ++-- .../com/github/kokorin/jaffree/ffprobe/FFprobeResultTest.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/github/kokorin/jaffree/ffprobe/TagAware.java b/src/main/java/com/github/kokorin/jaffree/ffprobe/TagAware.java index 00c8d4b9..7e0a9a80 100644 --- a/src/main/java/com/github/kokorin/jaffree/ffprobe/TagAware.java +++ b/src/main/java/com/github/kokorin/jaffree/ffprobe/TagAware.java @@ -5,10 +5,10 @@ public interface TagAware { /** - * Returns data section which holds all the data provided by ffprobe for current {@link Format}. + * Returns data section which holds all the data provided by ffprobe. *

* Use this method if you have to access properties which are not accessible through - * other getters in this class. + * other getters. * * @return data section */ diff --git a/src/test/java/com/github/kokorin/jaffree/ffprobe/FFprobeResultTest.java b/src/test/java/com/github/kokorin/jaffree/ffprobe/FFprobeResultTest.java index 66287044..76dfcc1a 100644 --- a/src/test/java/com/github/kokorin/jaffree/ffprobe/FFprobeResultTest.java +++ b/src/test/java/com/github/kokorin/jaffree/ffprobe/FFprobeResultTest.java @@ -27,7 +27,7 @@ public void verifyChaptersFFprobeResult(FFprobeResult result) { assertEquals(StreamType.VIDEO, videoStream.getCodecType()); assertEquals("hevc", videoStream.getCodecName()); - assertEquals((Integer) 1, videoStream.getDisposition().getDefault()); + assertEquals(Boolean.TRUE, videoStream.getDisposition().getDefault()); Stream audioStream = streams.get(1); assertEquals(StreamType.AUDIO, audioStream.getCodecType()); From b70e5bbd5861d789bca5474a8720c2505518e638 Mon Sep 17 00:00:00 2001 From: kokorin <9crqUVAXd6Q17EY354lkbYeB> Date: Tue, 20 Apr 2021 08:06:13 +0300 Subject: [PATCH 6/7] More generic PipeInputNegotiator, JavaDoc & FrameIOTest --- .../kokorin/jaffree/ffmpeg/PipeInput.java | 21 +++------ .../kokorin/jaffree/ffprobe/ChannelInput.java | 43 ++++++++++++++----- .../kokorin/jaffree/ffprobe/FFprobe.java | 36 +++++++++++----- .../kokorin/jaffree/ffprobe/PipeInput.java | 21 +++------ .../kokorin/jaffree/ffprobe/Program.java | 14 ++++++ .../jaffree/net/PipeInputNegotiator.java | 10 +---- .../kokorin/jaffree/util/ParseUtil.java | 1 - .../kokorin/jaffree/ffmpeg/FrameIOTest.java | 15 ++----- 8 files changed, 88 insertions(+), 73 deletions(-) diff --git a/src/main/java/com/github/kokorin/jaffree/ffmpeg/PipeInput.java b/src/main/java/com/github/kokorin/jaffree/ffmpeg/PipeInput.java index daca7ea8..f58f2225 100644 --- a/src/main/java/com/github/kokorin/jaffree/ffmpeg/PipeInput.java +++ b/src/main/java/com/github/kokorin/jaffree/ffmpeg/PipeInput.java @@ -27,29 +27,18 @@ * @see ChannelInput */ public class PipeInput extends TcpInput implements Input { - private final PipeInputNegotiator negotiator; + private static final int DEFAULT_BUFFER_SIZE = 1_000_000; - public PipeInput(InputStream source) { - this(new PipeInputNegotiator(source)); - } - - public PipeInput(PipeInputNegotiator negotiator) { - super(negotiator); - this.negotiator = negotiator; - } - - public PipeInput setBufferSize(int bufferSize) { - negotiator.setBufferSize(bufferSize); - return this; + protected PipeInput(final InputStream source, final int bufferSize) { + super(new PipeInputNegotiator(source, bufferSize)); } public static PipeInput pumpFrom(InputStream source) { - return new PipeInput(source); + return pumpFrom(source, DEFAULT_BUFFER_SIZE); } public static PipeInput pumpFrom(InputStream source, int bufferSize) { - return pumpFrom(source) - .setBufferSize(bufferSize); + return new PipeInput(source, bufferSize); } } diff --git a/src/main/java/com/github/kokorin/jaffree/ffprobe/ChannelInput.java b/src/main/java/com/github/kokorin/jaffree/ffprobe/ChannelInput.java index bf88af2a..8dc5ca32 100644 --- a/src/main/java/com/github/kokorin/jaffree/ffprobe/ChannelInput.java +++ b/src/main/java/com/github/kokorin/jaffree/ffprobe/ChannelInput.java @@ -27,23 +27,29 @@ */ public class ChannelInput extends TcpInput { + private static final int DEFAULT_BUFFER_SIZE = 1_000_000; + /** * Creates {@link ChannelInput}. * - * @param channel byte channel + * @param channel byte channel + * @param fileName file name + * @param bufferSize buffer size to use when copying data */ - public ChannelInput(final SeekableByteChannel channel) { - this("", channel); + protected ChannelInput(final String fileName, final SeekableByteChannel channel, final int bufferSize) { + super("ftp", "/" + fileName, FtpServer.onRandomPorts(channel, bufferSize)); } /** * Creates {@link ChannelInput}. + *

+ * ffmpeg uses fileName's extension to autodetect input format * - * @param channel byte channel - * @param fileName file name + * @param channel byte channel + * @return ChannelInput */ - public ChannelInput(final String fileName, final SeekableByteChannel channel) { - super("ftp", "/" + fileName, FtpServer.onRandomPorts(channel)); + public static ChannelInput fromChannel(final SeekableByteChannel channel) { + return fromChannel("", channel); } /** @@ -52,10 +58,11 @@ public ChannelInput(final String fileName, final SeekableByteChannel channel) { * ffmpeg uses fileName's extension to autodetect input format * * @param channel byte channel + * @param bufferSize buffer size to copy data * @return ChannelInput */ - public static ChannelInput fromChannel(final SeekableByteChannel channel) { - return new ChannelInput(channel); + public static ChannelInput fromChannel(final SeekableByteChannel channel, final int bufferSize) { + return fromChannel("", channel, bufferSize); } /** @@ -69,6 +76,22 @@ public static ChannelInput fromChannel(final SeekableByteChannel channel) { */ public static ChannelInput fromChannel(final String fileName, final SeekableByteChannel channel) { - return new ChannelInput(fileName, channel); + return fromChannel(fileName, channel, DEFAULT_BUFFER_SIZE); + } + + /** + * Creates {@link ChannelInput}. + *

+ * ffmpeg uses fileName's extension to autodetect input format + * + * @param fileName file name + * @param channel byte channel + * @param bufferSize buffer size to copy data + * @return ChannelInput + */ + public static ChannelInput fromChannel(final String fileName, + final SeekableByteChannel channel, + final int bufferSize) { + return new ChannelInput(fileName, channel, bufferSize); } } diff --git a/src/main/java/com/github/kokorin/jaffree/ffprobe/FFprobe.java b/src/main/java/com/github/kokorin/jaffree/ffprobe/FFprobe.java index 5985cf5f..790500f3 100644 --- a/src/main/java/com/github/kokorin/jaffree/ffprobe/FFprobe.java +++ b/src/main/java/com/github/kokorin/jaffree/ffprobe/FFprobe.java @@ -68,7 +68,6 @@ public class FFprobe { private final List additionalArguments = new ArrayList<>(); - // TODO: make it final? private Input input; private FormatParser formatParser = new JsonFormatParser(); @@ -409,8 +408,7 @@ public FFprobe setInput(final Path inputPath) { * @return this */ public FFprobe setInput(final String inputUriOrPath) { - this.input = new UrlInput(inputUriOrPath); - return this; + return setInput(new UrlInput(inputUriOrPath)); } /** @@ -420,8 +418,7 @@ public FFprobe setInput(final String inputUriOrPath) { * @return this */ public FFprobe setInput(final InputStream inputStream) { - this.input = PipeInput.pumpFrom(inputStream); - return this; + return setInput(PipeInput.pumpFrom(inputStream)); } /** @@ -432,8 +429,7 @@ public FFprobe setInput(final InputStream inputStream) { * @return this */ public FFprobe setInput(final InputStream inputStream, final int bufferSize) { - this.input = PipeInput.pumpFrom(inputStream, bufferSize); - return this; + return setInput(PipeInput.pumpFrom(inputStream, bufferSize)); } /** @@ -442,16 +438,36 @@ public FFprobe setInput(final InputStream inputStream, final int bufferSize) { * @param inputChannel byte channel to analyze * @return this */ - //TODO add setInput(SeekableByteChannel, int) to allow custom buffer size public FFprobe setInput(final SeekableByteChannel inputChannel) { - this.input = ChannelInput.fromChannel(inputChannel); + return setInput(ChannelInput.fromChannel(inputChannel)); + } + + /** + * Sets input to analyze with ffprobe. + * + * @param inputChannel byte channel to analyze + * @param bufferSize buffer size to copy bytes from input stream + * @return this + */ + public FFprobe setInput(final SeekableByteChannel inputChannel, final int bufferSize) { + return setInput(ChannelInput.fromChannel(inputChannel, bufferSize)); + } + + /** + * Sets input to analyze with ffprobe. + * + * @param input input to analyze + * @return this + */ + public FFprobe setInput(Input input) { + this.input = input; return this; } /** * Sets ffprobe output format parser (and corresponding output format). *

- * {@link FlatFormatParser} is used by default. It's possible to provide custom implementation. + * {@link JsonFormatParser} is used by default. It's possible to provide custom implementation. * * @param formatParser format parser * @return this diff --git a/src/main/java/com/github/kokorin/jaffree/ffprobe/PipeInput.java b/src/main/java/com/github/kokorin/jaffree/ffprobe/PipeInput.java index 8e96ff12..1b4cada3 100644 --- a/src/main/java/com/github/kokorin/jaffree/ffprobe/PipeInput.java +++ b/src/main/java/com/github/kokorin/jaffree/ffprobe/PipeInput.java @@ -22,28 +22,17 @@ import java.io.InputStream; public class PipeInput extends TcpInput { - private final PipeInputNegotiator negotiator; + private static final int DEFAULT_BUFFER_SIZE = 1_000_000; - public PipeInput(InputStream source) { - this(new PipeInputNegotiator(source)); - } - - public PipeInput(PipeInputNegotiator negotiator) { - super(negotiator); - this.negotiator = negotiator; - } - - public PipeInput setBufferSize(int bufferSize) { - negotiator.setBufferSize(bufferSize); - return this; + protected PipeInput(final InputStream source, final int bufferSize) { + super(new PipeInputNegotiator(source, bufferSize)); } public static PipeInput pumpFrom(InputStream source) { - return new PipeInput(source); + return pumpFrom(source, DEFAULT_BUFFER_SIZE); } public static PipeInput pumpFrom(InputStream source, int bufferSize) { - return pumpFrom(source) - .setBufferSize(bufferSize); + return new PipeInput(source, bufferSize); } } diff --git a/src/main/java/com/github/kokorin/jaffree/ffprobe/Program.java b/src/main/java/com/github/kokorin/jaffree/ffprobe/Program.java index a919da76..6644bb1a 100644 --- a/src/main/java/com/github/kokorin/jaffree/ffprobe/Program.java +++ b/src/main/java/com/github/kokorin/jaffree/ffprobe/Program.java @@ -63,6 +63,13 @@ public Float getStartTime() { return probeData.getFloat("start_time"); } + /** + * Returns program start PTS. + *

+ * Timebase is hardcoded in ffmpeg code and is equal to 1_000_000. + * + * @return start pts + */ public Long getStartPts() { return probeData.getLong("start_pts"); } @@ -71,6 +78,13 @@ public Float getEndTime() { return probeData.getFloat("end_time"); } + /** + * Returns program end PTS. + *

+ * Timebase is hardcoded in ffmpeg code and is equal to 1_000_000. + * + * @return end pts + */ public Long getEndPts() { return probeData.getLong("end_pts"); } diff --git a/src/main/java/com/github/kokorin/jaffree/net/PipeInputNegotiator.java b/src/main/java/com/github/kokorin/jaffree/net/PipeInputNegotiator.java index bdc54dcf..b256504b 100644 --- a/src/main/java/com/github/kokorin/jaffree/net/PipeInputNegotiator.java +++ b/src/main/java/com/github/kokorin/jaffree/net/PipeInputNegotiator.java @@ -32,18 +32,12 @@ @ThreadSafe public class PipeInputNegotiator implements TcpNegotiator { private final InputStream source; + private final int bufferSize; - @GuardedBy("this") - private int bufferSize = DEFAULT_BUFFER_SIZE; - - private static final int DEFAULT_BUFFER_SIZE = 1_000_000; private static final Logger LOGGER = LoggerFactory.getLogger(PipeInputNegotiator.class); - public PipeInputNegotiator(InputStream source) { + public PipeInputNegotiator(InputStream source, int bufferSize) { this.source = source; - } - - public synchronized void setBufferSize(int bufferSize) { this.bufferSize = bufferSize; } diff --git a/src/main/java/com/github/kokorin/jaffree/util/ParseUtil.java b/src/main/java/com/github/kokorin/jaffree/util/ParseUtil.java index f1854fb0..618944d7 100644 --- a/src/main/java/com/github/kokorin/jaffree/util/ParseUtil.java +++ b/src/main/java/com/github/kokorin/jaffree/util/ParseUtil.java @@ -122,7 +122,6 @@ public static Double parseBitrateInKBits(final String value) { * @param value string to parse * @return parsed double or null if value can't be parsed */ - // TODO probably too specific method, instead parseDoubleWithSuffix can be public public static Double parseSpeed(final String value) { return parseDoubleWithSuffix(value, SPEED_SUFFIX); } diff --git a/src/test/java/com/github/kokorin/jaffree/ffmpeg/FrameIOTest.java b/src/test/java/com/github/kokorin/jaffree/ffmpeg/FrameIOTest.java index f3c30b21..024d6751 100644 --- a/src/test/java/com/github/kokorin/jaffree/ffmpeg/FrameIOTest.java +++ b/src/test/java/com/github/kokorin/jaffree/ffmpeg/FrameIOTest.java @@ -5,6 +5,7 @@ import com.github.kokorin.jaffree.StreamType; import com.github.kokorin.jaffree.ffprobe.FFprobe; import com.github.kokorin.jaffree.ffprobe.FFprobeResult; +import org.apache.commons.io.output.NullOutputStream; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Ignore; @@ -89,8 +90,6 @@ public void consume(Frame frame) { } @Test - @Ignore - // TODO unstable test public void testStreamId() throws Exception { expectedException.expect(new StackTraceMatcher("Stream ids must start with 0 and increase by 1 subsequently")); @@ -113,16 +112,8 @@ public Frame produce() { } }; - FFmpegResult result = FFmpeg.atPath(BIN) - .addInput( - FrameInput.withProducer(producer) - ) - .addOutput( - new NullOutput() - ) - .execute(); - - Assert.assertNotNull(result); + NutFrameWriter writer = new NutFrameWriter(producer, ImageFormats.BGR24); + writer.write(new NullOutputStream()); } @Test From a551332b490c53a6c2e0362c757a55d7f5ebdda9 Mon Sep 17 00:00:00 2001 From: kokorin <9crqUVAXd6Q17EY354lkbYeB> Date: Tue, 20 Apr 2021 08:25:40 +0300 Subject: [PATCH 7/7] Add addArguments usage to CutAndScaleExample & ReEncodeExample --- README.md | 20 ++++++++++--------- .../java/examples/CutAndScaleExample.java | 5 +++-- src/test/java/examples/ReEncodeExample.java | 5 +++-- 3 files changed, 17 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 399bc276..19b37b0f 100644 --- a/README.md +++ b/README.md @@ -88,8 +88,8 @@ See whole example [here](/src/test/java/examples/ReEncodeExample.java). final AtomicLong duration = new AtomicLong(); FFmpeg.atPath() .addInput(UrlInput.fromUrl(pathToSrc)) - .addOutput(new NullOutput()) .setOverwriteOutput(true) + .addOutput(new NullOutput()) .setProgressListener(new ProgressListener() { @Override public void onProgress(FFmpegProgress progress) { @@ -100,6 +100,8 @@ FFmpeg.atPath() FFmpeg.atPath() .addInput(UrlInput.fromUrl(pathToSrc)) + .setOverwriteOutput(true) + .addArguments("-movflags", "faststart") .addOutput(UrlOutput.toUrl(pathToDst)) .setProgressListener(new ProgressListener() { @Override @@ -108,7 +110,6 @@ FFmpeg.atPath() System.out.println("Progress: " + percents + "%"); } }) - .setOverwriteOutput(true) .execute(); ``` @@ -121,16 +122,17 @@ See whole example [here](/src/test/java/examples/CutAndScaleExample.java). ```java FFmpeg.atPath() .addInput( - UrlInput.fromUrl(pathToSrc) - .setPosition(10, TimeUnit.SECONDS) - .setDuration(42, TimeUnit.SECONDS) - ) - .addOutput( - UrlOutput.toUrl(pathToDst) - .setPosition(10, TimeUnit.SECONDS) + UrlInput.fromUrl(pathToSrc) + .setPosition(10, TimeUnit.SECONDS) + .setDuration(42, TimeUnit.SECONDS) ) .setFilter(StreamType.VIDEO, "scale=160:-2") .setOverwriteOutput(true) + .addArguments("-movflags", "faststart") + .addOutput( + UrlOutput.toUrl(pathToDst) + .setPosition(10, TimeUnit.SECONDS) + ) .execute(); ``` diff --git a/src/test/java/examples/CutAndScaleExample.java b/src/test/java/examples/CutAndScaleExample.java index 5e30bb8f..7ff4dcba 100644 --- a/src/test/java/examples/CutAndScaleExample.java +++ b/src/test/java/examples/CutAndScaleExample.java @@ -26,12 +26,13 @@ public static void main(String[] args) throws Exception { .setPosition(10, TimeUnit.SECONDS) .setDuration(42, TimeUnit.SECONDS) ) + .setFilter(StreamType.VIDEO, "scale=160:-2") + .setOverwriteOutput(true) + .addArguments("-movflags", "faststart") .addOutput( UrlOutput.toUrl(pathToDst) .setPosition(10, TimeUnit.SECONDS) ) - .setFilter(StreamType.VIDEO, "scale=160:-2") - .setOverwriteOutput(true) .execute(); } } diff --git a/src/test/java/examples/ReEncodeExample.java b/src/test/java/examples/ReEncodeExample.java index c2346a86..5fba4048 100644 --- a/src/test/java/examples/ReEncodeExample.java +++ b/src/test/java/examples/ReEncodeExample.java @@ -23,8 +23,8 @@ public static void main(String[] args) throws Exception { final AtomicLong duration = new AtomicLong(); FFmpeg.atPath() .addInput(UrlInput.fromUrl(pathToSrc)) - .addOutput(new NullOutput()) .setOverwriteOutput(true) + .addOutput(new NullOutput()) .setProgressListener(new ProgressListener() { @Override public void onProgress(FFmpegProgress progress) { @@ -35,6 +35,8 @@ public void onProgress(FFmpegProgress progress) { FFmpeg.atPath() .addInput(UrlInput.fromUrl(pathToSrc)) + .setOverwriteOutput(true) + .addArguments("-movflags", "faststart") .addOutput(UrlOutput.toUrl(pathToDst)) .setProgressListener(new ProgressListener() { @Override @@ -43,7 +45,6 @@ public void onProgress(FFmpegProgress progress) { System.out.println("Progress: " + percents + "%"); } }) - .setOverwriteOutput(true) .execute(); }