Helper enumeration type for Android ABIs; includes only supported ABIs. + *
Enumeration type for Android ABIs. + * + * @author Taner Sener */ public enum Abi { @@ -59,7 +61,7 @@ public enum Abi { */ ABI_UNKNOWN("unknown"); - private String name; + private final String name; /** *
Returns enumeration defined by ABI name. @@ -97,7 +99,7 @@ public enum Abi { } /** - * Creates new enum. + * Creates a new enum. * * @param abiName ABI name */ diff --git a/android/app/src/main/java/com/arthenica/ffmpegkit/AbiDetect.java b/android/app/src/main/java/com/arthenica/ffmpegkit/AbiDetect.java index bfddd0d..08b9a8d 100644 --- a/android/app/src/main/java/com/arthenica/ffmpegkit/AbiDetect.java +++ b/android/app/src/main/java/com/arthenica/ffmpegkit/AbiDetect.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2020 Taner Sener + * Copyright (c) 2018-2021 Taner Sener * * This file is part of FFmpegKit. * @@ -20,7 +20,7 @@ package com.arthenica.ffmpegkit; /** - *
This class is used to detect running ABI name using Google cpu-features
library.
+ *
Detects running ABI name using Google Utility class to execute an FFmpeg command asynchronously.
+ * Executes an FFmpeg session asynchronously.
*/
-public class AsyncFFmpegExecuteTask extends AsyncTask Utility class to execute an FFprobe command asynchronously.
+ * Executes an FFprobe execution asynchronously.
*/
-public class AsyncFFprobeExecuteTask extends AsyncTask Utility class to get media information asynchronously.
+ * Executes a MediaInformation session asynchronously.
*/
-public class AsyncGetMediaInformationTask extends AsyncTask Helper class to find camera devices supported.
+ * Note that camera devices can only be detected on Android API Level 24+. On older API levels
+ * an empty list will be returned.
*/
class CameraSupport {
diff --git a/android/app/src/main/java/com/arthenica/ffmpegkit/ExecuteCallback.java b/android/app/src/main/java/com/arthenica/ffmpegkit/ExecuteCallback.java
index e2f334e..6e460bd 100644
--- a/android/app/src/main/java/com/arthenica/ffmpegkit/ExecuteCallback.java
+++ b/android/app/src/main/java/com/arthenica/ffmpegkit/ExecuteCallback.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2018-2020 Taner Sener
+ * Copyright (c) 2018-2021 Taner Sener
*
* This file is part of FFmpegKit.
*
@@ -20,18 +20,16 @@
package com.arthenica.ffmpegkit;
/**
- * Represents a callback function to receive an asynchronous execution result.
+ * Callback function to receive execution results.
*/
@FunctionalInterface
public interface ExecuteCallback {
/**
- * Called when an asynchronous FFmpeg execution is completed.
+ * Called when an execution is completed.
*
- * @param executionId id of the execution that completed
- * @param returnCode return code of the execution completed, 0 on successful completion, 255
- * on user cancel, other non-zero codes on error
+ * @param session of with completed execution
*/
- void apply(long executionId, int returnCode);
+ void apply(final Session session);
}
diff --git a/android/app/src/main/java/com/arthenica/ffmpegkit/FFmpegKit.java b/android/app/src/main/java/com/arthenica/ffmpegkit/FFmpegKit.java
index 690c894..9ccfc25 100644
--- a/android/app/src/main/java/com/arthenica/ffmpegkit/FFmpegKit.java
+++ b/android/app/src/main/java/com/arthenica/ffmpegkit/FFmpegKit.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2018-2020 Taner Sener
+ * Copyright (c) 2018-2021 Taner Sener
*
* This file is part of FFmpegKit.
*
@@ -19,12 +19,9 @@
package com.arthenica.ffmpegkit;
-import android.os.AsyncTask;
-
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
-import java.util.concurrent.atomic.AtomicLong;
/**
* Main class for FFmpeg operations. Supports synchronous {@link #execute(String...)} and
@@ -40,10 +37,6 @@ import java.util.concurrent.atomic.AtomicLong;
*/
public class FFmpegKit {
- static final long DEFAULT_EXECUTION_ID = 0;
-
- private static final AtomicLong executionIdCounter = new AtomicLong(3000);
-
static {
AbiDetect.class.getName();
FFmpegKitConfig.class.getName();
@@ -59,26 +52,50 @@ public class FFmpegKit {
* Synchronously executes FFmpeg with arguments provided.
*
* @param arguments FFmpeg command options/arguments as string array
- * @return 0 on successful execution, 255 on user cancel, other non-zero codes on error
+ * @return ffmpeg session created for this execution
*/
- public static int execute(final String[] arguments) {
- return FFmpegKitConfig.ffmpegExecute(DEFAULT_EXECUTION_ID, arguments);
+ public static FFmpegSession execute(final String[] arguments) {
+ final FFmpegSession session = new FFmpegSession(arguments, null, null, null);
+
+ FFmpegKitConfig.ffmpegExecute(session);
+
+ return session;
}
/**
* Asynchronously executes FFmpeg with arguments provided.
*
* @param arguments FFmpeg command options/arguments as string array
- * @param executeCallback callback that will be notified when execution is completed
- * @return returns a unique id that represents this execution
+ * @param executeCallback callback that will be notified when the execution is completed
+ * @return ffmpeg session created for this execution
+ */
+ public static FFmpegSession executeAsync(final String[] arguments,
+ final ExecuteCallback executeCallback) {
+ final FFmpegSession session = new FFmpegSession(arguments, executeCallback, null, null);
+
+ FFmpegKitConfig.asyncFFmpegExecute(session);
+
+ return session;
+ }
+
+ /**
+ * Asynchronously executes FFmpeg with arguments provided.
+ *
+ * @param arguments FFmpeg command options/arguments as string array
+ * @param executeCallback callback that will be notified when execution is completed
+ * @param logCallback callback that will receive log entries
+ * @param statisticsCallback callback that will receive statistics
+ * @return ffmpeg session created for this execution
*/
- public static long executeAsync(final String[] arguments, final ExecuteCallback executeCallback) {
- final long newExecutionId = executionIdCounter.incrementAndGet();
+ public static FFmpegSession executeAsync(final String[] arguments,
+ final ExecuteCallback executeCallback,
+ final LogCallback logCallback,
+ final StatisticsCallback statisticsCallback) {
+ final FFmpegSession session = new FFmpegSession(arguments, executeCallback, logCallback, statisticsCallback);
- AsyncFFmpegExecuteTask asyncFFmpegExecuteTask = new AsyncFFmpegExecuteTask(newExecutionId, arguments, executeCallback);
- asyncFFmpegExecuteTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+ FFmpegKitConfig.asyncFFmpegExecute(session);
- return newExecutionId;
+ return session;
}
/**
@@ -87,31 +104,40 @@ public class FFmpegKit {
* @param arguments FFmpeg command options/arguments as string array
* @param executeCallback callback that will be notified when execution is completed
* @param executor executor that will be used to run this asynchronous operation
- * @return returns a unique id that represents this execution
+ * @return ffmpeg session created for this execution
*/
- public static long executeAsync(final String[] arguments, final ExecuteCallback executeCallback, final Executor executor) {
- final long newExecutionId = executionIdCounter.incrementAndGet();
+ public static FFmpegSession executeAsync(final String[] arguments,
+ final ExecuteCallback executeCallback,
+ final Executor executor) {
+ final FFmpegSession session = new FFmpegSession(arguments, executeCallback, null, null);
- AsyncFFmpegExecuteTask asyncFFmpegExecuteTask = new AsyncFFmpegExecuteTask(newExecutionId, arguments, executeCallback);
- asyncFFmpegExecuteTask.executeOnExecutor(executor);
+ AsyncFFmpegExecuteTask asyncFFmpegExecuteTask = new AsyncFFmpegExecuteTask(session);
+ executor.execute(asyncFFmpegExecuteTask);
- return newExecutionId;
+ return session;
}
/**
- * Synchronously executes FFmpeg command provided. Command is split into arguments using
- * provided delimiter character.
+ * Asynchronously executes FFmpeg with arguments provided.
*
- * @param command FFmpeg command
- * @param delimiter delimiter used to split arguments
- * @return 0 on successful execution, 255 on user cancel, other non-zero codes on error
- * @since 3.0
- * @deprecated argument splitting mechanism used in this method is pretty simple and prone to
- * errors. Consider using a more advanced method like {@link #execute(String)} or
- * {@link #execute(String[])}
+ * @param arguments FFmpeg command options/arguments as string array
+ * @param executeCallback callback that will be notified when execution is completed
+ * @param logCallback callback that will receive log entries
+ * @param statisticsCallback callback that will receive statistics
+ * @param executor executor that will be used to run this asynchronous operation
+ * @return ffmpeg session created for this execution
*/
- public static int execute(final String command, final String delimiter) {
- return execute((command == null) ? new String[]{""} : command.split((delimiter == null) ? " " : delimiter));
+ public static FFmpegSession executeAsync(final String[] arguments,
+ final ExecuteCallback executeCallback,
+ final LogCallback logCallback,
+ final StatisticsCallback statisticsCallback,
+ final Executor executor) {
+ final FFmpegSession session = new FFmpegSession(arguments, executeCallback, logCallback, statisticsCallback);
+
+ AsyncFFmpegExecuteTask asyncFFmpegExecuteTask = new AsyncFFmpegExecuteTask(session);
+ executor.execute(asyncFFmpegExecuteTask);
+
+ return session;
}
/**
@@ -120,9 +146,9 @@ public class FFmpegKit {
* your command.
*
* @param command FFmpeg command
- * @return 0 on successful execution, 255 on user cancel, other non-zero codes on error
+ * @return ffmpeg session created for this execution
*/
- public static int execute(final String command) {
+ public static FFmpegSession execute(final String command) {
return execute(parseArguments(command));
}
@@ -133,15 +159,29 @@ public class FFmpegKit {
*
* @param command FFmpeg command
* @param executeCallback callback that will be notified when execution is completed
- * @return returns a unique id that represents this execution
+ * @return ffmpeg session created for this execution
*/
- public static long executeAsync(final String command, final ExecuteCallback executeCallback) {
- final long newExecutionId = executionIdCounter.incrementAndGet();
-
- AsyncFFmpegExecuteTask asyncFFmpegExecuteTask = new AsyncFFmpegExecuteTask(newExecutionId, command, executeCallback);
- asyncFFmpegExecuteTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+ public static FFmpegSession executeAsync(final String command,
+ final ExecuteCallback executeCallback) {
+ return executeAsync(parseArguments(command), executeCallback);
+ }
- return newExecutionId;
+ /**
+ * Asynchronously executes FFmpeg command provided. Space character is used to split command
+ * into arguments. You can use single and double quote characters to specify arguments inside
+ * your command.
+ *
+ * @param command FFmpeg command
+ * @param executeCallback callback that will be notified when execution is completed
+ * @param logCallback callback that will receive log entries
+ * @param statisticsCallback callback that will receive statistics
+ * @return ffmpeg session created for this execution
+ */
+ public static FFmpegSession executeAsync(final String command,
+ final ExecuteCallback executeCallback,
+ final LogCallback logCallback,
+ final StatisticsCallback statisticsCallback) {
+ return executeAsync(parseArguments(command), executeCallback, logCallback, statisticsCallback);
}
/**
@@ -152,44 +192,76 @@ public class FFmpegKit {
* @param command FFmpeg command
* @param executeCallback callback that will be notified when execution is completed
* @param executor executor that will be used to run this asynchronous operation
- * @return returns a unique id that represents this execution
+ * @return ffmpeg session created for this execution
+ */
+ public static FFmpegSession executeAsync(final String command,
+ final ExecuteCallback executeCallback,
+ final Executor executor) {
+ final FFmpegSession session = new FFmpegSession(parseArguments(command), executeCallback, null, null);
+
+ AsyncFFmpegExecuteTask asyncFFmpegExecuteTask = new AsyncFFmpegExecuteTask(session);
+ executor.execute(asyncFFmpegExecuteTask);
+
+ return session;
+ }
+
+ /**
+ * Asynchronously executes FFmpeg command provided. Space character is used to split command
+ * into arguments. You can use single and double quote characters to specify arguments inside
+ * your command.
+ *
+ * @param command FFmpeg command
+ * @param executeCallback callback that will be notified when execution is completed
+ * @param logCallback callback that will receive log entries
+ * @param statisticsCallback callback that will receive statistics
+ * @param executor executor that will be used to run this asynchronous operation
+ * @return ffmpeg session created for this execution
*/
- public static long executeAsync(final String command, final ExecuteCallback executeCallback, final Executor executor) {
- final long newExecutionId = executionIdCounter.incrementAndGet();
+ public static FFmpegSession executeAsync(final String command,
+ final ExecuteCallback executeCallback,
+ final LogCallback logCallback,
+ final StatisticsCallback statisticsCallback,
+ final Executor executor) {
+ final FFmpegSession session = new FFmpegSession(parseArguments(command), executeCallback, logCallback, statisticsCallback);
- AsyncFFmpegExecuteTask asyncFFmpegExecuteTask = new AsyncFFmpegExecuteTask(newExecutionId, command, executeCallback);
- asyncFFmpegExecuteTask.executeOnExecutor(executor);
+ AsyncFFmpegExecuteTask asyncFFmpegExecuteTask = new AsyncFFmpegExecuteTask(session);
+ executor.execute(asyncFFmpegExecuteTask);
- return newExecutionId;
+ return session;
}
/**
- * Cancels an ongoing operation.
+ * Cancels the last execution started.
*
* This function does not wait for termination to complete and returns immediately.
*/
public static void cancel() {
- FFmpegKitConfig.nativeFFmpegCancel(DEFAULT_EXECUTION_ID);
+ Session lastSession = FFmpegKitConfig.getLastSession();
+ if (lastSession != null) {
+ FFmpegKitConfig.nativeFFmpegCancel(lastSession.getSessionId());
+ } else {
+ android.util.Log.w(FFmpegKitConfig.TAG, "FFmpegKit cancel skipped. The last execution does not exist.");
+ }
}
/**
- * Cancels an ongoing operation.
+ * Cancels the given execution.
*
* This function does not wait for termination to complete and returns immediately.
*
- * @param executionId id of the execution
+ * @param sessionId id of the session that will be stopped
*/
- public static void cancel(final long executionId) {
- FFmpegKitConfig.nativeFFmpegCancel(executionId);
+ public static void cancel(final long sessionId) {
+ FFmpegKitConfig.nativeFFmpegCancel(sessionId);
}
/**
- * Lists ongoing executions.
+ * Lists all FFmpeg sessions in the session history
*
- * @return list of ongoing executions
+ * @return all FFmpeg sessions in the session history
*/
- public static List This class is used to configure FFmpegKit library utilities/tools.
+ * This class is used to configure FFmpegKit library and tools coming with it.
*
* 1. {@link LogCallback}: This class redirects FFmpeg/FFprobe output to Logcat by default. As
* an alternative, it is possible not to print messages to Logcat and pass them to a
@@ -53,34 +71,43 @@ import java.util.concurrent.atomic.AtomicReference;
*/
public class FFmpegKitConfig {
- public static final int RETURN_CODE_SUCCESS = 0;
-
- public static final int RETURN_CODE_CANCEL = 255;
-
- private static int lastReturnCode = 0;
-
/**
- * Defines tag used for logging.
+ * The tag used for logging.
*/
public static final String TAG = "ffmpeg-kit";
- public static final String FFMPEG_KIT_PIPE_PREFIX = "mf_pipe_";
+ /**
+ * Prefix of named pipes created by ffmpeg kit.
+ */
+ public static final String FFMPEG_KIT_NAMED_PIPE_PREFIX = "fk_pipe_";
- private static LogCallback logCallbackFunction;
+ /**
+ * Generates ids for named ffmpeg kit pipes.
+ */
+ private static final AtomicLong pipeIndexGenerator;
+ private static final Map Sets a callback function to redirect FFmpeg/FFprobe logs.
- *
- * @param newLogCallback new log callback function or NULL to disable a previously defined callback
- */
- public static void enableLogCallback(final LogCallback newLogCallback) {
- logCallbackFunction = newLogCallback;
- }
-
- /**
- * Sets a callback function to redirect FFmpeg statistics.
- *
- * @param statisticsCallback new statistics callback function or NULL to disable a previously defined callback
- */
- public static void enableStatisticsCallback(final StatisticsCallback statisticsCallback) {
- statisticsCallbackFunction = statisticsCallback;
- }
-
/**
* Log redirection method called by JNI/native part.
*
- * @param executionId id of the execution that generated this log, 0 by default
- * @param levelValue log level as defined in {@link Level}
- * @param logMessage redirected log message
+ * @param sessionId id of the session that generated this log, 0 by default
+ * @param levelValue log level as defined in {@link Level}
+ * @param logMessage redirected log message
*/
- private static void log(final long executionId, final int levelValue, final byte[] logMessage) {
+ private static void log(final long sessionId, final int levelValue, final byte[] logMessage) {
final Level level = Level.from(levelValue);
final String text = new String(logMessage);
+ final Log log = new Log(sessionId, level, text);
// AV_LOG_STDERR logs are always redirected
if ((activeLogLevel == Level.AV_LOG_QUIET && levelValue != Level.AV_LOG_STDERR.getValue()) || levelValue > activeLogLevel.getValue()) {
@@ -243,11 +243,23 @@ public class FFmpegKitConfig {
return;
}
- if (logCallbackFunction != null) {
+ final Session session = getSession(sessionId);
+ if (session != null && session.getLogCallback() != null) {
try {
- logCallbackFunction.apply(new LogMessage(executionId, level, text));
+ // NOTIFY SESSION CALLBACK IF DEFINED
+ session.getLogCallback().apply(log);
} catch (final Exception e) {
- Log.e(FFmpegKitConfig.TAG, "Exception thrown inside LogCallback block", e);
+ android.util.Log.e(FFmpegKitConfig.TAG, String.format("Exception thrown inside session LogCallback block.%s", Exceptions.getStackTraceString(e)));
+ }
+ }
+
+ final LogCallback globalLogCallbackFunction = FFmpegKitConfig.globalLogCallbackFunction;
+ if (globalLogCallbackFunction != null) {
+ try {
+ // NOTIFY GLOBAL CALLBACK IF DEFINED
+ globalLogCallbackFunction.apply(log);
+ } catch (final Exception e) {
+ android.util.Log.e(FFmpegKitConfig.TAG, String.format("Exception thrown inside global LogCallback block.%s", Exceptions.getStackTraceString(e)));
}
} else {
switch (level) {
@@ -290,7 +302,7 @@ public class FFmpegKitConfig {
/**
* Statistics redirection method called by JNI/native part.
*
- * @param executionId id of the execution that generated this statistics, 0 by default
+ * @param sessionId id of the session that generated this statistics, 0 by default
* @param videoFrameNumber last processed frame number for videos
* @param videoFps frames processed per second for videos
* @param videoQuality quality of the video stream
@@ -299,17 +311,28 @@ public class FFmpegKitConfig {
* @param bitrate output bit rate in kbits/s
* @param speed processing speed = processed duration / operation duration
*/
- private static void statistics(final long executionId, final int videoFrameNumber,
+ private static void statistics(final long sessionId, final int videoFrameNumber,
final float videoFps, final float videoQuality, final long size,
final int time, final double bitrate, final double speed) {
- final Statistics newStatistics = new Statistics(executionId, videoFrameNumber, videoFps, videoQuality, size, time, bitrate, speed);
- lastReceivedStatistics.update(newStatistics);
+ final Statistics newStatistics = new Statistics(sessionId, videoFrameNumber, videoFps, videoQuality, size, time, bitrate, speed);
+
+ final Session session = getSession(sessionId);
+ if (session != null && session.getStatisticsCallback() != null) {
+ try {
+ // NOTIFY SESSION CALLBACK IF DEFINED
+ session.getStatisticsCallback().apply(newStatistics);
+ } catch (final Exception e) {
+ android.util.Log.e(FFmpegKitConfig.TAG, String.format("Exception thrown inside session StatisticsCallback block.%s", Exceptions.getStackTraceString(e)));
+ }
+ }
- if (statisticsCallbackFunction != null) {
+ final StatisticsCallback globalStatisticsCallbackFunction = FFmpegKitConfig.globalStatisticsCallbackFunction;
+ if (globalStatisticsCallbackFunction != null) {
try {
- statisticsCallbackFunction.apply(lastReceivedStatistics);
+ // NOTIFY GLOBAL CALLBACK IF DEFINED
+ globalStatisticsCallbackFunction.apply(newStatistics);
} catch (final Exception e) {
- Log.e(FFmpegKitConfig.TAG, "Exception thrown inside StatisticsCallback block", e);
+ android.util.Log.e(FFmpegKitConfig.TAG, String.format("Exception thrown inside global StatisticsCallback block.%s", Exceptions.getStackTraceString(e)));
}
}
}
@@ -317,17 +340,15 @@ public class FFmpegKitConfig {
/**
* Returns the last received statistics data.
*
- * @return last received statistics data
+ * @return last received statistics data or null if no statistics data is available
*/
public static Statistics getLastReceivedStatistics() {
- return lastReceivedStatistics;
- }
-
- /**
- * Resets last received statistics. It is recommended to call it before starting a new execution.
- */
- public static void resetStatistics() {
- lastReceivedStatistics = new Statistics();
+ final Session lastSession = getLastSession();
+ if (lastSession != null) {
+ return lastSession.getStatistics().peek();
+ } else {
+ return null;
+ }
}
/**
@@ -341,14 +362,15 @@ public class FFmpegKitConfig {
}
/**
- * Registers fonts inside the given path, so they are available to use in FFmpeg filters.
+ * Registers fonts inside the given path, so they become available to use in FFmpeg filters.
*
* Note that you need to build Returns package name.
+ * Returns Returns supported external libraries.
+ * Returns the list of supported external libraries.
*
* @return list of supported external libraries
- * @since 3.0
*/
public static List Please note that creator is responsible of closing created pipes.
*
* @param context application context
- * @return the full path of named pipe
+ * @return the full path of the named pipe
*/
public static String registerNewFFmpegPipe(final Context context) {
// PIPES ARE CREATED UNDER THE CACHE DIRECTORY
final File cacheDir = context.getCacheDir();
- final String newFFmpegPipePath = cacheDir + File.separator + FFMPEG_KIT_PIPE_PREFIX + (++lastCreatedPipeIndex);
+ final String newFFmpegPipePath = MessageFormat.format("{0}{1}{2}{3}", cacheDir, File.separator, FFMPEG_KIT_NAMED_PIPE_PREFIX, pipeIndexGenerator.getAndIncrement());
// FIRST CLOSE OLD PIPES WITH THE SAME NAME
closeFFmpegPipe(newFFmpegPipePath);
@@ -466,7 +486,7 @@ public class FFmpegKitConfig {
if (rc == 0) {
return newFFmpegPipePath;
} else {
- Log.e(TAG, String.format("Failed to register new FFmpeg pipe %s. Operation failed with rc=%d.", newFFmpegPipePath, rc));
+ android.util.Log.e(TAG, String.format("Failed to register new FFmpeg pipe %s. Operation failed with rc=%d.", newFFmpegPipePath, rc));
return null;
}
}
@@ -477,7 +497,7 @@ public class FFmpegKitConfig {
* @param ffmpegPipePath full path of ffmpeg pipe
*/
public static void closeFFmpegPipe(final String ffmpegPipePath) {
- File file = new File(ffmpegPipePath);
+ final File file = new File(ffmpegPipePath);
if (file.exists()) {
file.delete();
}
@@ -486,8 +506,11 @@ public class FFmpegKitConfig {
/**
* Returns the list of camera ids supported.
*
+ * Note that this method requires API Level >= 24. On older API levels it returns an empty
+ * list.
+ *
* @param context application context
- * @return the list of camera ids supported or an empty list if no supported camera is found
+ * @return the list of camera ids supported or an empty list if no supported cameras are found
*/
public static List Returns whether FFmpegKit release is a long term release or not.
+ * Returns whether FFmpegKit release is a Long Term Release or not.
*
- * @return YES or NO
+ * @return true/yes or false/no
*/
public static boolean isLTSBuild() {
return AbiDetect.isNativeLTSBuild();
@@ -540,63 +563,64 @@ public class FFmpegKitConfig {
}
/**
- * Returns return code of last executed command.
+ * Returns the return code of the last completed execution.
*
- * @return return code of last executed command
- * @since 3.0
+ * @return return code of the last completed execution
*/
public static int getLastReturnCode() {
- return lastReturnCode;
+ final Session lastSession = getLastSession();
+ if (lastSession != null) {
+ return lastSession.getReturnCode();
+ } else {
+ return 0;
+ }
}
/**
- * Returns log output of last executed single FFmpeg/FFprobe command.
+ * Returns the log output of the last executed FFmpeg/FFprobe command.
*
- * This method does not support executing multiple concurrent commands. If you execute
- * multiple commands at the same time, this method will return output from all executions.
- *
- * Please note that disabling redirection using {@link FFmpegKitConfig#disableRedirection()} method
- * also disables this functionality.
+ * Please note that disabling redirection using {@link FFmpegKitConfig#disableRedirection()}
+ * method also disables this functionality.
*
* @return output of the last executed command
- * @since 3.0
*/
public static String getLastCommandOutput() {
- String nativeLastCommandOutput = getNativeLastCommandOutput();
- if (nativeLastCommandOutput != null) {
+ final Session lastSession = getLastSession();
+ if (lastSession != null) {
// REPLACING CH(13) WITH CH(10)
- nativeLastCommandOutput = nativeLastCommandOutput.replace('\r', '\n');
+ return lastSession.getLogsAsString().replace('\r', '\n');
+ } else {
+ return "";
}
- return nativeLastCommandOutput;
}
/**
* Prints the output of the last executed FFmpeg/FFprobe command to the Logcat at the
* specified priority.
*
- * This method does not support executing multiple concurrent commands. If you execute
- * multiple commands at the same time, this method will print output from all executions.
- *
- * @param logPriority one of {@link Log#VERBOSE}, {@link Log#DEBUG}, {@link Log#INFO},
- * {@link Log#WARN}, {@link Log#ERROR}, {@link Log#ASSERT}
- * @since 4.3
+ * @param logPriority one of {@link android.util.Log#VERBOSE},
+ * {@link android.util.Log#DEBUG},
+ * {@link android.util.Log#INFO},
+ * {@link android.util.Log#WARN},
+ * {@link android.util.Log#ERROR},
+ * {@link android.util.Log#ASSERT}
*/
- public static void printLastCommandOutput(int logPriority) {
+ public static void printLastCommandOutput(final int logPriority) {
final int LOGGER_ENTRY_MAX_LEN = 4 * 1000;
String buffer = getLastCommandOutput();
do {
if (buffer.length() <= LOGGER_ENTRY_MAX_LEN) {
- Log.println(logPriority, FFmpegKitConfig.TAG, buffer);
+ android.util.Log.println(logPriority, FFmpegKitConfig.TAG, buffer);
buffer = "";
} else {
final int index = buffer.substring(0, LOGGER_ENTRY_MAX_LEN).lastIndexOf('\n');
if (index < 0) {
- Log.println(logPriority, FFmpegKitConfig.TAG, buffer.substring(0, LOGGER_ENTRY_MAX_LEN));
+ android.util.Log.println(logPriority, FFmpegKitConfig.TAG, buffer.substring(0, LOGGER_ENTRY_MAX_LEN));
buffer = buffer.substring(LOGGER_ENTRY_MAX_LEN);
} else {
- Log.println(logPriority, FFmpegKitConfig.TAG, buffer.substring(0, index));
+ android.util.Log.println(logPriority, FFmpegKitConfig.TAG, buffer.substring(0, index));
buffer = buffer.substring(index);
}
}
@@ -624,43 +648,409 @@ public class FFmpegKitConfig {
}
/**
- * Synchronously executes FFmpeg with arguments provided.
+ * Synchronously executes the ffmpeg session provided.
*
- * @param executionId id of the execution
- * @param arguments FFmpeg command options/arguments as string array
- * @return zero on successful execution, 255 on user cancel and non-zero on error
+ * @param ffmpegSession FFmpeg session which includes command options/arguments
*/
- static int ffmpegExecute(final long executionId, final String[] arguments) {
- final FFmpegExecution currentFFmpegExecution = new FFmpegExecution(executionId, arguments);
- executions.add(currentFFmpegExecution);
+ static void ffmpegExecute(final FFmpegSession ffmpegSession) {
+ addSession(ffmpegSession);
+ ffmpegSession.startRunning();
try {
- final int lastReturnCode = nativeFFmpegExecute(executionId, arguments);
+ final int returnCode = nativeFFmpegExecute(ffmpegSession.getSessionId(), ffmpegSession.getArguments());
+ ffmpegSession.complete(returnCode);
+ } catch (final Exception e) {
+ ffmpegSession.fail(e);
+ android.util.Log.w(FFmpegKitConfig.TAG, String.format("FFmpeg execute failed: %s.%s", FFmpegKit.argumentsToString(ffmpegSession.getArguments()), Exceptions.getStackTraceString(e)));
+ }
+ }
- FFmpegKitConfig.setLastReturnCode(lastReturnCode);
+ /**
+ * Synchronously executes the ffprobe session provided.
+ *
+ * @param ffprobeSession FFprobe session which includes command options/arguments
+ */
+ static void ffprobeExecute(final FFprobeSession ffprobeSession) {
+ addSession(ffprobeSession);
+ ffprobeSession.startRunning();
- return lastReturnCode;
- } finally {
- executions.remove(currentFFmpegExecution);
+ try {
+ final int returnCode = nativeFFprobeExecute(ffprobeSession.getSessionId(), ffprobeSession.getArguments());
+ ffprobeSession.complete(returnCode);
+ } catch (final Exception e) {
+ ffprobeSession.fail(e);
+ android.util.Log.w(FFmpegKitConfig.TAG, String.format("FFprobe execute failed: %s.%s", FFmpegKit.argumentsToString(ffprobeSession.getArguments()), Exceptions.getStackTraceString(e)));
+ }
+ }
+
+ /**
+ * Synchronously executes the media information session provided.
+ *
+ * @param mediaInformationSession media information session which includes command options/arguments
+ */
+ static void getMediaInformationExecute(final MediaInformationSession mediaInformationSession) {
+ addSession(mediaInformationSession);
+ mediaInformationSession.startRunning();
+
+ try {
+ final int returnCode = nativeFFprobeExecute(mediaInformationSession.getSessionId(), mediaInformationSession.getArguments());
+ mediaInformationSession.complete(returnCode);
+ if (returnCode == ReturnCode.SUCCESS) {
+ MediaInformation mediaInformation = MediaInformationParser.fromWithError(mediaInformationSession.getLogsAsString());
+ mediaInformationSession.setMediaInformation(mediaInformation);
+ }
+ } catch (final Exception e) {
+ mediaInformationSession.fail(e);
+ android.util.Log.w(FFmpegKitConfig.TAG, String.format("Get media information execute failed: %s.%s", FFmpegKit.argumentsToString(mediaInformationSession.getArguments()), Exceptions.getStackTraceString(e)));
+ }
+ }
+
+ /**
+ * Asynchronously executes the ffmpeg session provided.
+ *
+ * @param ffmpegSession FFmpeg session which includes command options/arguments
+ */
+ static void asyncFFmpegExecute(final FFmpegSession ffmpegSession) {
+ AsyncFFmpegExecuteTask asyncFFmpegExecuteTask = new AsyncFFmpegExecuteTask(ffmpegSession);
+ Future> future = asyncExecutorService.submit(asyncFFmpegExecuteTask);
+ ffmpegSession.setFuture(future);
+ }
+
+ /**
+ * Asynchronously executes the ffprobe session provided.
+ *
+ * @param ffprobeSession FFprobe session which includes command options/arguments
+ */
+ static void asyncFFprobeExecute(final FFprobeSession ffprobeSession) {
+ AsyncFFprobeExecuteTask asyncFFmpegExecuteTask = new AsyncFFprobeExecuteTask(ffprobeSession);
+ Future> future = asyncExecutorService.submit(asyncFFmpegExecuteTask);
+ ffprobeSession.setFuture(future);
+ }
+
+ /**
+ * Asynchronously executes the media information session provided.
+ *
+ * @param mediaInformationSession media information session which includes command options/arguments
+ */
+ static void asyncGetMediaInformationExecute(final MediaInformationSession mediaInformationSession) {
+ AsyncGetMediaInformationTask asyncGetMediaInformationTask = new AsyncGetMediaInformationTask(mediaInformationSession);
+ Future> future = asyncExecutorService.submit(asyncGetMediaInformationTask);
+ mediaInformationSession.setFuture(future);
+ }
+
+ /**
+ * Returns the maximum number of async operations that will be executed in parallel.
+ *
+ * @return maximum number of async operations that will be executed in parallel
+ */
+ public static int getAsyncConcurrencyLimit() {
+ return asyncConcurrencyLimit;
+ }
+
+ /**
+ * Sets the maximum number of async operations that will be executed in parallel. If more
+ * operations are submitted those will be queued.
+ *
+ * @param asyncConcurrencyLimit new async concurrency limit
+ */
+ public static void setAsyncConcurrencyLimit(final int asyncConcurrencyLimit) {
+
+ if (asyncConcurrencyLimit > 0) {
+
+ /* SET THE NEW LIMIT */
+ FFmpegKitConfig.asyncConcurrencyLimit = asyncConcurrencyLimit;
+ ExecutorService oldAsyncExecutorService = FFmpegKitConfig.asyncExecutorService;
+
+ /* CREATE THE NEW ASYNC THREAD POOL */
+ FFmpegKitConfig.asyncExecutorService = Executors.newFixedThreadPool(asyncConcurrencyLimit);
+
+ /* STOP THE OLD ASYNC THREAD POOL */
+ oldAsyncExecutorService.shutdown();
+ }
+ }
+
+ /**
+ * Sets a global callback function to redirect FFmpeg/FFprobe logs.
+ *
+ * @param newLogCallback new log callback function or null to disable a previously defined
+ * callback
+ */
+ public static void enableLogCallback(final LogCallback newLogCallback) {
+ globalLogCallbackFunction = newLogCallback;
+ }
+
+ /**
+ * Sets a global callback function to redirect FFmpeg statistics.
+ *
+ * @param statisticsCallback new statistics callback function or null to disable a previously
+ * defined callback
+ */
+ public static void enableStatisticsCallback(final StatisticsCallback statisticsCallback) {
+ globalStatisticsCallbackFunction = statisticsCallback;
+ }
+
+ /**
+ * Sets a global callback function to receive execution results.
+ *
+ * @param executeCallback new execute callback function or null to disable a previously
+ * defined callback
+ */
+ public static void enableExecuteCallback(final ExecuteCallback executeCallback) {
+ globalExecuteCallbackFunction = executeCallback;
+ }
+
+ /**
+ * Returns global execute callback function.
+ *
+ * @return global execute callback function
+ */
+ public static ExecuteCallback getGlobalExecuteCallbackFunction() {
+ return globalExecuteCallbackFunction;
+ }
+
+ /**
+ * Returns the current log level.
+ *
+ * @return current log level
+ */
+ public static Level getLogLevel() {
+ return activeLogLevel;
+ }
+
+ /**
+ * Sets the log level.
+ *
+ * @param level new log level
+ */
+ public static void setLogLevel(final Level level) {
+ if (level != null) {
+ activeLogLevel = level;
+ setNativeLogLevel(level.getValue());
+ }
+ }
+
+ /**
+ * Converts the given Structured Access Framework Uri ( Converts the given Structured Access Framework Uri ( Converts the given Structured Access Framework Uri ( Returns all sessions in the session history.
+ *
+ * @return all sessions in the session history
+ */
+ public static List Returns all FFmpeg sessions in the session history.
+ *
+ * @return all FFmpeg sessions in the session history
+ */
+ static List Returns all FFprobe sessions in the session history.
*
- * @param newLastReturnCode new last return code value
+ * @return all FFprobe sessions in the session history
*/
- static void setLastReturnCode(int newLastReturnCode) {
- lastReturnCode = newLastReturnCode;
+ static List Lists ongoing FFmpeg executions.
+ * Returns sessions that have the given state.
*
- * @return list of ongoing FFmpeg executions
+ * @return sessions that have the given state from the session history
*/
- static List Returns FFmpeg version bundled within the library natively.
@@ -702,32 +1092,33 @@ public class FFmpegKitConfig {
private native static String getNativeVersion();
/**
- * Synchronously executes FFmpeg natively with arguments provided.
+ * Synchronously executes FFmpeg natively.
*
- * @param executionId id of the execution
- * @param arguments FFmpeg command options/arguments as string array
+ * @param sessionId id of the session
+ * @param arguments FFmpeg command options/arguments as string array
* @return zero on successful execution, 255 on user cancel and non-zero on error
*/
- private native static int nativeFFmpegExecute(final long executionId, final String[] arguments);
+ private native static int nativeFFmpegExecute(final long sessionId, final String[] arguments);
/**
* Cancels an ongoing FFmpeg operation natively. This function does not wait for termination
* to complete and returns immediately.
*
- * @param executionId id of the execution
+ * @param sessionId id of the session
*/
- native static void nativeFFmpegCancel(final long executionId);
+ native static void nativeFFmpegCancel(final long sessionId);
/**
- * Synchronously executes FFprobe natively with arguments provided.
+ * Synchronously executes FFprobe natively.
*
+ * @param sessionId id of the session
* @param arguments FFprobe command options/arguments as string array
* @return zero on successful execution, 255 on user cancel and non-zero on error
*/
- native static int nativeFFprobeExecute(final String[] arguments);
+ native static int nativeFFprobeExecute(final long sessionId, final String[] arguments);
/**
- * Creates natively a new named pipe to use in Creates a new named pipe to use in Please note that creator is responsible of closing created pipes.
*
@@ -752,13 +1143,6 @@ public class FFmpegKitConfig {
*/
private native static int setNativeEnvironmentVariable(final String variableName, final String variableValue);
- /**
- * Returns log output of the last executed single command natively.
- *
- * @return output of the last executed single command
- */
- private native static String getNativeLastCommandOutput();
-
/**
* Registers a new ignored signal natively. Ignored signals are not handled by the library.
*
diff --git a/android/app/src/main/java/com/arthenica/ffmpegkit/FFmpegSession.java b/android/app/src/main/java/com/arthenica/ffmpegkit/FFmpegSession.java
new file mode 100644
index 0000000..b4d388d
--- /dev/null
+++ b/android/app/src/main/java/com/arthenica/ffmpegkit/FFmpegSession.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (c) 2020-2021 Taner Sener
+ *
+ * This file is part of FFmpegKit.
+ *
+ * FFmpegKit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * FFmpegKit 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with FFmpegKit. If not, see An FFmpeg execute session.
+ */
+public class FFmpegSession extends AbstractSession implements Session {
+ private final Queue Main class for FFprobe operations. Provides {@link #execute(String...)} method to execute
- * FFprobe commands.
+ * Main class for FFprobe operations.
+ * Supports running FFprobe commands using {@link #execute(String...)} method.
* It can also extract media information for a file or a url, using {@link #getMediaInformation(String)} method.
+ * Synchronously executes FFprobe with arguments provided.
*
* @param arguments FFprobe command options/arguments as string array
- * @return zero on successful execution, 255 on user cancel and non-zero on error
+ * @return ffprobe session created for this execution
*/
- public static int execute(final String[] arguments) {
- final int lastReturnCode = FFmpegKitConfig.nativeFFprobeExecute(arguments);
+ public static FFprobeSession execute(final String[] arguments) {
+ final FFprobeSession session = new FFprobeSession(arguments, null, null, null);
- FFmpegKitConfig.setLastReturnCode(lastReturnCode);
+ FFmpegKitConfig.ffprobeExecute(session);
- return lastReturnCode;
+ return session;
}
/**
@@ -62,69 +67,246 @@ public class FFprobeKit {
* your command.
*
* @param command FFprobe command
- * @return zero on successful execution, 255 on user cancel and non-zero on error
+ * @return ffprobe session created for this execution
*/
- public static int execute(final String command) {
+ public static FFprobeSession execute(final String command) {
return execute(FFmpegKit.parseArguments(command));
}
/**
- * Returns media information for the given file.
+ * Asynchronously executes FFprobe command provided. Space character is used to split command
+ * into arguments. You can use single and double quote characters to specify arguments inside
+ * your command.
*
- * This method does not support executing multiple concurrent operations. If you execute
- * multiple operations (execute or getMediaInformation) at the same time, the response of this
- * method is not predictable.
+ * @param command FFprobe command
+ * @param executeCallback callback that will be notified when the execution is completed
+ * @return ffprobe session created for this execution
+ */
+ public static FFprobeSession executeAsync(final String command,
+ final ExecuteCallback executeCallback) {
+ return executeAsync(FFmpegKit.parseArguments(command), executeCallback);
+ }
+
+ /**
+ * Asynchronously executes FFprobe with arguments provided.
*
- * @param path path or uri of media file
- * @return media information
- * @since 3.0
+ * @param arguments FFprobe command options/arguments as string array
+ * @param executeCallback callback that will be notified when the execution is completed
+ * @return ffprobe session created for this execution
*/
- public static MediaInformation getMediaInformation(final String path) {
- return getMediaInformationFromCommandArguments(new String[]{"-v", "error", "-hide_banner", "-print_format", "json", "-show_format", "-show_streams", "-i", path});
+ public static FFprobeSession executeAsync(final String[] arguments,
+ final ExecuteCallback executeCallback) {
+ final FFprobeSession session = new FFprobeSession(arguments, executeCallback, null, null);
+
+ FFmpegKitConfig.asyncFFprobeExecute(session);
+
+ return session;
}
/**
- * Returns media information for the given command.
+ * Asynchronously executes FFprobe command provided. Space character is used to split command
+ * into arguments. You can use single and double quote characters to specify arguments inside
+ * your command.
*
- * This method does not support executing multiple concurrent operations. If you execute
- * multiple operations (execute or getMediaInformation) at the same time, the response of this
- * method is not predictable.
+ * @param command FFprobe command
+ * @param executeCallback callback that will be notified when execution is completed
+ * @param logCallback callback that will receive log entries
+ * @param statisticsCallback callback that will receive statistics
+ * @return ffprobe session created for this execution
+ */
+ public static FFprobeSession executeAsync(final String command,
+ final ExecuteCallback executeCallback,
+ final LogCallback logCallback,
+ final StatisticsCallback statisticsCallback) {
+ return executeAsync(FFmpegKit.parseArguments(command), executeCallback, logCallback, statisticsCallback);
+ }
+
+ /**
+ * Asynchronously executes FFprobe with arguments provided.
*
- * @param command command to execute
- * @return media information
- * @since 4.3.3
+ * @param arguments FFprobe command options/arguments as string array
+ * @param executeCallback callback that will be notified when execution is completed
+ * @param logCallback callback that will receive log entries
+ * @param statisticsCallback callback that will receive statistics
+ * @return ffprobe session created for this execution
+ */
+ public static FFprobeSession executeAsync(final String[] arguments,
+ final ExecuteCallback executeCallback,
+ final LogCallback logCallback,
+ final StatisticsCallback statisticsCallback) {
+ final FFprobeSession session = new FFprobeSession(arguments, executeCallback, logCallback, statisticsCallback);
+
+ FFmpegKitConfig.asyncFFprobeExecute(session);
+
+ return session;
+ }
+
+ /**
+ * Asynchronously executes FFprobe with arguments provided.
+ *
+ * @param arguments FFprobe command options/arguments as string array
+ * @param executeCallback callback that will be notified when the execution is completed
+ * @param executor executor that will be used to run this asynchronous operation
+ * @return ffprobe session created for this execution
+ */
+ public static FFprobeSession executeAsync(final String[] arguments,
+ final ExecuteCallback executeCallback,
+ final Executor executor) {
+ final FFprobeSession session = new FFprobeSession(arguments, executeCallback, null, null);
+
+ AsyncFFprobeExecuteTask asyncFFprobeExecuteTask = new AsyncFFprobeExecuteTask(session);
+ executor.execute(asyncFFprobeExecuteTask);
+
+ return session;
+ }
+
+ /**
+ * Asynchronously executes FFprobe with arguments provided.
+ *
+ * @param arguments FFprobe command options/arguments as string array
+ * @param executeCallback callback that will be notified when execution is completed
+ * @param logCallback callback that will receive log entries
+ * @param statisticsCallback callback that will receive statistics
+ * @param executor executor that will be used to run this asynchronous operation
+ * @return ffprobe session created for this execution
+ */
+ public static FFprobeSession executeAsync(final String[] arguments,
+ final ExecuteCallback executeCallback,
+ final LogCallback logCallback,
+ final StatisticsCallback statisticsCallback,
+ final Executor executor) {
+ final FFprobeSession session = new FFprobeSession(arguments, executeCallback, logCallback, statisticsCallback);
+
+ AsyncFFprobeExecuteTask asyncFFprobeExecuteTask = new AsyncFFprobeExecuteTask(session);
+ executor.execute(asyncFFprobeExecuteTask);
+
+ return session;
+ }
+
+ /**
+ * Returns media information for the given path.
+ *
+ * @param path path or uri of a media file
+ * @return media information session created for this execution
+ */
+ public static MediaInformationSession getMediaInformation(final String path) {
+ return getMediaInformationFromCommandArguments(new String[]{"-v", "error", "-hide_banner", "-print_format", "json", "-show_format", "-show_streams", "-i", path}, null, null, null);
+ }
+
+ /**
+ * Returns media information for the given path asynchronously.
+ *
+ * @param path path or uri of a media file
+ * @param executeCallback callback that will be notified when the execution is completed
+ * @return media information session created for this execution
+ */
+ public static MediaInformationSession getMediaInformationAsync(final String path,
+ final ExecuteCallback executeCallback) {
+ final MediaInformationSession session = new MediaInformationSession(new String[]{"-v", "error", "-hide_banner", "-print_format", "json", "-show_format", "-show_streams", "-i", path}, executeCallback, null, null);
+
+ FFmpegKitConfig.asyncGetMediaInformationExecute(session);
+
+ return session;
+ }
+
+ /**
+ * Returns media information for the given path asynchronously.
+ *
+ * @param path path or uri of a media file
+ * @param executeCallback callback that will be notified when execution is completed
+ * @param logCallback callback that will receive log entries
+ * @param statisticsCallback callback that will receive statistics
+ * @return media information session created for this execution
+ */
+ public static MediaInformationSession getMediaInformationAsync(final String path,
+ final ExecuteCallback executeCallback,
+ final LogCallback logCallback,
+ final StatisticsCallback statisticsCallback) {
+ final MediaInformationSession session = new MediaInformationSession(new String[]{"-v", "error", "-hide_banner", "-print_format", "json", "-show_format", "-show_streams", "-i", path}, executeCallback, logCallback, statisticsCallback);
+
+ FFmpegKitConfig.asyncGetMediaInformationExecute(session);
+
+ return session;
+ }
+
+ /**
+ * Returns media information for the given path asynchronously.
+ *
+ * @param path path or uri of a media file
+ * @param executeCallback callback that will be notified when the execution is completed
+ * @param executor executor that will be used to run this asynchronous operation
+ * @return media information session created for this execution
+ */
+ public static MediaInformationSession getMediaInformationAsync(final String path,
+ final ExecuteCallback executeCallback,
+ final Executor executor) {
+ final MediaInformationSession session = new MediaInformationSession(new String[]{"-v", "error", "-hide_banner", "-print_format", "json", "-show_format", "-show_streams", "-i", path}, executeCallback, null, null);
+
+ AsyncGetMediaInformationTask asyncGetMediaInformationTask = new AsyncGetMediaInformationTask(session);
+ executor.execute(asyncGetMediaInformationTask);
+
+ return session;
+ }
+
+ /**
+ * Returns media information for the given path asynchronously.
+ *
+ * @param path path or uri of a media file
+ * @param executeCallback callback that will be notified when execution is completed
+ * @param logCallback callback that will receive log entries
+ * @param statisticsCallback callback that will receive statistics
+ * @param executor executor that will be used to run this asynchronous operation
+ * @return media information session created for this execution
*/
- public static MediaInformation getMediaInformationFromCommand(final String command) {
- return getMediaInformationFromCommandArguments(FFmpegKit.parseArguments(command));
+ public static MediaInformationSession getMediaInformationAsync(final String path,
+ final ExecuteCallback executeCallback,
+ final LogCallback logCallback,
+ final StatisticsCallback statisticsCallback,
+ final Executor executor) {
+ final MediaInformationSession session = new MediaInformationSession(new String[]{"-v", "error", "-hide_banner", "-print_format", "json", "-show_format", "-show_streams", "-i", path}, executeCallback, logCallback, statisticsCallback);
+
+ AsyncGetMediaInformationTask asyncGetMediaInformationTask = new AsyncGetMediaInformationTask(session);
+ executor.execute(asyncGetMediaInformationTask);
+
+ return session;
}
/**
- * Returns media information for given file.
+ * Returns media information for the given command.
*
- * This method does not support executing multiple concurrent operations. If you execute
- * multiple operations (execute or getMediaInformation) at the same time, the response of this
- * method is not predictable.
+ * @param command command to execute
+ * @return media information session created for this execution
+ */
+ public static MediaInformationSession getMediaInformationFromCommand(final String command) {
+ return getMediaInformationFromCommandArguments(FFmpegKit.parseArguments(command), null, null, null);
+ }
+
+
+ /**
+ * Returns media information for the given command.
*
- * @param path path or uri of media file
- * @param timeout complete timeout
- * @return media information
- * @since 3.0
- * @deprecated this method is deprecated since v4.3.1. You can still use this method but
- * An FFprobe execute session.
+ */
+public class FFprobeSession extends AbstractSession implements Session {
+
+ public FFprobeSession(final String[] arguments,
+ final ExecuteCallback executeCallback,
+ final LogCallback logCallback,
+ final StatisticsCallback statisticsCallback) {
+ super(arguments, executeCallback, logCallback, statisticsCallback);
+ }
+
+ @Override
+ public Queue Helper enumeration type for log levels.
+ * Enumeration type for log levels.
*/
public enum Level {
@@ -79,7 +79,7 @@ public enum Level {
*/
AV_LOG_TRACE(56);
- private int value;
+ private final int value;
/**
* Returns enumeration defined by value.
diff --git a/android/app/src/main/java/com/arthenica/ffmpegkit/LogMessage.java b/android/app/src/main/java/com/arthenica/ffmpegkit/Log.java
similarity index 65%
rename from android/app/src/main/java/com/arthenica/ffmpegkit/LogMessage.java
rename to android/app/src/main/java/com/arthenica/ffmpegkit/Log.java
index a29b3ee..2b5d09e 100644
--- a/android/app/src/main/java/com/arthenica/ffmpegkit/LogMessage.java
+++ b/android/app/src/main/java/com/arthenica/ffmpegkit/Log.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2018-2020 Taner Sener
+ * Copyright (c) 2018-2021 Taner Sener
*
* This file is part of FFmpegKit.
*
@@ -20,44 +20,43 @@
package com.arthenica.ffmpegkit;
/**
- * Logs for running executions.
+ * Log entry for an execute session.
*/
-public class LogMessage {
-
- private final long executionId;
+public class Log {
+ private final long sessionId;
private final Level level;
- private final String text;
+ private final String message;
- public LogMessage(final long executionId, final Level level, final String text) {
- this.executionId = executionId;
+ public Log(final long sessionId, final Level level, final String message) {
+ this.sessionId = sessionId;
this.level = level;
- this.text = text;
+ this.message = message;
}
- public long getExecutionId() {
- return executionId;
+ public long getSessionId() {
+ return sessionId;
}
public Level getLevel() {
return level;
}
- public String getText() {
- return text;
+ public String getMessage() {
+ return message;
}
@Override
public String toString() {
final StringBuilder stringBuilder = new StringBuilder();
- stringBuilder.append("LogMessage{");
- stringBuilder.append("executionId=");
- stringBuilder.append(executionId);
+ stringBuilder.append("Log{");
+ stringBuilder.append("sessionId=");
+ stringBuilder.append(sessionId);
stringBuilder.append(", level=");
stringBuilder.append(level);
- stringBuilder.append(", text=");
+ stringBuilder.append(", message=");
stringBuilder.append("\'");
- stringBuilder.append(text);
+ stringBuilder.append(message);
stringBuilder.append('\'');
stringBuilder.append('}');
diff --git a/android/app/src/main/java/com/arthenica/ffmpegkit/LogCallback.java b/android/app/src/main/java/com/arthenica/ffmpegkit/LogCallback.java
index 3923886..aa10572 100644
--- a/android/app/src/main/java/com/arthenica/ffmpegkit/LogCallback.java
+++ b/android/app/src/main/java/com/arthenica/ffmpegkit/LogCallback.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2018-2020 Taner Sener
+ * Copyright (c) 2018-2021 Taner Sener
*
* This file is part of FFmpegKit.
*
@@ -20,11 +20,16 @@
package com.arthenica.ffmpegkit;
/**
- * Represents a callback function to receive logs from running executions
+ * Callback function to receive logs for executions.
*/
@FunctionalInterface
public interface LogCallback {
- void apply(final LogMessage message);
+ /**
+ * Called when a log entry is received.
+ *
+ * @param log log entry
+ */
+ void apply(final Log log);
}
diff --git a/android/app/src/main/java/com/arthenica/ffmpegkit/MediaInformation.java b/android/app/src/main/java/com/arthenica/ffmpegkit/MediaInformation.java
index 662dcd0..83d04a3 100644
--- a/android/app/src/main/java/com/arthenica/ffmpegkit/MediaInformation.java
+++ b/android/app/src/main/java/com/arthenica/ffmpegkit/MediaInformation.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2018-2020 Taner Sener
+ * Copyright (c) 2018-2021 Taner Sener
*
* This file is part of FFmpegKit.
*
@@ -28,15 +28,16 @@ import java.util.List;
*/
public class MediaInformation {
- private static final String KEY_MEDIA_PROPERTIES = "format";
- private static final String KEY_FILENAME = "filename";
- private static final String KEY_FORMAT = "format_name";
- private static final String KEY_FORMAT_LONG = "format_long_name";
- private static final String KEY_START_TIME = "start_time";
- private static final String KEY_DURATION = "duration";
- private static final String KEY_SIZE = "size";
- private static final String KEY_BIT_RATE = "bit_rate";
- private static final String KEY_TAGS = "tags";
+ /* COMMON KEYS */
+ public static final String KEY_MEDIA_PROPERTIES = "format";
+ public static final String KEY_FILENAME = "filename";
+ public static final String KEY_FORMAT = "format_name";
+ public static final String KEY_FORMAT_LONG = "format_long_name";
+ public static final String KEY_START_TIME = "start_time";
+ public static final String KEY_DURATION = "duration";
+ public static final String KEY_SIZE = "size";
+ public static final String KEY_BIT_RATE = "bit_rate";
+ public static final String KEY_TAGS = "tags";
/**
* Stores all properties.
diff --git a/android/app/src/main/java/com/arthenica/ffmpegkit/MediaInformationParser.java b/android/app/src/main/java/com/arthenica/ffmpegkit/MediaInformationParser.java
index 8324e7a..de5734b 100644
--- a/android/app/src/main/java/com/arthenica/ffmpegkit/MediaInformationParser.java
+++ b/android/app/src/main/java/com/arthenica/ffmpegkit/MediaInformationParser.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2018-2020 Taner Sener
+ * Copyright (c) 2018-2021 Taner Sener
*
* This file is part of FFmpegKit.
*
@@ -21,6 +21,8 @@ package com.arthenica.ffmpegkit;
import android.util.Log;
+import com.arthenica.smartexception.java.Exceptions;
+
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
@@ -28,12 +30,14 @@ import org.json.JSONObject;
import java.util.ArrayList;
/**
- * Helper class for parsing {@link MediaInformation}.
+ * Parser for {@link MediaInformation}.
*/
public class MediaInformationParser {
/**
- * Extracts MediaInformation from the given ffprobe json output.
+ * Extracts A custom FFprobe execute session, which produces a Provides helper methods to extract binary package information.
+ * Helper class to extract binary package information.
*/
class Packages {
@@ -39,7 +39,6 @@ class Packages {
supportedExternalLibraries.add("gnutls");
supportedExternalLibraries.add("kvazaar");
supportedExternalLibraries.add("mp3lame");
- supportedExternalLibraries.add("libaom");
supportedExternalLibraries.add("libass");
supportedExternalLibraries.add("iconv");
supportedExternalLibraries.add("libilbc");
@@ -137,7 +136,6 @@ class Packages {
externalLibraryList.contains("gnutls") &&
externalLibraryList.contains("kvazaar") &&
externalLibraryList.contains("mp3lame") &&
- externalLibraryList.contains("libaom") &&
externalLibraryList.contains("libass") &&
externalLibraryList.contains("iconv") &&
externalLibraryList.contains("libilbc") &&
@@ -172,7 +170,6 @@ class Packages {
externalLibraryList.contains("gnutls") &&
externalLibraryList.contains("kvazaar") &&
externalLibraryList.contains("mp3lame") &&
- externalLibraryList.contains("libaom") &&
externalLibraryList.contains("libass") &&
externalLibraryList.contains("iconv") &&
externalLibraryList.contains("libilbc") &&
@@ -200,7 +197,6 @@ class Packages {
externalLibraryList.contains("freetype") &&
externalLibraryList.contains("fribidi") &&
externalLibraryList.contains("kvazaar") &&
- externalLibraryList.contains("libaom") &&
externalLibraryList.contains("libass") &&
externalLibraryList.contains("iconv") &&
externalLibraryList.contains("libtheora") &&
diff --git a/android/app/src/main/java/com/arthenica/ffmpegkit/FFmpegExecution.java b/android/app/src/main/java/com/arthenica/ffmpegkit/ReturnCode.java
similarity index 54%
rename from android/app/src/main/java/com/arthenica/ffmpegkit/FFmpegExecution.java
rename to android/app/src/main/java/com/arthenica/ffmpegkit/ReturnCode.java
index 6cd7ed3..7affedf 100644
--- a/android/app/src/main/java/com/arthenica/ffmpegkit/FFmpegExecution.java
+++ b/android/app/src/main/java/com/arthenica/ffmpegkit/ReturnCode.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2020 Taner Sener
+ * Copyright (c) 2021 Taner Sener
*
* This file is part of FFmpegKit.
*
@@ -19,32 +19,24 @@
package com.arthenica.ffmpegkit;
-import java.util.Date;
+public class ReturnCode {
-/**
- * Represents an ongoing FFmpeg execution.
- */
-public class FFmpegExecution {
- private final Date startTime;
- private final long executionId;
- private final String command;
-
- public FFmpegExecution(final long executionId, final String[] arguments) {
- this.startTime = new Date();
- this.executionId = executionId;
- this.command = FFmpegKit.argumentsToString(arguments);
- }
+ public static int NOT_SET = -999;
+
+ public static int SUCCESS = 0;
+
+ public static int CANCEL = 255;
- public Date getStartTime() {
- return startTime;
+ public static boolean isSuccess(final int returnCode) {
+ return (returnCode == SUCCESS);
}
- public long getExecutionId() {
- return executionId;
+ public static boolean isFailure(final int returnCode) {
+ return (returnCode != NOT_SET) && (returnCode != SUCCESS) && (returnCode != CANCEL);
}
- public String getCommand() {
- return command;
+ public static boolean isCancel(final int returnCode) {
+ return (returnCode == CANCEL);
}
}
diff --git a/android/app/src/main/java/com/arthenica/ffmpegkit/Session.java b/android/app/src/main/java/com/arthenica/ffmpegkit/Session.java
new file mode 100644
index 0000000..fa10329
--- /dev/null
+++ b/android/app/src/main/java/com/arthenica/ffmpegkit/Session.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) 2021 Taner Sener
+ *
+ * This file is part of FFmpegKit.
+ *
+ * FFmpegKit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * FFmpegKit 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 Lesser General License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General License
+ * along with FFmpegKit. If not, see Interface for ffmpeg and ffprobe execute sessions.
+ */
+public interface Session {
+
+ ExecuteCallback getExecuteCallback();
+
+ LogCallback getLogCallback();
+
+ StatisticsCallback getStatisticsCallback();
+
+ long getSessionId();
+
+ Date getCreateTime();
+
+ Date getStartTime();
+
+ Date getEndTime();
+
+ long getDuration();
+
+ String[] getArguments();
+
+ String getCommand();
+
+ Queue Represents a callback function to receive asynchronous getMediaInformation result.
- */
-@FunctionalInterface
-public interface GetMediaInformationCallback {
-
- void apply(MediaInformation mediaInformation);
-
+public enum SessionState {
+ CREATED,
+ RUNNING,
+ FAILED,
+ COMPLETED
}
diff --git a/android/app/src/main/java/com/arthenica/ffmpegkit/Signal.java b/android/app/src/main/java/com/arthenica/ffmpegkit/Signal.java
index 88f06da..85b27be 100644
--- a/android/app/src/main/java/com/arthenica/ffmpegkit/Signal.java
+++ b/android/app/src/main/java/com/arthenica/ffmpegkit/Signal.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2020 Taner Sener
+ * Copyright (c) 2020-2021 Taner Sener
*
* This file is part of FFmpegKit.
*
@@ -30,7 +30,7 @@ public enum Signal {
SIGTERM(15),
SIGXCPU(24);
- private int value;
+ private final int value;
Signal(int value) {
this.value = value;
diff --git a/android/app/src/main/java/com/arthenica/ffmpegkit/Statistics.java b/android/app/src/main/java/com/arthenica/ffmpegkit/Statistics.java
index 025b52b..710c62f 100644
--- a/android/app/src/main/java/com/arthenica/ffmpegkit/Statistics.java
+++ b/android/app/src/main/java/com/arthenica/ffmpegkit/Statistics.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2018-2020 Taner Sener
+ * Copyright (c) 2018-2021 Taner Sener
*
* This file is part of FFmpegKit.
*
@@ -20,11 +20,10 @@
package com.arthenica.ffmpegkit;
/**
- * Statistics for running executions.
+ * Statistics entry for an FFmpeg execute session.
*/
public class Statistics {
-
- private long executionId;
+ private long sessionId;
private int videoFrameNumber;
private float videoFps;
private float videoQuality;
@@ -34,7 +33,7 @@ public class Statistics {
private double speed;
public Statistics() {
- executionId = 0;
+ sessionId = 0;
videoFrameNumber = 0;
videoFps = 0;
videoQuality = 0;
@@ -44,8 +43,8 @@ public class Statistics {
speed = 0;
}
- public Statistics(long executionId, int videoFrameNumber, float videoFps, float videoQuality, long size, int time, double bitrate, double speed) {
- this.executionId = executionId;
+ public Statistics(final long sessionId, final int videoFrameNumber, final float videoFps, final float videoQuality, final long size, final int time, final double bitrate, final double speed) {
+ this.sessionId = sessionId;
this.videoFrameNumber = videoFrameNumber;
this.videoFps = videoFps;
this.videoQuality = videoQuality;
@@ -55,44 +54,12 @@ public class Statistics {
this.speed = speed;
}
- public void update(final Statistics newStatistics) {
- if (newStatistics != null) {
- this.executionId = newStatistics.getExecutionId();
- if (newStatistics.getVideoFrameNumber() > 0) {
- this.videoFrameNumber = newStatistics.getVideoFrameNumber();
- }
- if (newStatistics.getVideoFps() > 0) {
- this.videoFps = newStatistics.getVideoFps();
- }
-
- if (newStatistics.getVideoQuality() > 0) {
- this.videoQuality = newStatistics.getVideoQuality();
- }
-
- if (newStatistics.getSize() > 0) {
- this.size = newStatistics.getSize();
- }
-
- if (newStatistics.getTime() > 0) {
- this.time = newStatistics.getTime();
- }
-
- if (newStatistics.getBitrate() > 0) {
- this.bitrate = newStatistics.getBitrate();
- }
-
- if (newStatistics.getSpeed() > 0) {
- this.speed = newStatistics.getSpeed();
- }
- }
- }
-
- public long getExecutionId() {
- return executionId;
+ public long getSessionId() {
+ return sessionId;
}
- public void setExecutionId(long executionId) {
- this.executionId = executionId;
+ public void setSessionId(long sessionId) {
+ this.sessionId = sessionId;
}
public int getVideoFrameNumber() {
@@ -156,8 +123,8 @@ public class Statistics {
final StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("Statistics{");
- stringBuilder.append("executionId=");
- stringBuilder.append(executionId);
+ stringBuilder.append("sessionId=");
+ stringBuilder.append(sessionId);
stringBuilder.append(", videoFrameNumber=");
stringBuilder.append(videoFrameNumber);
stringBuilder.append(", videoFps=");
diff --git a/android/app/src/main/java/com/arthenica/ffmpegkit/StatisticsCallback.java b/android/app/src/main/java/com/arthenica/ffmpegkit/StatisticsCallback.java
index e3e9964..940a74d 100644
--- a/android/app/src/main/java/com/arthenica/ffmpegkit/StatisticsCallback.java
+++ b/android/app/src/main/java/com/arthenica/ffmpegkit/StatisticsCallback.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2018-2020 Taner Sener
+ * Copyright (c) 2018-2021 Taner Sener
*
* This file is part of FFmpegKit.
*
@@ -20,11 +20,16 @@
package com.arthenica.ffmpegkit;
/**
- * Represents a callback function to receive statistics from running executions.
+ * Callback function to receive statistics for executions.
*/
@FunctionalInterface
public interface StatisticsCallback {
+ /**
+ * Called when a statistics entry is received.
+ *
+ * @param statistics statistics entry
+ */
void apply(final Statistics statistics);
}
diff --git a/android/app/src/main/java/com/arthenica/ffmpegkit/StreamInformation.java b/android/app/src/main/java/com/arthenica/ffmpegkit/StreamInformation.java
index c441c19..012b712 100644
--- a/android/app/src/main/java/com/arthenica/ffmpegkit/StreamInformation.java
+++ b/android/app/src/main/java/com/arthenica/ffmpegkit/StreamInformation.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2018-2020 Taner Sener
+ * Copyright (c) 2018-2021 Taner Sener
*
* This file is part of FFmpegKit.
*
@@ -26,6 +26,7 @@ import org.json.JSONObject;
*/
public class StreamInformation {
+ /* COMMON KEYS */
private static final String KEY_INDEX = "index";
private static final String KEY_TYPE = "codec_type";
private static final String KEY_CODEC = "codec_name";
diff --git a/android/app/src/test/java/com/arthenica/ffmpegkit/AbstractSessionTest.java b/android/app/src/test/java/com/arthenica/ffmpegkit/AbstractSessionTest.java
new file mode 100644
index 0000000..e6f26bc
--- /dev/null
+++ b/android/app/src/test/java/com/arthenica/ffmpegkit/AbstractSessionTest.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2021 Taner Sener
+ *
+ * This file is part of FFmpegKit.
+ *
+ * FFmpegKit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * FFmpegKit 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with FFmpegKit. If not, see cpu-features
library.
*/
public class AbiDetect {
@@ -46,8 +46,8 @@ public class AbiDetect {
private AbiDetect() {
}
- static void setArmV7aNeonLoaded(final boolean armV7aNeonLoaded) {
- AbiDetect.armV7aNeonLoaded = armV7aNeonLoaded;
+ static void setArmV7aNeonLoaded() {
+ armV7aNeonLoaded = true;
}
/**
diff --git a/android/app/src/main/java/com/arthenica/ffmpegkit/AbstractSession.java b/android/app/src/main/java/com/arthenica/ffmpegkit/AbstractSession.java
new file mode 100644
index 0000000..62750b8
--- /dev/null
+++ b/android/app/src/main/java/com/arthenica/ffmpegkit/AbstractSession.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (c) 2021 Taner Sener
+ *
+ * This file is part of FFmpegKit.
+ *
+ * FFmpegKit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * FFmpegKit 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with FFmpegKit. If not, see FFmpegKit
with fontconfig
* enabled or use a prebuilt package with fontconfig
inside to use this feature.
*
* @param context application context to access application data
* @param fontDirectoryPath directory which contains fonts (.ttf and .otf files)
- * @param fontNameMapping custom font name mappings, useful to access your fonts with more friendly names
+ * @param fontNameMapping custom font name mappings, useful to access your fonts with more
+ * friendly names
*/
public static void setFontDirectory(final Context context, final String fontDirectoryPath, final MapFFmpegKit
package name.
*
- * @return guessed package name according to supported external libraries
- * @since 3.0
+ * @return FFmpegKit package name
*/
public static String getPackageName() {
return Packages.getPackageName();
}
/**
- * "content:…"
) into an
+ * input/output url that can be used in FFmpegKit and FFprobeKit.
+ *
+ * @return input/output url that can be passed to FFmpegKit or FFprobeKit
+ */
+ private static String getSafParameter(final Context context, final Uri uri, final String openMode) {
+ String displayName = "unknown";
+ try (Cursor cursor = context.getContentResolver().query(uri, null, null, null, null)) {
+ if (cursor != null && cursor.moveToFirst()) {
+ displayName = cursor.getString(cursor.getColumnIndex(DocumentsContract.Document.COLUMN_DISPLAY_NAME));
+ }
+ } catch (final Throwable t) {
+ android.util.Log.e(TAG, String.format("Failed to get %s column for %s.%s", DocumentsContract.Document.COLUMN_DISPLAY_NAME, uri.toString(), Exceptions.getStackTraceString(t)));
+ }
+
+ int fd = -1;
+ try {
+ ParcelFileDescriptor parcelFileDescriptor = context.getContentResolver().openFileDescriptor(uri, openMode);
+ fd = parcelFileDescriptor.getFd();
+ pfdMap.put(fd, parcelFileDescriptor);
+ } catch (final Throwable t) {
+ android.util.Log.e(TAG, String.format("Failed to obtain %s parcelFileDescriptor for %s.%s", openMode, uri.toString(), Exceptions.getStackTraceString(t)));
+ }
+
+ // workaround for https://issuetracker.google.com/issues/162440528: ANDROID_CREATE_DOCUMENT generating file names like "transcode.mp3 (2)"
+ if (displayName.lastIndexOf('.') > 0 && displayName.lastIndexOf(' ') > displayName.lastIndexOf('.')) {
+ String extension = displayName.substring(displayName.lastIndexOf('.'), displayName.lastIndexOf(' '));
+ displayName += extension;
+ }
+ // spaces can break argument list parsing, see https://github.com/alexcohn/mobile-ffmpeg/pull/1#issuecomment-688643836
+ final char NBSP = (char) 0xa0;
+ return "saf:" + fd + "/" + displayName.replace(' ', NBSP);
+ }
+
+ /**
+ * "content:…"
) into an
+ * input url that can be used in FFmpegKit and FFprobeKit.
+ *
+ * @return input url that can be passed to FFmpegKit or FFprobeKit
+ */
+ public static String getSafParameterForRead(final Context context, final Uri uri) {
+ return getSafParameter(context, uri, "r");
+ }
+
+ /**
+ * "content:…"
) into an
+ * output url that can be used in FFmpegKit and FFprobeKit.
+ *
+ * @return output url that can be passed to FFmpegKit or FFprobeKit
+ */
+ public static String getSafParameterForWrite(final Context context, final Uri uri) {
+ return getSafParameter(context, uri, "w");
+ }
+
+ /**
+ * Called by saf_wrapper from JNI/native part to close a parcel file descriptor.
+ *
+ * @param fd parcel file descriptor created for a saf uri
+ */
+ private static void closeParcelFileDescriptor(final int fd) {
+ try {
+ ParcelFileDescriptor pfd = pfdMap.get(fd);
+ if (pfd != null) {
+ pfd.close();
+ pfdMap.delete(fd);
+ }
+ } catch (final Throwable t) {
+ android.util.Log.e(TAG, String.format("Failed to close file descriptor %d.%s", fd, Exceptions.getStackTraceString(t)));
+ }
+ }
+
+ /**
+ * Returns the session history size.
+ *
+ * @return session history size
+ */
+ public static int getSessionHistorySize() {
+ return sessionHistorySize;
+ }
+
+ /**
+ * Sets the session history size.
+ *
+ * @param sessionHistorySize new session history size
+ */
+ public static void setSessionHistorySize(int sessionHistorySize) {
+ FFmpegKitConfig.sessionHistorySize = sessionHistorySize;
+ }
+
+ /**
+ * Adds a session to the session history.
+ *
+ * @param session new session
+ */
+ static void addSession(final Session session) {
+ synchronized (sessionHistoryLock) {
+ sessionHistoryMap.put(session.getSessionId(), session);
+ sessionHistoryQueue.offer(session);
+
+ if (sessionHistoryQueue.size() > sessionHistorySize) {
+ final Session oldestElement = sessionHistoryQueue.poll();
+ if (oldestElement != null) {
+ sessionHistoryMap.remove(oldestElement.getSessionId());
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns the session specified with sessionId from the session history.
+ *
+ * @param sessionId session identifier
+ * @return session specified with sessionId or null if it is not found in the history
+ */
+ public static Session getSession(final long sessionId) {
+ synchronized (sessionHistoryLock) {
+ return sessionHistoryMap.get(sessionId);
+ }
+ }
+
+ /**
+ * Returns the last session from the session history.
+ *
+ * @return the last session from the session history
+ */
+ public static Session getLastSession() {
+ synchronized (sessionHistoryLock) {
+ return sessionHistoryQueue.peek();
+ }
+ }
+
+ /**
+ * >() {
+
+ @Override
+ public List
>() {
+
+ @Override
+ public List
>() {
+
+ @Override
+ public List
FFmpeg
operations.
+ * FFmpeg
operations natively.
*
*
- * int rc = FFprobe.execute("-hide_banner -v error -show_entries format=size -of default=noprint_wrappers=1 file1.mp4");
- * Log.i(Config.TAG, String.format("Command execution %s.", (rc == 0?"completed successfully":"failed with rc=" + rc));
+ * FFprobeSession session = FFprobe.execute("-hide_banner -v error -show_entries format=size -of default=noprint_wrappers=1 file1.mp4");
+ * Log.i(FFmpegKitConfig.TAG, String.format("Command execution %s.", (session.getReturnCode() == 0?"completed successfully":"failed with rc=" + session.getReturnCode()));
+ *
+ *
+ * MediaInformationSession session = FFprobe.getMediaInformation("file1.mp4");
+ * Log.i(FFmpegKitConfig.TAG, String.format("Media information %s.", (session.getReturnCode() == 0?"extracted successfully":"was not extracted due to rc=" + session.getReturnCode()));
*
*/
public class FFprobeKit {
@@ -46,14 +51,14 @@ public class FFprobeKit {
* timeout
parameter is not effective anymore.
- */
- public static MediaInformation getMediaInformation(final String path, final Long timeout) {
- return getMediaInformation(path);
- }
-
- private static MediaInformation getMediaInformationFromCommandArguments(final String[] arguments) {
- final int rc = execute(arguments);
-
- if (rc == 0) {
- return MediaInformationParser.from(FFmpegKitConfig.getLastCommandOutput());
- } else {
- Log.w(FFmpegKitConfig.TAG, FFmpegKitConfig.getLastCommandOutput());
- return null;
- }
+ * @param command command to execute
+ * @param executeCallback callback that will be notified when execution is completed
+ * @param logCallback callback that will receive log entries
+ * @param statisticsCallback callback that will receive statistics
+ * @return media information session created for this execution
+ */
+ public static MediaInformationSession getMediaInformationFromCommand(final String command,
+ final ExecuteCallback executeCallback,
+ final LogCallback logCallback,
+ final StatisticsCallback statisticsCallback) {
+ return getMediaInformationFromCommandArguments(FFmpegKit.parseArguments(command), executeCallback, logCallback, statisticsCallback);
+ }
+
+ private static MediaInformationSession getMediaInformationFromCommandArguments(final String[] arguments,
+ final ExecuteCallback executeCallback,
+ final LogCallback logCallback,
+ final StatisticsCallback statisticsCallback) {
+ final MediaInformationSession session = new MediaInformationSession(arguments, executeCallback, logCallback, statisticsCallback);
+
+ FFmpegKitConfig.getMediaInformationExecute(session);
+
+ return session;
}
}
diff --git a/android/app/src/main/java/com/arthenica/ffmpegkit/FFprobeSession.java b/android/app/src/main/java/com/arthenica/ffmpegkit/FFprobeSession.java
new file mode 100644
index 0000000..0a72666
--- /dev/null
+++ b/android/app/src/main/java/com/arthenica/ffmpegkit/FFprobeSession.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (c) 2020-2021 Taner Sener
+ *
+ * This file is part of FFmpegKit.
+ *
+ * FFmpegKit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * FFmpegKit 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with FFmpegKit. If not, see MediaInformation
from the given ffprobe json output. Note that this
+ * method does not throw {@link JSONException} as {@link #fromWithError(String)} does and
+ * handles errors internally.
*
* @param ffprobeJsonOutput ffprobe json output
* @return created {@link MediaInformation} instance of null if a parsing error occurs
@@ -42,8 +46,7 @@ public class MediaInformationParser {
try {
return fromWithError(ffprobeJsonOutput);
} catch (JSONException e) {
- Log.e(FFmpegKitConfig.TAG, "MediaInformation parsing failed.", e);
- e.printStackTrace();
+ Log.e(FFmpegKitConfig.TAG, String.format("MediaInformation parsing failed.%s", Exceptions.getStackTraceString(e)));
return null;
}
}
diff --git a/android/app/src/main/java/com/arthenica/ffmpegkit/MediaInformationSession.java b/android/app/src/main/java/com/arthenica/ffmpegkit/MediaInformationSession.java
new file mode 100644
index 0000000..fb94d34
--- /dev/null
+++ b/android/app/src/main/java/com/arthenica/ffmpegkit/MediaInformationSession.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2021 Taner Sener
+ *
+ * This file is part of FFmpegKit.
+ *
+ * FFmpegKit is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * FFmpegKit 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with FFmpegKit. If not, see MediaInformation
object
+ * using the output of the execution.
+ */
+public class MediaInformationSession extends FFprobeSession implements Session {
+ private MediaInformation mediaInformation;
+
+ public MediaInformationSession(final String[] arguments,
+ final ExecuteCallback executeCallback,
+ final LogCallback logCallback,
+ final StatisticsCallback statisticsCallback) {
+ super(arguments, executeCallback, logCallback, statisticsCallback);
+ }
+
+ public MediaInformation getMediaInformation() {
+ return mediaInformation;
+ }
+
+ public void setMediaInformation(MediaInformation mediaInformation) {
+ this.mediaInformation = mediaInformation;
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder stringBuilder = new StringBuilder();
+
+ stringBuilder.append("MediaInformationSession{");
+ stringBuilder.append("sessionId=");
+ stringBuilder.append(sessionId);
+ stringBuilder.append(", createTime=");
+ stringBuilder.append(createTime);
+ stringBuilder.append(", startTime=");
+ stringBuilder.append(startTime);
+ stringBuilder.append(", endTime=");
+ stringBuilder.append(endTime);
+ stringBuilder.append(", arguments=");
+ stringBuilder.append(FFmpegKit.argumentsToString(arguments));
+ stringBuilder.append(", logs=");
+ stringBuilder.append(getLogsAsString());
+ stringBuilder.append(", state=");
+ stringBuilder.append(state);
+ stringBuilder.append(", returnCode=");
+ stringBuilder.append(returnCode);
+ stringBuilder.append(", failStackTrace=");
+ stringBuilder.append('\'');
+ stringBuilder.append(failStackTrace);
+ stringBuilder.append('\'');
+ stringBuilder.append('}');
+
+ return stringBuilder.toString();
+ }
+
+}
diff --git a/android/app/src/main/java/com/arthenica/ffmpegkit/Packages.java b/android/app/src/main/java/com/arthenica/ffmpegkit/Packages.java
index 6ab534d..cdb571a 100644
--- a/android/app/src/main/java/com/arthenica/ffmpegkit/Packages.java
+++ b/android/app/src/main/java/com/arthenica/ffmpegkit/Packages.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2018-2020 Taner Sener
+ * Copyright (c) 2018-2021 Taner Sener
*
* This file is part of FFmpegKit.
*
@@ -24,7 +24,7 @@ import java.util.Collections;
import java.util.List;
/**
- *