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