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