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