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 -info INPUT
webpmux [-h|-help] webpmux [-h|-help]
webpmux -version webpmux -version
webpmux argument_file_name
GET_OPTIONS: GET_OPTIONS:
Extract relevant data: 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 Note: The nature of EXIF, XMP and ICC data is not checked and is assumed to be
valid. 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: Visualization tool:
=================== ===================

View File

@ -47,6 +47,7 @@
webpmux -info in.webp webpmux -info in.webp
webpmux [ -h | -help ] webpmux [ -h | -help ]
webpmux -version webpmux -version
webpmux argument_file_name
*/ */
#ifdef HAVE_CONFIG_H #ifdef HAVE_CONFIG_H
@ -108,28 +109,29 @@ static const char* const kDescriptions[LAST_FEATURE] = {
}; };
typedef struct { typedef struct {
FeatureType type_; // command line arguments
FeatureArg* args_; int argc_;
int arg_count_; const char** argv_;
} Feature; WebPData argv_data_;
int own_argv_;
typedef struct {
ActionType action_type_; ActionType action_type_;
const char* input_; const char* input_;
const char* output_; const char* output_;
Feature feature_; FeatureType type_;
} WebPMuxConfig; FeatureArg* args_;
int arg_count_;
} Config;
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Helper functions. // Helper functions.
static int CountOccurrences(const char* arglist[], int list_length, static int CountOccurrences(const Config* const config, const char* arg) {
const char* arg) {
int i; int i;
int num_occurences = 0; int num_occurences = 0;
for (i = 0; i < list_length; ++i) { for (i = 0; i < config->argc_; ++i) {
if (!strcmp(arglist[i], arg)) { if (!strcmp(config->argv_[i], arg)) {
++num_occurences; ++num_occurences;
} }
} }
@ -301,6 +303,7 @@ static void PrintHelp(void) {
printf(" webpmux -info INPUT\n"); printf(" webpmux -info INPUT\n");
printf(" webpmux [-h|-help]\n"); printf(" webpmux [-h|-help]\n");
printf(" webpmux -version\n"); printf(" webpmux -version\n");
printf(" webpmux argument_file_name\n");
printf("\n"); printf("\n");
printf("GET_OPTIONS:\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("\nNote: The nature of EXIF, XMP and ICC data is not checked");
printf(" and is assumed to be\nvalid.\n"); 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) { static void WarnAboutOddOffset(const WebPMuxFrameInfo* const info) {
@ -394,7 +401,7 @@ static int CreateMux(const char* const filename, WebPMux** mux) {
assert(mux != NULL); assert(mux != NULL);
if (!ReadFileToWebPData(filename, &bitstream)) return 0; if (!ReadFileToWebPData(filename, &bitstream)) return 0;
*mux = WebPMuxCreate(&bitstream, 1); *mux = WebPMuxCreate(&bitstream, 1);
free((void*)bitstream.bytes); WebPDataClear(&bitstream);
if (*mux != NULL) return 1; if (*mux != NULL) return 1;
fprintf(stderr, "Failed to create mux object from file %s.\n", filename); fprintf(stderr, "Failed to create mux object from file %s.\n", filename);
return 0; return 0;
@ -517,10 +524,15 @@ static int ParseBgcolorArgs(const char* args, uint32_t* const bgcolor) {
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Clean-up. // Clean-up.
static void DeleteConfig(WebPMuxConfig* config) { static void DeleteConfig(Config* const config) {
if (config != NULL) { 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)); memset(config, 0, sizeof(*config));
WebPDataInit(&config->argv_data_);
} }
} }
@ -531,7 +543,7 @@ static void DeleteConfig(WebPMuxConfig* config) {
// Returns 1 on valid, 0 otherwise. // Returns 1 on valid, 0 otherwise.
// Also fills up num_feature_args to be number of feature arguments given. // 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). // (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_feature_args) {
int num_frame_args; int num_frame_args;
int num_loop_args; int num_loop_args;
@ -543,27 +555,27 @@ static int ValidateCommandLine(int argc, const char* argv[],
*num_feature_args = 0; *num_feature_args = 0;
// Simple checks. // Simple checks.
if (CountOccurrences(argv, argc, "-get") > 1) { if (CountOccurrences(config, "-get") > 1) {
ERROR_GOTO1("ERROR: Multiple '-get' arguments specified.\n", ErrValidate); 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); 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); 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); 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); ERROR_GOTO1("ERROR: Multiple output files specified.\n", ErrValidate);
} }
// Compound checks. // Compound checks.
num_frame_args = CountOccurrences(argv, argc, "-frame"); num_frame_args = CountOccurrences(config, "-frame");
num_loop_args = CountOccurrences(argv, argc, "-loop"); num_loop_args = CountOccurrences(config, "-loop");
num_bgcolor_args = CountOccurrences(argv, argc, "-bgcolor"); num_bgcolor_args = CountOccurrences(config, "-bgcolor");
num_durations_args = CountOccurrences(argv, argc, "-duration"); num_durations_args = CountOccurrences(config, "-duration");
if (num_loop_args > 1) { if (num_loop_args > 1) {
ERROR_GOTO1("ERROR: Multiple loop counts specified.\n", ErrValidate); 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 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) \ #define CHECK_NUM_ARGS_LESS(NUM, LABEL) \
if (argc < i + (NUM)) { \ 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 // Parses command-line arguments to fill up config object. Also performs some
// semantic checks. // semantic checks.
static int ParseCommandLine(int argc, const char* argv[], static int ParseCommandLine(Config* config) {
WebPMuxConfig* config) {
int i = 0; int i = 0;
int feature_arg_index = 0; int feature_arg_index = 0;
int ok = 1; int ok = 1;
int argc = config->argc_;
const char* const* argv = config->argv_;
while (i < argc) { while (i < argc) {
Feature* const feature = &config->feature_; FeatureArg* const arg = &config->args_[feature_arg_index];
FeatureArg* const arg = &feature->args_[feature_arg_index];
if (argv[i][0] == '-') { // One of the action types or output. if (argv[i][0] == '-') { // One of the action types or output.
if (!strcmp(argv[i], "-set")) { if (!strcmp(argv[i], "-set")) {
if (ACTION_IS_NIL) { if (ACTION_IS_NIL) {
@ -638,8 +650,8 @@ static int ParseCommandLine(int argc, const char* argv[],
} else { } else {
ERROR_GOTO1("ERROR: Multiple actions specified.\n", ErrParse); ERROR_GOTO1("ERROR: Multiple actions specified.\n", ErrParse);
} }
if (FEATURETYPE_IS_NIL || feature->type_ == FEATURE_DURATION) { if (FEATURETYPE_IS_NIL || config->type_ == FEATURE_DURATION) {
feature->type_ = FEATURE_DURATION; config->type_ = FEATURE_DURATION;
} else { } else {
ERROR_GOTO1("ERROR: Multiple features specified.\n", ErrParse); 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")) { } else if (!strcmp(argv[i], "-strip")) {
if (ACTION_IS_NIL) { if (ACTION_IS_NIL) {
config->action_type_ = ACTION_STRIP; config->action_type_ = ACTION_STRIP;
feature->arg_count_ = 0; config->arg_count_ = 0;
} else { } else {
ERROR_GOTO1("ERROR: Multiple actions specified.\n", ErrParse); ERROR_GOTO1("ERROR: Multiple actions specified.\n", ErrParse);
} }
@ -668,8 +680,8 @@ static int ParseCommandLine(int argc, const char* argv[],
} else { } else {
ERROR_GOTO1("ERROR: Multiple actions specified.\n", ErrParse); ERROR_GOTO1("ERROR: Multiple actions specified.\n", ErrParse);
} }
if (FEATURETYPE_IS_NIL || feature->type_ == FEATURE_ANMF) { if (FEATURETYPE_IS_NIL || config->type_ == FEATURE_ANMF) {
feature->type_ = FEATURE_ANMF; config->type_ = FEATURE_ANMF;
} else { } else {
ERROR_GOTO1("ERROR: Multiple features specified.\n", ErrParse); ERROR_GOTO1("ERROR: Multiple features specified.\n", ErrParse);
} }
@ -685,8 +697,8 @@ static int ParseCommandLine(int argc, const char* argv[],
} else { } else {
ERROR_GOTO1("ERROR: Multiple actions specified.\n", ErrParse); ERROR_GOTO1("ERROR: Multiple actions specified.\n", ErrParse);
} }
if (FEATURETYPE_IS_NIL || feature->type_ == FEATURE_ANMF) { if (FEATURETYPE_IS_NIL || config->type_ == FEATURE_ANMF) {
feature->type_ = FEATURE_ANMF; config->type_ = FEATURE_ANMF;
} else { } else {
ERROR_GOTO1("ERROR: Multiple features specified.\n", ErrParse); 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); ERROR_GOTO1("ERROR: Multiple actions specified.\n", ErrParse);
} else { } else {
config->action_type_ = ACTION_INFO; config->action_type_ = ACTION_INFO;
feature->arg_count_ = 0; config->arg_count_ = 0;
config->input_ = argv[i + 1]; config->input_ = argv[i + 1];
} }
i += 2; i += 2;
@ -741,7 +753,7 @@ static int ParseCommandLine(int argc, const char* argv[],
if (!strcmp(argv[i], "icc") || !strcmp(argv[i], "exif") || if (!strcmp(argv[i], "icc") || !strcmp(argv[i], "exif") ||
!strcmp(argv[i], "xmp")) { !strcmp(argv[i], "xmp")) {
if (FEATURETYPE_IS_NIL) { 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; (!strcmp(argv[i], "exif")) ? FEATURE_EXIF : FEATURE_XMP;
} else { } else {
ERROR_GOTO1("ERROR: Multiple features specified.\n", ErrParse); 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") && } else if (!strcmp(argv[i], "frame") &&
(config->action_type_ == ACTION_GET)) { (config->action_type_ == ACTION_GET)) {
CHECK_NUM_ARGS_LESS(2, ErrParse); CHECK_NUM_ARGS_LESS(2, ErrParse);
feature->type_ = FEATURE_ANMF; config->type_ = FEATURE_ANMF;
arg->params_ = argv[i + 1]; arg->params_ = argv[i + 1];
++feature_arg_index; ++feature_arg_index;
i += 2; i += 2;
@ -777,9 +789,8 @@ static int ParseCommandLine(int argc, const char* argv[],
} }
// Additional checks after config is filled. // Additional checks after config is filled.
static int ValidateConfig(WebPMuxConfig* config) { static int ValidateConfig(Config* const config) {
int ok = 1; int ok = 1;
Feature* const feature = &config->feature_;
// Action. // Action.
if (ACTION_IS_NIL) { if (ACTION_IS_NIL) {
@ -795,7 +806,7 @@ static int ValidateConfig(WebPMuxConfig* config) {
if (config->input_ == NULL) { if (config->input_ == NULL) {
if (config->action_type_ != ACTION_SET) { if (config->action_type_ != ACTION_SET) {
ERROR_GOTO1("ERROR: No input file specified.\n", ErrValidate2); 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); ERROR_GOTO1("ERROR: No input file specified.\n", ErrValidate2);
} }
} }
@ -809,29 +820,63 @@ static int ValidateConfig(WebPMuxConfig* config) {
return ok; return ok;
} }
// Create config object from command-line arguments. #define MAX_ARGC 16384
static int InitializeConfig(int argc, const char* argv[], static int SetArguments(int argc, const char* argv[],
WebPMuxConfig* config) { Config* const config) {
int num_feature_args = 0;
int ok = 1;
assert(config != NULL); assert(config != NULL);
memset(config, 0, sizeof(*config)); 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. // 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); ERROR_GOTO1("Exiting due to command-line parsing error.\n", Err1);
} }
config->feature_.arg_count_ = num_feature_args; config->arg_count_ = num_feature_args;
config->feature_.args_ = config->args_ = (FeatureArg*)calloc(num_feature_args, sizeof(*config->args_));
(FeatureArg*)calloc(num_feature_args, sizeof(*config->feature_.args_)); if (config->args_ == NULL) {
if (config->feature_.args_ == NULL) {
ERROR_GOTO1("ERROR: Memory allocation error.\n", Err1); ERROR_GOTO1("ERROR: Memory allocation error.\n", Err1);
} }
// Parse command-line. // 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); ERROR_GOTO1("Exiting due to command-line parsing error.\n", Err1);
} }
@ -847,7 +892,7 @@ static int InitializeConfig(int argc, const char* argv[],
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Processing. // Processing.
static int GetFrame(const WebPMux* mux, const WebPMuxConfig* config) { static int GetFrame(const WebPMux* mux, const Config* config) {
WebPMuxError err = WEBP_MUX_OK; WebPMuxError err = WEBP_MUX_OK;
WebPMux* mux_single = NULL; WebPMux* mux_single = NULL;
int num = 0; int num = 0;
@ -857,7 +902,7 @@ static int GetFrame(const WebPMux* mux, const WebPMuxConfig* config) {
WebPMuxFrameInfo info; WebPMuxFrameInfo info;
WebPDataInit(&info.bitstream); 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) { if (num < 0) {
ERROR_GOTO1("ERROR: Frame/Fragment index must be non-negative.\n", ErrGet); 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. // Read and process config.
static int Process(const WebPMuxConfig* config) { static int Process(const Config* config) {
WebPMux* mux = NULL; WebPMux* mux = NULL;
WebPData chunk; WebPData chunk;
WebPMuxError err = WEBP_MUX_OK; WebPMuxError err = WEBP_MUX_OK;
int ok = 1; int ok = 1;
const Feature* const feature = &config->feature_;
switch (config->action_type_) { switch (config->action_type_) {
case ACTION_GET: { case ACTION_GET: {
ok = CreateMux(config->input_, &mux); ok = CreateMux(config->input_, &mux);
if (!ok) goto Err2; if (!ok) goto Err2;
switch (feature->type_) { switch (config->type_) {
case FEATURE_ANMF: case FEATURE_ANMF:
ok = GetFrame(mux, config); ok = GetFrame(mux, config);
break; break;
@ -910,10 +954,10 @@ static int Process(const WebPMuxConfig* config) {
case FEATURE_ICCP: case FEATURE_ICCP:
case FEATURE_EXIF: case FEATURE_EXIF:
case FEATURE_XMP: case FEATURE_XMP:
err = WebPMuxGetChunk(mux, kFourccList[feature->type_], &chunk); err = WebPMuxGetChunk(mux, kFourccList[config->type_], &chunk);
if (err != WEBP_MUX_OK) { if (err != WEBP_MUX_OK) {
ERROR_GOTO3("ERROR (%s): Could not get the %s.\n", 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); ok = WriteData(config->output_, &chunk);
break; break;
@ -925,7 +969,7 @@ static int Process(const WebPMuxConfig* config) {
break; break;
} }
case ACTION_SET: { case ACTION_SET: {
switch (feature->type_) { switch (config->type_) {
case FEATURE_ANMF: { case FEATURE_ANMF: {
int i; int i;
WebPMuxAnimParams params = { 0xFFFFFFFF, 0 }; 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", ERROR_GOTO2("ERROR (%s): Could not allocate a mux object.\n",
ErrorString(WEBP_MUX_MEMORY_ERROR), Err2); ErrorString(WEBP_MUX_MEMORY_ERROR), Err2);
} }
for (i = 0; i < feature->arg_count_; ++i) { for (i = 0; i < config->arg_count_; ++i) {
switch (feature->args_[i].subtype_) { switch (config->args_[i].subtype_) {
case SUBTYPE_BGCOLOR: { case SUBTYPE_BGCOLOR: {
uint32_t bgcolor; uint32_t bgcolor;
ok = ParseBgcolorArgs(feature->args_[i].params_, &bgcolor); ok = ParseBgcolorArgs(config->args_[i].params_, &bgcolor);
if (!ok) { if (!ok) {
ERROR_GOTO1("ERROR: Could not parse the background color \n", ERROR_GOTO1("ERROR: Could not parse the background color \n",
Err2); Err2);
@ -949,7 +993,7 @@ static int Process(const WebPMuxConfig* config) {
case SUBTYPE_LOOP: { case SUBTYPE_LOOP: {
int parse_error = 0; int parse_error = 0;
const int loop_count = 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) { if (loop_count < 0 || loop_count > 65535) {
// Note: This is only a 'necessary' condition for loop_count // Note: This is only a 'necessary' condition for loop_count
// to be valid. The 'sufficient' conditioned in checked in // to be valid. The 'sufficient' conditioned in checked in
@ -965,10 +1009,10 @@ static int Process(const WebPMuxConfig* config) {
case SUBTYPE_ANMF: { case SUBTYPE_ANMF: {
WebPMuxFrameInfo frame; WebPMuxFrameInfo frame;
frame.id = WEBP_CHUNK_ANMF; frame.id = WEBP_CHUNK_ANMF;
ok = ReadFileToWebPData(feature->args_[i].filename_, ok = ReadFileToWebPData(config->args_[i].filename_,
&frame.bitstream); &frame.bitstream);
if (!ok) goto Err2; if (!ok) goto Err2;
ok = ParseFrameArgs(feature->args_[i].params_, &frame); ok = ParseFrameArgs(config->args_[i].params_, &frame);
if (!ok) { if (!ok) {
WebPDataClear(&frame.bitstream); WebPDataClear(&frame.bitstream);
ERROR_GOTO1("ERROR: Could not parse frame properties.\n", ERROR_GOTO1("ERROR: Could not parse frame properties.\n",
@ -1001,13 +1045,13 @@ static int Process(const WebPMuxConfig* config) {
case FEATURE_XMP: { case FEATURE_XMP: {
ok = CreateMux(config->input_, &mux); ok = CreateMux(config->input_, &mux);
if (!ok) goto Err2; if (!ok) goto Err2;
ok = ReadFileToWebPData(feature->args_[0].filename_, &chunk); ok = ReadFileToWebPData(config->args_[0].filename_, &chunk);
if (!ok) goto Err2; if (!ok) goto Err2;
err = WebPMuxSetChunk(mux, kFourccList[feature->type_], &chunk, 1); err = WebPMuxSetChunk(mux, kFourccList[config->type_], &chunk, 1);
free((void*)chunk.bytes); free((void*)chunk.bytes);
if (err != WEBP_MUX_OK) { if (err != WEBP_MUX_OK) {
ERROR_GOTO3("ERROR (%s): Could not set the %s.\n", ERROR_GOTO3("ERROR (%s): Could not set the %s.\n",
ErrorString(err), kDescriptions[feature->type_], Err2); ErrorString(err), kDescriptions[config->type_], Err2);
} }
break; break;
} }
@ -1043,11 +1087,11 @@ static int Process(const WebPMuxConfig* config) {
for (i = 0; i < num_frames; ++i) durations[i] = -1; for (i = 0; i < num_frames; ++i) durations[i] = -1;
// Parse intervals to process. // Parse intervals to process.
for (i = 0; i < feature->arg_count_; ++i) { for (i = 0; i < config->arg_count_; ++i) {
int k; int k;
int args[3]; int args[3];
int duration, start, end; 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); 10, 3, args);
ok = (nb_args >= 1); ok = (nb_args >= 1);
if (!ok) goto Err3; if (!ok) goto Err3;
@ -1105,12 +1149,12 @@ static int Process(const WebPMuxConfig* config) {
case ACTION_STRIP: { case ACTION_STRIP: {
ok = CreateMux(config->input_, &mux); ok = CreateMux(config->input_, &mux);
if (!ok) goto Err2; if (!ok) goto Err2;
if (feature->type_ == FEATURE_ICCP || feature->type_ == FEATURE_EXIF || if (config->type_ == FEATURE_ICCP || config->type_ == FEATURE_EXIF ||
feature->type_ == FEATURE_XMP) { config->type_ == FEATURE_XMP) {
err = WebPMuxDeleteChunk(mux, kFourccList[feature->type_]); err = WebPMuxDeleteChunk(mux, kFourccList[config->type_]);
if (err != WEBP_MUX_OK) { if (err != WEBP_MUX_OK) {
ERROR_GOTO3("ERROR (%s): Could not strip the %s.\n", ERROR_GOTO3("ERROR (%s): Could not strip the %s.\n",
ErrorString(err), kDescriptions[feature->type_], Err2); ErrorString(err), kDescriptions[config->type_], Err2);
} }
} else { } else {
ERROR_GOTO1("ERROR: Invalid feature for action 'strip'.\n", Err2); ERROR_GOTO1("ERROR: Invalid feature for action 'strip'.\n", Err2);
@ -1140,7 +1184,7 @@ static int Process(const WebPMuxConfig* config) {
// Main. // Main.
int main(int argc, const char* argv[]) { int main(int argc, const char* argv[]) {
WebPMuxConfig config; Config config;
int ok = InitializeConfig(argc - 1, argv + 1, &config); int ok = InitializeConfig(argc - 1, argv + 1, &config);
if (ok) { if (ok) {
ok = Process(&config); ok = Process(&config);

View File

@ -47,7 +47,8 @@ int ImgIoUtilReadFromStdin(const uint8_t** data, size_t* data_size) {
while (!feof(stdin)) { while (!feof(stdin)) {
// We double the buffer size each time and read as much as possible. // We double the buffer size each time and read as much as possible.
const size_t extra_size = (max_size == 0) ? kBlockSize : max_size; 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; if (new_data == NULL) goto Error;
input = (uint8_t*)new_data; input = (uint8_t*)new_data;
max_size += extra_size; max_size += extra_size;
@ -55,6 +56,7 @@ int ImgIoUtilReadFromStdin(const uint8_t** data, size_t* data_size) {
if (size < max_size) break; if (size < max_size) break;
} }
if (ferror(stdin)) goto Error; if (ferror(stdin)) goto Error;
if (input != NULL) input[size] = '\0'; // convenient 0-terminator
*data = input; *data = input;
*data_size = size; *data_size = size;
return 1; return 1;
@ -68,7 +70,7 @@ int ImgIoUtilReadFromStdin(const uint8_t** data, size_t* data_size) {
int ImgIoUtilReadFile(const char* const file_name, int ImgIoUtilReadFile(const char* const file_name,
const uint8_t** data, size_t* data_size) { const uint8_t** data, size_t* data_size) {
int ok; int ok;
void* file_data; uint8_t* file_data;
size_t file_size; size_t file_size;
FILE* in; FILE* in;
const int from_stdin = (file_name == NULL) || !strcmp(file_name, "-"); 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); fseek(in, 0, SEEK_END);
file_size = ftell(in); file_size = ftell(in);
fseek(in, 0, SEEK_SET); 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) { if (file_data == NULL) {
fclose(in); fclose(in);
fprintf(stderr, "memory allocation failure when reading file %s\n", fprintf(stderr, "memory allocation failure when reading file %s\n",
@ -103,11 +106,14 @@ int ImgIoUtilReadFile(const char* const file_name,
free(file_data); free(file_data);
return 0; return 0;
} }
*data = (uint8_t*)file_data; file_data[file_size] = '\0'; // convenient 0-terminator
*data = file_data;
*data_size = file_size; *data_size = file_size;
return 1; return 1;
} }
// -----------------------------------------------------------------------------
int ImgIoUtilWriteFile(const char* const file_name, int ImgIoUtilWriteFile(const char* const file_name,
const uint8_t* data, size_t data_size) { const uint8_t* data, size_t data_size) {
int ok; int ok;

View File

@ -30,6 +30,9 @@ FILE* ImgIoUtilSetBinaryMode(FILE* file);
// Allocates storage for entire file 'file_name' and returns contents and size // 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 // in 'data' and 'data_size'. Returns 1 on success, 0 otherwise. '*data' should
// be deleted using free(). // 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 // If 'file_name' is NULL or equal to "-", input is read from stdin by calling
// the function ImgIoUtilReadFromStdin(). // the function ImgIoUtilReadFromStdin().
int ImgIoUtilReadFile(const char* const file_name, int ImgIoUtilReadFile(const char* const file_name,

View File

@ -1,5 +1,5 @@
.\" Hey, EMACS: -*- nroff -*- .\" Hey, EMACS: -*- nroff -*-
.TH WEBPMUX 1 "November 10, 2016" .TH WEBPMUX 1 "December 1, 2017"
.SH NAME .SH NAME
webpmux \- create animated WebP files from non\-animated WebP images, extract webpmux \- create animated WebP files from non\-animated WebP images, extract
frames from animated WebP images, and manage XMP/EXIF metadata and ICC profile. 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] .B webpmux [\-h|\-help]
.br .br
.B webpmux \-version .B webpmux \-version
.br
.B webpmux argument_file_name
.SH DESCRIPTION .SH DESCRIPTION
This manual page documents the This manual page documents the
.B webpmux .B webpmux
@ -55,6 +57,9 @@ command.
.PP .PP
\fBwebpmux\fP can be used to create/extract from animated WebP files, as well as \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. 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 .SH OPTIONS
.SS GET_OPTIONS (\-get): .SS GET_OPTIONS (\-get):
.TP .TP