Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Process Listeners V2 #416

Open
wants to merge 3 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions src/main/java/com/github/kokorin/jaffree/ffmpeg/FFmpeg.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import com.github.kokorin.jaffree.process.LoggingStdReader;
import com.github.kokorin.jaffree.process.ProcessHandler;
import com.github.kokorin.jaffree.process.ProcessHelper;
import com.github.kokorin.jaffree.process.ProcessListener;
import com.github.kokorin.jaffree.process.StdReader;
import com.github.kokorin.jaffree.process.Stopper;
import org.slf4j.Logger;
Expand All @@ -49,6 +50,7 @@ public class FFmpeg {
private final List<String> additionalArguments = new ArrayList<>();
private boolean overwriteOutput;
private ProgressListener progressListener;
private ProcessListener processListener;
private OutputListener outputListener;
private String progress;
//-filter_threads nb_threads (global)
Expand Down Expand Up @@ -343,6 +345,20 @@ public FFmpeg setOutputListener(final OutputListener outputListener) {
this.outputListener = outputListener;
return this;
}

/**
* Send a Process Listener to receive the Process Instance when FFMpeg is executed
* <p>
* This can really help when more than basic controls are required and/or you want to keep track of all the ffpmeg instances going around.
* Note: Use with Responsibility!
*
* @param processListener process listener
* @return this
*/
public FFmpeg setProcessListener(final ProcessListener processListener) {
this.processListener = processListener;
return this;
}

/**
* Send program-friendly progress information to url.
Expand Down Expand Up @@ -506,6 +522,7 @@ protected ProcessHandler<FFmpegResult> createProcessHandler() {
.setStdErrReader(createStdErrReader(outputListener))
.setStdOutReader(createStdOutReader())
.setHelpers(helpers)
.setProcessListener(processListener)
.setArguments(buildArguments());
if (executorTimeoutMillis != null) {
processHandler.setExecutorTimeoutMillis(executorTimeoutMillis);
Expand Down
17 changes: 17 additions & 0 deletions src/main/java/com/github/kokorin/jaffree/ffprobe/FFprobe.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import com.github.kokorin.jaffree.ffprobe.data.JsonFormatParser;
import com.github.kokorin.jaffree.process.ProcessHandler;
import com.github.kokorin.jaffree.process.ProcessHelper;
import com.github.kokorin.jaffree.process.ProcessListener;
import com.github.kokorin.jaffree.process.StdReader;

import java.io.InputStream;
Expand Down Expand Up @@ -69,6 +70,7 @@ public class FFprobe {
private Input input;

private FormatParser formatParser = new JsonFormatParser();
private ProcessListener processListener;

private final Path executable;

Expand Down Expand Up @@ -488,6 +490,20 @@ public FFprobe setFormatParser(final FormatParser formatParser) {
this.formatParser = formatParser;
return this;
}

/**
* Send a Process Listener to receive the Process Instance when FFMpeg is executed
* <p>
* This can really help when more than basic controls are required and/or you want to keep track of all the ffpmeg instances going around.
* Note: Use with Responsibility!
*
* @param processListener process listener
* @return this
*/
public FFprobe setProcessListener(final ProcessListener processListener) {
this.processListener = processListener;
return this;
}

/**
* Sets ffprobe logging level.
Expand Down Expand Up @@ -545,6 +561,7 @@ public FFprobeResult execute() {
.setStdOutReader(createStdOutReader(formatParser))
.setStdErrReader(createStdErrReader())
.setHelpers(helpers)
.setProcessListener(processListener)
.setArguments(buildArguments())
.execute();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ public class ProcessHandler<T> {
private StdReader<T> stdOutReader = new GobblingStdReader<>();
private StdReader<T> stdErrReader = new GobblingStdReader<>();
private List<ProcessHelper> helpers = null;
private ProcessListener listener;
private Stopper stopper = null;
private List<String> arguments = Collections.emptyList();
private int executorTimeoutMillis = DEFAULT_EXECUTOR_TIMEOUT_MILLIS;
Expand Down Expand Up @@ -98,7 +99,18 @@ public synchronized ProcessHandler<T> setHelpers(final List<ProcessHelper> helpe
this.helpers = helpers;
return this;
}


/**
* Sets {@link ProcessListener} which can be used to track program execution.
*
* @param listener listener
* @return this
*/
public synchronized ProcessHandler<T> setProcessListener(ProcessListener listener) {
this.listener = listener;
return this;
}

/**
* Sets {@link Stopper} which can be used to interrupt program execution.
*
Expand All @@ -109,7 +121,7 @@ public synchronized ProcessHandler<T> setStopper(final Stopper stopper) {
this.stopper = stopper;
return this;
}

/**
* Sets arguments list to pass to a program.
*
Expand Down Expand Up @@ -158,13 +170,20 @@ public synchronized T execute() {
if (stopper != null) {
stopper.setProcess(process);
}
if(listener != null) {
listener.onStart(process);
}

return interactWithProcess(process);
} catch (IOException e) {
collectDebugInformation();
throw new JaffreeException("Failed to start process.", e);
} finally {
if (process != null) {
if(listener != null) {
//Done before the Process is destroyed just in case.
listener.onStop(process);
}
process.destroy();
// Process must be destroyed before closing streams, can't use
// try-with-resources, as resources are closing when leaving try block,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package com.github.kokorin.jaffree.process;

import java.util.Collections;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

/**
* @author Speiger
*/
public interface ProcessListener {
/**
* Provides the Process instance that was started
* @param process the process
*/
void onStart(Process process);

/**
* Provides the Process instance that was just completed
* @param process the process
*/
void onStop(Process process);

/**
* Simple tracker wrapper that allows to track all instances being loaded.
*
* @param instances Set. Highly Suggest {@link Collections#newSetFromMap} using a {@link ConcurrentHashMap} for multithreading support
* @return ProcessListener wrapper
*/
static ProcessListener of(Set<Process> instances) {
return new Impl(instances);
}

static class Impl implements ProcessListener {
Set<Process> instances;

public Impl(Set<Process> instances) {
this.instances = instances;
}

@Override
public void onStart(Process process) {
instances.add(process);
}

@Override
public void onStop(Process process) {
instances.remove(process);
}

}
}
40 changes: 40 additions & 0 deletions src/test/java/com/github/kokorin/jaffree/ffmpeg/FFmpegTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import com.github.kokorin.jaffree.ffprobe.FFprobeResult;
import com.github.kokorin.jaffree.ffprobe.Stream;
import com.github.kokorin.jaffree.process.ProcessHelper;
import com.github.kokorin.jaffree.process.ProcessListener;
import com.github.kokorin.jaffree.process.JaffreeAbnormalExitException;
import org.hamcrest.core.AllOf;
import org.hamcrest.core.StringContains;
Expand Down Expand Up @@ -55,7 +56,46 @@ public class FFmpegTest {

@Rule
public ExpectedException expectedException = ExpectedException.none();

@Test
public void testProcessTracking() throws Exception {
AtomicLong started = new AtomicLong();
AtomicLong stopped = new AtomicLong();
ProcessListener listener = new ProcessListener() {
@Override
public void onStart(Process process) { started.getAndAdd(1); }
@Override
public void onStop(Process process) { stopped.getAndAdd(1); }
};

Path tempDir = Files.createTempDirectory("jaffree");
Path outputPath = tempDir.resolve("test.mkv");

FFmpegResult result = FFmpeg.atPath(Config.FFMPEG_BIN)
.addInput(UrlInput.fromPath(Artifacts.VIDEO_FLV))
.addOutput(UrlOutput.toPath(outputPath))
.setProcessListener(listener)
.execute();

Assert.assertNotNull(result);
assertTrue("Process was never started", started.get() > 0);
assertTrue("Process was never stopped", stopped.get() > 0);

outputPath = tempDir.resolve("test.flv");
started.set(0L);
stopped.set(0L);

result = FFmpeg.atPath(Config.FFMPEG_BIN)
.addInput(UrlInput.fromPath(Artifacts.SMALL_MP4))
.addOutput(UrlOutput.toPath(outputPath))
.setProcessListener(listener)
.execute();

Assert.assertNotNull(result);
assertTrue("Process was never started", started.get() > 0);
assertTrue("Process was never stopped", stopped.get() > 0);
}

@Test
public void testSimpleCopy() throws Exception {
Path tempDir = Files.createTempDirectory("jaffree");
Expand Down
33 changes: 33 additions & 0 deletions src/test/java/com/github/kokorin/jaffree/ffprobe/FFprobeTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
import com.github.kokorin.jaffree.ffprobe.data.FormatParser;
import com.github.kokorin.jaffree.ffprobe.data.JsonFormatParser;
import com.github.kokorin.jaffree.process.JaffreeAbnormalExitException;
import com.github.kokorin.jaffree.process.ProcessListener;

import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Rule;
Expand All @@ -28,6 +30,7 @@
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.core.IsCollectionContaining.hasItems;
Expand All @@ -54,7 +57,37 @@ public FFprobeTest(FormatParser formatParser) {
}

//private boolean showData;
@Test
public void testProcessListener() throws Exception {
AtomicLong started = new AtomicLong();
AtomicLong stopped = new AtomicLong();
ProcessListener listener = new ProcessListener() {
@Override
public void onStart(Process process) { started.getAndAdd(1); }
@Override
public void onStop(Process process) { stopped.getAndAdd(1); }
};

FFprobeResult result = FFprobe.atPath(Config.FFMPEG_BIN)
.setInput(Artifacts.VIDEO_MP4)
.setShowData(true)
.setShowStreams(true)
.setFormatParser(formatParser)
.setProcessListener(listener)
.execute();

assertNotNull(result);
assertNotNull(result.getStreams());
assertFalse(result.getStreams().isEmpty());

Stream stream = result.getStreams().get(0);
assertNotNull(stream.getExtradata());
assertEquals(Rational.valueOf(30L), stream.getAvgFrameRate());
assertTrue("Process was never started", started.get() > 0);
assertTrue("Process was never stopped", stopped.get() > 0);
}


@Test
public void testShowDataWithShowStreams() throws Exception {
FFprobeResult result = FFprobe.atPath(Config.FFMPEG_BIN)
Expand Down
Loading