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