go-libwebp

Experimental translation from libwebp to Go source.
Log | Files | Refs | README | LICENSE

webpmux.c (44128B)


      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     webpmux -set loop 1 in.webp -o out_looped.webp
     30 
     31   Extract relevant data from WebP container file:
     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   Change duration of frame intervals:
     43     webpmux -duration 150 in.webp -o out.webp
     44     webpmux -duration 33,2 in.webp -o out.webp
     45     webpmux -duration 200,10,0 -duration 150,6,50 in.webp -o out.webp
     46 
     47   Misc:
     48     webpmux -info in.webp
     49     webpmux [ -h | -help ]
     50     webpmux -version
     51     webpmux argument_file_name
     52 */
     53 
     54 #ifdef HAVE_CONFIG_H
     55 #include "webp/config.h"
     56 #endif
     57 
     58 #include <assert.h>
     59 #include <stdio.h>
     60 #include <stdlib.h>
     61 #include <string.h>
     62 
     63 #include "../examples/example_util.h"
     64 #include "../imageio/imageio_util.h"
     65 #include "./unicode.h"
     66 #include "webp/decode.h"
     67 #include "webp/mux.h"
     68 #include "webp/mux_types.h"
     69 #include "webp/types.h"
     70 
     71 //------------------------------------------------------------------------------
     72 // Config object to parse command-line arguments.
     73 
     74 typedef enum {
     75   NIL_ACTION = 0,
     76   ACTION_GET,
     77   ACTION_SET,
     78   ACTION_STRIP,
     79   ACTION_INFO,
     80   ACTION_HELP,
     81   ACTION_DURATION
     82 } ActionType;
     83 
     84 typedef enum {
     85   NIL_SUBTYPE = 0,
     86   SUBTYPE_ANMF,
     87   SUBTYPE_LOOP,
     88   SUBTYPE_BGCOLOR
     89 } FeatureSubType;
     90 
     91 typedef struct {
     92   FeatureSubType subtype;
     93   const char* filename;
     94   const char* params;
     95 } FeatureArg;
     96 
     97 typedef enum {
     98   NIL_FEATURE = 0,
     99   FEATURE_EXIF,
    100   FEATURE_XMP,
    101   FEATURE_ICCP,
    102   FEATURE_ANMF,
    103   FEATURE_DURATION,
    104   FEATURE_LOOP,
    105   FEATURE_BGCOLOR,
    106   LAST_FEATURE
    107 } FeatureType;
    108 
    109 static const char* const kFourccList[LAST_FEATURE] = {
    110   NULL, "EXIF", "XMP ", "ICCP", "ANMF"
    111 };
    112 
    113 static const char* const kDescriptions[LAST_FEATURE] = {
    114   NULL, "EXIF metadata", "XMP metadata", "ICC profile",
    115   "Animation frame"
    116 };
    117 
    118 typedef struct {
    119   CommandLineArguments cmd_args;
    120 
    121   ActionType action_type;
    122   const char* input;
    123   const char* output;
    124   FeatureType type;
    125   FeatureArg* args;
    126   int arg_count;
    127 } Config;
    128 
    129 //------------------------------------------------------------------------------
    130 // Helper functions.
    131 
    132 static int CountOccurrences(const CommandLineArguments* const args,
    133                             const char* const arg) {
    134   int i;
    135   int num_occurences = 0;
    136 
    137   for (i = 0; i < args->argc; ++i) {
    138     if (!strcmp(args->argv[i], arg)) {
    139       ++num_occurences;
    140     }
    141   }
    142   return num_occurences;
    143 }
    144 
    145 static const char* const kErrorMessages[-WEBP_MUX_NOT_ENOUGH_DATA + 1] = {
    146   "WEBP_MUX_NOT_FOUND", "WEBP_MUX_INVALID_ARGUMENT", "WEBP_MUX_BAD_DATA",
    147   "WEBP_MUX_MEMORY_ERROR", "WEBP_MUX_NOT_ENOUGH_DATA"
    148 };
    149 
    150 static const char* ErrorString(WebPMuxError err) {
    151   assert(err <= WEBP_MUX_NOT_FOUND && err >= WEBP_MUX_NOT_ENOUGH_DATA);
    152   return kErrorMessages[-err];
    153 }
    154 
    155 #define RETURN_IF_ERROR(ERR_MSG)                                     \
    156   do {                                                               \
    157     if (err != WEBP_MUX_OK) {                                        \
    158       fprintf(stderr, ERR_MSG);                                      \
    159       return err;                                                    \
    160     }                                                                \
    161   } while (0)
    162 
    163 #define RETURN_IF_ERROR3(ERR_MSG, FORMAT_STR1, FORMAT_STR2)          \
    164   do {                                                               \
    165     if (err != WEBP_MUX_OK) {                                        \
    166       fprintf(stderr, ERR_MSG, FORMAT_STR1, FORMAT_STR2);            \
    167       return err;                                                    \
    168     }                                                                \
    169   } while (0)
    170 
    171 #define ERROR_GOTO1(ERR_MSG, LABEL)                                  \
    172   do {                                                               \
    173     fprintf(stderr, ERR_MSG);                                        \
    174     ok = 0;                                                          \
    175     goto LABEL;                                                      \
    176   } while (0)
    177 
    178 #define ERROR_GOTO2(ERR_MSG, FORMAT_STR, LABEL)                      \
    179   do {                                                               \
    180     fprintf(stderr, ERR_MSG, FORMAT_STR);                            \
    181     ok = 0;                                                          \
    182     goto LABEL;                                                      \
    183   } while (0)
    184 
    185 #define ERROR_GOTO3(ERR_MSG, FORMAT_STR1, FORMAT_STR2, LABEL)        \
    186   do {                                                               \
    187     fprintf(stderr, ERR_MSG, FORMAT_STR1, FORMAT_STR2);              \
    188     ok = 0;                                                          \
    189     goto LABEL;                                                      \
    190   } while (0)
    191 
    192 static WebPMuxError DisplayInfo(const WebPMux* mux) {
    193   int width, height;
    194   uint32_t flag;
    195 
    196   WebPMuxError err = WebPMuxGetCanvasSize(mux, &width, &height);
    197   assert(err == WEBP_MUX_OK);  // As WebPMuxCreate() was successful earlier.
    198   printf("Canvas size: %d x %d\n", width, height);
    199 
    200   err = WebPMuxGetFeatures(mux, &flag);
    201   RETURN_IF_ERROR("Failed to retrieve features\n");
    202 
    203   if (flag == 0) {
    204     printf("No features present.\n");
    205     return err;
    206   }
    207 
    208   // Print the features present.
    209   printf("Features present:");
    210   if (flag & ANIMATION_FLAG) printf(" animation");
    211   if (flag & ICCP_FLAG)      printf(" ICC profile");
    212   if (flag & EXIF_FLAG)      printf(" EXIF metadata");
    213   if (flag & XMP_FLAG)       printf(" XMP metadata");
    214   if (flag & ALPHA_FLAG)     printf(" transparency");
    215   printf("\n");
    216 
    217   if (flag & ANIMATION_FLAG) {
    218     const WebPChunkId id = WEBP_CHUNK_ANMF;
    219     const char* const type_str = "frame";
    220     int nFrames;
    221 
    222     WebPMuxAnimParams params;
    223     err = WebPMuxGetAnimationParams(mux, &params);
    224     assert(err == WEBP_MUX_OK);
    225     printf("Background color : 0x%.8X  Loop Count : %d\n",
    226            params.bgcolor, params.loop_count);
    227 
    228     err = WebPMuxNumChunks(mux, id, &nFrames);
    229     assert(err == WEBP_MUX_OK);
    230 
    231     printf("Number of %ss: %d\n", type_str, nFrames);
    232     if (nFrames > 0) {
    233       int i;
    234       printf("No.: width height alpha x_offset y_offset ");
    235       printf("duration   dispose blend ");
    236       printf("image_size  compression\n");
    237       for (i = 1; i <= nFrames; i++) {
    238         WebPMuxFrameInfo frame;
    239         err = WebPMuxGetFrame(mux, i, &frame);
    240         if (err == WEBP_MUX_OK) {
    241           WebPBitstreamFeatures features;
    242           const VP8StatusCode status = WebPGetFeatures(
    243               frame.bitstream.bytes, frame.bitstream.size, &features);
    244           assert(status == VP8_STATUS_OK);  // Checked by WebPMuxCreate().
    245           (void)status;
    246           printf("%3d: %5d %5d %5s %8d %8d ", i, features.width,
    247                  features.height, features.has_alpha ? "yes" : "no",
    248                  frame.x_offset, frame.y_offset);
    249           {
    250             const char* const dispose =
    251                 (frame.dispose_method == WEBP_MUX_DISPOSE_NONE) ? "none"
    252                                                                 : "background";
    253             const char* const blend =
    254                 (frame.blend_method == WEBP_MUX_BLEND) ? "yes" : "no";
    255             printf("%8d %10s %5s ", frame.duration, dispose, blend);
    256           }
    257           printf("%10d %11s\n", (int)frame.bitstream.size,
    258                  (features.format == 1) ? "lossy" :
    259                  (features.format == 2) ? "lossless" :
    260                                           "undefined");
    261         }
    262         WebPDataClear(&frame.bitstream);
    263         RETURN_IF_ERROR3("Failed to retrieve %s#%d\n", type_str, i);
    264       }
    265     }
    266   }
    267 
    268   if (flag & ICCP_FLAG) {
    269     WebPData icc_profile;
    270     err = WebPMuxGetChunk(mux, "ICCP", &icc_profile);
    271     assert(err == WEBP_MUX_OK);
    272     printf("Size of the ICC profile data: %d\n", (int)icc_profile.size);
    273   }
    274 
    275   if (flag & EXIF_FLAG) {
    276     WebPData exif;
    277     err = WebPMuxGetChunk(mux, "EXIF", &exif);
    278     assert(err == WEBP_MUX_OK);
    279     printf("Size of the EXIF metadata: %d\n", (int)exif.size);
    280   }
    281 
    282   if (flag & XMP_FLAG) {
    283     WebPData xmp;
    284     err = WebPMuxGetChunk(mux, "XMP ", &xmp);
    285     assert(err == WEBP_MUX_OK);
    286     printf("Size of the XMP metadata: %d\n", (int)xmp.size);
    287   }
    288 
    289   if ((flag & ALPHA_FLAG) && !(flag & ANIMATION_FLAG)) {
    290     WebPMuxFrameInfo image;
    291     err = WebPMuxGetFrame(mux, 1, &image);
    292     if (err == WEBP_MUX_OK) {
    293       printf("Size of the image (with alpha): %d\n", (int)image.bitstream.size);
    294     }
    295     WebPDataClear(&image.bitstream);
    296     RETURN_IF_ERROR("Failed to retrieve the image\n");
    297   }
    298 
    299   return WEBP_MUX_OK;
    300 }
    301 
    302 static void PrintHelp(void) {
    303   printf("Usage: webpmux -get GET_OPTIONS INPUT -o OUTPUT\n");
    304   printf("       webpmux -set SET_OPTIONS INPUT -o OUTPUT\n");
    305   printf("       webpmux -duration DURATION_OPTIONS [-duration ...]\n");
    306   printf("               INPUT -o OUTPUT\n");
    307   printf("       webpmux -strip STRIP_OPTIONS INPUT -o OUTPUT\n");
    308   printf("       webpmux -frame FRAME_OPTIONS [-frame...] [-loop LOOP_COUNT]"
    309          "\n");
    310   printf("               [-bgcolor BACKGROUND_COLOR] -o OUTPUT\n");
    311   printf("       webpmux -info INPUT\n");
    312   printf("       webpmux [-h|-help]\n");
    313   printf("       webpmux -version\n");
    314   printf("       webpmux argument_file_name\n");
    315 
    316   printf("\n");
    317   printf("GET_OPTIONS:\n");
    318   printf(" Extract relevant data:\n");
    319   printf("   icc       get ICC profile\n");
    320   printf("   exif      get EXIF metadata\n");
    321   printf("   xmp       get XMP metadata\n");
    322   printf("   frame n   get nth frame\n");
    323 
    324   printf("\n");
    325   printf("SET_OPTIONS:\n");
    326   printf(" Set color profile/metadata/parameters:\n");
    327   printf("   loop LOOP_COUNT            set the loop count\n");
    328   printf("   bgcolor BACKGROUND_COLOR   set the animation background color\n");
    329   printf("   icc  file.icc              set ICC profile\n");
    330   printf("   exif file.exif             set EXIF metadata\n");
    331   printf("   xmp  file.xmp              set XMP metadata\n");
    332   printf("   where:    'file.icc' contains the ICC profile to be set,\n");
    333   printf("             'file.exif' contains the EXIF metadata to be set\n");
    334   printf("             'file.xmp' contains the XMP metadata to be set\n");
    335 
    336   printf("\n");
    337   printf("DURATION_OPTIONS:\n");
    338   printf(" Set duration of selected frames:\n");
    339   printf("   duration            set duration for all frames\n");
    340   printf("   duration,frame      set duration of a particular frame\n");
    341   printf("   duration,start,end  set duration of frames in the\n");
    342   printf("                        interval [start,end])\n");
    343   printf("   where: 'duration' is the duration in milliseconds\n");
    344   printf("          'start' is the start frame index\n");
    345   printf("          'end' is the inclusive end frame index\n");
    346   printf("           The special 'end' value '0' means: last frame.\n");
    347 
    348   printf("\n");
    349   printf("STRIP_OPTIONS:\n");
    350   printf(" Strip color profile/metadata:\n");
    351   printf("   icc       strip ICC profile\n");
    352   printf("   exif      strip EXIF metadata\n");
    353   printf("   xmp       strip XMP metadata\n");
    354 
    355   printf("\n");
    356   printf("FRAME_OPTIONS(i):\n");
    357   printf(" Create animation:\n");
    358   printf("   file_i +di[+xi+yi[+mi[bi]]]\n");
    359   printf("   where:    'file_i' is the i'th animation frame (WebP format),\n");
    360   printf("             'di' is the pause duration before next frame,\n");
    361   printf("             'xi','yi' specify the image offset for this frame,\n");
    362   printf("             'mi' is the dispose method for this frame (0 or 1),\n");
    363   printf("             'bi' is the blending method for this frame (+b or -b)"
    364          "\n");
    365 
    366   printf("\n");
    367   printf("LOOP_COUNT:\n");
    368   printf(" Number of times to repeat the animation.\n");
    369   printf(" Valid range is 0 to 65535 [Default: 0 (infinite)].\n");
    370 
    371   printf("\n");
    372   printf("BACKGROUND_COLOR:\n");
    373   printf(" Background color of the canvas.\n");
    374   printf("  A,R,G,B\n");
    375   printf("  where:    'A', 'R', 'G' and 'B' are integers in the range 0 to 255 "
    376          "specifying\n");
    377   printf("            the Alpha, Red, Green and Blue component values "
    378          "respectively\n");
    379   printf("            [Default: 255,255,255,255]\n");
    380 
    381   printf("\nINPUT & OUTPUT are in WebP format.\n");
    382 
    383   printf("\nNote: The nature of EXIF, XMP and ICC data is not checked");
    384   printf(" and is assumed to be\nvalid.\n");
    385   printf("\nNote: if a single file name is passed as the argument, the "
    386          "arguments will be\n");
    387   printf("tokenized from this file. The file name must not start with "
    388          "the character '-'.\n");
    389 }
    390 
    391 static void WarnAboutOddOffset(const WebPMuxFrameInfo* const info) {
    392   if ((info->x_offset | info->y_offset) & 1) {
    393     fprintf(stderr, "Warning: odd offsets will be snapped to even values"
    394             " (%d, %d) -> (%d, %d)\n", info->x_offset, info->y_offset,
    395             info->x_offset & ~1, info->y_offset & ~1);
    396   }
    397 }
    398 
    399 static int CreateMux(const char* const filename, WebPMux** mux) {
    400   WebPData bitstream;
    401   assert(mux != NULL);
    402   if (!ExUtilReadFileToWebPData(filename, &bitstream)) return 0;
    403   *mux = WebPMuxCreate(&bitstream, 1);
    404   WebPDataClear(&bitstream);
    405   if (*mux != NULL) return 1;
    406   WFPRINTF(stderr, "Failed to create mux object from file %s.\n",
    407            (const W_CHAR*)filename);
    408   return 0;
    409 }
    410 
    411 static int WriteData(const char* filename, const WebPData* const webpdata) {
    412   int ok = 0;
    413   FILE* fout = WSTRCMP(filename, "-") ? WFOPEN(filename, "wb")
    414                                       : ImgIoUtilSetBinaryMode(stdout);
    415   if (fout == NULL) {
    416     WFPRINTF(stderr, "Error opening output WebP file %s!\n",
    417              (const W_CHAR*)filename);
    418     return 0;
    419   }
    420   if (fwrite(webpdata->bytes, webpdata->size, 1, fout) != 1) {
    421     WFPRINTF(stderr, "Error writing file %s!\n", (const W_CHAR*)filename);
    422   } else {
    423     WFPRINTF(stderr, "Saved file %s (%d bytes)\n",
    424              (const W_CHAR*)filename, (int)webpdata->size);
    425     ok = 1;
    426   }
    427   if (fout != stdout) fclose(fout);
    428   return ok;
    429 }
    430 
    431 static int WriteWebP(WebPMux* const mux, const char* filename) {
    432   int ok;
    433   WebPData webp_data;
    434   const WebPMuxError err = WebPMuxAssemble(mux, &webp_data);
    435   if (err != WEBP_MUX_OK) {
    436     fprintf(stderr, "Error (%s) assembling the WebP file.\n", ErrorString(err));
    437     return 0;
    438   }
    439   ok = WriteData(filename, &webp_data);
    440   WebPDataClear(&webp_data);
    441   return ok;
    442 }
    443 
    444 static WebPMux* DuplicateMuxHeader(const WebPMux* const mux) {
    445   WebPMux* new_mux = WebPMuxNew();
    446   WebPMuxAnimParams p;
    447   WebPMuxError err;
    448   int i;
    449   int ok = 1;
    450 
    451   if (new_mux == NULL) return NULL;
    452 
    453   err = WebPMuxGetAnimationParams(mux, &p);
    454   if (err == WEBP_MUX_OK) {
    455     err = WebPMuxSetAnimationParams(new_mux, &p);
    456     if (err != WEBP_MUX_OK) {
    457       ERROR_GOTO2("Error (%s) handling animation params.\n",
    458                   ErrorString(err), End);
    459     }
    460   } else {
    461     /* it might not be an animation. Just keep moving. */
    462   }
    463 
    464   for (i = 1; i <= 3; ++i) {
    465     WebPData metadata;
    466     err = WebPMuxGetChunk(mux, kFourccList[i], &metadata);
    467     if (err == WEBP_MUX_OK && metadata.size > 0) {
    468       err = WebPMuxSetChunk(new_mux, kFourccList[i], &metadata, 1);
    469       if (err != WEBP_MUX_OK) {
    470         ERROR_GOTO1("Error transferring metadata in DuplicateMuxHeader().",
    471                     End);
    472       }
    473     }
    474   }
    475 
    476  End:
    477   if (!ok) {
    478     WebPMuxDelete(new_mux);
    479     new_mux = NULL;
    480   }
    481   return new_mux;
    482 }
    483 
    484 static int ParseFrameArgs(const char* args, WebPMuxFrameInfo* const info) {
    485   int dispose_method, unused;
    486   char plus_minus, blend_method;
    487   const int num_args = sscanf(args, "+%d+%d+%d+%d%c%c+%d", &info->duration,
    488                               &info->x_offset, &info->y_offset, &dispose_method,
    489                               &plus_minus, &blend_method, &unused);
    490   switch (num_args) {
    491     case 1:
    492       info->x_offset = info->y_offset = 0;  // fall through
    493     case 3:
    494       dispose_method = 0;  // fall through
    495     case 4:
    496       plus_minus = '+';
    497       blend_method = 'b';  // fall through
    498     case 6:
    499       break;
    500     case 2:
    501     case 5:
    502     default:
    503       return 0;
    504   }
    505 
    506   WarnAboutOddOffset(info);
    507 
    508   // Note: The validity of the following conversion is checked by
    509   // WebPMuxPushFrame().
    510   info->dispose_method = (WebPMuxAnimDispose)dispose_method;
    511 
    512   if (blend_method != 'b') return 0;
    513   if (plus_minus != '-' && plus_minus != '+') return 0;
    514   info->blend_method =
    515       (plus_minus == '+') ? WEBP_MUX_BLEND : WEBP_MUX_NO_BLEND;
    516   return 1;
    517 }
    518 
    519 static int ParseBgcolorArgs(const char* args, uint32_t* const bgcolor) {
    520   uint32_t a, r, g, b;
    521   if (sscanf(args, "%u,%u,%u,%u", &a, &r, &g, &b) != 4) return 0;
    522   if (a >= 256 || r >= 256 || g >= 256 || b >= 256) return 0;
    523   *bgcolor = (a << 24) | (r << 16) | (g << 8) | (b << 0);
    524   return 1;
    525 }
    526 
    527 //------------------------------------------------------------------------------
    528 // Clean-up.
    529 
    530 static void DeleteConfig(Config* const config) {
    531   if (config != NULL) {
    532     free(config->args);
    533     ExUtilDeleteCommandLineArguments(&config->cmd_args);
    534     memset(config, 0, sizeof(*config));
    535   }
    536 }
    537 
    538 //------------------------------------------------------------------------------
    539 // Parsing.
    540 
    541 // Basic syntactic checks on the command-line arguments.
    542 // Returns 1 on valid, 0 otherwise.
    543 // Also fills up num_feature_args to be number of feature arguments given.
    544 // (e.g. if there are 4 '-frame's and 1 '-loop', then num_feature_args = 5).
    545 static int ValidateCommandLine(const CommandLineArguments* const cmd_args,
    546                                int* num_feature_args) {
    547   int num_frame_args;
    548   int num_loop_args;
    549   int num_bgcolor_args;
    550   int num_durations_args;
    551   int ok = 1;
    552 
    553   assert(num_feature_args != NULL);
    554   *num_feature_args = 0;
    555 
    556   // Simple checks.
    557   if (CountOccurrences(cmd_args, "-get") > 1) {
    558     ERROR_GOTO1("ERROR: Multiple '-get' arguments specified.\n", ErrValidate);
    559   }
    560   if (CountOccurrences(cmd_args, "-set") > 1) {
    561     ERROR_GOTO1("ERROR: Multiple '-set' arguments specified.\n", ErrValidate);
    562   }
    563   if (CountOccurrences(cmd_args, "-strip") > 1) {
    564     ERROR_GOTO1("ERROR: Multiple '-strip' arguments specified.\n", ErrValidate);
    565   }
    566   if (CountOccurrences(cmd_args, "-info") > 1) {
    567     ERROR_GOTO1("ERROR: Multiple '-info' arguments specified.\n", ErrValidate);
    568   }
    569   if (CountOccurrences(cmd_args, "-o") > 1) {
    570     ERROR_GOTO1("ERROR: Multiple output files specified.\n", ErrValidate);
    571   }
    572 
    573   // Compound checks.
    574   num_frame_args = CountOccurrences(cmd_args, "-frame");
    575   num_loop_args = CountOccurrences(cmd_args, "-loop");
    576   num_bgcolor_args = CountOccurrences(cmd_args, "-bgcolor");
    577   num_durations_args = CountOccurrences(cmd_args, "-duration");
    578 
    579   if (num_loop_args > 1) {
    580     ERROR_GOTO1("ERROR: Multiple loop counts specified.\n", ErrValidate);
    581   }
    582   if (num_bgcolor_args > 1) {
    583     ERROR_GOTO1("ERROR: Multiple background colors specified.\n", ErrValidate);
    584   }
    585 
    586   if ((num_frame_args == 0) && (num_loop_args + num_bgcolor_args > 0)) {
    587     ERROR_GOTO1("ERROR: Loop count and background color are relevant only in "
    588                 "case of animation.\n", ErrValidate);
    589   }
    590   if (num_durations_args > 0 && num_frame_args != 0) {
    591     ERROR_GOTO1("ERROR: Can not combine -duration and -frame commands.\n",
    592                 ErrValidate);
    593   }
    594 
    595   assert(ok == 1);
    596   if (num_durations_args > 0) {
    597     *num_feature_args = num_durations_args;
    598   } else if (num_frame_args == 0) {
    599     // Single argument ('set' action for ICCP/EXIF/XMP, OR a 'get' action).
    600     *num_feature_args = 1;
    601   } else {
    602     // Multiple arguments ('set' action for animation)
    603     *num_feature_args = num_frame_args + num_loop_args + num_bgcolor_args;
    604   }
    605 
    606  ErrValidate:
    607   return ok;
    608 }
    609 
    610 #define ACTION_IS_NIL (config->action_type == NIL_ACTION)
    611 
    612 #define FEATURETYPE_IS_NIL (config->type == NIL_FEATURE)
    613 
    614 #define CHECK_NUM_ARGS_AT_LEAST(NUM, LABEL)                              \
    615   do {                                                                   \
    616     if (argc < i + (NUM)) {                                              \
    617       fprintf(stderr, "ERROR: Too few arguments for '%s'.\n", argv[i]);  \
    618       goto LABEL;                                                        \
    619     }                                                                    \
    620   } while (0)
    621 
    622 #define CHECK_NUM_ARGS_AT_MOST(NUM, LABEL)                               \
    623   do {                                                                   \
    624     if (argc > i + (NUM)) {                                              \
    625       fprintf(stderr, "ERROR: Too many arguments for '%s'.\n", argv[i]); \
    626       goto LABEL;                                                        \
    627     }                                                                    \
    628   } while (0)
    629 
    630 #define CHECK_NUM_ARGS_EXACTLY(NUM, LABEL)                               \
    631   do {                                                                   \
    632     CHECK_NUM_ARGS_AT_LEAST(NUM, LABEL);                                 \
    633     CHECK_NUM_ARGS_AT_MOST(NUM, LABEL);                                  \
    634   } while (0)
    635 
    636 // Parses command-line arguments to fill up config object. Also performs some
    637 // semantic checks. unicode_argv contains wchar_t arguments or is null.
    638 static int ParseCommandLine(Config* config, const W_CHAR** const unicode_argv) {
    639   int i = 0;
    640   int feature_arg_index = 0;
    641   int ok = 1;
    642   int argc = config->cmd_args.argc;
    643   const char* const* argv = config->cmd_args.argv;
    644   // Unicode file paths will be used if available.
    645   const char* const* wargv =
    646       (unicode_argv != NULL) ? (const char**)(unicode_argv + 1) : argv;
    647 
    648   while (i < argc) {
    649     FeatureArg* const arg = &config->args[feature_arg_index];
    650     if (argv[i][0] == '-') {  // One of the action types or output.
    651       if (!strcmp(argv[i], "-set")) {
    652         if (ACTION_IS_NIL) {
    653           config->action_type = ACTION_SET;
    654         } else {
    655           ERROR_GOTO1("ERROR: Multiple actions specified.\n", ErrParse);
    656         }
    657         ++i;
    658       } else if (!strcmp(argv[i], "-duration")) {
    659         CHECK_NUM_ARGS_AT_LEAST(2, ErrParse);
    660         if (ACTION_IS_NIL || config->action_type == ACTION_DURATION) {
    661           config->action_type = ACTION_DURATION;
    662         } else {
    663           ERROR_GOTO1("ERROR: Multiple actions specified.\n", ErrParse);
    664         }
    665         if (FEATURETYPE_IS_NIL || config->type == FEATURE_DURATION) {
    666           config->type = FEATURE_DURATION;
    667         } else {
    668           ERROR_GOTO1("ERROR: Multiple features specified.\n", ErrParse);
    669         }
    670         arg->params = argv[i + 1];
    671         ++feature_arg_index;
    672         i += 2;
    673       } else if (!strcmp(argv[i], "-get")) {
    674         if (ACTION_IS_NIL) {
    675           config->action_type = ACTION_GET;
    676         } else {
    677           ERROR_GOTO1("ERROR: Multiple actions specified.\n", ErrParse);
    678         }
    679         ++i;
    680       } else if (!strcmp(argv[i], "-strip")) {
    681         if (ACTION_IS_NIL) {
    682           config->action_type = ACTION_STRIP;
    683         } else {
    684           ERROR_GOTO1("ERROR: Multiple actions specified.\n", ErrParse);
    685         }
    686         ++i;
    687       } else if (!strcmp(argv[i], "-frame")) {
    688         CHECK_NUM_ARGS_AT_LEAST(3, ErrParse);
    689         if (ACTION_IS_NIL || config->action_type == ACTION_SET) {
    690           config->action_type = ACTION_SET;
    691         } else {
    692           ERROR_GOTO1("ERROR: Multiple actions specified.\n", ErrParse);
    693         }
    694         if (FEATURETYPE_IS_NIL || config->type == FEATURE_ANMF) {
    695           config->type = FEATURE_ANMF;
    696         } else {
    697           ERROR_GOTO1("ERROR: Multiple features specified.\n", ErrParse);
    698         }
    699         arg->subtype = SUBTYPE_ANMF;
    700         arg->filename = wargv[i + 1];
    701         arg->params = argv[i + 2];
    702         ++feature_arg_index;
    703         i += 3;
    704       } else if (!strcmp(argv[i], "-loop") || !strcmp(argv[i], "-bgcolor")) {
    705         CHECK_NUM_ARGS_AT_LEAST(2, ErrParse);
    706         if (ACTION_IS_NIL || config->action_type == ACTION_SET) {
    707           config->action_type = ACTION_SET;
    708         } else {
    709           ERROR_GOTO1("ERROR: Multiple actions specified.\n", ErrParse);
    710         }
    711         if (FEATURETYPE_IS_NIL || config->type == FEATURE_ANMF) {
    712           config->type = FEATURE_ANMF;
    713         } else {
    714           ERROR_GOTO1("ERROR: Multiple features specified.\n", ErrParse);
    715         }
    716         arg->subtype =
    717             !strcmp(argv[i], "-loop") ? SUBTYPE_LOOP : SUBTYPE_BGCOLOR;
    718         arg->params = argv[i + 1];
    719         ++feature_arg_index;
    720         i += 2;
    721       } else if (!strcmp(argv[i], "-o")) {
    722         CHECK_NUM_ARGS_AT_LEAST(2, ErrParse);
    723         config->output = wargv[i + 1];
    724         i += 2;
    725       } else if (!strcmp(argv[i], "-info")) {
    726         CHECK_NUM_ARGS_EXACTLY(2, ErrParse);
    727         if (config->action_type != NIL_ACTION) {
    728           ERROR_GOTO1("ERROR: Multiple actions specified.\n", ErrParse);
    729         } else {
    730           config->action_type = ACTION_INFO;
    731           config->arg_count = 0;
    732           config->input = wargv[i + 1];
    733         }
    734         i += 2;
    735       } else if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "-help")) {
    736         PrintHelp();
    737         DeleteConfig(config);
    738         LOCAL_FREE((W_CHAR** const)unicode_argv);
    739         exit(0);
    740       } else if (!strcmp(argv[i], "-version")) {
    741         const int version = WebPGetMuxVersion();
    742         printf("%d.%d.%d\n",
    743                (version >> 16) & 0xff, (version >> 8) & 0xff, version & 0xff);
    744         DeleteConfig(config);
    745         LOCAL_FREE((W_CHAR** const)unicode_argv);
    746         exit(0);
    747       } else if (!strcmp(argv[i], "--")) {
    748         if (i < argc - 1) {
    749           ++i;
    750           if (config->input == NULL) {
    751             config->input = wargv[i];
    752           } else {
    753             ERROR_GOTO2("ERROR at '%s': Multiple input files specified.\n",
    754                         argv[i], ErrParse);
    755           }
    756         }
    757         break;
    758       } else {
    759         ERROR_GOTO2("ERROR: Unknown option: '%s'.\n", argv[i], ErrParse);
    760       }
    761     } else {  // One of the feature types or input.
    762       // After consuming the arguments to -get/-set/-strip, treat any remaining
    763       // arguments as input. This allows files that are named the same as the
    764       // keywords used with these options.
    765       int is_input = feature_arg_index == config->arg_count;
    766       if (ACTION_IS_NIL) {
    767         ERROR_GOTO1("ERROR: Action must be specified before other arguments.\n",
    768                     ErrParse);
    769       }
    770       if (!is_input) {
    771         if (!strcmp(argv[i], "icc") || !strcmp(argv[i], "exif") ||
    772             !strcmp(argv[i], "xmp")) {
    773           if (FEATURETYPE_IS_NIL) {
    774             config->type = (!strcmp(argv[i], "icc")) ? FEATURE_ICCP :
    775                 (!strcmp(argv[i], "exif")) ? FEATURE_EXIF : FEATURE_XMP;
    776           } else {
    777             ERROR_GOTO1("ERROR: Multiple features specified.\n", ErrParse);
    778           }
    779           if (config->action_type == ACTION_SET) {
    780             CHECK_NUM_ARGS_AT_LEAST(2, ErrParse);
    781             arg->filename = wargv[i + 1];
    782             ++feature_arg_index;
    783             i += 2;
    784           } else {
    785             // Note: 'arg->params' is not used in this case. 'arg_count' is
    786             // used as a flag to indicate the -get/-strip feature has already
    787             // been consumed, allowing input types to be named the same as the
    788             // feature type.
    789             config->arg_count = 0;
    790             ++i;
    791           }
    792         } else if (!strcmp(argv[i], "frame") &&
    793                    (config->action_type == ACTION_GET)) {
    794           CHECK_NUM_ARGS_AT_LEAST(2, ErrParse);
    795           config->type = FEATURE_ANMF;
    796           arg->params = argv[i + 1];
    797           ++feature_arg_index;
    798           i += 2;
    799         } else if (!strcmp(argv[i], "loop") &&
    800                    (config->action_type == ACTION_SET)) {
    801           CHECK_NUM_ARGS_AT_LEAST(2, ErrParse);
    802           config->type = FEATURE_LOOP;
    803           arg->params = argv[i + 1];
    804           ++feature_arg_index;
    805           i += 2;
    806         } else if (!strcmp(argv[i], "bgcolor") &&
    807                    (config->action_type == ACTION_SET)) {
    808           CHECK_NUM_ARGS_AT_LEAST(2, ErrParse);
    809           config->type = FEATURE_BGCOLOR;
    810           arg->params = argv[i + 1];
    811           ++feature_arg_index;
    812           i += 2;
    813         } else {
    814           is_input = 1;
    815         }
    816       }
    817 
    818       if (is_input) {
    819         if (config->input == NULL) {
    820           config->input = wargv[i];
    821         } else {
    822           ERROR_GOTO2("ERROR at '%s': Multiple input files specified.\n",
    823                       argv[i], ErrParse);
    824         }
    825         ++i;
    826       }
    827     }
    828   }
    829  ErrParse:
    830   return ok;
    831 }
    832 
    833 // Additional checks after config is filled.
    834 static int ValidateConfig(Config* const config) {
    835   int ok = 1;
    836 
    837   // Action.
    838   if (ACTION_IS_NIL) {
    839     ERROR_GOTO1("ERROR: No action specified.\n", ErrValidate2);
    840   }
    841 
    842   // Feature type.
    843   if (FEATURETYPE_IS_NIL && config->action_type != ACTION_INFO) {
    844     ERROR_GOTO1("ERROR: No feature specified.\n", ErrValidate2);
    845   }
    846 
    847   // Input file.
    848   if (config->input == NULL) {
    849     if (config->action_type != ACTION_SET) {
    850       ERROR_GOTO1("ERROR: No input file specified.\n", ErrValidate2);
    851     } else if (config->type != FEATURE_ANMF) {
    852       ERROR_GOTO1("ERROR: No input file specified.\n", ErrValidate2);
    853     }
    854   }
    855 
    856   // Output file.
    857   if (config->output == NULL && config->action_type != ACTION_INFO) {
    858     ERROR_GOTO1("ERROR: No output file specified.\n", ErrValidate2);
    859   }
    860 
    861  ErrValidate2:
    862   return ok;
    863 }
    864 
    865 // Create config object from command-line arguments.
    866 static int InitializeConfig(int argc, const char* argv[], Config* const config,
    867                             const W_CHAR** const unicode_argv) {
    868   int num_feature_args = 0;
    869   int ok;
    870 
    871   memset(config, 0, sizeof(*config));
    872 
    873   ok = ExUtilInitCommandLineArguments(argc, argv, &config->cmd_args);
    874   if (!ok) return 0;
    875 
    876   // Validate command-line arguments.
    877   if (!ValidateCommandLine(&config->cmd_args, &num_feature_args)) {
    878     ERROR_GOTO1("Exiting due to command-line parsing error.\n", Err1);
    879   }
    880 
    881   config->arg_count = num_feature_args;
    882   config->args = (FeatureArg*)calloc(num_feature_args, sizeof(*config->args));
    883   if (config->args == NULL) {
    884     ERROR_GOTO1("ERROR: Memory allocation error.\n", Err1);
    885   }
    886 
    887   // Parse command-line.
    888   if (!ParseCommandLine(config, unicode_argv) || !ValidateConfig(config)) {
    889     ERROR_GOTO1("Exiting due to command-line parsing error.\n", Err1);
    890   }
    891 
    892  Err1:
    893   return ok;
    894 }
    895 
    896 #undef ACTION_IS_NIL
    897 #undef FEATURETYPE_IS_NIL
    898 #undef CHECK_NUM_ARGS_AT_LEAST
    899 #undef CHECK_NUM_ARGS_AT_MOST
    900 #undef CHECK_NUM_ARGS_EXACTLY
    901 
    902 //------------------------------------------------------------------------------
    903 // Processing.
    904 
    905 static int GetFrame(const WebPMux* mux, const Config* config) {
    906   WebPMuxError err = WEBP_MUX_OK;
    907   WebPMux* mux_single = NULL;
    908   int num = 0;
    909   int ok = 1;
    910   int parse_error = 0;
    911   const WebPChunkId id = WEBP_CHUNK_ANMF;
    912   WebPMuxFrameInfo info;
    913   WebPDataInit(&info.bitstream);
    914 
    915   num = ExUtilGetInt(config->args[0].params, 10, &parse_error);
    916   if (num < 0) {
    917     ERROR_GOTO1("ERROR: Frame/Fragment index must be non-negative.\n", ErrGet);
    918   }
    919   if (parse_error) goto ErrGet;
    920 
    921   err = WebPMuxGetFrame(mux, num, &info);
    922   if (err == WEBP_MUX_OK && info.id != id) err = WEBP_MUX_NOT_FOUND;
    923   if (err != WEBP_MUX_OK) {
    924     ERROR_GOTO3("ERROR (%s): Could not get frame %d.\n",
    925                 ErrorString(err), num, ErrGet);
    926   }
    927 
    928   mux_single = WebPMuxNew();
    929   if (mux_single == NULL) {
    930     err = WEBP_MUX_MEMORY_ERROR;
    931     ERROR_GOTO2("ERROR (%s): Could not allocate a mux object.\n",
    932                 ErrorString(err), ErrGet);
    933   }
    934   err = WebPMuxSetImage(mux_single, &info.bitstream, 1);
    935   if (err != WEBP_MUX_OK) {
    936     ERROR_GOTO2("ERROR (%s): Could not create single image mux object.\n",
    937                 ErrorString(err), ErrGet);
    938   }
    939 
    940   ok = WriteWebP(mux_single, config->output);
    941 
    942  ErrGet:
    943   WebPDataClear(&info.bitstream);
    944   WebPMuxDelete(mux_single);
    945   return ok && !parse_error;
    946 }
    947 
    948 // Read and process config.
    949 static int Process(const Config* config) {
    950   WebPMux* mux = NULL;
    951   WebPData chunk;
    952   WebPMuxError err = WEBP_MUX_OK;
    953   int ok = 1;
    954 
    955   switch (config->action_type) {
    956     case ACTION_GET: {
    957       ok = CreateMux(config->input, &mux);
    958       if (!ok) goto Err2;
    959       switch (config->type) {
    960         case FEATURE_ANMF:
    961           ok = GetFrame(mux, config);
    962           break;
    963 
    964         case FEATURE_ICCP:
    965         case FEATURE_EXIF:
    966         case FEATURE_XMP:
    967           err = WebPMuxGetChunk(mux, kFourccList[config->type], &chunk);
    968           if (err != WEBP_MUX_OK) {
    969             ERROR_GOTO3("ERROR (%s): Could not get the %s.\n",
    970                         ErrorString(err), kDescriptions[config->type], Err2);
    971           }
    972           ok = WriteData(config->output, &chunk);
    973           break;
    974 
    975         default:
    976           ERROR_GOTO1("ERROR: Invalid feature for action 'get'.\n", Err2);
    977           break;
    978       }
    979       break;
    980     }
    981     case ACTION_SET: {
    982       switch (config->type) {
    983         case FEATURE_ANMF: {
    984           int i;
    985           WebPMuxAnimParams params = { 0xFFFFFFFF, 0 };
    986           mux = WebPMuxNew();
    987           if (mux == NULL) {
    988             ERROR_GOTO2("ERROR (%s): Could not allocate a mux object.\n",
    989                         ErrorString(WEBP_MUX_MEMORY_ERROR), Err2);
    990           }
    991           for (i = 0; i < config->arg_count; ++i) {
    992             switch (config->args[i].subtype) {
    993               case SUBTYPE_BGCOLOR: {
    994                 uint32_t bgcolor;
    995                 ok = ParseBgcolorArgs(config->args[i].params, &bgcolor);
    996                 if (!ok) {
    997                   ERROR_GOTO1("ERROR: Could not parse the background color \n",
    998                               Err2);
    999                 }
   1000                 params.bgcolor = bgcolor;
   1001                 break;
   1002               }
   1003               case SUBTYPE_LOOP: {
   1004                 int parse_error = 0;
   1005                 const int loop_count =
   1006                     ExUtilGetInt(config->args[i].params, 10, &parse_error);
   1007                 if (loop_count < 0 || loop_count > 65535) {
   1008                   // Note: This is only a 'necessary' condition for loop_count
   1009                   // to be valid. The 'sufficient' conditioned in checked in
   1010                   // WebPMuxSetAnimationParams() method called later.
   1011                   ERROR_GOTO1("ERROR: Loop count must be in the range 0 to "
   1012                               "65535.\n", Err2);
   1013                 }
   1014                 ok = !parse_error;
   1015                 if (!ok) goto Err2;
   1016                 params.loop_count = loop_count;
   1017                 break;
   1018               }
   1019               case SUBTYPE_ANMF: {
   1020                 WebPMuxFrameInfo frame;
   1021                 frame.id = WEBP_CHUNK_ANMF;
   1022                 ok = ExUtilReadFileToWebPData(config->args[i].filename,
   1023                                               &frame.bitstream);
   1024                 if (!ok) goto Err2;
   1025                 ok = ParseFrameArgs(config->args[i].params, &frame);
   1026                 if (!ok) {
   1027                   WebPDataClear(&frame.bitstream);
   1028                   ERROR_GOTO1("ERROR: Could not parse frame properties.\n",
   1029                               Err2);
   1030                 }
   1031                 err = WebPMuxPushFrame(mux, &frame, 1);
   1032                 WebPDataClear(&frame.bitstream);
   1033                 if (err != WEBP_MUX_OK) {
   1034                   ERROR_GOTO3("ERROR (%s): Could not add a frame at index %d."
   1035                               "\n", ErrorString(err), i, Err2);
   1036                 }
   1037                 break;
   1038               }
   1039               default: {
   1040                 ERROR_GOTO1("ERROR: Invalid subtype for 'frame'", Err2);
   1041                 break;
   1042               }
   1043             }
   1044           }
   1045           err = WebPMuxSetAnimationParams(mux, &params);
   1046           if (err != WEBP_MUX_OK) {
   1047             ERROR_GOTO2("ERROR (%s): Could not set animation parameters.\n",
   1048                         ErrorString(err), Err2);
   1049           }
   1050           break;
   1051         }
   1052 
   1053         case FEATURE_ICCP:
   1054         case FEATURE_EXIF:
   1055         case FEATURE_XMP: {
   1056           ok = CreateMux(config->input, &mux);
   1057           if (!ok) goto Err2;
   1058           ok = ExUtilReadFileToWebPData(config->args[0].filename, &chunk);
   1059           if (!ok) goto Err2;
   1060           err = WebPMuxSetChunk(mux, kFourccList[config->type], &chunk, 1);
   1061           WebPDataClear(&chunk);
   1062           if (err != WEBP_MUX_OK) {
   1063             ERROR_GOTO3("ERROR (%s): Could not set the %s.\n",
   1064                         ErrorString(err), kDescriptions[config->type], Err2);
   1065           }
   1066           break;
   1067         }
   1068         case FEATURE_LOOP: {
   1069           WebPMuxAnimParams params = { 0xFFFFFFFF, 0 };
   1070           int parse_error = 0;
   1071           const int loop_count =
   1072               ExUtilGetInt(config->args[0].params, 10, &parse_error);
   1073           if (loop_count < 0 || loop_count > 65535 || parse_error) {
   1074             ERROR_GOTO1("ERROR: Loop count must be in the range 0 to 65535.\n",
   1075                         Err2);
   1076           }
   1077           ok = CreateMux(config->input, &mux);
   1078           if (!ok) goto Err2;
   1079           ok = (WebPMuxGetAnimationParams(mux, &params) == WEBP_MUX_OK);
   1080           if (!ok) {
   1081             ERROR_GOTO1("ERROR: input file does not seem to be an animation.\n",
   1082                         Err2);
   1083           }
   1084           params.loop_count = loop_count;
   1085           err = WebPMuxSetAnimationParams(mux, &params);
   1086           ok = (err == WEBP_MUX_OK);
   1087           if (!ok) {
   1088             ERROR_GOTO2("ERROR (%s): Could not set animation parameters.\n",
   1089                         ErrorString(err), Err2);
   1090           }
   1091           break;
   1092         }
   1093         case FEATURE_BGCOLOR: {
   1094           WebPMuxAnimParams params = { 0xFFFFFFFF, 0 };
   1095           uint32_t bgcolor;
   1096           ok = ParseBgcolorArgs(config->args[0].params, &bgcolor);
   1097           if (!ok) {
   1098             ERROR_GOTO1("ERROR: Could not parse the background color.\n",
   1099                         Err2);
   1100           }
   1101           ok = CreateMux(config->input, &mux);
   1102           if (!ok) goto Err2;
   1103           ok = (WebPMuxGetAnimationParams(mux, &params) == WEBP_MUX_OK);
   1104           if (!ok) {
   1105             ERROR_GOTO1("ERROR: input file does not seem to be an animation.\n",
   1106                         Err2);
   1107           }
   1108           params.bgcolor = bgcolor;
   1109           err = WebPMuxSetAnimationParams(mux, &params);
   1110           ok = (err == WEBP_MUX_OK);
   1111           if (!ok) {
   1112             ERROR_GOTO2("ERROR (%s): Could not set animation parameters.\n",
   1113                         ErrorString(err), Err2);
   1114           }
   1115           break;
   1116         }
   1117         default: {
   1118           ERROR_GOTO1("ERROR: Invalid feature for action 'set'.\n", Err2);
   1119           break;
   1120         }
   1121       }
   1122       ok = WriteWebP(mux, config->output);
   1123       break;
   1124     }
   1125     case ACTION_DURATION: {
   1126       int num_frames;
   1127       ok = CreateMux(config->input, &mux);
   1128       if (!ok) goto Err2;
   1129       err = WebPMuxNumChunks(mux, WEBP_CHUNK_ANMF, &num_frames);
   1130       ok = (err == WEBP_MUX_OK);
   1131       if (!ok) {
   1132         ERROR_GOTO1("ERROR: can not parse the number of frames.\n", Err2);
   1133       }
   1134       if (num_frames == 0) {
   1135         fprintf(stderr, "Doesn't look like the source is animated. "
   1136                         "Skipping duration setting.\n");
   1137         ok = WriteWebP(mux, config->output);
   1138         if (!ok) goto Err2;
   1139       } else {
   1140         int i;
   1141         int* durations = NULL;
   1142         WebPMux* new_mux = DuplicateMuxHeader(mux);
   1143         if (new_mux == NULL) goto Err2;
   1144         durations = (int*)WebPMalloc((size_t)num_frames * sizeof(*durations));
   1145         if (durations == NULL) goto Err2;
   1146         for (i = 0; i < num_frames; ++i) durations[i] = -1;
   1147 
   1148         // Parse intervals to process.
   1149         for (i = 0; i < config->arg_count; ++i) {
   1150           int k;
   1151           int args[3];
   1152           int duration, start, end;
   1153           const int nb_args = ExUtilGetInts(config->args[i].params,
   1154                                             10, 3, args);
   1155           ok = (nb_args >= 1);
   1156           if (!ok) goto Err3;
   1157           duration = args[0];
   1158           if (duration < 0) {
   1159             ERROR_GOTO1("ERROR: duration must be strictly positive.\n", Err3);
   1160           }
   1161 
   1162           if (nb_args == 1) {   // only duration is present -> use full interval
   1163             start = 1;
   1164             end = num_frames;
   1165           } else {
   1166             start = args[1];
   1167             if (start <= 0) {
   1168               start = 1;
   1169             } else if (start > num_frames) {
   1170               start = num_frames;
   1171             }
   1172             end = (nb_args >= 3) ? args[2] : start;
   1173             if (end == 0 || end > num_frames) end = num_frames;
   1174           }
   1175 
   1176           for (k = start; k <= end; ++k) {
   1177             assert(k >= 1 && k <= num_frames);
   1178             durations[k - 1] = duration;
   1179           }
   1180         }
   1181 
   1182         // Apply non-negative durations to their destination frames.
   1183         for (i = 1; i <= num_frames; ++i) {
   1184           WebPMuxFrameInfo frame;
   1185           err = WebPMuxGetFrame(mux, i, &frame);
   1186           if (err != WEBP_MUX_OK || frame.id != WEBP_CHUNK_ANMF) {
   1187             ERROR_GOTO2("ERROR: can not retrieve frame #%d.\n", i, Err3);
   1188           }
   1189           if (durations[i - 1] >= 0) frame.duration = durations[i - 1];
   1190           err = WebPMuxPushFrame(new_mux, &frame, 1);
   1191           if (err != WEBP_MUX_OK) {
   1192             ERROR_GOTO2("ERROR: error push frame data #%d\n", i, Err3);
   1193           }
   1194           WebPDataClear(&frame.bitstream);
   1195         }
   1196         WebPMuxDelete(mux);
   1197         ok = WriteWebP(new_mux, config->output);
   1198         mux = new_mux;  // transfer for the WebPMuxDelete() call
   1199         new_mux = NULL;
   1200 
   1201  Err3:
   1202         WebPFree(durations);
   1203         WebPMuxDelete(new_mux);
   1204         if (!ok) goto Err2;
   1205       }
   1206       break;
   1207     }
   1208     case ACTION_STRIP: {
   1209       ok = CreateMux(config->input, &mux);
   1210       if (!ok) goto Err2;
   1211       if (config->type == FEATURE_ICCP || config->type == FEATURE_EXIF ||
   1212           config->type == FEATURE_XMP) {
   1213         err = WebPMuxDeleteChunk(mux, kFourccList[config->type]);
   1214         if (err != WEBP_MUX_OK) {
   1215           ERROR_GOTO3("ERROR (%s): Could not strip the %s.\n",
   1216                       ErrorString(err), kDescriptions[config->type], Err2);
   1217         }
   1218       } else {
   1219         ERROR_GOTO1("ERROR: Invalid feature for action 'strip'.\n", Err2);
   1220         break;
   1221       }
   1222       ok = WriteWebP(mux, config->output);
   1223       break;
   1224     }
   1225     case ACTION_INFO: {
   1226       ok = CreateMux(config->input, &mux);
   1227       if (!ok) goto Err2;
   1228       ok = (DisplayInfo(mux) == WEBP_MUX_OK);
   1229       break;
   1230     }
   1231     default: {
   1232       assert(0);  // Invalid action.
   1233       break;
   1234     }
   1235   }
   1236 
   1237  Err2:
   1238   WebPMuxDelete(mux);
   1239   return ok;
   1240 }
   1241 
   1242 //------------------------------------------------------------------------------
   1243 // Main.
   1244 
   1245 // Returns EXIT_SUCCESS on success, EXIT_FAILURE on failure.
   1246 int main(int argc, const char* argv[]) {
   1247   Config config;
   1248   int ok;
   1249 
   1250   INIT_WARGV(argc, argv);
   1251 
   1252   ok = InitializeConfig(argc - 1, argv + 1, &config, GET_WARGV_OR_NULL());
   1253   if (ok) {
   1254     ok = Process(&config);
   1255   } else {
   1256     PrintHelp();
   1257   }
   1258   DeleteConfig(&config);
   1259   FREE_WARGV_AND_RETURN(ok ? EXIT_SUCCESS : EXIT_FAILURE);
   1260 }
   1261 
   1262 //------------------------------------------------------------------------------