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