github.com/cellofellow/gopkg@v0.0.0-20140722061823-eec0544a62ad/image/webp/libwebp/examples/webpmux.c (about)

     1  // Copyright 2011 Google Inc. All Rights Reserved.
     2  //
     3  // Use of this source code is governed by a BSD-style license
     4  // that can be found in the COPYING file in the root of the source
     5  // tree. An additional intellectual property rights grant can be found
     6  // in the file PATENTS. All contributing project authors may
     7  // be found in the AUTHORS file in the root of the source tree.
     8  // -----------------------------------------------------------------------------
     9  //
    10  //  Simple command-line to create a WebP container file and to extract or strip
    11  //  relevant data from the container file.
    12  //
    13  // Authors: Vikas (vikaas.arora@gmail.com),
    14  //          Urvang (urvang@google.com)
    15  
    16  /*  Usage examples:
    17  
    18    Create container WebP file:
    19      webpmux -frame anim_1.webp +100+10+10   \
    20              -frame anim_2.webp +100+25+25+1 \
    21              -frame anim_3.webp +100+50+50+1 \
    22              -frame anim_4.webp +100         \
    23              -loop 10 -bgcolor 128,255,255,255 \
    24              -o out_animation_container.webp
    25  
    26      webpmux -set icc image_profile.icc in.webp -o out_icc_container.webp
    27      webpmux -set exif image_metadata.exif in.webp -o out_exif_container.webp
    28      webpmux -set xmp image_metadata.xmp in.webp -o out_xmp_container.webp
    29  
    30    Extract relevant data from WebP container file:
    31      webpmux -get frgm n in.webp -o out_fragment.webp
    32      webpmux -get frame n in.webp -o out_frame.webp
    33      webpmux -get icc in.webp -o image_profile.icc
    34      webpmux -get exif in.webp -o image_metadata.exif
    35      webpmux -get xmp in.webp -o image_metadata.xmp
    36  
    37    Strip data from WebP Container file:
    38      webpmux -strip icc in.webp -o out.webp
    39      webpmux -strip exif in.webp -o out.webp
    40      webpmux -strip xmp in.webp -o out.webp
    41  
    42    Misc:
    43      webpmux -info in.webp
    44      webpmux [ -h | -help ]
    45      webpmux -version
    46  */
    47  
    48  #ifdef HAVE_CONFIG_H
    49  #include "config.h"
    50  #endif
    51  
    52  #include <assert.h>
    53  #include <stdio.h>
    54  #include <stdlib.h>
    55  #include <string.h>
    56  #include "webp/decode.h"
    57  #include "webp/mux.h"
    58  #include "./example_util.h"
    59  
    60  //------------------------------------------------------------------------------
    61  // Config object to parse command-line arguments.
    62  
    63  typedef enum {
    64    NIL_ACTION = 0,
    65    ACTION_GET,
    66    ACTION_SET,
    67    ACTION_STRIP,
    68    ACTION_INFO,
    69    ACTION_HELP
    70  } ActionType;
    71  
    72  typedef enum {
    73    NIL_SUBTYPE = 0,
    74    SUBTYPE_ANMF,
    75    SUBTYPE_LOOP,
    76    SUBTYPE_BGCOLOR
    77  } FeatureSubType;
    78  
    79  typedef struct {
    80    FeatureSubType subtype_;
    81    const char* filename_;
    82    const char* params_;
    83  } FeatureArg;
    84  
    85  typedef enum {
    86    NIL_FEATURE = 0,
    87    FEATURE_EXIF,
    88    FEATURE_XMP,
    89    FEATURE_ICCP,
    90    FEATURE_ANMF,
    91    FEATURE_FRGM,
    92    LAST_FEATURE
    93  } FeatureType;
    94  
    95  static const char* const kFourccList[LAST_FEATURE] = {
    96    NULL, "EXIF", "XMP ", "ICCP", "ANMF", "FRGM"
    97  };
    98  
    99  static const char* const kDescriptions[LAST_FEATURE] = {
   100    NULL, "EXIF metadata", "XMP metadata", "ICC profile",
   101    "Animation frame", "Image fragment"
   102  };
   103  
   104  typedef struct {
   105    FeatureType type_;
   106    FeatureArg* args_;
   107    int arg_count_;
   108  } Feature;
   109  
   110  typedef struct {
   111    ActionType action_type_;
   112    const char* input_;
   113    const char* output_;
   114    Feature feature_;
   115  } WebPMuxConfig;
   116  
   117  //------------------------------------------------------------------------------
   118  // Helper functions.
   119  
   120  static int CountOccurrences(const char* arglist[], int list_length,
   121                              const char* arg) {
   122    int i;
   123    int num_occurences = 0;
   124  
   125    for (i = 0; i < list_length; ++i) {
   126      if (!strcmp(arglist[i], arg)) {
   127        ++num_occurences;
   128      }
   129    }
   130    return num_occurences;
   131  }
   132  
   133  static const char* const kErrorMessages[] = {
   134    "WEBP_MUX_NOT_FOUND", "WEBP_MUX_INVALID_ARGUMENT", "WEBP_MUX_BAD_DATA",
   135    "WEBP_MUX_MEMORY_ERROR", "WEBP_MUX_NOT_ENOUGH_DATA"
   136  };
   137  
   138  static const char* ErrorString(WebPMuxError err) {
   139    assert(err <= WEBP_MUX_NOT_FOUND && err >= WEBP_MUX_NOT_ENOUGH_DATA);
   140    return kErrorMessages[-err];
   141  }
   142  
   143  #define RETURN_IF_ERROR(ERR_MSG)                                     \
   144    if (err != WEBP_MUX_OK) {                                          \
   145      fprintf(stderr, ERR_MSG);                                        \
   146      return err;                                                      \
   147    }
   148  
   149  #define RETURN_IF_ERROR3(ERR_MSG, FORMAT_STR1, FORMAT_STR2)          \
   150    if (err != WEBP_MUX_OK) {                                          \
   151      fprintf(stderr, ERR_MSG, FORMAT_STR1, FORMAT_STR2);              \
   152      return err;                                                      \
   153    }
   154  
   155  #define ERROR_GOTO1(ERR_MSG, LABEL)                                  \
   156    do {                                                               \
   157      fprintf(stderr, ERR_MSG);                                        \
   158      ok = 0;                                                          \
   159      goto LABEL;                                                      \
   160    } while (0)
   161  
   162  #define ERROR_GOTO2(ERR_MSG, FORMAT_STR, LABEL)                      \
   163    do {                                                               \
   164      fprintf(stderr, ERR_MSG, FORMAT_STR);                            \
   165      ok = 0;                                                          \
   166      goto LABEL;                                                      \
   167    } while (0)
   168  
   169  #define ERROR_GOTO3(ERR_MSG, FORMAT_STR1, FORMAT_STR2, LABEL)        \
   170    do {                                                               \
   171      fprintf(stderr, ERR_MSG, FORMAT_STR1, FORMAT_STR2);              \
   172      ok = 0;                                                          \
   173      goto LABEL;                                                      \
   174    } while (0)
   175  
   176  static WebPMuxError DisplayInfo(const WebPMux* mux) {
   177    int width, height;
   178    uint32_t flag;
   179  
   180    WebPMuxError err = WebPMuxGetCanvasSize(mux, &width, &height);
   181    assert(err == WEBP_MUX_OK);  // As WebPMuxCreate() was successful earlier.
   182    printf("Canvas size: %d x %d\n", width, height);
   183  
   184    err = WebPMuxGetFeatures(mux, &flag);
   185  #ifndef WEBP_EXPERIMENTAL_FEATURES
   186    if (flag & FRAGMENTS_FLAG) err = WEBP_MUX_INVALID_ARGUMENT;
   187  #endif
   188    RETURN_IF_ERROR("Failed to retrieve features\n");
   189  
   190    if (flag == 0) {
   191      fprintf(stderr, "No features present.\n");
   192      return err;
   193    }
   194  
   195    // Print the features present.
   196    printf("Features present:");
   197    if (flag & ANIMATION_FLAG) printf(" animation");
   198    if (flag & FRAGMENTS_FLAG) printf(" image fragments");
   199    if (flag & ICCP_FLAG)      printf(" ICC profile");
   200    if (flag & EXIF_FLAG)      printf(" EXIF metadata");
   201    if (flag & XMP_FLAG)       printf(" XMP metadata");
   202    if (flag & ALPHA_FLAG)     printf(" transparency");
   203    printf("\n");
   204  
   205    if ((flag & ANIMATION_FLAG) || (flag & FRAGMENTS_FLAG)) {
   206      const int is_anim = !!(flag & ANIMATION_FLAG);
   207      const WebPChunkId id = is_anim ? WEBP_CHUNK_ANMF : WEBP_CHUNK_FRGM;
   208      const char* const type_str = is_anim ? "frame" : "fragment";
   209      int nFrames;
   210  
   211      if (is_anim) {
   212        WebPMuxAnimParams params;
   213        err = WebPMuxGetAnimationParams(mux, &params);
   214        assert(err == WEBP_MUX_OK);
   215        printf("Background color : 0x%.8X  Loop Count : %d\n",
   216               params.bgcolor, params.loop_count);
   217      }
   218  
   219      err = WebPMuxNumChunks(mux, id, &nFrames);
   220      assert(err == WEBP_MUX_OK);
   221  
   222      printf("Number of %ss: %d\n", type_str, nFrames);
   223      if (nFrames > 0) {
   224        int i;
   225        printf("No.: width height alpha x_offset y_offset ");
   226        if (is_anim) printf("duration   dispose blend ");
   227        printf("image_size\n");
   228        for (i = 1; i <= nFrames; i++) {
   229          WebPMuxFrameInfo frame;
   230          err = WebPMuxGetFrame(mux, i, &frame);
   231          if (err == WEBP_MUX_OK) {
   232            WebPBitstreamFeatures features;
   233            const VP8StatusCode status = WebPGetFeatures(
   234                frame.bitstream.bytes, frame.bitstream.size, &features);
   235            assert(status == VP8_STATUS_OK);  // Checked by WebPMuxCreate().
   236            (void)status;
   237            printf("%3d: %5d %5d %5s %8d %8d ", i, features.width,
   238                   features.height, features.has_alpha ? "yes" : "no",
   239                   frame.x_offset, frame.y_offset);
   240            if (is_anim) {
   241              const char* const dispose =
   242                  (frame.dispose_method == WEBP_MUX_DISPOSE_NONE) ? "none"
   243                                                                  : "background";
   244              const char* const blend =
   245                  (frame.blend_method == WEBP_MUX_BLEND) ? "yes" : "no";
   246              printf("%8d %10s %5s ", frame.duration, dispose, blend);
   247            }
   248            printf("%10d\n", (int)frame.bitstream.size);
   249          }
   250          WebPDataClear(&frame.bitstream);
   251          RETURN_IF_ERROR3("Failed to retrieve %s#%d\n", type_str, i);
   252        }
   253      }
   254    }
   255  
   256    if (flag & ICCP_FLAG) {
   257      WebPData icc_profile;
   258      err = WebPMuxGetChunk(mux, "ICCP", &icc_profile);
   259      assert(err == WEBP_MUX_OK);
   260      printf("Size of the ICC profile data: %d\n", (int)icc_profile.size);
   261    }
   262  
   263    if (flag & EXIF_FLAG) {
   264      WebPData exif;
   265      err = WebPMuxGetChunk(mux, "EXIF", &exif);
   266      assert(err == WEBP_MUX_OK);
   267      printf("Size of the EXIF metadata: %d\n", (int)exif.size);
   268    }
   269  
   270    if (flag & XMP_FLAG) {
   271      WebPData xmp;
   272      err = WebPMuxGetChunk(mux, "XMP ", &xmp);
   273      assert(err == WEBP_MUX_OK);
   274      printf("Size of the XMP metadata: %d\n", (int)xmp.size);
   275    }
   276  
   277    if ((flag & ALPHA_FLAG) && !(flag & (ANIMATION_FLAG | FRAGMENTS_FLAG))) {
   278      WebPMuxFrameInfo image;
   279      err = WebPMuxGetFrame(mux, 1, &image);
   280      if (err == WEBP_MUX_OK) {
   281        printf("Size of the image (with alpha): %d\n", (int)image.bitstream.size);
   282      }
   283      WebPDataClear(&image.bitstream);
   284      RETURN_IF_ERROR("Failed to retrieve the image\n");
   285    }
   286  
   287    return WEBP_MUX_OK;
   288  }
   289  
   290  static void PrintHelp(void) {
   291    printf("Usage: webpmux -get GET_OPTIONS INPUT -o OUTPUT\n");
   292    printf("       webpmux -set SET_OPTIONS INPUT -o OUTPUT\n");
   293    printf("       webpmux -strip STRIP_OPTIONS INPUT -o OUTPUT\n");
   294  #ifdef WEBP_EXPERIMENTAL_FEATURES
   295    printf("       webpmux -frgm FRAGMENT_OPTIONS [-frgm...] -o OUTPUT\n");
   296  #endif
   297    printf("       webpmux -frame FRAME_OPTIONS [-frame...] [-loop LOOP_COUNT]"
   298           "\n");
   299    printf("               [-bgcolor BACKGROUND_COLOR] -o OUTPUT\n");
   300    printf("       webpmux -info INPUT\n");
   301    printf("       webpmux [-h|-help]\n");
   302    printf("       webpmux -version\n");
   303  
   304    printf("\n");
   305    printf("GET_OPTIONS:\n");
   306    printf(" Extract relevant data.\n");
   307    printf("   icc       Get ICC profile.\n");
   308    printf("   exif      Get EXIF metadata.\n");
   309    printf("   xmp       Get XMP metadata.\n");
   310  #ifdef WEBP_EXPERIMENTAL_FEATURES
   311    printf("   frgm n    Get nth fragment.\n");
   312  #endif
   313    printf("   frame n   Get nth frame.\n");
   314  
   315    printf("\n");
   316    printf("SET_OPTIONS:\n");
   317    printf(" Set color profile/metadata.\n");
   318    printf("   icc  file.icc     Set ICC profile.\n");
   319    printf("   exif file.exif    Set EXIF metadata.\n");
   320    printf("   xmp  file.xmp     Set XMP metadata.\n");
   321    printf("   where:    'file.icc' contains the ICC profile to be set,\n");
   322    printf("             'file.exif' contains the EXIF metadata to be set\n");
   323    printf("             'file.xmp' contains the XMP metadata to be set\n");
   324  
   325    printf("\n");
   326    printf("STRIP_OPTIONS:\n");
   327    printf(" Strip color profile/metadata.\n");
   328    printf("   icc       Strip ICC profile.\n");
   329    printf("   exif      Strip EXIF metadata.\n");
   330    printf("   xmp       Strip XMP metadata.\n");
   331  
   332  #ifdef WEBP_EXPERIMENTAL_FEATURES
   333    printf("\n");
   334    printf("FRAGMENT_OPTIONS(i):\n");
   335    printf(" Create fragmented image.\n");
   336    printf("   file_i +xi+yi\n");
   337    printf("   where:    'file_i' is the i'th fragment (WebP format),\n");
   338    printf("             'xi','yi' specify the image offset for this fragment."
   339           "\n");
   340  #endif
   341  
   342    printf("\n");
   343    printf("FRAME_OPTIONS(i):\n");
   344    printf(" Create animation.\n");
   345    printf("   file_i +di+[xi+yi[+mi[bi]]]\n");
   346    printf("   where:    'file_i' is the i'th animation frame (WebP format),\n");
   347    printf("             'di' is the pause duration before next frame.\n");
   348    printf("             'xi','yi' specify the image offset for this frame.\n");
   349    printf("             'mi' is the dispose method for this frame (0 or 1).\n");
   350    printf("             'bi' is the blending method for this frame (+b or -b)."
   351           "\n");
   352  
   353    printf("\n");
   354    printf("LOOP_COUNT:\n");
   355    printf(" Number of times to repeat the animation.\n");
   356    printf(" Valid range is 0 to 65535 [Default: 0 (infinite)].\n");
   357  
   358    printf("\n");
   359    printf("BACKGROUND_COLOR:\n");
   360    printf(" Background color of the canvas.\n");
   361    printf("  A,R,G,B\n");
   362    printf("  where:    'A', 'R', 'G' and 'B' are integers in the range 0 to 255 "
   363           "specifying\n");
   364    printf("            the Alpha, Red, Green and Blue component values "
   365           "respectively\n");
   366    printf("            [Default: 255,255,255,255].\n");
   367  
   368    printf("\nINPUT & OUTPUT are in WebP format.\n");
   369  
   370    printf("\nNote: The nature of EXIF, XMP and ICC data is not checked");
   371    printf(" and is assumed to be\nvalid.\n");
   372  }
   373  
   374  static int ReadFileToWebPData(const char* const filename,
   375                                WebPData* const webp_data) {
   376    const uint8_t* data;
   377    size_t size;
   378    if (!ExUtilReadFile(filename, &data, &size)) return 0;
   379    webp_data->bytes = data;
   380    webp_data->size = size;
   381    return 1;
   382  }
   383  
   384  static int CreateMux(const char* const filename, WebPMux** mux) {
   385    WebPData bitstream;
   386    assert(mux != NULL);
   387    if (!ReadFileToWebPData(filename, &bitstream)) return 0;
   388    *mux = WebPMuxCreate(&bitstream, 1);
   389    free((void*)bitstream.bytes);
   390    if (*mux != NULL) return 1;
   391    fprintf(stderr, "Failed to create mux object from file %s.\n", filename);
   392    return 0;
   393  }
   394  
   395  static int WriteData(const char* filename, const WebPData* const webpdata) {
   396    int ok = 0;
   397    FILE* fout = strcmp(filename, "-") ? fopen(filename, "wb") : stdout;
   398    if (!fout) {
   399      fprintf(stderr, "Error opening output WebP file %s!\n", filename);
   400      return 0;
   401    }
   402    if (fwrite(webpdata->bytes, webpdata->size, 1, fout) != 1) {
   403      fprintf(stderr, "Error writing file %s!\n", filename);
   404    } else {
   405      fprintf(stderr, "Saved file %s (%d bytes)\n",
   406              filename, (int)webpdata->size);
   407      ok = 1;
   408    }
   409    if (fout != stdout) fclose(fout);
   410    return ok;
   411  }
   412  
   413  static int WriteWebP(WebPMux* const mux, const char* filename) {
   414    int ok;
   415    WebPData webp_data;
   416    const WebPMuxError err = WebPMuxAssemble(mux, &webp_data);
   417    if (err != WEBP_MUX_OK) {
   418      fprintf(stderr, "Error (%s) assembling the WebP file.\n", ErrorString(err));
   419      return 0;
   420    }
   421    ok = WriteData(filename, &webp_data);
   422    WebPDataClear(&webp_data);
   423    return ok;
   424  }
   425  
   426  static int ParseFrameArgs(const char* args, WebPMuxFrameInfo* const info) {
   427    int dispose_method, dummy;
   428    char plus_minus, blend_method;
   429    const int num_args = sscanf(args, "+%d+%d+%d+%d%c%c+%d", &info->duration,
   430                                &info->x_offset, &info->y_offset, &dispose_method,
   431                                &plus_minus, &blend_method, &dummy);
   432    switch (num_args) {
   433      case 1:
   434        info->x_offset = info->y_offset = 0;  // fall through
   435      case 3:
   436        dispose_method = 0;  // fall through
   437      case 4:
   438        plus_minus = '+';
   439        blend_method = 'b';  // fall through
   440      case 6:
   441        break;
   442      case 2:
   443      case 5:
   444      default:
   445        return 0;
   446    }
   447    // Note: The sanity of the following conversion is checked by
   448    // WebPMuxPushFrame().
   449    info->dispose_method = (WebPMuxAnimDispose)dispose_method;
   450  
   451    if (blend_method != 'b') return 0;
   452    if (plus_minus != '-' && plus_minus != '+') return 0;
   453    info->blend_method =
   454        (plus_minus == '+') ? WEBP_MUX_BLEND : WEBP_MUX_NO_BLEND;
   455    return 1;
   456  }
   457  
   458  static int ParseFragmentArgs(const char* args, WebPMuxFrameInfo* const info) {
   459    return (sscanf(args, "+%d+%d", &info->x_offset, &info->y_offset) == 2);
   460  }
   461  
   462  static int ParseBgcolorArgs(const char* args, uint32_t* const bgcolor) {
   463    uint32_t a, r, g, b;
   464    if (sscanf(args, "%u,%u,%u,%u", &a, &r, &g, &b) != 4) return 0;
   465    if (a >= 256 || r >= 256 || g >= 256 || b >= 256) return 0;
   466    *bgcolor = (a << 24) | (r << 16) | (g << 8) | (b << 0);
   467    return 1;
   468  }
   469  
   470  //------------------------------------------------------------------------------
   471  // Clean-up.
   472  
   473  static void DeleteConfig(WebPMuxConfig* config) {
   474    if (config != NULL) {
   475      free(config->feature_.args_);
   476      free(config);
   477    }
   478  }
   479  
   480  //------------------------------------------------------------------------------
   481  // Parsing.
   482  
   483  // Basic syntactic checks on the command-line arguments.
   484  // Returns 1 on valid, 0 otherwise.
   485  // Also fills up num_feature_args to be number of feature arguments given.
   486  // (e.g. if there are 4 '-frame's and 1 '-loop', then num_feature_args = 5).
   487  static int ValidateCommandLine(int argc, const char* argv[],
   488                                 int* num_feature_args) {
   489    int num_frame_args;
   490    int num_frgm_args;
   491    int num_loop_args;
   492    int num_bgcolor_args;
   493    int ok = 1;
   494  
   495    assert(num_feature_args != NULL);
   496    *num_feature_args = 0;
   497  
   498    // Simple checks.
   499    if (CountOccurrences(argv, argc, "-get") > 1) {
   500      ERROR_GOTO1("ERROR: Multiple '-get' arguments specified.\n", ErrValidate);
   501    }
   502    if (CountOccurrences(argv, argc, "-set") > 1) {
   503      ERROR_GOTO1("ERROR: Multiple '-set' arguments specified.\n", ErrValidate);
   504    }
   505    if (CountOccurrences(argv, argc, "-strip") > 1) {
   506      ERROR_GOTO1("ERROR: Multiple '-strip' arguments specified.\n", ErrValidate);
   507    }
   508    if (CountOccurrences(argv, argc, "-info") > 1) {
   509      ERROR_GOTO1("ERROR: Multiple '-info' arguments specified.\n", ErrValidate);
   510    }
   511    if (CountOccurrences(argv, argc, "-o") > 1) {
   512      ERROR_GOTO1("ERROR: Multiple output files specified.\n", ErrValidate);
   513    }
   514  
   515    // Compound checks.
   516    num_frame_args = CountOccurrences(argv, argc, "-frame");
   517    num_frgm_args = CountOccurrences(argv, argc, "-frgm");
   518    num_loop_args = CountOccurrences(argv, argc, "-loop");
   519    num_bgcolor_args = CountOccurrences(argv, argc, "-bgcolor");
   520  
   521    if (num_loop_args > 1) {
   522      ERROR_GOTO1("ERROR: Multiple loop counts specified.\n", ErrValidate);
   523    }
   524    if (num_bgcolor_args > 1) {
   525      ERROR_GOTO1("ERROR: Multiple background colors specified.\n", ErrValidate);
   526    }
   527  
   528    if ((num_frame_args == 0) && (num_loop_args + num_bgcolor_args > 0)) {
   529      ERROR_GOTO1("ERROR: Loop count and background color are relevant only in "
   530                  "case of animation.\n", ErrValidate);
   531    }
   532    if (num_frame_args > 0 && num_frgm_args > 0) {
   533      ERROR_GOTO1("ERROR: Only one of frames & fragments can be specified at a "
   534                  "time.\n", ErrValidate);
   535    }
   536  
   537    assert(ok == 1);
   538    if (num_frame_args == 0 && num_frgm_args == 0) {
   539      // Single argument ('set' action for ICCP/EXIF/XMP, OR a 'get' action).
   540      *num_feature_args = 1;
   541    } else {
   542      // Multiple arguments ('set' action for animation or fragmented image).
   543      if (num_frame_args > 0) {
   544        *num_feature_args = num_frame_args + num_loop_args + num_bgcolor_args;
   545      } else {
   546        *num_feature_args = num_frgm_args;
   547      }
   548    }
   549  
   550   ErrValidate:
   551    return ok;
   552  }
   553  
   554  #define ACTION_IS_NIL (config->action_type_ == NIL_ACTION)
   555  
   556  #define FEATURETYPE_IS_NIL (feature->type_ == NIL_FEATURE)
   557  
   558  #define CHECK_NUM_ARGS_LESS(NUM, LABEL)                                  \
   559    if (argc < i + (NUM)) {                                                \
   560      fprintf(stderr, "ERROR: Too few arguments for '%s'.\n", argv[i]);    \
   561      goto LABEL;                                                          \
   562    }
   563  
   564  #define CHECK_NUM_ARGS_NOT_EQUAL(NUM, LABEL)                             \
   565    if (argc != i + (NUM)) {                                               \
   566      fprintf(stderr, "ERROR: Too many arguments for '%s'.\n", argv[i]);   \
   567      goto LABEL;                                                          \
   568    }
   569  
   570  // Parses command-line arguments to fill up config object. Also performs some
   571  // semantic checks.
   572  static int ParseCommandLine(int argc, const char* argv[],
   573                              WebPMuxConfig* config) {
   574    int i = 0;
   575    int feature_arg_index = 0;
   576    int ok = 1;
   577  
   578    while (i < argc) {
   579      Feature* const feature = &config->feature_;
   580      FeatureArg* const arg = &feature->args_[feature_arg_index];
   581      if (argv[i][0] == '-') {  // One of the action types or output.
   582        if (!strcmp(argv[i], "-set")) {
   583          if (ACTION_IS_NIL) {
   584            config->action_type_ = ACTION_SET;
   585          } else {
   586            ERROR_GOTO1("ERROR: Multiple actions specified.\n", ErrParse);
   587          }
   588          ++i;
   589        } else if (!strcmp(argv[i], "-get")) {
   590          if (ACTION_IS_NIL) {
   591            config->action_type_ = ACTION_GET;
   592          } else {
   593            ERROR_GOTO1("ERROR: Multiple actions specified.\n", ErrParse);
   594          }
   595          ++i;
   596        } else if (!strcmp(argv[i], "-strip")) {
   597          if (ACTION_IS_NIL) {
   598            config->action_type_ = ACTION_STRIP;
   599            feature->arg_count_ = 0;
   600          } else {
   601            ERROR_GOTO1("ERROR: Multiple actions specified.\n", ErrParse);
   602          }
   603          ++i;
   604        } else if (!strcmp(argv[i], "-frame")) {
   605          CHECK_NUM_ARGS_LESS(3, ErrParse);
   606          if (ACTION_IS_NIL || config->action_type_ == ACTION_SET) {
   607            config->action_type_ = ACTION_SET;
   608          } else {
   609            ERROR_GOTO1("ERROR: Multiple actions specified.\n", ErrParse);
   610          }
   611          if (FEATURETYPE_IS_NIL || feature->type_ == FEATURE_ANMF) {
   612            feature->type_ = FEATURE_ANMF;
   613          } else {
   614            ERROR_GOTO1("ERROR: Multiple features specified.\n", ErrParse);
   615          }
   616          arg->subtype_ = SUBTYPE_ANMF;
   617          arg->filename_ = argv[i + 1];
   618          arg->params_ = argv[i + 2];
   619          ++feature_arg_index;
   620          i += 3;
   621        } else if (!strcmp(argv[i], "-loop") || !strcmp(argv[i], "-bgcolor")) {
   622          CHECK_NUM_ARGS_LESS(2, ErrParse);
   623          if (ACTION_IS_NIL || config->action_type_ == ACTION_SET) {
   624            config->action_type_ = ACTION_SET;
   625          } else {
   626            ERROR_GOTO1("ERROR: Multiple actions specified.\n", ErrParse);
   627          }
   628          if (FEATURETYPE_IS_NIL || feature->type_ == FEATURE_ANMF) {
   629            feature->type_ = FEATURE_ANMF;
   630          } else {
   631            ERROR_GOTO1("ERROR: Multiple features specified.\n", ErrParse);
   632          }
   633          arg->subtype_ =
   634              !strcmp(argv[i], "-loop") ? SUBTYPE_LOOP : SUBTYPE_BGCOLOR;
   635          arg->params_ = argv[i + 1];
   636          ++feature_arg_index;
   637          i += 2;
   638  #ifdef WEBP_EXPERIMENTAL_FEATURES
   639        } else if (!strcmp(argv[i], "-frgm")) {
   640          CHECK_NUM_ARGS_LESS(3, ErrParse);
   641          if (ACTION_IS_NIL || config->action_type_ == ACTION_SET) {
   642            config->action_type_ = ACTION_SET;
   643          } else {
   644            ERROR_GOTO1("ERROR: Multiple actions specified.\n", ErrParse);
   645          }
   646          if (FEATURETYPE_IS_NIL || feature->type_ == FEATURE_FRGM) {
   647            feature->type_ = FEATURE_FRGM;
   648          } else {
   649            ERROR_GOTO1("ERROR: Multiple features specified.\n", ErrParse);
   650          }
   651          arg->filename_ = argv[i + 1];
   652          arg->params_ = argv[i + 2];
   653          ++feature_arg_index;
   654          i += 3;
   655  #endif
   656        } else if (!strcmp(argv[i], "-o")) {
   657          CHECK_NUM_ARGS_LESS(2, ErrParse);
   658          config->output_ = argv[i + 1];
   659          i += 2;
   660        } else if (!strcmp(argv[i], "-info")) {
   661          CHECK_NUM_ARGS_NOT_EQUAL(2, ErrParse);
   662          if (config->action_type_ != NIL_ACTION) {
   663            ERROR_GOTO1("ERROR: Multiple actions specified.\n", ErrParse);
   664          } else {
   665            config->action_type_ = ACTION_INFO;
   666            feature->arg_count_ = 0;
   667            config->input_ = argv[i + 1];
   668          }
   669          i += 2;
   670        } else if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "-help")) {
   671          PrintHelp();
   672          DeleteConfig(config);
   673          exit(0);
   674        } else if (!strcmp(argv[i], "-version")) {
   675          const int version = WebPGetMuxVersion();
   676          printf("%d.%d.%d\n",
   677                 (version >> 16) & 0xff, (version >> 8) & 0xff, version & 0xff);
   678          DeleteConfig(config);
   679          exit(0);
   680        } else if (!strcmp(argv[i], "--")) {
   681          if (i < argc - 1) {
   682            ++i;
   683            if (config->input_ == NULL) {
   684              config->input_ = argv[i];
   685            } else {
   686              ERROR_GOTO2("ERROR at '%s': Multiple input files specified.\n",
   687                          argv[i], ErrParse);
   688            }
   689          }
   690          break;
   691        } else {
   692          ERROR_GOTO2("ERROR: Unknown option: '%s'.\n", argv[i], ErrParse);
   693        }
   694      } else {  // One of the feature types or input.
   695        if (ACTION_IS_NIL) {
   696          ERROR_GOTO1("ERROR: Action must be specified before other arguments.\n",
   697                      ErrParse);
   698        }
   699        if (!strcmp(argv[i], "icc") || !strcmp(argv[i], "exif") ||
   700            !strcmp(argv[i], "xmp")) {
   701          if (FEATURETYPE_IS_NIL) {
   702            feature->type_ = (!strcmp(argv[i], "icc")) ? FEATURE_ICCP :
   703                (!strcmp(argv[i], "exif")) ? FEATURE_EXIF : FEATURE_XMP;
   704          } else {
   705            ERROR_GOTO1("ERROR: Multiple features specified.\n", ErrParse);
   706          }
   707          if (config->action_type_ == ACTION_SET) {
   708            CHECK_NUM_ARGS_LESS(2, ErrParse);
   709            arg->filename_ = argv[i + 1];
   710            ++feature_arg_index;
   711            i += 2;
   712          } else {
   713            ++i;
   714          }
   715  #ifdef WEBP_EXPERIMENTAL_FEATURES
   716        } else if ((!strcmp(argv[i], "frame") ||
   717                    !strcmp(argv[i], "frgm")) &&
   718  #else
   719        } else if (!strcmp(argv[i], "frame") &&
   720  #endif
   721                    (config->action_type_ == ACTION_GET)) {
   722          CHECK_NUM_ARGS_LESS(2, ErrParse);
   723          feature->type_ = (!strcmp(argv[i], "frame")) ? FEATURE_ANMF :
   724              FEATURE_FRGM;
   725          arg->params_ = argv[i + 1];
   726          ++feature_arg_index;
   727          i += 2;
   728        } else {  // Assume input file.
   729          if (config->input_ == NULL) {
   730            config->input_ = argv[i];
   731          } else {
   732            ERROR_GOTO2("ERROR at '%s': Multiple input files specified.\n",
   733                        argv[i], ErrParse);
   734          }
   735          ++i;
   736        }
   737      }
   738    }
   739   ErrParse:
   740    return ok;
   741  }
   742  
   743  // Additional checks after config is filled.
   744  static int ValidateConfig(WebPMuxConfig* config) {
   745    int ok = 1;
   746    Feature* const feature = &config->feature_;
   747  
   748    // Action.
   749    if (ACTION_IS_NIL) {
   750      ERROR_GOTO1("ERROR: No action specified.\n", ErrValidate2);
   751    }
   752  
   753    // Feature type.
   754    if (FEATURETYPE_IS_NIL && config->action_type_ != ACTION_INFO) {
   755      ERROR_GOTO1("ERROR: No feature specified.\n", ErrValidate2);
   756    }
   757  
   758    // Input file.
   759    if (config->input_ == NULL) {
   760      if (config->action_type_ != ACTION_SET) {
   761        ERROR_GOTO1("ERROR: No input file specified.\n", ErrValidate2);
   762      } else if (feature->type_ != FEATURE_ANMF &&
   763                 feature->type_ != FEATURE_FRGM) {
   764        ERROR_GOTO1("ERROR: No input file specified.\n", ErrValidate2);
   765      }
   766    }
   767  
   768    // Output file.
   769    if (config->output_ == NULL && config->action_type_ != ACTION_INFO) {
   770      ERROR_GOTO1("ERROR: No output file specified.\n", ErrValidate2);
   771    }
   772  
   773   ErrValidate2:
   774    return ok;
   775  }
   776  
   777  // Create config object from command-line arguments.
   778  static int InitializeConfig(int argc, const char* argv[],
   779                              WebPMuxConfig** config) {
   780    int num_feature_args = 0;
   781    int ok = 1;
   782  
   783    assert(config != NULL);
   784    *config = NULL;
   785  
   786    // Validate command-line arguments.
   787    if (!ValidateCommandLine(argc, argv, &num_feature_args)) {
   788      ERROR_GOTO1("Exiting due to command-line parsing error.\n", Err1);
   789    }
   790  
   791    // Allocate memory.
   792    *config = (WebPMuxConfig*)calloc(1, sizeof(**config));
   793    if (*config == NULL) {
   794      ERROR_GOTO1("ERROR: Memory allocation error.\n", Err1);
   795    }
   796    (*config)->feature_.arg_count_ = num_feature_args;
   797    (*config)->feature_.args_ =
   798        (FeatureArg*)calloc(num_feature_args, sizeof(FeatureArg));
   799    if ((*config)->feature_.args_ == NULL) {
   800      ERROR_GOTO1("ERROR: Memory allocation error.\n", Err1);
   801    }
   802  
   803    // Parse command-line.
   804    if (!ParseCommandLine(argc, argv, *config) ||
   805        !ValidateConfig(*config)) {
   806      ERROR_GOTO1("Exiting due to command-line parsing error.\n", Err1);
   807    }
   808  
   809   Err1:
   810    return ok;
   811  }
   812  
   813  #undef ACTION_IS_NIL
   814  #undef FEATURETYPE_IS_NIL
   815  #undef CHECK_NUM_ARGS_LESS
   816  #undef CHECK_NUM_ARGS_MORE
   817  
   818  //------------------------------------------------------------------------------
   819  // Processing.
   820  
   821  static int GetFrameFragment(const WebPMux* mux,
   822                              const WebPMuxConfig* config, int is_frame) {
   823    WebPMuxError err = WEBP_MUX_OK;
   824    WebPMux* mux_single = NULL;
   825    long num = 0;
   826    int ok = 1;
   827    const WebPChunkId id = is_frame ? WEBP_CHUNK_ANMF : WEBP_CHUNK_FRGM;
   828    WebPMuxFrameInfo info;
   829    WebPDataInit(&info.bitstream);
   830  
   831    num = strtol(config->feature_.args_[0].params_, NULL, 10);
   832    if (num < 0) {
   833      ERROR_GOTO1("ERROR: Frame/Fragment index must be non-negative.\n", ErrGet);
   834    }
   835  
   836    err = WebPMuxGetFrame(mux, num, &info);
   837    if (err == WEBP_MUX_OK && info.id != id) err = WEBP_MUX_NOT_FOUND;
   838    if (err != WEBP_MUX_OK) {
   839      ERROR_GOTO3("ERROR (%s): Could not get frame %ld.\n",
   840                  ErrorString(err), num, ErrGet);
   841    }
   842  
   843    mux_single = WebPMuxNew();
   844    if (mux_single == NULL) {
   845      err = WEBP_MUX_MEMORY_ERROR;
   846      ERROR_GOTO2("ERROR (%s): Could not allocate a mux object.\n",
   847                  ErrorString(err), ErrGet);
   848    }
   849    err = WebPMuxSetImage(mux_single, &info.bitstream, 1);
   850    if (err != WEBP_MUX_OK) {
   851      ERROR_GOTO2("ERROR (%s): Could not create single image mux object.\n",
   852                  ErrorString(err), ErrGet);
   853    }
   854  
   855    ok = WriteWebP(mux_single, config->output_);
   856  
   857   ErrGet:
   858    WebPDataClear(&info.bitstream);
   859    WebPMuxDelete(mux_single);
   860    return ok;
   861  }
   862  
   863  // Read and process config.
   864  static int Process(const WebPMuxConfig* config) {
   865    WebPMux* mux = NULL;
   866    WebPData chunk;
   867    WebPMuxError err = WEBP_MUX_OK;
   868    int ok = 1;
   869    const Feature* const feature = &config->feature_;
   870  
   871    switch (config->action_type_) {
   872      case ACTION_GET: {
   873        ok = CreateMux(config->input_, &mux);
   874        if (!ok) goto Err2;
   875        switch (feature->type_) {
   876          case FEATURE_ANMF:
   877          case FEATURE_FRGM:
   878            ok = GetFrameFragment(mux, config,
   879                                  (feature->type_ == FEATURE_ANMF) ? 1 : 0);
   880            break;
   881  
   882          case FEATURE_ICCP:
   883          case FEATURE_EXIF:
   884          case FEATURE_XMP:
   885            err = WebPMuxGetChunk(mux, kFourccList[feature->type_], &chunk);
   886            if (err != WEBP_MUX_OK) {
   887              ERROR_GOTO3("ERROR (%s): Could not get the %s.\n",
   888                          ErrorString(err), kDescriptions[feature->type_], Err2);
   889            }
   890            ok = WriteData(config->output_, &chunk);
   891            break;
   892  
   893          default:
   894            ERROR_GOTO1("ERROR: Invalid feature for action 'get'.\n", Err2);
   895            break;
   896        }
   897        break;
   898      }
   899      case ACTION_SET: {
   900        switch (feature->type_) {
   901          case FEATURE_ANMF: {
   902            int i;
   903            WebPMuxAnimParams params = { 0xFFFFFFFF, 0 };
   904            mux = WebPMuxNew();
   905            if (mux == NULL) {
   906              ERROR_GOTO2("ERROR (%s): Could not allocate a mux object.\n",
   907                          ErrorString(WEBP_MUX_MEMORY_ERROR), Err2);
   908            }
   909            for (i = 0; i < feature->arg_count_; ++i) {
   910              switch (feature->args_[i].subtype_) {
   911                case SUBTYPE_BGCOLOR: {
   912                  uint32_t bgcolor;
   913                  ok = ParseBgcolorArgs(feature->args_[i].params_, &bgcolor);
   914                  if (!ok) {
   915                    ERROR_GOTO1("ERROR: Could not parse the background color \n",
   916                                Err2);
   917                  }
   918                  params.bgcolor = bgcolor;
   919                  break;
   920                }
   921                case SUBTYPE_LOOP: {
   922                  const long loop_count =
   923                      strtol(feature->args_[i].params_, NULL, 10);
   924                  if (loop_count != (int)loop_count) {
   925                    // Note: This is only a 'necessary' condition for loop_count
   926                    // to be valid. The 'sufficient' conditioned in checked in
   927                    // WebPMuxSetAnimationParams() method called later.
   928                    ERROR_GOTO1("ERROR: Loop count must be in the range 0 to "
   929                                "65535.\n", Err2);
   930                  }
   931                  params.loop_count = (int)loop_count;
   932                  break;
   933                }
   934                case SUBTYPE_ANMF: {
   935                  WebPMuxFrameInfo frame;
   936                  frame.id = WEBP_CHUNK_ANMF;
   937                  ok = ReadFileToWebPData(feature->args_[i].filename_,
   938                                          &frame.bitstream);
   939                  if (!ok) goto Err2;
   940                  ok = ParseFrameArgs(feature->args_[i].params_, &frame);
   941                  if (!ok) {
   942                    WebPDataClear(&frame.bitstream);
   943                    ERROR_GOTO1("ERROR: Could not parse frame properties.\n",
   944                                Err2);
   945                  }
   946                  err = WebPMuxPushFrame(mux, &frame, 1);
   947                  WebPDataClear(&frame.bitstream);
   948                  if (err != WEBP_MUX_OK) {
   949                    ERROR_GOTO3("ERROR (%s): Could not add a frame at index %d."
   950                                "\n", ErrorString(err), i, Err2);
   951                  }
   952                  break;
   953                }
   954                default: {
   955                  ERROR_GOTO1("ERROR: Invalid subtype for 'frame'", Err2);
   956                  break;
   957                }
   958              }
   959            }
   960            err = WebPMuxSetAnimationParams(mux, &params);
   961            if (err != WEBP_MUX_OK) {
   962              ERROR_GOTO2("ERROR (%s): Could not set animation parameters.\n",
   963                          ErrorString(err), Err2);
   964            }
   965            break;
   966          }
   967  
   968          case FEATURE_FRGM: {
   969            int i;
   970            mux = WebPMuxNew();
   971            if (mux == NULL) {
   972              ERROR_GOTO2("ERROR (%s): Could not allocate a mux object.\n",
   973                          ErrorString(WEBP_MUX_MEMORY_ERROR), Err2);
   974            }
   975            for (i = 0; i < feature->arg_count_; ++i) {
   976              WebPMuxFrameInfo frgm;
   977              frgm.id = WEBP_CHUNK_FRGM;
   978              ok = ReadFileToWebPData(feature->args_[i].filename_,
   979                                      &frgm.bitstream);
   980              if (!ok) goto Err2;
   981              ok = ParseFragmentArgs(feature->args_[i].params_, &frgm);
   982              if (!ok) {
   983                WebPDataClear(&frgm.bitstream);
   984                ERROR_GOTO1("ERROR: Could not parse fragment properties.\n",
   985                            Err2);
   986              }
   987              err = WebPMuxPushFrame(mux, &frgm, 1);
   988              WebPDataClear(&frgm.bitstream);
   989              if (err != WEBP_MUX_OK) {
   990                ERROR_GOTO3("ERROR (%s): Could not add a fragment at index %d.\n",
   991                            ErrorString(err), i, Err2);
   992              }
   993            }
   994            break;
   995          }
   996  
   997          case FEATURE_ICCP:
   998          case FEATURE_EXIF:
   999          case FEATURE_XMP: {
  1000            ok = CreateMux(config->input_, &mux);
  1001            if (!ok) goto Err2;
  1002            ok = ReadFileToWebPData(feature->args_[0].filename_, &chunk);
  1003            if (!ok) goto Err2;
  1004            err = WebPMuxSetChunk(mux, kFourccList[feature->type_], &chunk, 1);
  1005            free((void*)chunk.bytes);
  1006            if (err != WEBP_MUX_OK) {
  1007              ERROR_GOTO3("ERROR (%s): Could not set the %s.\n",
  1008                          ErrorString(err), kDescriptions[feature->type_], Err2);
  1009            }
  1010            break;
  1011          }
  1012          default: {
  1013            ERROR_GOTO1("ERROR: Invalid feature for action 'set'.\n", Err2);
  1014            break;
  1015          }
  1016        }
  1017        ok = WriteWebP(mux, config->output_);
  1018        break;
  1019      }
  1020      case ACTION_STRIP: {
  1021        ok = CreateMux(config->input_, &mux);
  1022        if (!ok) goto Err2;
  1023        if (feature->type_ == FEATURE_ICCP || feature->type_ == FEATURE_EXIF ||
  1024            feature->type_ == FEATURE_XMP) {
  1025          err = WebPMuxDeleteChunk(mux, kFourccList[feature->type_]);
  1026          if (err != WEBP_MUX_OK) {
  1027            ERROR_GOTO3("ERROR (%s): Could not strip the %s.\n",
  1028                        ErrorString(err), kDescriptions[feature->type_], Err2);
  1029          }
  1030        } else {
  1031            ERROR_GOTO1("ERROR: Invalid feature for action 'strip'.\n", Err2);
  1032            break;
  1033        }
  1034        ok = WriteWebP(mux, config->output_);
  1035        break;
  1036      }
  1037      case ACTION_INFO: {
  1038        ok = CreateMux(config->input_, &mux);
  1039        if (!ok) goto Err2;
  1040        ok = (DisplayInfo(mux) == WEBP_MUX_OK);
  1041        break;
  1042      }
  1043      default: {
  1044        assert(0);  // Invalid action.
  1045        break;
  1046      }
  1047    }
  1048  
  1049   Err2:
  1050    WebPMuxDelete(mux);
  1051    return ok;
  1052  }
  1053  
  1054  //------------------------------------------------------------------------------
  1055  // Main.
  1056  
  1057  int main(int argc, const char* argv[]) {
  1058    WebPMuxConfig* config;
  1059    int ok = InitializeConfig(argc - 1, argv + 1, &config);
  1060    if (ok) {
  1061      ok = Process(config);
  1062    } else {
  1063      PrintHelp();
  1064    }
  1065    DeleteConfig(config);
  1066    return !ok;
  1067  }
  1068  
  1069  //------------------------------------------------------------------------------