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