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