diff --git a/examples/Makefile.am b/examples/Makefile.am index 30f09fd2..ac60f240 100644 --- a/examples/Makefile.am +++ b/examples/Makefile.am @@ -1,6 +1,6 @@ AM_CPPFLAGS = -I$(top_srcdir)/src -bin_PROGRAMS = dwebp cwebp +bin_PROGRAMS = dwebp cwebp webpmux dwebp_SOURCES = dwebp.c stopwatch.h dwebp_CPPFLAGS = $(AM_CPPFLAGS) $(PNG_INCLUDES) $(JPEG_INCLUDES) $(USE_EXPERIMENTAL_CODE) @@ -9,3 +9,7 @@ dwebp_LDADD = ../src/libwebp.la $(PNG_LIBS) $(JPEG_LIBS) cwebp_SOURCES = cwebp.c stopwatch.h cwebp_CPPFLAGS = $(AM_CPPFLAGS) $(PNG_INCLUDES) $(JPEG_INCLUDES) $(USE_EXPERIMENTAL_CODE) cwebp_LDADD = ../src/libwebp.la $(PNG_LIBS) $(JPEG_LIBS) + +webpmux_SOURCES = webpmux.c +webpmux_CPPFLAGS = $(AM_CPPFLAGS) $(USE_EXPERIMENTAL_CODE) +webpmux_LDADD = ../src/libwebp.la ../src/mux/libwebpmux.la diff --git a/examples/webpmux.c b/examples/webpmux.c new file mode 100644 index 00000000..9a17db11 --- /dev/null +++ b/examples/webpmux.c @@ -0,0 +1,1033 @@ +// Copyright 2011 Google Inc. +// +// This code is licensed under the same terms as WebM: +// Software License Agreement: http://www.webmproject.org/license/software/ +// Additional IP Rights Grant: http://www.webmproject.org/license/additional/ +// ----------------------------------------------------------------------------- +// +// Simple command-line to create a WebP container file and to extract or strip +// relevant data from the container file. +// +// Compile with: gcc -o webpmux webpmux.c -lwebpmux +// +// +// Authors: Vikas (vikaas.arora@gmail.com), +// Urvang (urvang@google.com) + +/* Usage examples: + + Create container WebP file: + + webpmux -tile tile_1.webp +0+0 \ + -tile tile_2.webp +960+0 \ + -tile tile_3.webp +0+576 \ + -tile tile_4.webp +960+576 \ + -o out_tile_container.webp + + webpmux -frame anim_1.webp +0+0+0 \ + -frame anim_2.webp +25+25+100 \ + -frame anim_3.webp +50+50+100 \ + -frame anim_4.webp +0+0+100 \ + -loop 10 \ + -o out_animation_container.webp + + webpmux -set icc image_profile.icc in.webp -o out_icc_container.webp + + webpmux -set xmp image_metadata.xmp in.webp -o out_xmp_container.webp + + + Extract relevant data from WebP container file: + + webpmux -get tile n in.webp -o out_tile.webp + + webpmux -get frame n in.webp -o out_frame.webp + + webpmux -get icc in.webp -o image_profile.icc + + webpmux -get xmp in.webp -o image_metadata.xmp + + + Strip data from WebP Container file: + + webmux -strip icc in.webp -o out.webp + + webmux -strip xmp in.webp -o out.webp + + + Misc: + + webpmux -info in.webp + + webpmux -help + + webpmux -h +*/ + +#include +#include +#include +#include +#include "webp/mux.h" + +#if defined(__cplusplus) || defined(c_plusplus) +extern "C" { +#endif + +//------------------------------------------------------------------------------ +// Config object to parse command-line arguments. + +typedef enum { + NIL_ACTION = 0, + ACTION_GET, + ACTION_SET, + ACTION_STRIP, + ACTION_INFO, + ACTION_HELP +} ActionType; + +typedef enum { + NIL_SUBTYPE = 0, + SUBTYPE_FRM, + SUBTYPE_LOOP +} FeatureSubType; + +typedef struct { + FeatureSubType subtype_; + const char* filename_; + const char* params_; +} FeatureArg; + +typedef enum { + NIL_FEATURE = 0, + FEATURE_XMP, + FEATURE_ICCP, + FEATURE_FRM, + FEATURE_TILE +} FeatureType; + +typedef struct { + FeatureType type_; + FeatureArg* args_; + int arg_count_; +} Feature; + +typedef struct { + ActionType action_type_; + const char* input_; + const char* output_; + Feature feature_; +} WebPMuxConfig; + +//------------------------------------------------------------------------------ +// Helper functions. + +static int CountOccurences(const char* arglist[], int list_length, + const char* arg) { + int i; + int num_occurences = 0; + + for (i = 0; i < list_length; ++i) { + if (!strcmp(arglist[i], arg)) { + ++num_occurences; + } + } + return num_occurences; +} + +static int IsNotCompatible(int count1, int count2) { + return (count1 > 0) != (count2 > 0); +} + +#define RETURN_IF_ERROR(ERR_MSG) \ + if (err != WEBP_MUX_OK) { \ + fprintf(stderr, ERR_MSG); \ + return err; \ + } + +#define RETURN_IF_ERROR2(ERR_MSG, FORMAT_STR) \ + if (err != WEBP_MUX_OK) { \ + fprintf(stderr, ERR_MSG, FORMAT_STR); \ + return err; \ + } + +#define ERROR_GOTO1(ERR_MSG, LABEL) \ + fprintf(stderr, ERR_MSG); \ + ok = 0; \ + goto LABEL; + +#define ERROR_GOTO2(ERR_MSG, FORMAT_STR, LABEL) \ + fprintf(stderr, ERR_MSG, FORMAT_STR); \ + ok = 0; \ + goto LABEL; + +#define ERROR_GOTO3(ERR_MSG, FORMAT_STR1, FORMAT_STR2, LABEL) \ + fprintf(stderr, ERR_MSG, FORMAT_STR1, FORMAT_STR2); \ + ok = 0; \ + goto LABEL; + +static WebPMuxError DisplayInfo(const WebPMux* mux) { + int nFrames; + int nTiles; + const uint8_t* data = NULL; + uint32_t size = 0; + uint32_t flag; + + WebPMuxError err = WebPMuxGetFeatures(mux, &flag); + RETURN_IF_ERROR("Failed to retrieve features\n"); + + if (flag == 0) { + fprintf(stderr, "No features present.\n"); + return err; + } + + // Print the features present. + fprintf(stderr, "Features present:"); + if (flag & ANIMATION_FLAG) fprintf(stderr, " animation"); + if (flag & TILE_FLAG) fprintf(stderr, " tiling"); + if (flag & ICCP_FLAG) fprintf(stderr, " icc profile"); + if (flag & META_FLAG) fprintf(stderr, " metadata"); + fprintf(stderr, "\n"); + + if (flag & ANIMATION_FLAG) { + uint32_t loop_count; + err = WebPMuxGetLoopCount(mux, &loop_count); + RETURN_IF_ERROR("Failed to retrieve loop count\n"); + fprintf(stderr, "Loop Count : %d\n", loop_count); + + err = WebPMuxNumNamedElements(mux, "frame", &nFrames); + RETURN_IF_ERROR("Failed to retrieve number of frames\n"); + + fprintf(stderr, "Number of frames: %d\n", nFrames); + if (nFrames > 0) { + int i; + uint32_t x_offset, y_offset, duration; + + fprintf(stderr, "No : x_offset y_offset duration\n"); + for (i = 1; i <= nFrames; i++) { + err = WebPMuxGetFrame(mux, i, &data, &size, &x_offset, &y_offset, + &duration); + assert(err == WEBP_MUX_OK); + fprintf(stderr, "%3d: %8d %8d %8d\n", i, x_offset, y_offset, duration); + } + } + } + + if (flag & TILE_FLAG) { + err = WebPMuxNumNamedElements(mux, "tile", &nTiles); + RETURN_IF_ERROR("Failed to retrieve number of tiles\n"); + + fprintf(stderr, "Number of tiles: %d\n", nTiles); + if (nTiles > 0) { + int i; + uint32_t x_offset, y_offset; + fprintf(stderr, "No : x_offset y_offset\n"); + for (i = 1; i <= nTiles; i++) { + err = WebPMuxGetTile(mux, i, &data, &size, &x_offset, &y_offset); + assert(err == WEBP_MUX_OK); + fprintf(stderr, "%3d: %8d %8d\n", i, x_offset, y_offset); + } + } + } + + if (flag & ICCP_FLAG) { + err = WebPMuxGetColorProfile(mux, &data, &size); + RETURN_IF_ERROR("Failed to retrieve the color profile\n"); + fprintf(stderr, "Size of the color profile data: %d\n", size); + } + + if (flag & META_FLAG) { + err = WebPMuxGetMetadata(mux, &data, &size); + RETURN_IF_ERROR("Failed to retrieve the XMP metadata\n"); + fprintf(stderr, "Size of the XMP metadata: %d\n", size); + } + + return WEBP_MUX_OK; +} + +static void PrintHelp() { + fprintf(stderr, "Usage: webpmux -get GET_OPTIONS INPUT -o OUTPUT " + " Extract relevant data.\n"); + fprintf(stderr, " or: webpmux -set SET_OPTIONS INPUT -o OUTPUT " + " Set color profile/metadata.\n"); + fprintf(stderr, " or: webpmux -strip STRIP_OPTIONS INPUT -o OUTPUT " + " Strip color profile/metadata.\n"); + fprintf(stderr, " or: webpmux [-tile TILE_OPTIONS]... -o OUTPUT " + " Create tiled image.\n"); + fprintf(stderr, " or: webpmux [-frame FRAME_OPTIONS]... -loop LOOP_COUNT" + " -o OUTPUT Create animation.\n"); + fprintf(stderr, " or: webpmux -info INPUT " + " Print info about given webp file.\n"); + fprintf(stderr, " or: webpmux -help OR -h " + " Print this help.\n"); + + fprintf(stderr, "\n"); + fprintf(stderr, "GET_OPTIONS:\n"); + fprintf(stderr, " icc Get ICCP Color profile.\n"); + fprintf(stderr, " xmp Get XMP metadata.\n"); + fprintf(stderr, " tile n Get nth tile.\n"); + fprintf(stderr, " frame n Get nth frame.\n"); + + fprintf(stderr, "\n"); + fprintf(stderr, "SET_OPTIONS:\n"); + fprintf(stderr, " icc Set ICC Color profile.\n"); + fprintf(stderr, " xmp Set XMP metadata.\n"); + + fprintf(stderr, "\n"); + fprintf(stderr, "STRIP_OPTIONS:\n"); + fprintf(stderr, " icc Strip ICCP color profile.\n"); + fprintf(stderr, " xmp Strip XMP metadata.\n"); + + fprintf(stderr, "\n"); + fprintf(stderr, "TILE_OPTIONS(i):\n"); + fprintf(stderr, " file_i +xi+yi\n"); + fprintf(stderr, " where: 'file_i' is the i'th tile (webp format),\n"); + fprintf(stderr, " 'xi','yi' specify the image offset for this " + "tile.\n"); + + fprintf(stderr, "\n"); + fprintf(stderr, "FRAME_OPTIONS(i):\n"); + fprintf(stderr, " file_i +xi+yi+di\n"); + fprintf(stderr, " where: 'file_i' is the i'th animation frame (webp " + "format),\n"); + fprintf(stderr, " 'xi','yi' specify the image offset for this " + "frame.\n"); + fprintf(stderr, " 'di' is the pause duration before next frame." + "\n"); + + fprintf(stderr, "\n"); + fprintf(stderr, "INPUT & OUTPUT are in webp format."); + fprintf(stderr, "\n"); +} + +static int ReadData(const char* filename, void** data_ptr, uint32_t* size_ptr) { + void* data = NULL; + long size = 0; + int ok = 0; + FILE* in; + + *size_ptr = 0; + in = fopen(filename, "rb"); + if (!in) { + fprintf(stderr, "Failed to open file %s\n", filename); + return 0; + } + fseek(in, 0, SEEK_END); + size = ftell(in); + fseek(in, 0, SEEK_SET); + if (size > 0xffffffffu) { + fprintf(stderr, "Size (%ld bytes) is out of range for file %s\n", + size, filename); + size = 0; + goto Err; + } + if (size < 0) { + size = 0; + goto Err; + } + data = malloc(size); + if (data) { + if (fread(data, size, 1, in) != 1) { + free(data); + data = NULL; + size = 0; + fprintf(stderr, "Failed to read %ld bytes from file %s\n", + size, filename); + goto Err; + } + ok = 1; + } else { + fprintf(stderr, "Failed to allocate %ld bytes for reading file %s\n", + size, filename); + size = 0; + } + + Err: + if (in != stdin) fclose(in); + *size_ptr = (uint32_t)size; + *data_ptr = data; + return ok; +} + +static int ReadFile(const char* const filename, WebPMux** mux) { + uint32_t size = 0; + void* data = NULL; + + assert(mux != NULL); + + if (!ReadData(filename, &data, &size)) return 0; + *mux = WebPMuxCreate((const uint8_t*)data, size, 1); + free(data); + if (*mux != NULL) return 1; + fprintf(stderr, "Failed to create mux object from file %s.\n", + filename); + return 0; +} + +static int ReadImageData(const char* filename, int image_index, + const uint8_t** data_ptr, uint32_t* size_ptr) { + uint32_t size = 0; + void* data = NULL; + WebPMux* mux; + WebPMuxError err; + int ok = 1; + + if (!ReadData(filename, &data, &size)) return 0; + + mux = WebPMuxCreate((const uint8_t*)data, size, 1); + free(data); + if (mux == NULL) { + fprintf(stderr, "Failed to create mux object from file %s.\n", + filename); + return 0; + } + err = WebPMuxGetNamedData(mux, "image", image_index, (const uint8_t**)&data, + &size); + if (err == WEBP_MUX_OK) { + *size_ptr = size; + *data_ptr = (uint8_t*)malloc(*size_ptr); + if (*data_ptr != NULL) { + memcpy((void*)*data_ptr, data, (size_t)size); + } else { + err = WEBP_MUX_MEMORY_ERROR; + fprintf(stderr, "Failed to allocate %d bytes to extract image data from" + " file %s. Error: %d\n", size, filename, err); + ok = 0; + } + } else { + fprintf(stderr, "Failed to extract image data from file %s. Error: %d\n", + filename, err); + ok = 0; + } + WebPMuxDelete(mux); + return ok; +} + +static int WriteData(const char* filename, void* data, uint32_t size) { + int ok = 0; + FILE* fout = strcmp(filename, "--") ? fopen(filename, "wb") : stdout; + if (!fout) { + fprintf(stderr, "Error opening output WebP file %s!\n", filename); + return 0; + } + if (fwrite(data, size, 1, fout) != 1) { + fprintf(stderr, "Error writing file %s!\n", filename); + } else { + fprintf(stderr, "Saved file %s (%d bytes)\n", filename, size); + ok = 1; + } + if (fout != stdout) fclose(fout); + return ok; +} + +static int WriteWebP(WebPMux* const mux, const char* filename) { + uint8_t* data = NULL; + uint32_t size = 0; + int ok; + + WebPMuxError err = WebPMuxAssemble(mux, &data, &size); + if (err != WEBP_MUX_OK) { + fprintf(stderr, "Error (%d) assembling the WebP file.\n", err); + return 0; + } + ok = WriteData(filename, data, size); + free(data); + return ok; +} + +static int ParseFrameArgs(const char* args, uint32_t* x_offset, + uint32_t* y_offset, uint32_t* duration) { + return (sscanf(args, "+%d+%d+%d", x_offset, y_offset, duration) == 3); +} + +static int ParseTileArgs(const char* args, uint32_t* x_offset, + uint32_t* y_offset) { + return (sscanf(args, "+%d+%d", x_offset, y_offset) == 2); +} + +//------------------------------------------------------------------------------ +// Clean-up. + +static void DeleteConfig(WebPMuxConfig* config) { + if (config != NULL) { + free(config->feature_.args_); + free(config); + } +} + +//------------------------------------------------------------------------------ +// Parsing. + +// Basic syntactic checks on the command-line arguments. +// 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[], + int* num_feature_args) { + int num_frame_args; + int num_tile_args; + int num_loop_args; + int ok = 1; + + assert(num_feature_args != NULL); + *num_feature_args = 0; + + // Simple checks. + if (CountOccurences(argv, argc, "-get") > 1) { + ERROR_GOTO1("ERROR: Multiple '-get' arguments specified.\n", ErrValidate); + } + if (CountOccurences(argv, argc, "-set") > 1) { + ERROR_GOTO1("ERROR: Multiple '-set' arguments specified.\n", ErrValidate); + } + if (CountOccurences(argv, argc, "-strip") > 1) { + ERROR_GOTO1("ERROR: Multiple '-strip' arguments specified.\n", ErrValidate); + } + if (CountOccurences(argv, argc, "-info") > 1) { + ERROR_GOTO1("ERROR: Multiple '-info' arguments specified.\n", ErrValidate); + } + if (CountOccurences(argv, argc, "-help") > 1) { + ERROR_GOTO1("ERROR: Multiple '-help' arguments specified.\n", ErrValidate); + } + if (CountOccurences(argv, argc, "-o") > 1) { + ERROR_GOTO1("ERROR: Multiple output files specified.\n", ErrValidate); + } + + // Compound checks. + num_frame_args = CountOccurences(argv, argc, "-frame"); + num_tile_args = CountOccurences(argv, argc, "-tile"); + num_loop_args = CountOccurences(argv, argc, "-loop"); + + if (num_loop_args > 1) { + ERROR_GOTO1("ERROR: Multiple loop counts specified.\n", ErrValidate); + } + + if (IsNotCompatible(num_frame_args, num_loop_args)) { + ERROR_GOTO1("ERROR: Both frames and loop count have to be specified.\n", + ErrValidate); + } + if (num_frame_args > 0 && num_tile_args > 0) { + ERROR_GOTO1("ERROR: Only one of frames & tiles can be specified at a time." + "\n", ErrValidate); + } + + assert(ok == 1); + if (num_frame_args == 0 && num_tile_args == 0) { + // Single argument ('set' action for XMP or ICCP, OR a 'get' action). + *num_feature_args = 1; + } else { + // Multiple arguments ('set' action for animation or tiling). + if (num_frame_args > 0) { + *num_feature_args = num_frame_args + num_loop_args; + } else { + *num_feature_args = num_tile_args; + } + } + + ErrValidate: + return ok; +} + +#define ACTION_IS_NIL (config->action_type_ == NIL_ACTION) + +#define FEATURETYPE_IS_NIL (feature->type_ == NIL_FEATURE) + +#define CHECK_NUM_ARGS_LESS(NUM, LABEL) \ + if (argc < i + (NUM)) { \ + fprintf(stderr, "ERROR: Too few arguments for '%s'.\n", argv[i]); \ + goto LABEL; \ + } + +#define CHECK_NUM_ARGS_NOT_EQUAL(NUM, LABEL) \ + if (argc != i + (NUM)) { \ + fprintf(stderr, "ERROR: Too many arguments for '%s'.\n", argv[i]); \ + goto LABEL; \ + } + +// Parses command-line arguments to fill up config object. Also performs some +// semantic checks. +static int ParseCommandLine(int argc, const char* argv[], + WebPMuxConfig* config) { + int i = 0; + int feature_arg_index = 0; + int ok = 1; + + while (i < argc) { + Feature* const feature = &config->feature_; + FeatureArg* arg = &feature->args_[feature_arg_index]; + if (argv[i][0] == '-') { // One of the action types or output. + if (!strcmp(argv[i], "-set")) { + if (ACTION_IS_NIL) { + config->action_type_ = ACTION_SET; + } else { + ERROR_GOTO1("ERROR: Multiple actions specified.\n", ErrParse); + } + ++i; + } else if (!strcmp(argv[i], "-get")) { + if (ACTION_IS_NIL) { + config->action_type_ = ACTION_GET; + } else { + ERROR_GOTO1("ERROR: Multiple actions specified.\n", ErrParse); + } + ++i; + } else if (!strcmp(argv[i], "-strip")) { + if (ACTION_IS_NIL) { + config->action_type_ = ACTION_STRIP; + feature->arg_count_ = 0; + } else { + ERROR_GOTO1("ERROR: Multiple actions specified.\n", ErrParse); + } + ++i; + } else if (!strcmp(argv[i], "-frame")) { + CHECK_NUM_ARGS_LESS(3, ErrParse); + if (ACTION_IS_NIL || config->action_type_ == ACTION_SET) { + config->action_type_ = ACTION_SET; + } else { + ERROR_GOTO1("ERROR: Multiple actions specified.\n", ErrParse); + } + if (FEATURETYPE_IS_NIL || feature->type_ == FEATURE_FRM) { + feature->type_ = FEATURE_FRM; + } else { + ERROR_GOTO1("ERROR: Multiple features specified.\n", ErrParse); + } + arg->subtype_ = SUBTYPE_FRM; + arg->filename_ = argv[i + 1]; + arg->params_ = argv[i + 2]; + ++feature_arg_index; + i += 3; + } else if (!strcmp(argv[i], "-loop")) { + CHECK_NUM_ARGS_LESS(2, ErrParse); + if (ACTION_IS_NIL || config->action_type_ == ACTION_SET) { + config->action_type_ = ACTION_SET; + } else { + ERROR_GOTO1("ERROR: Multiple actions specified.\n", ErrParse); + } + if (FEATURETYPE_IS_NIL || feature->type_ == FEATURE_FRM) { + feature->type_ = FEATURE_FRM; + } else { + ERROR_GOTO1("ERROR: Multiple features specified.\n", ErrParse); + } + arg->subtype_ = SUBTYPE_LOOP; + arg->params_ = argv[i + 1]; + ++feature_arg_index; + i += 2; + } else if (!strcmp(argv[i], "-tile")) { + CHECK_NUM_ARGS_LESS(3, ErrParse); + if (ACTION_IS_NIL || config->action_type_ == ACTION_SET) { + config->action_type_ = ACTION_SET; + } else { + ERROR_GOTO1("ERROR: Multiple actions specified.\n", ErrParse); + } + if (FEATURETYPE_IS_NIL || feature->type_ == FEATURE_TILE) { + feature->type_ = FEATURE_TILE; + } else { + ERROR_GOTO1("ERROR: Multiple features specified.\n", ErrParse); + } + arg->filename_ = argv[i + 1]; + arg->params_ = argv[i + 2]; + ++feature_arg_index; + i += 3; + } else if (!strcmp(argv[i], "-o")) { + CHECK_NUM_ARGS_LESS(2, ErrParse); + config->output_ = argv[i + 1]; + i += 2; + } else if (!strcmp(argv[i], "-info")) { + CHECK_NUM_ARGS_NOT_EQUAL(2, ErrParse); + if (config->action_type_ != NIL_ACTION) { + ERROR_GOTO1("ERROR: Multiple actions specified.\n", ErrParse); + } else { + config->action_type_ = ACTION_INFO; + feature->arg_count_ = 0; + config->input_ = argv[i + 1]; + } + i += 2; + } else if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "-help")) { + PrintHelp(); + DeleteConfig(config); + exit(1); + } else { + ERROR_GOTO2("ERROR: Unknown option: '%s'.\n", argv[i], ErrParse); + } + } else { // One of the feature types or input. + if (ACTION_IS_NIL) { + ERROR_GOTO1("ERROR: Action must be specified before other arguments.\n", + ErrParse); + } + if (!strcmp(argv[i], "icc") || !strcmp(argv[i], "xmp")) { + if (FEATURETYPE_IS_NIL) { + feature->type_ = (!strcmp(argv[i], "icc")) ? FEATURE_ICCP : + FEATURE_XMP; + } else { + ERROR_GOTO1("ERROR: Multiple features specified.\n", ErrParse); + } + if (config->action_type_ == ACTION_SET) { + CHECK_NUM_ARGS_LESS(2, ErrParse); + arg->filename_ = argv[i + 1]; + ++feature_arg_index; + i += 2; + } else { + ++i; + } + } else if ((!strcmp(argv[i], "frame") || + !strcmp(argv[i], "tile")) && + (config->action_type_ == ACTION_GET)) { + CHECK_NUM_ARGS_LESS(2, ErrParse); + feature->type_ = (!strcmp(argv[i], "frame")) ? FEATURE_FRM : + FEATURE_TILE; + arg->params_ = argv[i + 1]; + ++feature_arg_index; + i += 2; + } else { // Assume input file. + if (config->input_ == NULL) { + config->input_ = argv[i]; + } else { + ERROR_GOTO2("ERROR at '%s': Multiple input files specified.\n", + argv[i], ErrParse); + } + ++i; + } + } + } + ErrParse: + return ok; +} + +// Additional Checks after config is filled. +static int ValidateConfig(WebPMuxConfig* config) { + int ok = 1; + Feature* const feature = &config->feature_; + + // Action. + if (ACTION_IS_NIL) { + ERROR_GOTO1("ERROR: No action specified.\n", ErrValidate2); + } + + // Feature type. + if (FEATURETYPE_IS_NIL && config->action_type_ != ACTION_INFO) { + ERROR_GOTO1("ERROR: No feature specified.\n", ErrValidate2); + } + + // Input file. + if (config->input_ == NULL) { + if (config->action_type_ != ACTION_SET) { + ERROR_GOTO1("ERROR: No input file specified.\n", ErrValidate2); + } else if (feature->type_ != FEATURE_FRM && + feature->type_ != FEATURE_TILE) { + ERROR_GOTO1("ERROR: No input file specified.\n", ErrValidate2); + } + } + + // Output file. + if (config->output_ == NULL && config->action_type_ != ACTION_INFO) { + ERROR_GOTO1("ERROR: No output file specified.\n", ErrValidate2); + } + + ErrValidate2: + 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; + + assert(config != NULL); + *config = NULL; + + // Validate command-line arguments. + if (!ValidateCommandLine(argc, argv, &num_feature_args)) { + ERROR_GOTO1("Exiting due to command-line parsing error.\n", Err1); + } + + // Allocate memory. + *config = (WebPMuxConfig*)calloc(1, sizeof(**config)); + if (*config == NULL) { + ERROR_GOTO1("ERROR: Memory allocation error.\n", Err1); + } + (*config)->feature_.arg_count_ = num_feature_args; + (*config)->feature_.args_ = + (FeatureArg*)calloc(num_feature_args, sizeof(FeatureArg)); + if ((*config)->feature_.args_ == NULL) { + ERROR_GOTO1("ERROR: Memory allocation error.\n", Err1); + } + + // Parse command-line. + if (!ParseCommandLine(argc, argv, *config)) { + ERROR_GOTO1("Exiting due to command-line parsing error.\n", Err1); + } + + // Additional Checks. + if (!ValidateConfig(*config)) { + ERROR_GOTO1("Exiting due to command-line parsing error.\n", Err1); + } + + Err1: + return ok; +} + +#undef ACTION_IS_NIL +#undef FEATURETYPE_IS_NIL +#undef CHECK_NUM_ARGS_LESS +#undef CHECK_NUM_ARGS_MORE + +//------------------------------------------------------------------------------ +// Processing. + +static int GetFrameTile(WebPMux* mux, WebPMuxConfig* config, int isFrame) { + const uint8_t* data = NULL; + uint32_t size = 0; + uint32_t x_offset = 0; + uint32_t y_offset = 0; + uint32_t duration = 0; + WebPMuxError err = WEBP_MUX_OK; + WebPMux* mux_single = NULL; + long num = 0; + int ok = 1; + + num = strtol(config->feature_.args_[0].params_, NULL, 10); + if (num < 0) { + ERROR_GOTO1("ERROR: Frame/Tile index must be non-negative.\n", ErrGet); + } + + if (isFrame) { + err = WebPMuxGetFrame(mux, num, &data, &size, &x_offset, &y_offset, + &duration); + if (err != WEBP_MUX_OK) { + ERROR_GOTO3("ERROR#%d: Could not get frame %ld.\n", err, num, ErrGet); + } + } else { + err = WebPMuxGetTile(mux, num, &data, &size, &x_offset, &y_offset); + if (err != WEBP_MUX_OK) { + ERROR_GOTO3("ERROR#%d: Could not get frame %ld.\n", err, num, ErrGet); + } + } + + mux_single = WebPMuxNew(); + if (mux_single == NULL) { + err = WEBP_MUX_MEMORY_ERROR; + ERROR_GOTO2("ERROR#%d: Could not allocate a mux object.\n", err, ErrGet); + } + err = WebPMuxAddNamedData(mux_single, 0, "image", data, size, 1); + if (err != WEBP_MUX_OK) { + ERROR_GOTO2("ERROR#%d: Could not create single image mux object.\n", err, + ErrGet); + } + ok = WriteWebP(mux_single, config->output_); + + ErrGet: + WebPMuxDelete(mux_single); + return ok; +} + +// Read and process config. +static int Process(WebPMuxConfig* config) { + WebPMux* mux = NULL; + const uint8_t* data = NULL; + uint32_t size = 0; + uint32_t x_offset = 0; + uint32_t y_offset = 0; + uint32_t duration = 0; + uint32_t loop_count = 0; + WebPMuxError err = WEBP_MUX_OK; + long num; + int index = 0; + int ok = 1; + Feature* const feature = &config->feature_; + + switch(config->action_type_) { + case ACTION_GET: + ok = ReadFile(config->input_, &mux); + if (!ok) goto Err2; + switch(feature->type_) { + case FEATURE_FRM: + ok = GetFrameTile(mux, config, 1); + break; + + case FEATURE_TILE: + ok = GetFrameTile(mux, config, 0); + break; + + case FEATURE_ICCP: + err = WebPMuxGetColorProfile(mux, &data, &size); + if (err != WEBP_MUX_OK) { + ERROR_GOTO2("ERROR#%d: Could not get color profile.\n", err, Err2); + } + ok = WriteData(config->output_, (void*)data, size); + break; + + case FEATURE_XMP: + err = WebPMuxGetMetadata(mux, &data, &size); + if (err != WEBP_MUX_OK) { + ERROR_GOTO2("ERROR#%d: Could not get XMP metadata.\n", err, Err2); + } + ok = WriteData(config->output_, (void*)data, size); + break; + + default: + ERROR_GOTO1("ERROR: Invalid feature for action 'get'.\n", Err2); + break; + } + break; + + case ACTION_SET: + switch(feature->type_) { + case FEATURE_FRM: + mux = WebPMuxNew(); + if (mux == NULL) { + ERROR_GOTO2("ERROR#%d: Could not allocate a mux object.\n", + WEBP_MUX_MEMORY_ERROR, Err2); + } + for (index = 0; index < feature->arg_count_; ++index) { + if (feature->args_[index].subtype_ == SUBTYPE_LOOP) { + num = strtol(feature->args_[index].params_, NULL, 10); + if (num < 0) { + ERROR_GOTO1("ERROR: Loop count must be non-negative.\n", Err2); + } else { + loop_count = num; + } + err = WebPMuxSetLoopCount(mux, loop_count); + if (err != WEBP_MUX_OK) { + ERROR_GOTO2("ERROR#%d: Could not set loop count.\n", err, Err2); + } + } else if (feature->args_[index].subtype_ == SUBTYPE_FRM) { + ok = ReadImageData(feature->args_[index].filename_, 1, + &data, &size); + if (!ok) goto Err2; + ok = ParseFrameArgs(feature->args_[index].params_, + &x_offset, &y_offset, &duration); + if (!ok) { + free((void*)data); + ERROR_GOTO1("ERROR: Could not parse frame properties.\n", Err2); + } + err = WebPMuxAddFrame(mux, 0, data, size, x_offset, y_offset, + duration, 1); + free((void*)data); + if (err != WEBP_MUX_OK) { + ERROR_GOTO3("ERROR#%d: Could not add a frame at index %d.\n", + err, index, Err2); + } + } else { + ERROR_GOTO1("ERROR: Invalid subtype for 'frame'", Err2); + } + } + break; + + case FEATURE_TILE: + mux = WebPMuxNew(); + if (mux == NULL) { + ERROR_GOTO2("ERROR#%d: Could not allocate a mux object.\n", + WEBP_MUX_MEMORY_ERROR, Err2); + } + for (index = 0; index < feature->arg_count_; ++index) { + ok = ReadImageData(feature->args_[index].filename_, 1, + &data, &size); + if (!ok) goto Err2; + ok = ParseTileArgs(feature->args_[index].params_, &x_offset, + &y_offset); + if (!ok) { + free((void*)data); + ERROR_GOTO1("ERROR: Could not parse tile properties.\n", Err2); + } + err = WebPMuxAddTile(mux, 0, data, size, x_offset, y_offset, 1); + free((void*)data); + if (err != WEBP_MUX_OK) { + ERROR_GOTO3("ERROR#%d: Could not add a tile at index %d.\n", + err, index, Err2); + } + } + break; + + case FEATURE_ICCP: + ok = ReadFile(config->input_, &mux); + if (!ok) goto Err2; + ok = ReadData(feature->args_[0].filename_, (void**)&data, + &size); + if (!ok) goto Err2; + err = WebPMuxSetColorProfile(mux, data, size, 1); + free((void*)data); + if (err != WEBP_MUX_OK) { + ERROR_GOTO2("ERROR#%d: Could not set color profile.\n", err, Err2); + } + break; + + case FEATURE_XMP: + ok = ReadFile(config->input_, &mux); + if (!ok) goto Err2; + ok = ReadData(feature->args_[0].filename_, (void**)&data, + &size); + if (!ok) goto Err2; + err = WebPMuxSetMetadata(mux, data, size, 1); + free((void*)data); + if (err != WEBP_MUX_OK) { + ERROR_GOTO2("ERROR#%d: Could not set XMP metadata.\n", err, Err2); + } + break; + + default: + ERROR_GOTO1("ERROR: Invalid feature for action 'set'.\n", Err2); + break; + } + ok = WriteWebP(mux, config->output_); + break; + + case ACTION_STRIP: + ok = ReadFile(config->input_, &mux); + if (!ok) goto Err2; + switch(feature->type_) { + case FEATURE_ICCP: + err = WebPMuxDeleteColorProfile(mux); + if (err != WEBP_MUX_OK) { + ERROR_GOTO2("ERROR#%d: Could not delete color profile.\n", err, + Err2); + } + break; + case FEATURE_XMP: + err = WebPMuxDeleteMetadata(mux); + if (err != WEBP_MUX_OK) { + ERROR_GOTO2("ERROR#%d: Could not delete XMP metadata.\n", err, + Err2); + } + break; + default: + ERROR_GOTO1("ERROR: Invalid feature for action 'strip'.\n", Err2); + break; + } + ok = WriteWebP(mux, config->output_); + break; + + case ACTION_INFO: + ok = ReadFile(config->input_, &mux); + if (!ok) goto Err2; + ok = (DisplayInfo(mux) == WEBP_MUX_OK); + break; + + default: + assert(0); // Invalid action. + break; + } + + Err2: + WebPMuxDelete(mux); + return ok; +} + +//------------------------------------------------------------------------------ +// Main. + +int main(int argc, const char* argv[]) { + int ok = 1; + WebPMuxConfig* config; + ok = InitializeConfig(argc-1, argv+1, &config); + if (ok) { + Process(config); + } else { + PrintHelp(); + } + DeleteConfig(config); + return ok; +} + +//------------------------------------------------------------------------------ + +#if defined(__cplusplus) || defined(c_plusplus) +} // extern "C" +#endif diff --git a/makefile.unix b/makefile.unix index 608a552e..e828ebe6 100644 --- a/makefile.unix +++ b/makefile.unix @@ -72,14 +72,17 @@ UTILS_OBJS = src/utils/bit_reader.o src/utils/bit_writer.o src/utils/thread.o OBJS = $(DEC_OBJS) $(ENC_OBJS) $(DSP_OBJS) $(UTILS_OBJS) -HDRS = src/webp/encode.h src/enc/vp8enci.h src/enc/cost.h \ +MUX_OBJS = src/mux/mux.o + +HDRS = src/webp/encode.h src/enc/vp8enci.h src/enc/cost.h src/webp/mux.h \ src/dec/vp8i.h \ src/dsp/yuv.h src/dsp/dsp.h \ src/utils/bit_writer.h src/utils/bit_reader.h src/utils/thread.h -OUTPUT = examples/cwebp examples/dwebp src/libwebp.a src/mux/libwebpmux.a +OUTPUT = examples/cwebp examples/dwebp examples/webpmux \ + src/libwebp.a src/mux/libwebpmux.a -all:ex src/mux/libwebpmux.a +all:ex %.o: %.c $(HDRS) $(CC) $(CFLAGS) -c $< -o $@ @@ -87,23 +90,23 @@ all:ex src/mux/libwebpmux.a src/libwebp.a: $(OBJS) $(AR) $(ARFLAGS) $@ $^ -MUX_OBJS = src/mux/mux.o - src/mux/libwebpmux.a: $(MUX_OBJS) $(AR) $(ARFLAGS) $@ $^ -ex: examples/cwebp examples/dwebp +ex: examples/cwebp examples/dwebp examples/webpmux examples/cwebp: examples/cwebp.o src/libwebp.a examples/dwebp: examples/dwebp.o src/libwebp.a -examples/cwebp examples/dwebp: +examples/webpmux: examples/webpmux.o src/mux/libwebpmux.a src/libwebp.a +examples/cwebp examples/dwebp examples/webpmux: $(CC) -o $@ $^ $(LDFLAGS) dist: DESTDIR := dist dist: all $(INSTALL) -m755 -d $(DESTDIR)/include/webp \ $(DESTDIR)/doc $(DESTDIR)/lib - $(INSTALL) -m755 -s examples/cwebp examples/dwebp $(DESTDIR) + $(INSTALL) -m755 -s examples/cwebp examples/dwebp examples/webpmux \ + $(DESTDIR) $(INSTALL) -m644 src/webp/*.h $(DESTDIR)/include/webp $(INSTALL) -m644 src/libwebp.a $(DESTDIR)/lib umask 022; \