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, ¶ms);
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, ¶ms);
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, ¶ms) == 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, ¶ms);
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, ¶ms) == 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, ¶ms);
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