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