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
---
This commit is contained in:
Pascal Massimino 2017-12-01 00:21:13 -08:00
parent b69f18a73a
commit a7f93fe32d
5 changed files with 145 additions and 83 deletions

View File

@ -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:
===================

View File

@ -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);

View File

@ -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;

View File

@ -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,

View File

@ -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