diff --git a/build/ffmpeg-wasm.sh b/build/ffmpeg-wasm.sh index 0fe38f1..d36b174 100755 --- a/build/ffmpeg-wasm.sh +++ b/build/ffmpeg-wasm.sh @@ -30,6 +30,7 @@ CONF_FLAGS=( -lswscale -Wno-deprecated-declarations $LDFLAGS + -sENVIRONMENT=worker -sWASM_BIGINT # enable big int support -sUSE_SDL=2 # use emscripten SDL2 lib port -sMODULARIZE # modularized to use as a library @@ -49,6 +50,7 @@ CONF_FLAGS=( src/fftools/ffmpeg_mux.c src/fftools/ffmpeg_opt.c src/fftools/opt_common.c + src/fftools/ffprobe.c ) emcc "${CONF_FLAGS[@]}" $@ diff --git a/packages/ffmpeg/src/classes.ts b/packages/ffmpeg/src/classes.ts index 6af4bee..6f1b46e 100644 --- a/packages/ffmpeg/src/classes.ts +++ b/packages/ffmpeg/src/classes.ts @@ -62,6 +62,7 @@ export class FFmpeg { case FFMessageType.MOUNT: case FFMessageType.UNMOUNT: case FFMessageType.EXEC: + case FFMessageType.FFPROBE: case FFMessageType.WRITE_FILE: case FFMessageType.READ_FILE: case FFMessageType.DELETE_FILE: @@ -249,6 +250,42 @@ export class FFmpeg { signal ) as Promise; + /** + * Execute ffprobe command. + * + * @example + * ```ts + * const ffmpeg = new FFmpeg(); + * await ffmpeg.load(); + * await ffmpeg.writeFile("video.avi", ...); + * // Getting duration of a video in seconds: ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 video.avi -o output.txt + * await ffmpeg.ffprobe(["-v", "error", "-show_entries", "format=duration", "-of", "default=noprint_wrappers=1:nokey=1", "video.avi", "-o", "output.txt"]); + * const data = ffmpeg.readFile("output.txt"); + * ``` + * + * @returns `0` if no error, `!= 0` if timeout (1) or error. + * @category FFmpeg + */ + public ffprobe = ( + /** ffprobe command line args */ + args: string[], + /** + * milliseconds to wait before stopping the command execution. + * + * @defaultValue -1 + */ + timeout = -1, + { signal }: FFMessageOptions = {} + ): Promise => + this.#send( + { + type: FFMessageType.FFPROBE, + data: { args, timeout }, + }, + undefined, + signal + ) as Promise; + /** * Terminate all ongoing API calls and terminate web worker. * `FFmpeg.load()` must be called again before calling any other APIs. diff --git a/packages/ffmpeg/src/const.ts b/packages/ffmpeg/src/const.ts index c0e6b07..5dfe4ab 100644 --- a/packages/ffmpeg/src/const.ts +++ b/packages/ffmpeg/src/const.ts @@ -7,6 +7,7 @@ export const CORE_URL = `https://unpkg.com/@ffmpeg/core@${CORE_VERSION}/dist/umd export enum FFMessageType { LOAD = "LOAD", EXEC = "EXEC", + FFPROBE = "FFPROBE", WRITE_FILE = "WRITE_FILE", READ_FILE = "READ_FILE", DELETE_FILE = "DELETE_FILE", diff --git a/packages/ffmpeg/src/worker.ts b/packages/ffmpeg/src/worker.ts index 0746271..cf8b6c5 100644 --- a/packages/ffmpeg/src/worker.ts +++ b/packages/ffmpeg/src/worker.ts @@ -100,6 +100,14 @@ const exec = ({ args, timeout = -1 }: FFMessageExecData): ExitCode => { return ret; }; +const ffprobe = ({ args, timeout = -1 }: FFMessageExecData): ExitCode => { + ffmpeg.setTimeout(timeout); + ffmpeg.ffprobe(...args); + const ret = ffmpeg.ret; + ffmpeg.reset(); + return ret; +}; + const writeFile = ({ path, data }: FFMessageWriteFileData): OK => { ffmpeg.FS.writeFile(path, data); return true; @@ -170,6 +178,9 @@ self.onmessage = async ({ case FFMessageType.EXEC: data = exec(_data as FFMessageExecData); break; + case FFMessageType.FFPROBE: + data = ffprobe(_data as FFMessageExecData); + break; case FFMessageType.WRITE_FILE: data = writeFile(_data as FFMessageWriteFileData); break; diff --git a/packages/types/types/index.d.ts b/packages/types/types/index.d.ts index d3cd4fb..8795007 100644 --- a/packages/types/types/index.d.ts +++ b/packages/types/types/index.d.ts @@ -39,22 +39,28 @@ export interface Stat { blocks: number; } -export interface FSFilesystemWORKERFS { - -} +export interface FSFilesystemWORKERFS {} -export interface FSFilesystemMEMFS { - -} +export interface FSFilesystemMEMFS {} export interface FSFilesystems { WORKERFS: FSFilesystemWORKERFS; MEMFS: FSFilesystemMEMFS; } -export type FSFilesystem = -| FSFilesystemWORKERFS -| FSFilesystemMEMFS; +export type FSFilesystem = FSFilesystemWORKERFS | FSFilesystemMEMFS; + +export interface OptionReadFile { + encoding: string; +} + +export interface WorkerFSMountConfig { + blobs?: { + name: string; + data: Blob; + }[]; + files?: File[]; +} /** * Functions to interact with Emscripten FS library. @@ -75,7 +81,11 @@ export interface FS { isFile: (mode: number) => boolean; /** mode is a numeric notation of permission, @see [Numeric Notation](https://en.wikipedia.org/wiki/File-system_permissions#Numeric_notation) */ isDir: (mode: number) => boolean; - mount: (fileSystemType: FSFilesystem, data: WorkerFSMountConfig, path: string) => void; + mount: ( + fileSystemType: FSFilesystem, + data: WorkerFSMountConfig, + path: string + ) => void; unmount: (path: string) => void; filesystems: FSFilesystems; } @@ -115,6 +125,7 @@ export interface FFmpegCoreModule { mainScriptUrlOrBlob: string; exec: (...args: string[]) => number; + ffprobe: (...args: string[]) => number; reset: () => void; setLogger: (logger: (log: Log) => void) => void; setTimeout: (timeout: number) => void; diff --git a/src/bind/ffmpeg/bind.js b/src/bind/ffmpeg/bind.js index d8d71f4..14eec22 100644 --- a/src/bind/ffmpeg/bind.js +++ b/src/bind/ffmpeg/bind.js @@ -5,10 +5,12 @@ const NULL = 0; const SIZE_I32 = Uint32Array.BYTES_PER_ELEMENT; const DEFAULT_ARGS = ["./ffmpeg", "-nostdin", "-y"]; +const DEFAULT_ARGS_FFPROBE = ["./ffprobe"]; Module["NULL"] = NULL; Module["SIZE_I32"] = SIZE_I32; Module["DEFAULT_ARGS"] = DEFAULT_ARGS; +Module["DEFAULT_ARGS_FFPROBE"] = DEFAULT_ARGS_FFPROBE; /** * Variables @@ -62,6 +64,18 @@ function exec(..._args) { return Module["ret"]; } +function ffprobe(..._args) { + const args = [...Module["DEFAULT_ARGS_FFPROBE"], ..._args]; + try { + Module["_ffprobe"](args.length, stringsToPtr(args)); + } catch (e) { + if (!e.message.startsWith("Aborted")) { + throw e; + } + } + return Module["ret"]; +} + function setLogger(logger) { Module["logger"] = logger; } @@ -121,6 +135,7 @@ Module["printErr"] = printErr; Module["locateFile"] = _locateFile; Module["exec"] = exec; +Module["ffprobe"] = ffprobe; Module["setLogger"] = setLogger; Module["setTimeout"] = setTimeout; Module["setProgress"] = setProgress; diff --git a/src/bind/ffmpeg/export.js b/src/bind/ffmpeg/export.js index 1228de5..ae6a41f 100644 --- a/src/bind/ffmpeg/export.js +++ b/src/bind/ffmpeg/export.js @@ -1,3 +1,3 @@ -const EXPORTED_FUNCTIONS = ["_ffmpeg", "_abort", "_malloc"]; +const EXPORTED_FUNCTIONS = ["_ffmpeg", "_abort", "_malloc", "_ffprobe"]; console.log(EXPORTED_FUNCTIONS.join(",")); diff --git a/src/fftools/ffprobe.c b/src/fftools/ffprobe.c index 1aa403f..53f2135 100644 --- a/src/fftools/ffprobe.c +++ b/src/fftools/ffprobe.c @@ -90,8 +90,8 @@ typedef struct InputFile { int nb_streams; } InputFile; -const char program_name[] = "ffprobe"; -const int program_birth_year = 2007; +const char program_name_ffprobe[] = "ffprobe"; +const int program_birth_year_ffprobe = 2007; static int do_bitexact = 0; static int do_count_frames = 0; @@ -382,6 +382,58 @@ static void ffprobe_cleanup(int ret) #if HAVE_THREADS pthread_mutex_destroy(&log_mutex); #endif + + do_bitexact = 0; + do_count_frames = 0; + do_count_packets = 0; + do_read_frames = 0; + do_read_packets = 0; + do_show_chapters = 0; + do_show_error = 0; + do_show_format = 0; + do_show_frames = 0; + do_show_packets = 0; + do_show_programs = 0; + do_show_streams = 0; + do_show_stream_disposition = 0; + do_show_data = 0; + do_show_program_version = 0; + do_show_library_versions = 0; + do_show_pixel_formats = 0; + do_show_pixel_format_flags = 0; + do_show_pixel_format_components = 0; + do_show_log = 0; + do_show_chapter_tags = 0; + do_show_format_tags = 0; + do_show_frame_tags = 0; + do_show_program_tags = 0; + do_show_stream_tags = 0; + do_show_packet_tags = 0; + show_value_unit = 0; + use_value_prefix = 0; + use_byte_value_binary_prefix = 0; + use_value_sexagesimal_format = 0; + show_private_data = 1; + show_optional_fields = SHOW_OPTIONAL_FIELDS_AUTO; + print_format = NULL; + stream_specifier = NULL; + show_data_hash = NULL; + read_intervals = NULL; + read_intervals_nb = 0; + find_stream_info = 1; + input_filename = NULL; + print_input_filename = NULL; + iformat = NULL; + output_filename = NULL; + hash = NULL; + nb_streams = 0; + nb_streams_packets = NULL; + nb_streams_frames = NULL; + selected_streams = NULL; + log_buffer = NULL; + log_buffer_size = 0; + + av_log(NULL, AV_LOG_DEBUG, "FFprobe: Cleanup done.\n"); } struct unit_value { @@ -3491,7 +3543,7 @@ end: static void show_usage(void) { av_log(NULL, AV_LOG_INFO, "Simple multimedia streams analyzer\n"); - av_log(NULL, AV_LOG_INFO, "usage: %s [OPTIONS] INPUT_FILE\n", program_name); + av_log(NULL, AV_LOG_INFO, "usage: %s [OPTIONS] INPUT_FILE\n", program_name_ffprobe); av_log(NULL, AV_LOG_INFO, "\n"); } @@ -3503,7 +3555,7 @@ static void ffprobe_show_program_version(WriterContext *w) writer_print_section_header(w, SECTION_ID_PROGRAM_VERSION); print_str("version", FFMPEG_VERSION); print_fmt("copyright", "Copyright (c) %d-%d the FFmpeg developers", - program_birth_year, CONFIG_THIS_YEAR); + program_birth_year_ffprobe, CONFIG_THIS_YEAR); print_str("compiler_ident", CC_IDENT); print_str("configuration", FFMPEG_CONFIGURATION); writer_print_section_footer(w); @@ -3740,7 +3792,7 @@ static int opt_print_filename(void *optctx, const char *opt, const char *arg) return 0; } -void show_help_default(const char *opt, const char *arg) +void show_help_default_ffprobe(const char *opt, const char *arg) { av_log_set_callback(log_callback_help); show_usage(); @@ -4037,6 +4089,7 @@ int ffprobe(int argc, char **argv) } #endif av_log_set_flags(AV_LOG_SKIP_REPEATED); + ffprobe_cleanup(0); register_exit(ffprobe_cleanup); options = real_options; @@ -4142,7 +4195,7 @@ int ffprobe(int argc, char **argv) (!do_show_program_version && !do_show_library_versions && !do_show_pixel_formats))) { show_usage(); av_log(NULL, AV_LOG_ERROR, "You have to specify one input file.\n"); - av_log(NULL, AV_LOG_ERROR, "Use -h to get full help or, even better, run 'man %s'.\n", program_name); + av_log(NULL, AV_LOG_ERROR, "Use -h to get full help or, even better, run 'man %s'.\n", program_name_ffprobe); ret = AVERROR(EINVAL); } else if (input_filename) { ret = probe_file(wctx, input_filename, print_input_filename);