From a7f93fe32dd8e156b11ab4b08542888069b24b93 Mon Sep 17 00:00:00 2001 From: Pascal Massimino Date: Fri, 1 Dec 2017 00:21:13 -0800 Subject: [PATCH] webpmux: allow reading argument from a file if a single text file name is supplied as argument (e.g.: 'webpmux my_long_list_of_frames.txt'), the command line arguments are actually parsed from this file. Tokenizer will remove space, tabs, LF, CR, returns, etc. + changed ImgIoUtilReadFile() to return a null-terminated data, for convenience. + misc clean-up in the code BUG=webp:355 Change-Id: I76796305641d660933de5881763d723006712fa9 --- --- README.mux | 4 + examples/webpmux.c | 200 +++++++++++++++++++++++++---------------- imageio/imageio_util.c | 14 ++- imageio/imageio_util.h | 3 + man/webpmux.1 | 7 +- 5 files changed, 145 insertions(+), 83 deletions(-) diff --git a/README.mux b/README.mux index c15d3a51..d09ac179 100644 --- a/README.mux +++ b/README.mux @@ -33,6 +33,7 @@ Usage: webpmux -get GET_OPTIONS INPUT -o OUTPUT webpmux -info INPUT webpmux [-h|-help] webpmux -version + webpmux argument_file_name GET_OPTIONS: Extract relevant data: @@ -92,6 +93,9 @@ INPUT & OUTPUT are in WebP format. Note: The nature of EXIF, XMP and ICC data is not checked and is assumed to be valid. +Note: if a single file name is passed as the argument, the arguments will be +tokenized from this file. The file name must not start with the character '-'. + Visualization tool: =================== diff --git a/examples/webpmux.c b/examples/webpmux.c index 27de9e19..59327ac2 100644 --- a/examples/webpmux.c +++ b/examples/webpmux.c @@ -47,6 +47,7 @@ webpmux -info in.webp webpmux [ -h | -help ] webpmux -version + webpmux argument_file_name */ #ifdef HAVE_CONFIG_H @@ -108,28 +109,29 @@ static const char* const kDescriptions[LAST_FEATURE] = { }; typedef struct { - FeatureType type_; - FeatureArg* args_; - int arg_count_; -} Feature; + // command line arguments + int argc_; + const char** argv_; + WebPData argv_data_; + int own_argv_; -typedef struct { ActionType action_type_; const char* input_; const char* output_; - Feature feature_; -} WebPMuxConfig; + FeatureType type_; + FeatureArg* args_; + int arg_count_; +} Config; //------------------------------------------------------------------------------ // Helper functions. -static int CountOccurrences(const char* arglist[], int list_length, - const char* arg) { +static int CountOccurrences(const Config* const config, const char* arg) { int i; int num_occurences = 0; - for (i = 0; i < list_length; ++i) { - if (!strcmp(arglist[i], arg)) { + for (i = 0; i < config->argc_; ++i) { + if (!strcmp(config->argv_[i], arg)) { ++num_occurences; } } @@ -301,6 +303,7 @@ static void PrintHelp(void) { printf(" webpmux -info INPUT\n"); printf(" webpmux [-h|-help]\n"); printf(" webpmux -version\n"); + printf(" webpmux argument_file_name\n"); printf("\n"); printf("GET_OPTIONS:\n"); @@ -369,6 +372,10 @@ static void PrintHelp(void) { printf("\nNote: The nature of EXIF, XMP and ICC data is not checked"); printf(" and is assumed to be\nvalid.\n"); + printf("\nNote: if a single file name is passed as the argument, the " + "arguments will be\n"); + printf("tokenized from this file. The file name must not start with " + "the character '-'.\n"); } static void WarnAboutOddOffset(const WebPMuxFrameInfo* const info) { @@ -394,7 +401,7 @@ static int CreateMux(const char* const filename, WebPMux** mux) { assert(mux != NULL); if (!ReadFileToWebPData(filename, &bitstream)) return 0; *mux = WebPMuxCreate(&bitstream, 1); - free((void*)bitstream.bytes); + WebPDataClear(&bitstream); if (*mux != NULL) return 1; fprintf(stderr, "Failed to create mux object from file %s.\n", filename); return 0; @@ -517,10 +524,15 @@ static int ParseBgcolorArgs(const char* args, uint32_t* const bgcolor) { //------------------------------------------------------------------------------ // Clean-up. -static void DeleteConfig(WebPMuxConfig* config) { +static void DeleteConfig(Config* const config) { if (config != NULL) { - free(config->feature_.args_); + free(config->args_); + if (config->own_argv_) { + free(config->argv_); + WebPDataClear(&config->argv_data_); + } memset(config, 0, sizeof(*config)); + WebPDataInit(&config->argv_data_); } } @@ -531,7 +543,7 @@ static void DeleteConfig(WebPMuxConfig* config) { // Returns 1 on valid, 0 otherwise. // Also fills up num_feature_args to be number of feature arguments given. // (e.g. if there are 4 '-frame's and 1 '-loop', then num_feature_args = 5). -static int ValidateCommandLine(int argc, const char* argv[], +static int ValidateCommandLine(const Config* const config, int* num_feature_args) { int num_frame_args; int num_loop_args; @@ -543,27 +555,27 @@ static int ValidateCommandLine(int argc, const char* argv[], *num_feature_args = 0; // Simple checks. - if (CountOccurrences(argv, argc, "-get") > 1) { + if (CountOccurrences(config, "-get") > 1) { ERROR_GOTO1("ERROR: Multiple '-get' arguments specified.\n", ErrValidate); } - if (CountOccurrences(argv, argc, "-set") > 1) { + if (CountOccurrences(config, "-set") > 1) { ERROR_GOTO1("ERROR: Multiple '-set' arguments specified.\n", ErrValidate); } - if (CountOccurrences(argv, argc, "-strip") > 1) { + if (CountOccurrences(config, "-strip") > 1) { ERROR_GOTO1("ERROR: Multiple '-strip' arguments specified.\n", ErrValidate); } - if (CountOccurrences(argv, argc, "-info") > 1) { + if (CountOccurrences(config, "-info") > 1) { ERROR_GOTO1("ERROR: Multiple '-info' arguments specified.\n", ErrValidate); } - if (CountOccurrences(argv, argc, "-o") > 1) { + if (CountOccurrences(config, "-o") > 1) { ERROR_GOTO1("ERROR: Multiple output files specified.\n", ErrValidate); } // Compound checks. - num_frame_args = CountOccurrences(argv, argc, "-frame"); - num_loop_args = CountOccurrences(argv, argc, "-loop"); - num_bgcolor_args = CountOccurrences(argv, argc, "-bgcolor"); - num_durations_args = CountOccurrences(argv, argc, "-duration"); + num_frame_args = CountOccurrences(config, "-frame"); + num_loop_args = CountOccurrences(config, "-loop"); + num_bgcolor_args = CountOccurrences(config, "-bgcolor"); + num_durations_args = CountOccurrences(config, "-duration"); if (num_loop_args > 1) { ERROR_GOTO1("ERROR: Multiple loop counts specified.\n", ErrValidate); @@ -598,7 +610,7 @@ static int ValidateCommandLine(int argc, const char* argv[], #define ACTION_IS_NIL (config->action_type_ == NIL_ACTION) -#define FEATURETYPE_IS_NIL (feature->type_ == NIL_FEATURE) +#define FEATURETYPE_IS_NIL (config->type_ == NIL_FEATURE) #define CHECK_NUM_ARGS_LESS(NUM, LABEL) \ if (argc < i + (NUM)) { \ @@ -614,15 +626,15 @@ static int ValidateCommandLine(int argc, const char* argv[], // Parses command-line arguments to fill up config object. Also performs some // semantic checks. -static int ParseCommandLine(int argc, const char* argv[], - WebPMuxConfig* config) { +static int ParseCommandLine(Config* config) { int i = 0; int feature_arg_index = 0; int ok = 1; + int argc = config->argc_; + const char* const* argv = config->argv_; while (i < argc) { - Feature* const feature = &config->feature_; - FeatureArg* const arg = &feature->args_[feature_arg_index]; + FeatureArg* const arg = &config->args_[feature_arg_index]; if (argv[i][0] == '-') { // One of the action types or output. if (!strcmp(argv[i], "-set")) { if (ACTION_IS_NIL) { @@ -638,8 +650,8 @@ static int ParseCommandLine(int argc, const char* argv[], } else { ERROR_GOTO1("ERROR: Multiple actions specified.\n", ErrParse); } - if (FEATURETYPE_IS_NIL || feature->type_ == FEATURE_DURATION) { - feature->type_ = FEATURE_DURATION; + if (FEATURETYPE_IS_NIL || config->type_ == FEATURE_DURATION) { + config->type_ = FEATURE_DURATION; } else { ERROR_GOTO1("ERROR: Multiple features specified.\n", ErrParse); } @@ -656,7 +668,7 @@ static int ParseCommandLine(int argc, const char* argv[], } else if (!strcmp(argv[i], "-strip")) { if (ACTION_IS_NIL) { config->action_type_ = ACTION_STRIP; - feature->arg_count_ = 0; + config->arg_count_ = 0; } else { ERROR_GOTO1("ERROR: Multiple actions specified.\n", ErrParse); } @@ -668,8 +680,8 @@ static int ParseCommandLine(int argc, const char* argv[], } else { ERROR_GOTO1("ERROR: Multiple actions specified.\n", ErrParse); } - if (FEATURETYPE_IS_NIL || feature->type_ == FEATURE_ANMF) { - feature->type_ = FEATURE_ANMF; + if (FEATURETYPE_IS_NIL || config->type_ == FEATURE_ANMF) { + config->type_ = FEATURE_ANMF; } else { ERROR_GOTO1("ERROR: Multiple features specified.\n", ErrParse); } @@ -685,8 +697,8 @@ static int ParseCommandLine(int argc, const char* argv[], } else { ERROR_GOTO1("ERROR: Multiple actions specified.\n", ErrParse); } - if (FEATURETYPE_IS_NIL || feature->type_ == FEATURE_ANMF) { - feature->type_ = FEATURE_ANMF; + if (FEATURETYPE_IS_NIL || config->type_ == FEATURE_ANMF) { + config->type_ = FEATURE_ANMF; } else { ERROR_GOTO1("ERROR: Multiple features specified.\n", ErrParse); } @@ -705,7 +717,7 @@ static int ParseCommandLine(int argc, const char* argv[], ERROR_GOTO1("ERROR: Multiple actions specified.\n", ErrParse); } else { config->action_type_ = ACTION_INFO; - feature->arg_count_ = 0; + config->arg_count_ = 0; config->input_ = argv[i + 1]; } i += 2; @@ -741,7 +753,7 @@ static int ParseCommandLine(int argc, const char* argv[], if (!strcmp(argv[i], "icc") || !strcmp(argv[i], "exif") || !strcmp(argv[i], "xmp")) { if (FEATURETYPE_IS_NIL) { - feature->type_ = (!strcmp(argv[i], "icc")) ? FEATURE_ICCP : + config->type_ = (!strcmp(argv[i], "icc")) ? FEATURE_ICCP : (!strcmp(argv[i], "exif")) ? FEATURE_EXIF : FEATURE_XMP; } else { ERROR_GOTO1("ERROR: Multiple features specified.\n", ErrParse); @@ -757,7 +769,7 @@ static int ParseCommandLine(int argc, const char* argv[], } else if (!strcmp(argv[i], "frame") && (config->action_type_ == ACTION_GET)) { CHECK_NUM_ARGS_LESS(2, ErrParse); - feature->type_ = FEATURE_ANMF; + config->type_ = FEATURE_ANMF; arg->params_ = argv[i + 1]; ++feature_arg_index; i += 2; @@ -777,9 +789,8 @@ static int ParseCommandLine(int argc, const char* argv[], } // Additional checks after config is filled. -static int ValidateConfig(WebPMuxConfig* config) { +static int ValidateConfig(Config* const config) { int ok = 1; - Feature* const feature = &config->feature_; // Action. if (ACTION_IS_NIL) { @@ -795,7 +806,7 @@ static int ValidateConfig(WebPMuxConfig* config) { if (config->input_ == NULL) { if (config->action_type_ != ACTION_SET) { ERROR_GOTO1("ERROR: No input file specified.\n", ErrValidate2); - } else if (feature->type_ != FEATURE_ANMF) { + } else if (config->type_ != FEATURE_ANMF) { ERROR_GOTO1("ERROR: No input file specified.\n", ErrValidate2); } } @@ -809,29 +820,63 @@ static int ValidateConfig(WebPMuxConfig* config) { return ok; } -// Create config object from command-line arguments. -static int InitializeConfig(int argc, const char* argv[], - WebPMuxConfig* config) { - int num_feature_args = 0; - int ok = 1; - +#define MAX_ARGC 16384 +static int SetArguments(int argc, const char* argv[], + Config* const config) { assert(config != NULL); memset(config, 0, sizeof(*config)); + WebPDataInit(&config->argv_data_); + if (argc == 1 && argv[0][0] != '-') { + char* cur; + const char sep[] = " \t\r\n\f\v"; + if (!ReadFileToWebPData(argv[0], &config->argv_data_)) { + return 0; + } + config->own_argv_ = 1; + config->argv_ = (const char**)malloc(MAX_ARGC * sizeof(*config->argv_)); + if (config->argv_ == NULL) return 0; + + argc = 0; + for (cur = strtok((char*)config->argv_data_.bytes, sep); + cur != NULL; + cur = strtok(NULL, sep)) { + if (argc == MAX_ARGC) { + fprintf(stderr, "ERROR: Arguments limit %d reached\n", MAX_ARGC); + return 0; + } + assert(strlen(cur) != 0); + config->argv_[argc++] = cur; + } + config->argc_ = argc; + } else { + config->argc_ = argc; + config->argv_ = argv; + config->own_argv_ = 0; + } + return 1; +} + +// Create config object from command-line arguments. +static int InitializeConfig(int argc, const char* argv[], + Config* const config) { + int num_feature_args = 0; + int ok = SetArguments(argc, argv, config); + + if (!ok) return 0; // Validate command-line arguments. - if (!ValidateCommandLine(argc, argv, &num_feature_args)) { + if (!ValidateCommandLine(config, &num_feature_args)) { ERROR_GOTO1("Exiting due to command-line parsing error.\n", Err1); } - config->feature_.arg_count_ = num_feature_args; - config->feature_.args_ = - (FeatureArg*)calloc(num_feature_args, sizeof(*config->feature_.args_)); - if (config->feature_.args_ == NULL) { + config->arg_count_ = num_feature_args; + config->args_ = (FeatureArg*)calloc(num_feature_args, sizeof(*config->args_)); + if (config->args_ == NULL) { ERROR_GOTO1("ERROR: Memory allocation error.\n", Err1); } // Parse command-line. - if (!ParseCommandLine(argc, argv, config) || !ValidateConfig(config)) { + if (!ParseCommandLine(config) || !ValidateConfig(config)) { ERROR_GOTO1("Exiting due to command-line parsing error.\n", Err1); } @@ -847,7 +892,7 @@ static int InitializeConfig(int argc, const char* argv[], //------------------------------------------------------------------------------ // Processing. -static int GetFrame(const WebPMux* mux, const WebPMuxConfig* config) { +static int GetFrame(const WebPMux* mux, const Config* config) { WebPMuxError err = WEBP_MUX_OK; WebPMux* mux_single = NULL; int num = 0; @@ -857,7 +902,7 @@ static int GetFrame(const WebPMux* mux, const WebPMuxConfig* config) { WebPMuxFrameInfo info; WebPDataInit(&info.bitstream); - num = ExUtilGetInt(config->feature_.args_[0].params_, 10, &parse_error); + num = ExUtilGetInt(config->args_[0].params_, 10, &parse_error); if (num < 0) { ERROR_GOTO1("ERROR: Frame/Fragment index must be non-negative.\n", ErrGet); } @@ -891,18 +936,17 @@ static int GetFrame(const WebPMux* mux, const WebPMuxConfig* config) { } // Read and process config. -static int Process(const WebPMuxConfig* config) { +static int Process(const Config* config) { WebPMux* mux = NULL; WebPData chunk; WebPMuxError err = WEBP_MUX_OK; int ok = 1; - const Feature* const feature = &config->feature_; switch (config->action_type_) { case ACTION_GET: { ok = CreateMux(config->input_, &mux); if (!ok) goto Err2; - switch (feature->type_) { + switch (config->type_) { case FEATURE_ANMF: ok = GetFrame(mux, config); break; @@ -910,10 +954,10 @@ static int Process(const WebPMuxConfig* config) { case FEATURE_ICCP: case FEATURE_EXIF: case FEATURE_XMP: - err = WebPMuxGetChunk(mux, kFourccList[feature->type_], &chunk); + err = WebPMuxGetChunk(mux, kFourccList[config->type_], &chunk); if (err != WEBP_MUX_OK) { ERROR_GOTO3("ERROR (%s): Could not get the %s.\n", - ErrorString(err), kDescriptions[feature->type_], Err2); + ErrorString(err), kDescriptions[config->type_], Err2); } ok = WriteData(config->output_, &chunk); break; @@ -925,7 +969,7 @@ static int Process(const WebPMuxConfig* config) { break; } case ACTION_SET: { - switch (feature->type_) { + switch (config->type_) { case FEATURE_ANMF: { int i; WebPMuxAnimParams params = { 0xFFFFFFFF, 0 }; @@ -934,11 +978,11 @@ static int Process(const WebPMuxConfig* config) { ERROR_GOTO2("ERROR (%s): Could not allocate a mux object.\n", ErrorString(WEBP_MUX_MEMORY_ERROR), Err2); } - for (i = 0; i < feature->arg_count_; ++i) { - switch (feature->args_[i].subtype_) { + for (i = 0; i < config->arg_count_; ++i) { + switch (config->args_[i].subtype_) { case SUBTYPE_BGCOLOR: { uint32_t bgcolor; - ok = ParseBgcolorArgs(feature->args_[i].params_, &bgcolor); + ok = ParseBgcolorArgs(config->args_[i].params_, &bgcolor); if (!ok) { ERROR_GOTO1("ERROR: Could not parse the background color \n", Err2); @@ -949,7 +993,7 @@ static int Process(const WebPMuxConfig* config) { case SUBTYPE_LOOP: { int parse_error = 0; const int loop_count = - ExUtilGetInt(feature->args_[i].params_, 10, &parse_error); + ExUtilGetInt(config->args_[i].params_, 10, &parse_error); if (loop_count < 0 || loop_count > 65535) { // Note: This is only a 'necessary' condition for loop_count // to be valid. The 'sufficient' conditioned in checked in @@ -965,10 +1009,10 @@ static int Process(const WebPMuxConfig* config) { case SUBTYPE_ANMF: { WebPMuxFrameInfo frame; frame.id = WEBP_CHUNK_ANMF; - ok = ReadFileToWebPData(feature->args_[i].filename_, + ok = ReadFileToWebPData(config->args_[i].filename_, &frame.bitstream); if (!ok) goto Err2; - ok = ParseFrameArgs(feature->args_[i].params_, &frame); + ok = ParseFrameArgs(config->args_[i].params_, &frame); if (!ok) { WebPDataClear(&frame.bitstream); ERROR_GOTO1("ERROR: Could not parse frame properties.\n", @@ -1001,13 +1045,13 @@ static int Process(const WebPMuxConfig* config) { case FEATURE_XMP: { ok = CreateMux(config->input_, &mux); if (!ok) goto Err2; - ok = ReadFileToWebPData(feature->args_[0].filename_, &chunk); + ok = ReadFileToWebPData(config->args_[0].filename_, &chunk); if (!ok) goto Err2; - err = WebPMuxSetChunk(mux, kFourccList[feature->type_], &chunk, 1); + err = WebPMuxSetChunk(mux, kFourccList[config->type_], &chunk, 1); free((void*)chunk.bytes); if (err != WEBP_MUX_OK) { ERROR_GOTO3("ERROR (%s): Could not set the %s.\n", - ErrorString(err), kDescriptions[feature->type_], Err2); + ErrorString(err), kDescriptions[config->type_], Err2); } break; } @@ -1043,11 +1087,11 @@ static int Process(const WebPMuxConfig* config) { for (i = 0; i < num_frames; ++i) durations[i] = -1; // Parse intervals to process. - for (i = 0; i < feature->arg_count_; ++i) { + for (i = 0; i < config->arg_count_; ++i) { int k; int args[3]; int duration, start, end; - const int nb_args = ExUtilGetInts(feature->args_[i].params_, + const int nb_args = ExUtilGetInts(config->args_[i].params_, 10, 3, args); ok = (nb_args >= 1); if (!ok) goto Err3; @@ -1105,12 +1149,12 @@ static int Process(const WebPMuxConfig* config) { case ACTION_STRIP: { ok = CreateMux(config->input_, &mux); if (!ok) goto Err2; - if (feature->type_ == FEATURE_ICCP || feature->type_ == FEATURE_EXIF || - feature->type_ == FEATURE_XMP) { - err = WebPMuxDeleteChunk(mux, kFourccList[feature->type_]); + if (config->type_ == FEATURE_ICCP || config->type_ == FEATURE_EXIF || + config->type_ == FEATURE_XMP) { + err = WebPMuxDeleteChunk(mux, kFourccList[config->type_]); if (err != WEBP_MUX_OK) { ERROR_GOTO3("ERROR (%s): Could not strip the %s.\n", - ErrorString(err), kDescriptions[feature->type_], Err2); + ErrorString(err), kDescriptions[config->type_], Err2); } } else { ERROR_GOTO1("ERROR: Invalid feature for action 'strip'.\n", Err2); @@ -1140,7 +1184,7 @@ static int Process(const WebPMuxConfig* config) { // Main. int main(int argc, const char* argv[]) { - WebPMuxConfig config; + Config config; int ok = InitializeConfig(argc - 1, argv + 1, &config); if (ok) { ok = Process(&config); diff --git a/imageio/imageio_util.c b/imageio/imageio_util.c index 4cb06f2f..3a4ade0d 100644 --- a/imageio/imageio_util.c +++ b/imageio/imageio_util.c @@ -47,7 +47,8 @@ int ImgIoUtilReadFromStdin(const uint8_t** data, size_t* data_size) { while (!feof(stdin)) { // We double the buffer size each time and read as much as possible. const size_t extra_size = (max_size == 0) ? kBlockSize : max_size; - void* const new_data = realloc(input, max_size + extra_size); + // we allocate one extra byte for the \0 terminator + void* const new_data = realloc(input, max_size + extra_size + 1); if (new_data == NULL) goto Error; input = (uint8_t*)new_data; max_size += extra_size; @@ -55,6 +56,7 @@ int ImgIoUtilReadFromStdin(const uint8_t** data, size_t* data_size) { if (size < max_size) break; } if (ferror(stdin)) goto Error; + if (input != NULL) input[size] = '\0'; // convenient 0-terminator *data = input; *data_size = size; return 1; @@ -68,7 +70,7 @@ int ImgIoUtilReadFromStdin(const uint8_t** data, size_t* data_size) { int ImgIoUtilReadFile(const char* const file_name, const uint8_t** data, size_t* data_size) { int ok; - void* file_data; + uint8_t* file_data; size_t file_size; FILE* in; const int from_stdin = (file_name == NULL) || !strcmp(file_name, "-"); @@ -87,7 +89,8 @@ int ImgIoUtilReadFile(const char* const file_name, fseek(in, 0, SEEK_END); file_size = ftell(in); fseek(in, 0, SEEK_SET); - file_data = malloc(file_size); + // we allocate one extra byte for the \0 terminator + file_data = (uint8_t*)malloc(file_size + 1); if (file_data == NULL) { fclose(in); fprintf(stderr, "memory allocation failure when reading file %s\n", @@ -103,11 +106,14 @@ int ImgIoUtilReadFile(const char* const file_name, free(file_data); return 0; } - *data = (uint8_t*)file_data; + file_data[file_size] = '\0'; // convenient 0-terminator + *data = file_data; *data_size = file_size; return 1; } +// ----------------------------------------------------------------------------- + int ImgIoUtilWriteFile(const char* const file_name, const uint8_t* data, size_t data_size) { int ok; diff --git a/imageio/imageio_util.h b/imageio/imageio_util.h index b44f59fd..72db159b 100644 --- a/imageio/imageio_util.h +++ b/imageio/imageio_util.h @@ -30,6 +30,9 @@ FILE* ImgIoUtilSetBinaryMode(FILE* file); // Allocates storage for entire file 'file_name' and returns contents and size // in 'data' and 'data_size'. Returns 1 on success, 0 otherwise. '*data' should // be deleted using free(). +// Note: for convenience, the data will be null-terminated with an extra byte +// (not accounted for in *data_size), in case the file is text and intended +// to be used as a C-string. // If 'file_name' is NULL or equal to "-", input is read from stdin by calling // the function ImgIoUtilReadFromStdin(). int ImgIoUtilReadFile(const char* const file_name, diff --git a/man/webpmux.1 b/man/webpmux.1 index 0d146f6b..eb41a61f 100644 --- a/man/webpmux.1 +++ b/man/webpmux.1 @@ -1,5 +1,5 @@ .\" Hey, EMACS: -*- nroff -*- -.TH WEBPMUX 1 "November 10, 2016" +.TH WEBPMUX 1 "December 1, 2017" .SH NAME webpmux \- create animated WebP files from non\-animated WebP images, extract frames from animated WebP images, and manage XMP/EXIF metadata and ICC profile. @@ -48,6 +48,8 @@ frames from animated WebP images, and manage XMP/EXIF metadata and ICC profile. .B webpmux [\-h|\-help] .br .B webpmux \-version +.br +.B webpmux argument_file_name .SH DESCRIPTION This manual page documents the .B webpmux @@ -55,6 +57,9 @@ command. .PP \fBwebpmux\fP can be used to create/extract from animated WebP files, as well as to add/extract/strip XMP/EXIF metadata and ICC profile. +If a single file name (not starting with the character '\-') is supplied as +the argument, the command line argument are actually tokenized from this file. +This allows for easy scripting or using large number of arguments. .SH OPTIONS .SS GET_OPTIONS (\-get): .TP