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