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