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