| // Copyright 2011 Google Inc. All Rights Reserved. |
| // |
| // Use of this source code is governed by a BSD-style license |
| // that can be found in the COPYING file in the root of the source |
| // tree. An additional intellectual property rights grant can be found |
| // in the file PATENTS. All contributing project authors may |
| // be found in the AUTHORS file in the root of the source tree. |
| // ----------------------------------------------------------------------------- |
| // |
| // Simple command-line to create a WebP container file and to extract or strip |
| // relevant data from the container file. |
| // |
| // Authors: Vikas (vikaas.arora@gmail.com), |
| // Urvang (urvang@google.com) |
| |
| /* Usage examples: |
| |
| Create container WebP file: |
| webpmux -frame anim_1.webp +100+10+10 \ |
| -frame anim_2.webp +100+25+25+1 \ |
| -frame anim_3.webp +100+50+50+1 \ |
| -frame anim_4.webp +100 \ |
| -loop 10 -bgcolor 128,255,255,255 \ |
| -o out_animation_container.webp |
| |
| webpmux -set icc image_profile.icc in.webp -o out_icc_container.webp |
| webpmux -set exif image_metadata.exif in.webp -o out_exif_container.webp |
| webpmux -set xmp image_metadata.xmp in.webp -o out_xmp_container.webp |
| |
| Extract relevant data from WebP container file: |
| webpmux -get frgm n in.webp -o out_fragment.webp |
| webpmux -get frame n in.webp -o out_frame.webp |
| webpmux -get icc in.webp -o image_profile.icc |
| webpmux -get exif in.webp -o image_metadata.exif |
| webpmux -get xmp in.webp -o image_metadata.xmp |
| |
| Strip data from WebP Container file: |
| webpmux -strip icc in.webp -o out.webp |
| webpmux -strip exif in.webp -o out.webp |
| webpmux -strip xmp in.webp -o out.webp |
| |
| Misc: |
| webpmux -info in.webp |
| webpmux [ -h | -help ] |
| webpmux -version |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include "webp/config.h" |
| #endif |
| |
| #include <assert.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include "webp/decode.h" |
| #include "webp/mux.h" |
| #include "./example_util.h" |
| |
| //------------------------------------------------------------------------------ |
| // 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_ANMF, |
| SUBTYPE_LOOP, |
| SUBTYPE_BGCOLOR |
| } FeatureSubType; |
| |
| typedef struct { |
| FeatureSubType subtype_; |
| const char* filename_; |
| const char* params_; |
| } FeatureArg; |
| |
| typedef enum { |
| NIL_FEATURE = 0, |
| FEATURE_EXIF, |
| FEATURE_XMP, |
| FEATURE_ICCP, |
| FEATURE_ANMF, |
| FEATURE_FRGM, |
| LAST_FEATURE |
| } FeatureType; |
| |
| static const char* const kFourccList[LAST_FEATURE] = { |
| NULL, "EXIF", "XMP ", "ICCP", "ANMF", "FRGM" |
| }; |
| |
| static const char* const kDescriptions[LAST_FEATURE] = { |
| NULL, "EXIF metadata", "XMP metadata", "ICC profile", |
| "Animation frame", "Image fragment" |
| }; |
| |
| 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 CountOccurrences(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 const char* const kErrorMessages[-WEBP_MUX_NOT_ENOUGH_DATA + 1] = { |
| "WEBP_MUX_NOT_FOUND", "WEBP_MUX_INVALID_ARGUMENT", "WEBP_MUX_BAD_DATA", |
| "WEBP_MUX_MEMORY_ERROR", "WEBP_MUX_NOT_ENOUGH_DATA" |
| }; |
| |
| static const char* ErrorString(WebPMuxError err) { |
| assert(err <= WEBP_MUX_NOT_FOUND && err >= WEBP_MUX_NOT_ENOUGH_DATA); |
| return kErrorMessages[-err]; |
| } |
| |
| #define RETURN_IF_ERROR(ERR_MSG) \ |
| if (err != WEBP_MUX_OK) { \ |
| fprintf(stderr, ERR_MSG); \ |
| return err; \ |
| } |
| |
| #define RETURN_IF_ERROR3(ERR_MSG, FORMAT_STR1, FORMAT_STR2) \ |
| if (err != WEBP_MUX_OK) { \ |
| fprintf(stderr, ERR_MSG, FORMAT_STR1, FORMAT_STR2); \ |
| return err; \ |
| } |
| |
| #define ERROR_GOTO1(ERR_MSG, LABEL) \ |
| do { \ |
| fprintf(stderr, ERR_MSG); \ |
| ok = 0; \ |
| goto LABEL; \ |
| } while (0) |
| |
| #define ERROR_GOTO2(ERR_MSG, FORMAT_STR, LABEL) \ |
| do { \ |
| fprintf(stderr, ERR_MSG, FORMAT_STR); \ |
| ok = 0; \ |
| goto LABEL; \ |
| } while (0) |
| |
| #define ERROR_GOTO3(ERR_MSG, FORMAT_STR1, FORMAT_STR2, LABEL) \ |
| do { \ |
| fprintf(stderr, ERR_MSG, FORMAT_STR1, FORMAT_STR2); \ |
| ok = 0; \ |
| goto LABEL; \ |
| } while (0) |
| |
| static WebPMuxError DisplayInfo(const WebPMux* mux) { |
| int width, height; |
| uint32_t flag; |
| |
| WebPMuxError err = WebPMuxGetCanvasSize(mux, &width, &height); |
| assert(err == WEBP_MUX_OK); // As WebPMuxCreate() was successful earlier. |
| printf("Canvas size: %d x %d\n", width, height); |
| |
| err = WebPMuxGetFeatures(mux, &flag); |
| #ifndef WEBP_EXPERIMENTAL_FEATURES |
| if (flag & FRAGMENTS_FLAG) err = WEBP_MUX_INVALID_ARGUMENT; |
| #endif |
| RETURN_IF_ERROR("Failed to retrieve features\n"); |
| |
| if (flag == 0) { |
| fprintf(stderr, "No features present.\n"); |
| return err; |
| } |
| |
| // Print the features present. |
| printf("Features present:"); |
| if (flag & ANIMATION_FLAG) printf(" animation"); |
| if (flag & FRAGMENTS_FLAG) printf(" image fragments"); |
| if (flag & ICCP_FLAG) printf(" ICC profile"); |
| if (flag & EXIF_FLAG) printf(" EXIF metadata"); |
| if (flag & XMP_FLAG) printf(" XMP metadata"); |
| if (flag & ALPHA_FLAG) printf(" transparency"); |
| printf("\n"); |
| |
| if ((flag & ANIMATION_FLAG) || (flag & FRAGMENTS_FLAG)) { |
| const int is_anim = !!(flag & ANIMATION_FLAG); |
| const WebPChunkId id = is_anim ? WEBP_CHUNK_ANMF : WEBP_CHUNK_FRGM; |
| const char* const type_str = is_anim ? "frame" : "fragment"; |
| int nFrames; |
| |
| if (is_anim) { |
| WebPMuxAnimParams params; |
| err = WebPMuxGetAnimationParams(mux, ¶ms); |
| assert(err == WEBP_MUX_OK); |
| printf("Background color : 0x%.8X Loop Count : %d\n", |
| params.bgcolor, params.loop_count); |
| } |
| |
| err = WebPMuxNumChunks(mux, id, &nFrames); |
| assert(err == WEBP_MUX_OK); |
| |
| printf("Number of %ss: %d\n", type_str, nFrames); |
| if (nFrames > 0) { |
| int i; |
| printf("No.: width height alpha x_offset y_offset "); |
| if (is_anim) printf("duration dispose blend "); |
| printf("image_size\n"); |
| for (i = 1; i <= nFrames; i++) { |
| WebPMuxFrameInfo frame; |
| err = WebPMuxGetFrame(mux, i, &frame); |
| if (err == WEBP_MUX_OK) { |
| WebPBitstreamFeatures features; |
| const VP8StatusCode status = WebPGetFeatures( |
| frame.bitstream.bytes, frame.bitstream.size, &features); |
| assert(status == VP8_STATUS_OK); // Checked by WebPMuxCreate(). |
| (void)status; |
| printf("%3d: %5d %5d %5s %8d %8d ", i, features.width, |
| features.height, features.has_alpha ? "yes" : "no", |
| frame.x_offset, frame.y_offset); |
| if (is_anim) { |
| const char* const dispose = |
| (frame.dispose_method == WEBP_MUX_DISPOSE_NONE) ? "none" |
| : "background"; |
| const char* const blend = |
| (frame.blend_method == WEBP_MUX_BLEND) ? "yes" : "no"; |
| printf("%8d %10s %5s ", frame.duration, dispose, blend); |
| } |
| printf("%10d\n", (int)frame.bitstream.size); |
| } |
| WebPDataClear(&frame.bitstream); |
| RETURN_IF_ERROR3("Failed to retrieve %s#%d\n", type_str, i); |
| } |
| } |
| } |
| |
| if (flag & ICCP_FLAG) { |
| WebPData icc_profile; |
| err = WebPMuxGetChunk(mux, "ICCP", &icc_profile); |
| assert(err == WEBP_MUX_OK); |
| printf("Size of the ICC profile data: %d\n", (int)icc_profile.size); |
| } |
| |
| if (flag & EXIF_FLAG) { |
| WebPData exif; |
| err = WebPMuxGetChunk(mux, "EXIF", &exif); |
| assert(err == WEBP_MUX_OK); |
| printf("Size of the EXIF metadata: %d\n", (int)exif.size); |
| } |
| |
| if (flag & XMP_FLAG) { |
| WebPData xmp; |
| err = WebPMuxGetChunk(mux, "XMP ", &xmp); |
| assert(err == WEBP_MUX_OK); |
| printf("Size of the XMP metadata: %d\n", (int)xmp.size); |
| } |
| |
| if ((flag & ALPHA_FLAG) && !(flag & (ANIMATION_FLAG | FRAGMENTS_FLAG))) { |
| WebPMuxFrameInfo image; |
| err = WebPMuxGetFrame(mux, 1, &image); |
| if (err == WEBP_MUX_OK) { |
| printf("Size of the image (with alpha): %d\n", (int)image.bitstream.size); |
| } |
| WebPDataClear(&image.bitstream); |
| RETURN_IF_ERROR("Failed to retrieve the image\n"); |
| } |
| |
| return WEBP_MUX_OK; |
| } |
| |
| static void PrintHelp(void) { |
| printf("Usage: webpmux -get GET_OPTIONS INPUT -o OUTPUT\n"); |
| printf(" webpmux -set SET_OPTIONS INPUT -o OUTPUT\n"); |
| printf(" webpmux -strip STRIP_OPTIONS INPUT -o OUTPUT\n"); |
| #ifdef WEBP_EXPERIMENTAL_FEATURES |
| printf(" webpmux -frgm FRAGMENT_OPTIONS [-frgm...] -o OUTPUT\n"); |
| #endif |
| printf(" webpmux -frame FRAME_OPTIONS [-frame...] [-loop LOOP_COUNT]" |
| "\n"); |
| printf(" [-bgcolor BACKGROUND_COLOR] -o OUTPUT\n"); |
| printf(" webpmux -info INPUT\n"); |
| printf(" webpmux [-h|-help]\n"); |
| printf(" webpmux -version\n"); |
| |
| printf("\n"); |
| printf("GET_OPTIONS:\n"); |
| printf(" Extract relevant data:\n"); |
| printf(" icc get ICC profile\n"); |
| printf(" exif get EXIF metadata\n"); |
| printf(" xmp get XMP metadata\n"); |
| #ifdef WEBP_EXPERIMENTAL_FEATURES |
| printf(" frgm n get nth fragment\n"); |
| #endif |
| printf(" frame n get nth frame\n"); |
| |
| printf("\n"); |
| printf("SET_OPTIONS:\n"); |
| printf(" Set color profile/metadata:\n"); |
| printf(" icc file.icc set ICC profile\n"); |
| printf(" exif file.exif set EXIF metadata\n"); |
| printf(" xmp file.xmp set XMP metadata\n"); |
| printf(" where: 'file.icc' contains the ICC profile to be set,\n"); |
| printf(" 'file.exif' contains the EXIF metadata to be set\n"); |
| printf(" 'file.xmp' contains the XMP metadata to be set\n"); |
| |
| printf("\n"); |
| printf("STRIP_OPTIONS:\n"); |
| printf(" Strip color profile/metadata:\n"); |
| printf(" icc strip ICC profile\n"); |
| printf(" exif strip EXIF metadata\n"); |
| printf(" xmp strip XMP metadata\n"); |
| |
| #ifdef WEBP_EXPERIMENTAL_FEATURES |
| printf("\n"); |
| printf("FRAGMENT_OPTIONS(i):\n"); |
| printf(" Create fragmented image:\n"); |
| printf(" file_i +xi+yi\n"); |
| printf(" where: 'file_i' is the i'th fragment (WebP format),\n"); |
| printf(" 'xi','yi' specify the image offset for this fragment" |
| "\n"); |
| #endif |
| |
| printf("\n"); |
| printf("FRAME_OPTIONS(i):\n"); |
| printf(" Create animation:\n"); |
| printf(" file_i +di+[xi+yi[+mi[bi]]]\n"); |
| printf(" where: 'file_i' is the i'th animation frame (WebP format),\n"); |
| printf(" 'di' is the pause duration before next frame,\n"); |
| printf(" 'xi','yi' specify the image offset for this frame,\n"); |
| printf(" 'mi' is the dispose method for this frame (0 or 1),\n"); |
| printf(" 'bi' is the blending method for this frame (+b or -b)" |
| "\n"); |
| |
| printf("\n"); |
| printf("LOOP_COUNT:\n"); |
| printf(" Number of times to repeat the animation.\n"); |
| printf(" Valid range is 0 to 65535 [Default: 0 (infinite)].\n"); |
| |
| printf("\n"); |
| printf("BACKGROUND_COLOR:\n"); |
| printf(" Background color of the canvas.\n"); |
| printf(" A,R,G,B\n"); |
| printf(" where: 'A', 'R', 'G' and 'B' are integers in the range 0 to 255 " |
| "specifying\n"); |
| printf(" the Alpha, Red, Green and Blue component values " |
| "respectively\n"); |
| printf(" [Default: 255,255,255,255]\n"); |
| |
| printf("\nINPUT & OUTPUT are in WebP format.\n"); |
| |
| printf("\nNote: The nature of EXIF, XMP and ICC data is not checked"); |
| printf(" and is assumed to be\nvalid.\n"); |
| } |
| |
| static void WarnAboutOddOffset(const WebPMuxFrameInfo* const info) { |
| if ((info->x_offset | info->y_offset) & 1) { |
| fprintf(stderr, "Warning: odd offsets will be snapped to even values" |
| " (%d, %d) -> (%d, %d)\n", info->x_offset, info->y_offset, |
| info->x_offset & ~1, info->y_offset & ~1); |
| } |
| } |
| |
| static int ReadFileToWebPData(const char* const filename, |
| WebPData* const webp_data) { |
| const uint8_t* data; |
| size_t size; |
| if (!ExUtilReadFile(filename, &data, &size)) return 0; |
| webp_data->bytes = data; |
| webp_data->size = size; |
| return 1; |
| } |
| |
| static int CreateMux(const char* const filename, WebPMux** mux) { |
| WebPData bitstream; |
| assert(mux != NULL); |
| if (!ReadFileToWebPData(filename, &bitstream)) return 0; |
| *mux = WebPMuxCreate(&bitstream, 1); |
| free((void*)bitstream.bytes); |
| if (*mux != NULL) return 1; |
| fprintf(stderr, "Failed to create mux object from file %s.\n", filename); |
| return 0; |
| } |
| |
| static int WriteData(const char* filename, const WebPData* const webpdata) { |
| int ok = 0; |
| FILE* fout = strcmp(filename, "-") ? fopen(filename, "wb") |
| : ExUtilSetBinaryMode(stdout); |
| if (fout == NULL) { |
| fprintf(stderr, "Error opening output WebP file %s!\n", filename); |
| return 0; |
| } |
| if (fwrite(webpdata->bytes, webpdata->size, 1, fout) != 1) { |
| fprintf(stderr, "Error writing file %s!\n", filename); |
| } else { |
| fprintf(stderr, "Saved file %s (%d bytes)\n", |
| filename, (int)webpdata->size); |
| ok = 1; |
| } |
| if (fout != stdout) fclose(fout); |
| return ok; |
| } |
| |
| static int WriteWebP(WebPMux* const mux, const char* filename) { |
| int ok; |
| WebPData webp_data; |
| const WebPMuxError err = WebPMuxAssemble(mux, &webp_data); |
| if (err != WEBP_MUX_OK) { |
| fprintf(stderr, "Error (%s) assembling the WebP file.\n", ErrorString(err)); |
| return 0; |
| } |
| ok = WriteData(filename, &webp_data); |
| WebPDataClear(&webp_data); |
| return ok; |
| } |
| |
| static int ParseFrameArgs(const char* args, WebPMuxFrameInfo* const info) { |
| int dispose_method, dummy; |
| char plus_minus, blend_method; |
| const int num_args = sscanf(args, "+%d+%d+%d+%d%c%c+%d", &info->duration, |
| &info->x_offset, &info->y_offset, &dispose_method, |
| &plus_minus, &blend_method, &dummy); |
| switch (num_args) { |
| case 1: |
| info->x_offset = info->y_offset = 0; // fall through |
| case 3: |
| dispose_method = 0; // fall through |
| case 4: |
| plus_minus = '+'; |
| blend_method = 'b'; // fall through |
| case 6: |
| break; |
| case 2: |
| case 5: |
| default: |
| return 0; |
| } |
| |
| WarnAboutOddOffset(info); |
| |
| // Note: The sanity of the following conversion is checked by |
| // WebPMuxPushFrame(). |
| info->dispose_method = (WebPMuxAnimDispose)dispose_method; |
| |
| if (blend_method != 'b') return 0; |
| if (plus_minus != '-' && plus_minus != '+') return 0; |
| info->blend_method = |
| (plus_minus == '+') ? WEBP_MUX_BLEND : WEBP_MUX_NO_BLEND; |
| return 1; |
| } |
| |
| static int ParseFragmentArgs(const char* args, WebPMuxFrameInfo* const info) { |
| const int ok = |
| (sscanf(args, "+%d+%d", &info->x_offset, &info->y_offset) == 2); |
| if (ok) WarnAboutOddOffset(info); |
| return ok; |
| } |
| |
| static int ParseBgcolorArgs(const char* args, uint32_t* const bgcolor) { |
| uint32_t a, r, g, b; |
| if (sscanf(args, "%u,%u,%u,%u", &a, &r, &g, &b) != 4) return 0; |
| if (a >= 256 || r >= 256 || g >= 256 || b >= 256) return 0; |
| *bgcolor = (a << 24) | (r << 16) | (g << 8) | (b << 0); |
| return 1; |
| } |
| |
| //------------------------------------------------------------------------------ |
| // Clean-up. |
| |
| static void DeleteConfig(WebPMuxConfig* config) { |
| if (config != NULL) { |
| free(config->feature_.args_); |
| memset(config, 0, sizeof(*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_frgm_args; |
| int num_loop_args; |
| int num_bgcolor_args; |
| int ok = 1; |
| |
| assert(num_feature_args != NULL); |
| *num_feature_args = 0; |
| |
| // Simple checks. |
| if (CountOccurrences(argv, argc, "-get") > 1) { |
| ERROR_GOTO1("ERROR: Multiple '-get' arguments specified.\n", ErrValidate); |
| } |
| if (CountOccurrences(argv, argc, "-set") > 1) { |
| ERROR_GOTO1("ERROR: Multiple '-set' arguments specified.\n", ErrValidate); |
| } |
| if (CountOccurrences(argv, argc, "-strip") > 1) { |
| ERROR_GOTO1("ERROR: Multiple '-strip' arguments specified.\n", ErrValidate); |
| } |
| if (CountOccurrences(argv, argc, "-info") > 1) { |
| ERROR_GOTO1("ERROR: Multiple '-info' arguments specified.\n", ErrValidate); |
| } |
| if (CountOccurrences(argv, argc, "-o") > 1) { |
| ERROR_GOTO1("ERROR: Multiple output files specified.\n", ErrValidate); |
| } |
| |
| // Compound checks. |
| num_frame_args = CountOccurrences(argv, argc, "-frame"); |
| num_frgm_args = CountOccurrences(argv, argc, "-frgm"); |
| num_loop_args = CountOccurrences(argv, argc, "-loop"); |
| num_bgcolor_args = CountOccurrences(argv, argc, "-bgcolor"); |
| |
| if (num_loop_args > 1) { |
| ERROR_GOTO1("ERROR: Multiple loop counts specified.\n", ErrValidate); |
| } |
| if (num_bgcolor_args > 1) { |
| ERROR_GOTO1("ERROR: Multiple background colors specified.\n", ErrValidate); |
| } |
| |
| if ((num_frame_args == 0) && (num_loop_args + num_bgcolor_args > 0)) { |
| ERROR_GOTO1("ERROR: Loop count and background color are relevant only in " |
| "case of animation.\n", ErrValidate); |
| } |
| if (num_frame_args > 0 && num_frgm_args > 0) { |
| ERROR_GOTO1("ERROR: Only one of frames & fragments can be specified at a " |
| "time.\n", ErrValidate); |
| } |
| |
| assert(ok == 1); |
| if (num_frame_args == 0 && num_frgm_args == 0) { |
| // Single argument ('set' action for ICCP/EXIF/XMP, OR a 'get' action). |
| *num_feature_args = 1; |
| } else { |
| // Multiple arguments ('set' action for animation or fragmented image). |
| if (num_frame_args > 0) { |
| *num_feature_args = num_frame_args + num_loop_args + num_bgcolor_args; |
| } else { |
| *num_feature_args = num_frgm_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* const 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_ANMF) { |
| feature->type_ = FEATURE_ANMF; |
| } else { |
| ERROR_GOTO1("ERROR: Multiple features specified.\n", ErrParse); |
| } |
| arg->subtype_ = SUBTYPE_ANMF; |
| arg->filename_ = argv[i + 1]; |
| arg->params_ = argv[i + 2]; |
| ++feature_arg_index; |
| i += 3; |
| } else if (!strcmp(argv[i], "-loop") || !strcmp(argv[i], "-bgcolor")) { |
| 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_ANMF) { |
| feature->type_ = FEATURE_ANMF; |
| } else { |
| ERROR_GOTO1("ERROR: Multiple features specified.\n", ErrParse); |
| } |
| arg->subtype_ = |
| !strcmp(argv[i], "-loop") ? SUBTYPE_LOOP : SUBTYPE_BGCOLOR; |
| arg->params_ = argv[i + 1]; |
| ++feature_arg_index; |
| i += 2; |
| #ifdef WEBP_EXPERIMENTAL_FEATURES |
| } else if (!strcmp(argv[i], "-frgm")) { |
| 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_FRGM) { |
| feature->type_ = FEATURE_FRGM; |
| } else { |
| ERROR_GOTO1("ERROR: Multiple features specified.\n", ErrParse); |
| } |
| arg->filename_ = argv[i + 1]; |
| arg->params_ = argv[i + 2]; |
| ++feature_arg_index; |
| i += 3; |
| #endif |
| } 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(0); |
| } else if (!strcmp(argv[i], "-version")) { |
| const int version = WebPGetMuxVersion(); |
| printf("%d.%d.%d\n", |
| (version >> 16) & 0xff, (version >> 8) & 0xff, version & 0xff); |
| DeleteConfig(config); |
| exit(0); |
| } else if (!strcmp(argv[i], "--")) { |
| if (i < argc - 1) { |
| ++i; |
| if (config->input_ == NULL) { |
| config->input_ = argv[i]; |
| } else { |
| ERROR_GOTO2("ERROR at '%s': Multiple input files specified.\n", |
| argv[i], ErrParse); |
| } |
| } |
| break; |
| } 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], "exif") || |
| !strcmp(argv[i], "xmp")) { |
| if (FEATURETYPE_IS_NIL) { |
| feature->type_ = (!strcmp(argv[i], "icc")) ? FEATURE_ICCP : |
| (!strcmp(argv[i], "exif")) ? FEATURE_EXIF : 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; |
| } |
| #ifdef WEBP_EXPERIMENTAL_FEATURES |
| } else if ((!strcmp(argv[i], "frame") || |
| !strcmp(argv[i], "frgm")) && |
| #else |
| } else if (!strcmp(argv[i], "frame") && |
| #endif |
| (config->action_type_ == ACTION_GET)) { |
| CHECK_NUM_ARGS_LESS(2, ErrParse); |
| feature->type_ = (!strcmp(argv[i], "frame")) ? FEATURE_ANMF : |
| FEATURE_FRGM; |
| 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_ANMF && |
| feature->type_ != FEATURE_FRGM) { |
| 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); |
| memset(config, 0, sizeof(*config)); |
| |
| // Validate command-line arguments. |
| if (!ValidateCommandLine(argc, argv, &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) { |
| ERROR_GOTO1("ERROR: Memory allocation error.\n", Err1); |
| } |
| |
| // Parse command-line. |
| if (!ParseCommandLine(argc, argv, config) || !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 GetFrameFragment(const WebPMux* mux, |
| const WebPMuxConfig* config, int is_frame) { |
| WebPMuxError err = WEBP_MUX_OK; |
| WebPMux* mux_single = NULL; |
| long num = 0; |
| int ok = 1; |
| int parse_error = 0; |
| const WebPChunkId id = is_frame ? WEBP_CHUNK_ANMF : WEBP_CHUNK_FRGM; |
| WebPMuxFrameInfo info; |
| WebPDataInit(&info.bitstream); |
| |
| num = ExUtilGetInt(config->feature_.args_[0].params_, 10, &parse_error); |
| if (num < 0) { |
| ERROR_GOTO1("ERROR: Frame/Fragment index must be non-negative.\n", ErrGet); |
| } |
| if (parse_error) goto ErrGet; |
| |
| err = WebPMuxGetFrame(mux, num, &info); |
| if (err == WEBP_MUX_OK && info.id != id) err = WEBP_MUX_NOT_FOUND; |
| if (err != WEBP_MUX_OK) { |
| ERROR_GOTO3("ERROR (%s): Could not get frame %ld.\n", |
| ErrorString(err), num, ErrGet); |
| } |
| |
| mux_single = WebPMuxNew(); |
| if (mux_single == NULL) { |
| err = WEBP_MUX_MEMORY_ERROR; |
| ERROR_GOTO2("ERROR (%s): Could not allocate a mux object.\n", |
| ErrorString(err), ErrGet); |
| } |
| err = WebPMuxSetImage(mux_single, &info.bitstream, 1); |
| if (err != WEBP_MUX_OK) { |
| ERROR_GOTO2("ERROR (%s): Could not create single image mux object.\n", |
| ErrorString(err), ErrGet); |
| } |
| |
| ok = WriteWebP(mux_single, config->output_); |
| |
| ErrGet: |
| WebPDataClear(&info.bitstream); |
| WebPMuxDelete(mux_single); |
| return ok; |
| } |
| |
| // Read and process config. |
| static int Process(const WebPMuxConfig* 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_) { |
| case FEATURE_ANMF: |
| case FEATURE_FRGM: |
| ok = GetFrameFragment(mux, config, |
| (feature->type_ == FEATURE_ANMF) ? 1 : 0); |
| break; |
| |
| case FEATURE_ICCP: |
| case FEATURE_EXIF: |
| case FEATURE_XMP: |
| err = WebPMuxGetChunk(mux, kFourccList[feature->type_], &chunk); |
| if (err != WEBP_MUX_OK) { |
| ERROR_GOTO3("ERROR (%s): Could not get the %s.\n", |
| ErrorString(err), kDescriptions[feature->type_], Err2); |
| } |
| ok = WriteData(config->output_, &chunk); |
| break; |
| |
| default: |
| ERROR_GOTO1("ERROR: Invalid feature for action 'get'.\n", Err2); |
| break; |
| } |
| break; |
| } |
| case ACTION_SET: { |
| switch (feature->type_) { |
| case FEATURE_ANMF: { |
| int i; |
| WebPMuxAnimParams params = { 0xFFFFFFFF, 0 }; |
| mux = WebPMuxNew(); |
| if (mux == NULL) { |
| 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_) { |
| case SUBTYPE_BGCOLOR: { |
| uint32_t bgcolor; |
| ok = ParseBgcolorArgs(feature->args_[i].params_, &bgcolor); |
| if (!ok) { |
| ERROR_GOTO1("ERROR: Could not parse the background color \n", |
| Err2); |
| } |
| params.bgcolor = bgcolor; |
| break; |
| } |
| case SUBTYPE_LOOP: { |
| int parse_error = 0; |
| const int loop_count = |
| ExUtilGetInt(feature->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 |
| // WebPMuxSetAnimationParams() method called later. |
| ERROR_GOTO1("ERROR: Loop count must be in the range 0 to " |
| "65535.\n", Err2); |
| } |
| if (parse_error) goto Err2; |
| params.loop_count = (int)loop_count; |
| break; |
| } |
| case SUBTYPE_ANMF: { |
| WebPMuxFrameInfo frame; |
| frame.id = WEBP_CHUNK_ANMF; |
| ok = ReadFileToWebPData(feature->args_[i].filename_, |
| &frame.bitstream); |
| if (!ok) goto Err2; |
| ok = ParseFrameArgs(feature->args_[i].params_, &frame); |
| if (!ok) { |
| WebPDataClear(&frame.bitstream); |
| ERROR_GOTO1("ERROR: Could not parse frame properties.\n", |
| Err2); |
| } |
| err = WebPMuxPushFrame(mux, &frame, 1); |
| WebPDataClear(&frame.bitstream); |
| if (err != WEBP_MUX_OK) { |
| ERROR_GOTO3("ERROR (%s): Could not add a frame at index %d." |
| "\n", ErrorString(err), i, Err2); |
| } |
| break; |
| } |
| default: { |
| ERROR_GOTO1("ERROR: Invalid subtype for 'frame'", Err2); |
| break; |
| } |
| } |
| } |
| err = WebPMuxSetAnimationParams(mux, ¶ms); |
| if (err != WEBP_MUX_OK) { |
| ERROR_GOTO2("ERROR (%s): Could not set animation parameters.\n", |
| ErrorString(err), Err2); |
| } |
| break; |
| } |
| |
| case FEATURE_FRGM: { |
| int i; |
| mux = WebPMuxNew(); |
| if (mux == NULL) { |
| 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) { |
| WebPMuxFrameInfo frgm; |
| frgm.id = WEBP_CHUNK_FRGM; |
| ok = ReadFileToWebPData(feature->args_[i].filename_, |
| &frgm.bitstream); |
| if (!ok) goto Err2; |
| ok = ParseFragmentArgs(feature->args_[i].params_, &frgm); |
| if (!ok) { |
| WebPDataClear(&frgm.bitstream); |
| ERROR_GOTO1("ERROR: Could not parse fragment properties.\n", |
| Err2); |
| } |
| err = WebPMuxPushFrame(mux, &frgm, 1); |
| WebPDataClear(&frgm.bitstream); |
| if (err != WEBP_MUX_OK) { |
| ERROR_GOTO3("ERROR (%s): Could not add a fragment at index %d.\n", |
| ErrorString(err), i, Err2); |
| } |
| } |
| break; |
| } |
| |
| case FEATURE_ICCP: |
| case FEATURE_EXIF: |
| case FEATURE_XMP: { |
| ok = CreateMux(config->input_, &mux); |
| if (!ok) goto Err2; |
| ok = ReadFileToWebPData(feature->args_[0].filename_, &chunk); |
| if (!ok) goto Err2; |
| err = WebPMuxSetChunk(mux, kFourccList[feature->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); |
| } |
| break; |
| } |
| default: { |
| ERROR_GOTO1("ERROR: Invalid feature for action 'set'.\n", Err2); |
| break; |
| } |
| } |
| ok = WriteWebP(mux, config->output_); |
| break; |
| } |
| 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 (err != WEBP_MUX_OK) { |
| ERROR_GOTO3("ERROR (%s): Could not strip the %s.\n", |
| ErrorString(err), kDescriptions[feature->type_], Err2); |
| } |
| } else { |
| ERROR_GOTO1("ERROR: Invalid feature for action 'strip'.\n", Err2); |
| break; |
| } |
| ok = WriteWebP(mux, config->output_); |
| break; |
| } |
| case ACTION_INFO: { |
| ok = CreateMux(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[]) { |
| WebPMuxConfig config; |
| int ok = InitializeConfig(argc - 1, argv + 1, &config); |
| if (ok) { |
| ok = Process(&config); |
| } else { |
| PrintHelp(); |
| } |
| DeleteConfig(&config); |
| return !ok; |
| } |
| |
| //------------------------------------------------------------------------------ |