| // 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 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 |
| |
| Change duration of frame intervals: |
| webpmux -duration 150 in.webp -o out.webp |
| webpmux -duration 33,2 in.webp -o out.webp |
| webpmux -duration 200,10,0 -duration 150,6,50 in.webp -o out.webp |
| |
| Misc: |
| webpmux -info in.webp |
| webpmux [ -h | -help ] |
| webpmux -version |
| webpmux argument_file_name |
| */ |
| |
| #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 "../examples/example_util.h" |
| #include "../imageio/imageio_util.h" |
| |
| //------------------------------------------------------------------------------ |
| // Config object to parse command-line arguments. |
| |
| typedef enum { |
| NIL_ACTION = 0, |
| ACTION_GET, |
| ACTION_SET, |
| ACTION_STRIP, |
| ACTION_INFO, |
| ACTION_HELP, |
| ACTION_DURATION |
| } 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_DURATION, |
| LAST_FEATURE |
| } FeatureType; |
| |
| static const char* const kFourccList[LAST_FEATURE] = { |
| NULL, "EXIF", "XMP ", "ICCP", "ANMF" |
| }; |
| |
| static const char* const kDescriptions[LAST_FEATURE] = { |
| NULL, "EXIF metadata", "XMP metadata", "ICC profile", |
| "Animation frame" |
| }; |
| |
| typedef struct { |
| CommandLineArguments cmd_args_; |
| |
| ActionType action_type_; |
| const char* input_; |
| const char* output_; |
| FeatureType type_; |
| FeatureArg* args_; |
| int arg_count_; |
| } Config; |
| |
| //------------------------------------------------------------------------------ |
| // Helper functions. |
| |
| static int CountOccurrences(const CommandLineArguments* const args, |
| const char* const arg) { |
| int i; |
| int num_occurences = 0; |
| |
| for (i = 0; i < args->argc_; ++i) { |
| if (!strcmp(args->argv_[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); |
| RETURN_IF_ERROR("Failed to retrieve features\n"); |
| |
| if (flag == 0) { |
| printf("No features present.\n"); |
| return err; |
| } |
| |
| // Print the features present. |
| printf("Features present:"); |
| if (flag & ANIMATION_FLAG) printf(" animation"); |
| 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) { |
| const WebPChunkId id = WEBP_CHUNK_ANMF; |
| const char* const type_str = "frame"; |
| int nFrames; |
| |
| 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 "); |
| printf("duration dispose blend "); |
| printf("image_size compression\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); |
| { |
| 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 %11s\n", (int)frame.bitstream.size, |
| (features.format == 1) ? "lossy" : |
| (features.format == 2) ? "lossless" : |
| "undefined"); |
| } |
| 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)) { |
| 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 -duration DURATION_OPTIONS [-duration ...]\n"); |
| printf(" INPUT -o OUTPUT\n"); |
| printf(" webpmux -strip STRIP_OPTIONS INPUT -o OUTPUT\n"); |
| 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(" webpmux argument_file_name\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"); |
| 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("DURATION_OPTIONS:\n"); |
| printf(" Set duration of selected frames:\n"); |
| printf(" duration set duration for each frames\n"); |
| printf(" duration,frame set duration of a particular frame\n"); |
| printf(" duration,start,end set duration of frames in the\n"); |
| printf(" interval [start,end])\n"); |
| printf(" where: 'duration' is the duration in milliseconds\n"); |
| printf(" 'start' is the start frame index\n"); |
| printf(" 'end' is the inclusive end frame index\n"); |
| printf(" The special 'end' value '0' means: last frame.\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"); |
| |
| 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"); |
| 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) { |
| 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 CreateMux(const char* const filename, WebPMux** mux) { |
| WebPData bitstream; |
| assert(mux != NULL); |
| if (!ExUtilReadFileToWebPData(filename, &bitstream)) return 0; |
| *mux = WebPMuxCreate(&bitstream, 1); |
| WebPDataClear(&bitstream); |
| 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") |
| : ImgIoUtilSetBinaryMode(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 WebPMux* DuplicateMuxHeader(const WebPMux* const mux) { |
| WebPMux* new_mux = WebPMuxNew(); |
| WebPMuxAnimParams p; |
| WebPMuxError err; |
| int i; |
| int ok = 1; |
| |
| if (new_mux == NULL) return NULL; |
| |
| err = WebPMuxGetAnimationParams(mux, &p); |
| if (err == WEBP_MUX_OK) { |
| err = WebPMuxSetAnimationParams(new_mux, &p); |
| if (err != WEBP_MUX_OK) { |
| ERROR_GOTO2("Error (%s) handling animation params.\n", |
| ErrorString(err), End); |
| } |
| } else { |
| /* it might not be an animation. Just keep moving. */ |
| } |
| |
| for (i = 1; i <= 3; ++i) { |
| WebPData metadata; |
| err = WebPMuxGetChunk(mux, kFourccList[i], &metadata); |
| if (err == WEBP_MUX_OK && metadata.size > 0) { |
| err = WebPMuxSetChunk(new_mux, kFourccList[i], &metadata, 1); |
| if (err != WEBP_MUX_OK) { |
| ERROR_GOTO1("Error transferring metadata in DuplicateMux().", End); |
| } |
| } |
| } |
| |
| End: |
| if (!ok) { |
| WebPMuxDelete(new_mux); |
| new_mux = NULL; |
| } |
| return new_mux; |
| } |
| |
| 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 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(Config* const config) { |
| if (config != NULL) { |
| free(config->args_); |
| ExUtilDeleteCommandLineArguments(&config->cmd_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(const CommandLineArguments* const cmd_args, |
| int* num_feature_args) { |
| int num_frame_args; |
| int num_loop_args; |
| int num_bgcolor_args; |
| int num_durations_args; |
| int ok = 1; |
| |
| assert(num_feature_args != NULL); |
| *num_feature_args = 0; |
| |
| // Simple checks. |
| if (CountOccurrences(cmd_args, "-get") > 1) { |
| ERROR_GOTO1("ERROR: Multiple '-get' arguments specified.\n", ErrValidate); |
| } |
| if (CountOccurrences(cmd_args, "-set") > 1) { |
| ERROR_GOTO1("ERROR: Multiple '-set' arguments specified.\n", ErrValidate); |
| } |
| if (CountOccurrences(cmd_args, "-strip") > 1) { |
| ERROR_GOTO1("ERROR: Multiple '-strip' arguments specified.\n", ErrValidate); |
| } |
| if (CountOccurrences(cmd_args, "-info") > 1) { |
| ERROR_GOTO1("ERROR: Multiple '-info' arguments specified.\n", ErrValidate); |
| } |
| if (CountOccurrences(cmd_args, "-o") > 1) { |
| ERROR_GOTO1("ERROR: Multiple output files specified.\n", ErrValidate); |
| } |
| |
| // Compound checks. |
| num_frame_args = CountOccurrences(cmd_args, "-frame"); |
| num_loop_args = CountOccurrences(cmd_args, "-loop"); |
| num_bgcolor_args = CountOccurrences(cmd_args, "-bgcolor"); |
| num_durations_args = CountOccurrences(cmd_args, "-duration"); |
| |
| 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_durations_args > 0 && num_frame_args != 0) { |
| ERROR_GOTO1("ERROR: Can not combine -duration and -frame commands.\n", |
| ErrValidate); |
| } |
| |
| assert(ok == 1); |
| if (num_durations_args > 0) { |
| *num_feature_args = num_durations_args; |
| } else if (num_frame_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) |
| *num_feature_args = num_frame_args + num_loop_args + num_bgcolor_args; |
| } |
| |
| ErrValidate: |
| return ok; |
| } |
| |
| #define ACTION_IS_NIL (config->action_type_ == NIL_ACTION) |
| |
| #define FEATURETYPE_IS_NIL (config->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(Config* config) { |
| int i = 0; |
| int feature_arg_index = 0; |
| int ok = 1; |
| int argc = config->cmd_args_.argc_; |
| const char* const* argv = config->cmd_args_.argv_; |
| |
| while (i < argc) { |
| FeatureArg* const arg = &config->args_[feature_arg_index]; |
| if (argv[i][0] == '-') { // One of the action types or output. |
| if (!strcmp(argv[i], "-set")) { |
| if (ACTION_IS_NIL) { |
| config->action_type_ = ACTION_SET; |
| } else { |
| ERROR_GOTO1("ERROR: Multiple actions specified.\n", ErrParse); |
| } |
| ++i; |
| } else if (!strcmp(argv[i], "-duration")) { |
| CHECK_NUM_ARGS_LESS(2, ErrParse); |
| if (ACTION_IS_NIL || config->action_type_ == ACTION_DURATION) { |
| config->action_type_ = ACTION_DURATION; |
| } else { |
| ERROR_GOTO1("ERROR: Multiple actions specified.\n", ErrParse); |
| } |
| if (FEATURETYPE_IS_NIL || config->type_ == FEATURE_DURATION) { |
| config->type_ = FEATURE_DURATION; |
| } else { |
| ERROR_GOTO1("ERROR: Multiple features specified.\n", ErrParse); |
| } |
| arg->params_ = argv[i + 1]; |
| ++feature_arg_index; |
| i += 2; |
| } 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; |
| config->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 || config->type_ == FEATURE_ANMF) { |
| config->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 || config->type_ == FEATURE_ANMF) { |
| config->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; |
| } 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; |
| config->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) { |
| config->type_ = (!strcmp(argv[i], "icc")) ? FEATURE_ICCP : |
| (!strcmp(argv[i], "exif")) ? FEATURE_EXIF : FEATURE_XMP; |
| } else { |
| ERROR_GOTO1("ERROR: Multiple features specified.\n", ErrParse); |
| } |
| 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") && |
| (config->action_type_ == ACTION_GET)) { |
| CHECK_NUM_ARGS_LESS(2, ErrParse); |
| config->type_ = FEATURE_ANMF; |
| 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(Config* const config) { |
| int ok = 1; |
| |
| // 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 (config->type_ != FEATURE_ANMF) { |
| 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[], |
| Config* const config) { |
| int num_feature_args = 0; |
| int ok; |
| |
| memset(config, 0, sizeof(*config)); |
| |
| ok = ExUtilInitCommandLineArguments(argc, argv, &config->cmd_args_); |
| if (!ok) return 0; |
| |
| // Validate command-line arguments. |
| if (!ValidateCommandLine(&config->cmd_args_, &num_feature_args)) { |
| ERROR_GOTO1("Exiting due to command-line parsing error.\n", Err1); |
| } |
| |
| config->arg_count_ = num_feature_args; |
| config->args_ = (FeatureArg*)calloc(num_feature_args, sizeof(*config->args_)); |
| if (config->args_ == NULL) { |
| ERROR_GOTO1("ERROR: Memory allocation error.\n", Err1); |
| } |
| |
| // Parse command-line. |
| if (!ParseCommandLine(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 GetFrame(const WebPMux* mux, const Config* config) { |
| WebPMuxError err = WEBP_MUX_OK; |
| WebPMux* mux_single = NULL; |
| int num = 0; |
| int ok = 1; |
| int parse_error = 0; |
| const WebPChunkId id = WEBP_CHUNK_ANMF; |
| WebPMuxFrameInfo info; |
| WebPDataInit(&info.bitstream); |
| |
| num = ExUtilGetInt(config->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 %d.\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 && !parse_error; |
| } |
| |
| // Read and process config. |
| static int Process(const Config* config) { |
| WebPMux* mux = NULL; |
| WebPData chunk; |
| WebPMuxError err = WEBP_MUX_OK; |
| int ok = 1; |
| |
| switch (config->action_type_) { |
| case ACTION_GET: { |
| ok = CreateMux(config->input_, &mux); |
| if (!ok) goto Err2; |
| switch (config->type_) { |
| case FEATURE_ANMF: |
| ok = GetFrame(mux, config); |
| break; |
| |
| case FEATURE_ICCP: |
| case FEATURE_EXIF: |
| case FEATURE_XMP: |
| err = WebPMuxGetChunk(mux, kFourccList[config->type_], &chunk); |
| if (err != WEBP_MUX_OK) { |
| ERROR_GOTO3("ERROR (%s): Could not get the %s.\n", |
| ErrorString(err), kDescriptions[config->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 (config->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 < config->arg_count_; ++i) { |
| switch (config->args_[i].subtype_) { |
| case SUBTYPE_BGCOLOR: { |
| uint32_t bgcolor; |
| ok = ParseBgcolorArgs(config->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(config->args_[i].params_, 10, &parse_error); |
| if (loop_count < 0 || loop_count > 65535) { |
| // Note: This is only a 'necessary' condition for loop_count |
| // to be valid. The 'sufficient' conditioned in checked in |
| // WebPMuxSetAnimationParams() method called later. |
| ERROR_GOTO1("ERROR: Loop count must be in the range 0 to " |
| "65535.\n", Err2); |
| } |
| ok = !parse_error; |
| if (!ok) goto Err2; |
| params.loop_count = loop_count; |
| break; |
| } |
| case SUBTYPE_ANMF: { |
| WebPMuxFrameInfo frame; |
| frame.id = WEBP_CHUNK_ANMF; |
| ok = ExUtilReadFileToWebPData(config->args_[i].filename_, |
| &frame.bitstream); |
| if (!ok) goto Err2; |
| ok = ParseFrameArgs(config->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_ICCP: |
| case FEATURE_EXIF: |
| case FEATURE_XMP: { |
| ok = CreateMux(config->input_, &mux); |
| if (!ok) goto Err2; |
| ok = ExUtilReadFileToWebPData(config->args_[0].filename_, &chunk); |
| if (!ok) goto Err2; |
| err = WebPMuxSetChunk(mux, kFourccList[config->type_], &chunk, 1); |
| free((void*)chunk.bytes); |
| if (err != WEBP_MUX_OK) { |
| ERROR_GOTO3("ERROR (%s): Could not set the %s.\n", |
| ErrorString(err), kDescriptions[config->type_], Err2); |
| } |
| break; |
| } |
| default: { |
| ERROR_GOTO1("ERROR: Invalid feature for action 'set'.\n", Err2); |
| break; |
| } |
| } |
| ok = WriteWebP(mux, config->output_); |
| break; |
| } |
| case ACTION_DURATION: { |
| int num_frames; |
| ok = CreateMux(config->input_, &mux); |
| if (!ok) goto Err2; |
| err = WebPMuxNumChunks(mux, WEBP_CHUNK_ANMF, &num_frames); |
| ok = (err == WEBP_MUX_OK); |
| if (!ok) { |
| ERROR_GOTO1("ERROR: can not parse the number of frames.\n", Err2); |
| } |
| if (num_frames == 0) { |
| fprintf(stderr, "Doesn't look like the source is animated. " |
| "Skipping duration setting.\n"); |
| ok = WriteWebP(mux, config->output_); |
| if (!ok) goto Err2; |
| } else { |
| int i; |
| int* durations = NULL; |
| WebPMux* new_mux = DuplicateMuxHeader(mux); |
| if (new_mux == NULL) goto Err2; |
| durations = (int*)malloc((size_t)num_frames * sizeof(*durations)); |
| if (durations == NULL) goto Err2; |
| for (i = 0; i < num_frames; ++i) durations[i] = -1; |
| |
| // Parse intervals to process. |
| for (i = 0; i < config->arg_count_; ++i) { |
| int k; |
| int args[3]; |
| int duration, start, end; |
| const int nb_args = ExUtilGetInts(config->args_[i].params_, |
| 10, 3, args); |
| ok = (nb_args >= 1); |
| if (!ok) goto Err3; |
| duration = args[0]; |
| if (duration < 0) { |
| ERROR_GOTO1("ERROR: duration must be strictly positive.\n", Err3); |
| } |
| |
| if (nb_args == 1) { // only duration is present -> use full interval |
| start = 1; |
| end = num_frames; |
| } else { |
| start = args[1]; |
| if (start <= 0) { |
| start = 1; |
| } else if (start > num_frames) { |
| start = num_frames; |
| } |
| end = (nb_args >= 3) ? args[2] : start; |
| if (end == 0 || end > num_frames) end = num_frames; |
| } |
| |
| for (k = start; k <= end; ++k) { |
| assert(k >= 1 && k <= num_frames); |
| durations[k - 1] = duration; |
| } |
| } |
| |
| // Apply non-negative durations to their destination frames. |
| for (i = 1; i <= num_frames; ++i) { |
| WebPMuxFrameInfo frame; |
| err = WebPMuxGetFrame(mux, i, &frame); |
| if (err != WEBP_MUX_OK || frame.id != WEBP_CHUNK_ANMF) { |
| ERROR_GOTO2("ERROR: can not retrieve frame #%d.\n", i, Err3); |
| } |
| if (durations[i - 1] >= 0) frame.duration = durations[i - 1]; |
| err = WebPMuxPushFrame(new_mux, &frame, 1); |
| if (err != WEBP_MUX_OK) { |
| ERROR_GOTO2("ERROR: error push frame data #%d\n", i, Err3); |
| } |
| WebPDataClear(&frame.bitstream); |
| } |
| WebPMuxDelete(mux); |
| ok = WriteWebP(new_mux, config->output_); |
| mux = new_mux; // transfer for the WebPMuxDelete() call |
| new_mux = NULL; |
| |
| Err3: |
| free(durations); |
| WebPMuxDelete(new_mux); |
| if (!ok) goto Err2; |
| } |
| break; |
| } |
| case ACTION_STRIP: { |
| ok = CreateMux(config->input_, &mux); |
| if (!ok) goto Err2; |
| if (config->type_ == FEATURE_ICCP || config->type_ == FEATURE_EXIF || |
| config->type_ == FEATURE_XMP) { |
| err = WebPMuxDeleteChunk(mux, kFourccList[config->type_]); |
| if (err != WEBP_MUX_OK) { |
| ERROR_GOTO3("ERROR (%s): Could not strip the %s.\n", |
| ErrorString(err), kDescriptions[config->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[]) { |
| Config config; |
| int ok = InitializeConfig(argc - 1, argv + 1, &config); |
| if (ok) { |
| ok = Process(&config); |
| } else { |
| PrintHelp(); |
| } |
| DeleteConfig(&config); |
| return !ok; |
| } |
| |
| //------------------------------------------------------------------------------ |