blob: 9a17db1186d7060e5ed4f7de52168067bdef6278 [file] [log] [blame]
Urvang Joshia4f32ca2011-09-30 11:07:01 +05301// Copyright 2011 Google Inc.
2//
3// This code is licensed under the same terms as WebM:
4// Software License Agreement: http://www.webmproject.org/license/software/
5// Additional IP Rights Grant: http://www.webmproject.org/license/additional/
6// -----------------------------------------------------------------------------
7//
8// Simple command-line to create a WebP container file and to extract or strip
9// relevant data from the container file.
10//
11// Compile with: gcc -o webpmux webpmux.c -lwebpmux
12//
13//
14// Authors: Vikas (vikaas.arora@gmail.com),
15// Urvang (urvang@google.com)
16
17/* Usage examples:
18
19 Create container WebP file:
20
21 webpmux -tile tile_1.webp +0+0 \
22 -tile tile_2.webp +960+0 \
23 -tile tile_3.webp +0+576 \
24 -tile tile_4.webp +960+576 \
25 -o out_tile_container.webp
26
27 webpmux -frame anim_1.webp +0+0+0 \
28 -frame anim_2.webp +25+25+100 \
29 -frame anim_3.webp +50+50+100 \
30 -frame anim_4.webp +0+0+100 \
31 -loop 10 \
32 -o out_animation_container.webp
33
34 webpmux -set icc image_profile.icc in.webp -o out_icc_container.webp
35
36 webpmux -set xmp image_metadata.xmp in.webp -o out_xmp_container.webp
37
38
39 Extract relevant data from WebP container file:
40
41 webpmux -get tile n in.webp -o out_tile.webp
42
43 webpmux -get frame n in.webp -o out_frame.webp
44
45 webpmux -get icc in.webp -o image_profile.icc
46
47 webpmux -get xmp in.webp -o image_metadata.xmp
48
49
50 Strip data from WebP Container file:
51
52 webmux -strip icc in.webp -o out.webp
53
54 webmux -strip xmp in.webp -o out.webp
55
56
57 Misc:
58
59 webpmux -info in.webp
60
61 webpmux -help
62
63 webpmux -h
64*/
65
66#include <assert.h>
67#include <stdio.h>
68#include <stdlib.h>
69#include <string.h>
70#include "webp/mux.h"
71
72#if defined(__cplusplus) || defined(c_plusplus)
73extern "C" {
74#endif
75
76//------------------------------------------------------------------------------
77// Config object to parse command-line arguments.
78
79typedef enum {
80 NIL_ACTION = 0,
81 ACTION_GET,
82 ACTION_SET,
83 ACTION_STRIP,
84 ACTION_INFO,
85 ACTION_HELP
86} ActionType;
87
88typedef enum {
89 NIL_SUBTYPE = 0,
90 SUBTYPE_FRM,
91 SUBTYPE_LOOP
92} FeatureSubType;
93
94typedef struct {
95 FeatureSubType subtype_;
96 const char* filename_;
97 const char* params_;
98} FeatureArg;
99
100typedef enum {
101 NIL_FEATURE = 0,
102 FEATURE_XMP,
103 FEATURE_ICCP,
104 FEATURE_FRM,
105 FEATURE_TILE
106} FeatureType;
107
108typedef struct {
109 FeatureType type_;
110 FeatureArg* args_;
111 int arg_count_;
112} Feature;
113
114typedef struct {
115 ActionType action_type_;
116 const char* input_;
117 const char* output_;
118 Feature feature_;
119} WebPMuxConfig;
120
121//------------------------------------------------------------------------------
122// Helper functions.
123
124static int CountOccurences(const char* arglist[], int list_length,
125 const char* arg) {
126 int i;
127 int num_occurences = 0;
128
129 for (i = 0; i < list_length; ++i) {
130 if (!strcmp(arglist[i], arg)) {
131 ++num_occurences;
132 }
133 }
134 return num_occurences;
135}
136
137static int IsNotCompatible(int count1, int count2) {
138 return (count1 > 0) != (count2 > 0);
139}
140
141#define RETURN_IF_ERROR(ERR_MSG) \
142 if (err != WEBP_MUX_OK) { \
143 fprintf(stderr, ERR_MSG); \
144 return err; \
145 }
146
147#define RETURN_IF_ERROR2(ERR_MSG, FORMAT_STR) \
148 if (err != WEBP_MUX_OK) { \
149 fprintf(stderr, ERR_MSG, FORMAT_STR); \
150 return err; \
151 }
152
153#define ERROR_GOTO1(ERR_MSG, LABEL) \
154 fprintf(stderr, ERR_MSG); \
155 ok = 0; \
156 goto LABEL;
157
158#define ERROR_GOTO2(ERR_MSG, FORMAT_STR, LABEL) \
159 fprintf(stderr, ERR_MSG, FORMAT_STR); \
160 ok = 0; \
161 goto LABEL;
162
163#define ERROR_GOTO3(ERR_MSG, FORMAT_STR1, FORMAT_STR2, LABEL) \
164 fprintf(stderr, ERR_MSG, FORMAT_STR1, FORMAT_STR2); \
165 ok = 0; \
166 goto LABEL;
167
168static WebPMuxError DisplayInfo(const WebPMux* mux) {
169 int nFrames;
170 int nTiles;
171 const uint8_t* data = NULL;
172 uint32_t size = 0;
173 uint32_t flag;
174
175 WebPMuxError err = WebPMuxGetFeatures(mux, &flag);
176 RETURN_IF_ERROR("Failed to retrieve features\n");
177
178 if (flag == 0) {
179 fprintf(stderr, "No features present.\n");
180 return err;
181 }
182
183 // Print the features present.
184 fprintf(stderr, "Features present:");
185 if (flag & ANIMATION_FLAG) fprintf(stderr, " animation");
186 if (flag & TILE_FLAG) fprintf(stderr, " tiling");
187 if (flag & ICCP_FLAG) fprintf(stderr, " icc profile");
188 if (flag & META_FLAG) fprintf(stderr, " metadata");
189 fprintf(stderr, "\n");
190
191 if (flag & ANIMATION_FLAG) {
192 uint32_t loop_count;
193 err = WebPMuxGetLoopCount(mux, &loop_count);
194 RETURN_IF_ERROR("Failed to retrieve loop count\n");
195 fprintf(stderr, "Loop Count : %d\n", loop_count);
196
197 err = WebPMuxNumNamedElements(mux, "frame", &nFrames);
198 RETURN_IF_ERROR("Failed to retrieve number of frames\n");
199
200 fprintf(stderr, "Number of frames: %d\n", nFrames);
201 if (nFrames > 0) {
202 int i;
203 uint32_t x_offset, y_offset, duration;
204
205 fprintf(stderr, "No : x_offset y_offset duration\n");
206 for (i = 1; i <= nFrames; i++) {
207 err = WebPMuxGetFrame(mux, i, &data, &size, &x_offset, &y_offset,
208 &duration);
209 assert(err == WEBP_MUX_OK);
210 fprintf(stderr, "%3d: %8d %8d %8d\n", i, x_offset, y_offset, duration);
211 }
212 }
213 }
214
215 if (flag & TILE_FLAG) {
216 err = WebPMuxNumNamedElements(mux, "tile", &nTiles);
217 RETURN_IF_ERROR("Failed to retrieve number of tiles\n");
218
219 fprintf(stderr, "Number of tiles: %d\n", nTiles);
220 if (nTiles > 0) {
221 int i;
222 uint32_t x_offset, y_offset;
223 fprintf(stderr, "No : x_offset y_offset\n");
224 for (i = 1; i <= nTiles; i++) {
225 err = WebPMuxGetTile(mux, i, &data, &size, &x_offset, &y_offset);
226 assert(err == WEBP_MUX_OK);
227 fprintf(stderr, "%3d: %8d %8d\n", i, x_offset, y_offset);
228 }
229 }
230 }
231
232 if (flag & ICCP_FLAG) {
233 err = WebPMuxGetColorProfile(mux, &data, &size);
234 RETURN_IF_ERROR("Failed to retrieve the color profile\n");
235 fprintf(stderr, "Size of the color profile data: %d\n", size);
236 }
237
238 if (flag & META_FLAG) {
239 err = WebPMuxGetMetadata(mux, &data, &size);
240 RETURN_IF_ERROR("Failed to retrieve the XMP metadata\n");
241 fprintf(stderr, "Size of the XMP metadata: %d\n", size);
242 }
243
244 return WEBP_MUX_OK;
245}
246
247static void PrintHelp() {
248 fprintf(stderr, "Usage: webpmux -get GET_OPTIONS INPUT -o OUTPUT "
249 " Extract relevant data.\n");
250 fprintf(stderr, " or: webpmux -set SET_OPTIONS INPUT -o OUTPUT "
251 " Set color profile/metadata.\n");
252 fprintf(stderr, " or: webpmux -strip STRIP_OPTIONS INPUT -o OUTPUT "
253 " Strip color profile/metadata.\n");
254 fprintf(stderr, " or: webpmux [-tile TILE_OPTIONS]... -o OUTPUT "
255 " Create tiled image.\n");
256 fprintf(stderr, " or: webpmux [-frame FRAME_OPTIONS]... -loop LOOP_COUNT"
257 " -o OUTPUT Create animation.\n");
258 fprintf(stderr, " or: webpmux -info INPUT "
259 " Print info about given webp file.\n");
260 fprintf(stderr, " or: webpmux -help OR -h "
261 " Print this help.\n");
262
263 fprintf(stderr, "\n");
264 fprintf(stderr, "GET_OPTIONS:\n");
265 fprintf(stderr, " icc Get ICCP Color profile.\n");
266 fprintf(stderr, " xmp Get XMP metadata.\n");
267 fprintf(stderr, " tile n Get nth tile.\n");
268 fprintf(stderr, " frame n Get nth frame.\n");
269
270 fprintf(stderr, "\n");
271 fprintf(stderr, "SET_OPTIONS:\n");
272 fprintf(stderr, " icc Set ICC Color profile.\n");
273 fprintf(stderr, " xmp Set XMP metadata.\n");
274
275 fprintf(stderr, "\n");
276 fprintf(stderr, "STRIP_OPTIONS:\n");
277 fprintf(stderr, " icc Strip ICCP color profile.\n");
278 fprintf(stderr, " xmp Strip XMP metadata.\n");
279
280 fprintf(stderr, "\n");
281 fprintf(stderr, "TILE_OPTIONS(i):\n");
282 fprintf(stderr, " file_i +xi+yi\n");
283 fprintf(stderr, " where: 'file_i' is the i'th tile (webp format),\n");
284 fprintf(stderr, " 'xi','yi' specify the image offset for this "
285 "tile.\n");
286
287 fprintf(stderr, "\n");
288 fprintf(stderr, "FRAME_OPTIONS(i):\n");
289 fprintf(stderr, " file_i +xi+yi+di\n");
290 fprintf(stderr, " where: 'file_i' is the i'th animation frame (webp "
291 "format),\n");
292 fprintf(stderr, " 'xi','yi' specify the image offset for this "
293 "frame.\n");
294 fprintf(stderr, " 'di' is the pause duration before next frame."
295 "\n");
296
297 fprintf(stderr, "\n");
298 fprintf(stderr, "INPUT & OUTPUT are in webp format.");
299 fprintf(stderr, "\n");
300}
301
302static int ReadData(const char* filename, void** data_ptr, uint32_t* size_ptr) {
303 void* data = NULL;
304 long size = 0;
305 int ok = 0;
306 FILE* in;
307
308 *size_ptr = 0;
309 in = fopen(filename, "rb");
310 if (!in) {
311 fprintf(stderr, "Failed to open file %s\n", filename);
312 return 0;
313 }
314 fseek(in, 0, SEEK_END);
315 size = ftell(in);
316 fseek(in, 0, SEEK_SET);
317 if (size > 0xffffffffu) {
318 fprintf(stderr, "Size (%ld bytes) is out of range for file %s\n",
319 size, filename);
320 size = 0;
321 goto Err;
322 }
323 if (size < 0) {
324 size = 0;
325 goto Err;
326 }
327 data = malloc(size);
328 if (data) {
329 if (fread(data, size, 1, in) != 1) {
330 free(data);
331 data = NULL;
332 size = 0;
333 fprintf(stderr, "Failed to read %ld bytes from file %s\n",
334 size, filename);
335 goto Err;
336 }
337 ok = 1;
338 } else {
339 fprintf(stderr, "Failed to allocate %ld bytes for reading file %s\n",
340 size, filename);
341 size = 0;
342 }
343
344 Err:
345 if (in != stdin) fclose(in);
346 *size_ptr = (uint32_t)size;
347 *data_ptr = data;
348 return ok;
349}
350
351static int ReadFile(const char* const filename, WebPMux** mux) {
352 uint32_t size = 0;
353 void* data = NULL;
354
355 assert(mux != NULL);
356
357 if (!ReadData(filename, &data, &size)) return 0;
358 *mux = WebPMuxCreate((const uint8_t*)data, size, 1);
359 free(data);
360 if (*mux != NULL) return 1;
361 fprintf(stderr, "Failed to create mux object from file %s.\n",
362 filename);
363 return 0;
364}
365
366static int ReadImageData(const char* filename, int image_index,
367 const uint8_t** data_ptr, uint32_t* size_ptr) {
368 uint32_t size = 0;
369 void* data = NULL;
370 WebPMux* mux;
371 WebPMuxError err;
372 int ok = 1;
373
374 if (!ReadData(filename, &data, &size)) return 0;
375
376 mux = WebPMuxCreate((const uint8_t*)data, size, 1);
377 free(data);
378 if (mux == NULL) {
379 fprintf(stderr, "Failed to create mux object from file %s.\n",
380 filename);
381 return 0;
382 }
383 err = WebPMuxGetNamedData(mux, "image", image_index, (const uint8_t**)&data,
384 &size);
385 if (err == WEBP_MUX_OK) {
386 *size_ptr = size;
387 *data_ptr = (uint8_t*)malloc(*size_ptr);
388 if (*data_ptr != NULL) {
389 memcpy((void*)*data_ptr, data, (size_t)size);
390 } else {
391 err = WEBP_MUX_MEMORY_ERROR;
392 fprintf(stderr, "Failed to allocate %d bytes to extract image data from"
393 " file %s. Error: %d\n", size, filename, err);
394 ok = 0;
395 }
396 } else {
397 fprintf(stderr, "Failed to extract image data from file %s. Error: %d\n",
398 filename, err);
399 ok = 0;
400 }
401 WebPMuxDelete(mux);
402 return ok;
403}
404
405static int WriteData(const char* filename, void* data, uint32_t size) {
406 int ok = 0;
407 FILE* fout = strcmp(filename, "--") ? fopen(filename, "wb") : stdout;
408 if (!fout) {
409 fprintf(stderr, "Error opening output WebP file %s!\n", filename);
410 return 0;
411 }
412 if (fwrite(data, size, 1, fout) != 1) {
413 fprintf(stderr, "Error writing file %s!\n", filename);
414 } else {
415 fprintf(stderr, "Saved file %s (%d bytes)\n", filename, 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 uint8_t* data = NULL;
424 uint32_t size = 0;
425 int ok;
426
427 WebPMuxError err = WebPMuxAssemble(mux, &data, &size);
428 if (err != WEBP_MUX_OK) {
429 fprintf(stderr, "Error (%d) assembling the WebP file.\n", err);
430 return 0;
431 }
432 ok = WriteData(filename, data, size);
433 free(data);
434 return ok;
435}
436
437static int ParseFrameArgs(const char* args, uint32_t* x_offset,
438 uint32_t* y_offset, uint32_t* duration) {
439 return (sscanf(args, "+%d+%d+%d", x_offset, y_offset, duration) == 3);
440}
441
442static int ParseTileArgs(const char* args, uint32_t* x_offset,
443 uint32_t* y_offset) {
444 return (sscanf(args, "+%d+%d", x_offset, y_offset) == 2);
445}
446
447//------------------------------------------------------------------------------
448// Clean-up.
449
450static void DeleteConfig(WebPMuxConfig* config) {
451 if (config != NULL) {
452 free(config->feature_.args_);
453 free(config);
454 }
455}
456
457//------------------------------------------------------------------------------
458// Parsing.
459
460// Basic syntactic checks on the command-line arguments.
461// Returns 1 on valid, 0 otherwise.
462// Also fills up num_feature_args to be number of feature arguments given.
463// (e.g. if there are 4 '-frame's and 1 '-loop', then num_feature_args = 5).
464static int ValidateCommandLine(int argc, const char* argv[],
465 int* num_feature_args) {
466 int num_frame_args;
467 int num_tile_args;
468 int num_loop_args;
469 int ok = 1;
470
471 assert(num_feature_args != NULL);
472 *num_feature_args = 0;
473
474 // Simple checks.
475 if (CountOccurences(argv, argc, "-get") > 1) {
476 ERROR_GOTO1("ERROR: Multiple '-get' arguments specified.\n", ErrValidate);
477 }
478 if (CountOccurences(argv, argc, "-set") > 1) {
479 ERROR_GOTO1("ERROR: Multiple '-set' arguments specified.\n", ErrValidate);
480 }
481 if (CountOccurences(argv, argc, "-strip") > 1) {
482 ERROR_GOTO1("ERROR: Multiple '-strip' arguments specified.\n", ErrValidate);
483 }
484 if (CountOccurences(argv, argc, "-info") > 1) {
485 ERROR_GOTO1("ERROR: Multiple '-info' arguments specified.\n", ErrValidate);
486 }
487 if (CountOccurences(argv, argc, "-help") > 1) {
488 ERROR_GOTO1("ERROR: Multiple '-help' arguments specified.\n", ErrValidate);
489 }
490 if (CountOccurences(argv, argc, "-o") > 1) {
491 ERROR_GOTO1("ERROR: Multiple output files specified.\n", ErrValidate);
492 }
493
494 // Compound checks.
495 num_frame_args = CountOccurences(argv, argc, "-frame");
496 num_tile_args = CountOccurences(argv, argc, "-tile");
497 num_loop_args = CountOccurences(argv, argc, "-loop");
498
499 if (num_loop_args > 1) {
500 ERROR_GOTO1("ERROR: Multiple loop counts specified.\n", ErrValidate);
501 }
502
503 if (IsNotCompatible(num_frame_args, num_loop_args)) {
504 ERROR_GOTO1("ERROR: Both frames and loop count have to be specified.\n",
505 ErrValidate);
506 }
507 if (num_frame_args > 0 && num_tile_args > 0) {
508 ERROR_GOTO1("ERROR: Only one of frames & tiles can be specified at a time."
509 "\n", ErrValidate);
510 }
511
512 assert(ok == 1);
513 if (num_frame_args == 0 && num_tile_args == 0) {
514 // Single argument ('set' action for XMP or ICCP, OR a 'get' action).
515 *num_feature_args = 1;
516 } else {
517 // Multiple arguments ('set' action for animation or tiling).
518 if (num_frame_args > 0) {
519 *num_feature_args = num_frame_args + num_loop_args;
520 } else {
521 *num_feature_args = num_tile_args;
522 }
523 }
524
525 ErrValidate:
526 return ok;
527}
528
529#define ACTION_IS_NIL (config->action_type_ == NIL_ACTION)
530
531#define FEATURETYPE_IS_NIL (feature->type_ == NIL_FEATURE)
532
533#define CHECK_NUM_ARGS_LESS(NUM, LABEL) \
534 if (argc < i + (NUM)) { \
535 fprintf(stderr, "ERROR: Too few arguments for '%s'.\n", argv[i]); \
536 goto LABEL; \
537 }
538
539#define CHECK_NUM_ARGS_NOT_EQUAL(NUM, LABEL) \
540 if (argc != i + (NUM)) { \
541 fprintf(stderr, "ERROR: Too many arguments for '%s'.\n", argv[i]); \
542 goto LABEL; \
543 }
544
545// Parses command-line arguments to fill up config object. Also performs some
546// semantic checks.
547static int ParseCommandLine(int argc, const char* argv[],
548 WebPMuxConfig* config) {
549 int i = 0;
550 int feature_arg_index = 0;
551 int ok = 1;
552
553 while (i < argc) {
554 Feature* const feature = &config->feature_;
555 FeatureArg* arg = &feature->args_[feature_arg_index];
556 if (argv[i][0] == '-') { // One of the action types or output.
557 if (!strcmp(argv[i], "-set")) {
558 if (ACTION_IS_NIL) {
559 config->action_type_ = ACTION_SET;
560 } else {
561 ERROR_GOTO1("ERROR: Multiple actions specified.\n", ErrParse);
562 }
563 ++i;
564 } else if (!strcmp(argv[i], "-get")) {
565 if (ACTION_IS_NIL) {
566 config->action_type_ = ACTION_GET;
567 } else {
568 ERROR_GOTO1("ERROR: Multiple actions specified.\n", ErrParse);
569 }
570 ++i;
571 } else if (!strcmp(argv[i], "-strip")) {
572 if (ACTION_IS_NIL) {
573 config->action_type_ = ACTION_STRIP;
574 feature->arg_count_ = 0;
575 } else {
576 ERROR_GOTO1("ERROR: Multiple actions specified.\n", ErrParse);
577 }
578 ++i;
579 } else if (!strcmp(argv[i], "-frame")) {
580 CHECK_NUM_ARGS_LESS(3, ErrParse);
581 if (ACTION_IS_NIL || config->action_type_ == ACTION_SET) {
582 config->action_type_ = ACTION_SET;
583 } else {
584 ERROR_GOTO1("ERROR: Multiple actions specified.\n", ErrParse);
585 }
586 if (FEATURETYPE_IS_NIL || feature->type_ == FEATURE_FRM) {
587 feature->type_ = FEATURE_FRM;
588 } else {
589 ERROR_GOTO1("ERROR: Multiple features specified.\n", ErrParse);
590 }
591 arg->subtype_ = SUBTYPE_FRM;
592 arg->filename_ = argv[i + 1];
593 arg->params_ = argv[i + 2];
594 ++feature_arg_index;
595 i += 3;
596 } else if (!strcmp(argv[i], "-loop")) {
597 CHECK_NUM_ARGS_LESS(2, ErrParse);
598 if (ACTION_IS_NIL || config->action_type_ == ACTION_SET) {
599 config->action_type_ = ACTION_SET;
600 } else {
601 ERROR_GOTO1("ERROR: Multiple actions specified.\n", ErrParse);
602 }
603 if (FEATURETYPE_IS_NIL || feature->type_ == FEATURE_FRM) {
604 feature->type_ = FEATURE_FRM;
605 } else {
606 ERROR_GOTO1("ERROR: Multiple features specified.\n", ErrParse);
607 }
608 arg->subtype_ = SUBTYPE_LOOP;
609 arg->params_ = argv[i + 1];
610 ++feature_arg_index;
611 i += 2;
612 } else if (!strcmp(argv[i], "-tile")) {
613 CHECK_NUM_ARGS_LESS(3, ErrParse);
614 if (ACTION_IS_NIL || config->action_type_ == ACTION_SET) {
615 config->action_type_ = ACTION_SET;
616 } else {
617 ERROR_GOTO1("ERROR: Multiple actions specified.\n", ErrParse);
618 }
619 if (FEATURETYPE_IS_NIL || feature->type_ == FEATURE_TILE) {
620 feature->type_ = FEATURE_TILE;
621 } else {
622 ERROR_GOTO1("ERROR: Multiple features specified.\n", ErrParse);
623 }
624 arg->filename_ = argv[i + 1];
625 arg->params_ = argv[i + 2];
626 ++feature_arg_index;
627 i += 3;
628 } else if (!strcmp(argv[i], "-o")) {
629 CHECK_NUM_ARGS_LESS(2, ErrParse);
630 config->output_ = argv[i + 1];
631 i += 2;
632 } else if (!strcmp(argv[i], "-info")) {
633 CHECK_NUM_ARGS_NOT_EQUAL(2, ErrParse);
634 if (config->action_type_ != NIL_ACTION) {
635 ERROR_GOTO1("ERROR: Multiple actions specified.\n", ErrParse);
636 } else {
637 config->action_type_ = ACTION_INFO;
638 feature->arg_count_ = 0;
639 config->input_ = argv[i + 1];
640 }
641 i += 2;
642 } else if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "-help")) {
643 PrintHelp();
644 DeleteConfig(config);
645 exit(1);
646 } else {
647 ERROR_GOTO2("ERROR: Unknown option: '%s'.\n", argv[i], ErrParse);
648 }
649 } else { // One of the feature types or input.
650 if (ACTION_IS_NIL) {
651 ERROR_GOTO1("ERROR: Action must be specified before other arguments.\n",
652 ErrParse);
653 }
654 if (!strcmp(argv[i], "icc") || !strcmp(argv[i], "xmp")) {
655 if (FEATURETYPE_IS_NIL) {
656 feature->type_ = (!strcmp(argv[i], "icc")) ? FEATURE_ICCP :
657 FEATURE_XMP;
658 } else {
659 ERROR_GOTO1("ERROR: Multiple features specified.\n", ErrParse);
660 }
661 if (config->action_type_ == ACTION_SET) {
662 CHECK_NUM_ARGS_LESS(2, ErrParse);
663 arg->filename_ = argv[i + 1];
664 ++feature_arg_index;
665 i += 2;
666 } else {
667 ++i;
668 }
669 } else if ((!strcmp(argv[i], "frame") ||
670 !strcmp(argv[i], "tile")) &&
671 (config->action_type_ == ACTION_GET)) {
672 CHECK_NUM_ARGS_LESS(2, ErrParse);
673 feature->type_ = (!strcmp(argv[i], "frame")) ? FEATURE_FRM :
674 FEATURE_TILE;
675 arg->params_ = argv[i + 1];
676 ++feature_arg_index;
677 i += 2;
678 } else { // Assume input file.
679 if (config->input_ == NULL) {
680 config->input_ = argv[i];
681 } else {
682 ERROR_GOTO2("ERROR at '%s': Multiple input files specified.\n",
683 argv[i], ErrParse);
684 }
685 ++i;
686 }
687 }
688 }
689 ErrParse:
690 return ok;
691}
692
693// Additional Checks after config is filled.
694static int ValidateConfig(WebPMuxConfig* config) {
695 int ok = 1;
696 Feature* const feature = &config->feature_;
697
698 // Action.
699 if (ACTION_IS_NIL) {
700 ERROR_GOTO1("ERROR: No action specified.\n", ErrValidate2);
701 }
702
703 // Feature type.
704 if (FEATURETYPE_IS_NIL && config->action_type_ != ACTION_INFO) {
705 ERROR_GOTO1("ERROR: No feature specified.\n", ErrValidate2);
706 }
707
708 // Input file.
709 if (config->input_ == NULL) {
710 if (config->action_type_ != ACTION_SET) {
711 ERROR_GOTO1("ERROR: No input file specified.\n", ErrValidate2);
712 } else if (feature->type_ != FEATURE_FRM &&
713 feature->type_ != FEATURE_TILE) {
714 ERROR_GOTO1("ERROR: No input file specified.\n", ErrValidate2);
715 }
716 }
717
718 // Output file.
719 if (config->output_ == NULL && config->action_type_ != ACTION_INFO) {
720 ERROR_GOTO1("ERROR: No output file specified.\n", ErrValidate2);
721 }
722
723 ErrValidate2:
724 return ok;
725}
726
727// Create config object from command-line arguments.
728static int InitializeConfig(int argc, const char* argv[],
729 WebPMuxConfig** config) {
730 int num_feature_args = 0;
731 int ok = 1;
732
733 assert(config != NULL);
734 *config = NULL;
735
736 // Validate command-line arguments.
737 if (!ValidateCommandLine(argc, argv, &num_feature_args)) {
738 ERROR_GOTO1("Exiting due to command-line parsing error.\n", Err1);
739 }
740
741 // Allocate memory.
742 *config = (WebPMuxConfig*)calloc(1, sizeof(**config));
743 if (*config == NULL) {
744 ERROR_GOTO1("ERROR: Memory allocation error.\n", Err1);
745 }
746 (*config)->feature_.arg_count_ = num_feature_args;
747 (*config)->feature_.args_ =
748 (FeatureArg*)calloc(num_feature_args, sizeof(FeatureArg));
749 if ((*config)->feature_.args_ == NULL) {
750 ERROR_GOTO1("ERROR: Memory allocation error.\n", Err1);
751 }
752
753 // Parse command-line.
754 if (!ParseCommandLine(argc, argv, *config)) {
755 ERROR_GOTO1("Exiting due to command-line parsing error.\n", Err1);
756 }
757
758 // Additional Checks.
759 if (!ValidateConfig(*config)) {
760 ERROR_GOTO1("Exiting due to command-line parsing error.\n", Err1);
761 }
762
763 Err1:
764 return ok;
765}
766
767#undef ACTION_IS_NIL
768#undef FEATURETYPE_IS_NIL
769#undef CHECK_NUM_ARGS_LESS
770#undef CHECK_NUM_ARGS_MORE
771
772//------------------------------------------------------------------------------
773// Processing.
774
775static int GetFrameTile(WebPMux* mux, WebPMuxConfig* config, int isFrame) {
776 const uint8_t* data = NULL;
777 uint32_t size = 0;
778 uint32_t x_offset = 0;
779 uint32_t y_offset = 0;
780 uint32_t duration = 0;
781 WebPMuxError err = WEBP_MUX_OK;
782 WebPMux* mux_single = NULL;
783 long num = 0;
784 int ok = 1;
785
786 num = strtol(config->feature_.args_[0].params_, NULL, 10);
787 if (num < 0) {
788 ERROR_GOTO1("ERROR: Frame/Tile index must be non-negative.\n", ErrGet);
789 }
790
791 if (isFrame) {
792 err = WebPMuxGetFrame(mux, num, &data, &size, &x_offset, &y_offset,
793 &duration);
794 if (err != WEBP_MUX_OK) {
795 ERROR_GOTO3("ERROR#%d: Could not get frame %ld.\n", err, num, ErrGet);
796 }
797 } else {
798 err = WebPMuxGetTile(mux, num, &data, &size, &x_offset, &y_offset);
799 if (err != WEBP_MUX_OK) {
800 ERROR_GOTO3("ERROR#%d: Could not get frame %ld.\n", err, num, ErrGet);
801 }
802 }
803
804 mux_single = WebPMuxNew();
805 if (mux_single == NULL) {
806 err = WEBP_MUX_MEMORY_ERROR;
807 ERROR_GOTO2("ERROR#%d: Could not allocate a mux object.\n", err, ErrGet);
808 }
809 err = WebPMuxAddNamedData(mux_single, 0, "image", data, size, 1);
810 if (err != WEBP_MUX_OK) {
811 ERROR_GOTO2("ERROR#%d: Could not create single image mux object.\n", err,
812 ErrGet);
813 }
814 ok = WriteWebP(mux_single, config->output_);
815
816 ErrGet:
817 WebPMuxDelete(mux_single);
818 return ok;
819}
820
821// Read and process config.
822static int Process(WebPMuxConfig* config) {
823 WebPMux* mux = NULL;
824 const uint8_t* data = NULL;
825 uint32_t size = 0;
826 uint32_t x_offset = 0;
827 uint32_t y_offset = 0;
828 uint32_t duration = 0;
829 uint32_t loop_count = 0;
830 WebPMuxError err = WEBP_MUX_OK;
831 long num;
832 int index = 0;
833 int ok = 1;
834 Feature* const feature = &config->feature_;
835
836 switch(config->action_type_) {
837 case ACTION_GET:
838 ok = ReadFile(config->input_, &mux);
839 if (!ok) goto Err2;
840 switch(feature->type_) {
841 case FEATURE_FRM:
842 ok = GetFrameTile(mux, config, 1);
843 break;
844
845 case FEATURE_TILE:
846 ok = GetFrameTile(mux, config, 0);
847 break;
848
849 case FEATURE_ICCP:
850 err = WebPMuxGetColorProfile(mux, &data, &size);
851 if (err != WEBP_MUX_OK) {
852 ERROR_GOTO2("ERROR#%d: Could not get color profile.\n", err, Err2);
853 }
854 ok = WriteData(config->output_, (void*)data, size);
855 break;
856
857 case FEATURE_XMP:
858 err = WebPMuxGetMetadata(mux, &data, &size);
859 if (err != WEBP_MUX_OK) {
860 ERROR_GOTO2("ERROR#%d: Could not get XMP metadata.\n", err, Err2);
861 }
862 ok = WriteData(config->output_, (void*)data, size);
863 break;
864
865 default:
866 ERROR_GOTO1("ERROR: Invalid feature for action 'get'.\n", Err2);
867 break;
868 }
869 break;
870
871 case ACTION_SET:
872 switch(feature->type_) {
873 case FEATURE_FRM:
874 mux = WebPMuxNew();
875 if (mux == NULL) {
876 ERROR_GOTO2("ERROR#%d: Could not allocate a mux object.\n",
877 WEBP_MUX_MEMORY_ERROR, Err2);
878 }
879 for (index = 0; index < feature->arg_count_; ++index) {
880 if (feature->args_[index].subtype_ == SUBTYPE_LOOP) {
881 num = strtol(feature->args_[index].params_, NULL, 10);
882 if (num < 0) {
883 ERROR_GOTO1("ERROR: Loop count must be non-negative.\n", Err2);
884 } else {
885 loop_count = num;
886 }
887 err = WebPMuxSetLoopCount(mux, loop_count);
888 if (err != WEBP_MUX_OK) {
889 ERROR_GOTO2("ERROR#%d: Could not set loop count.\n", err, Err2);
890 }
891 } else if (feature->args_[index].subtype_ == SUBTYPE_FRM) {
892 ok = ReadImageData(feature->args_[index].filename_, 1,
893 &data, &size);
894 if (!ok) goto Err2;
895 ok = ParseFrameArgs(feature->args_[index].params_,
896 &x_offset, &y_offset, &duration);
897 if (!ok) {
898 free((void*)data);
899 ERROR_GOTO1("ERROR: Could not parse frame properties.\n", Err2);
900 }
901 err = WebPMuxAddFrame(mux, 0, data, size, x_offset, y_offset,
902 duration, 1);
903 free((void*)data);
904 if (err != WEBP_MUX_OK) {
905 ERROR_GOTO3("ERROR#%d: Could not add a frame at index %d.\n",
906 err, index, Err2);
907 }
908 } else {
909 ERROR_GOTO1("ERROR: Invalid subtype for 'frame'", Err2);
910 }
911 }
912 break;
913
914 case FEATURE_TILE:
915 mux = WebPMuxNew();
916 if (mux == NULL) {
917 ERROR_GOTO2("ERROR#%d: Could not allocate a mux object.\n",
918 WEBP_MUX_MEMORY_ERROR, Err2);
919 }
920 for (index = 0; index < feature->arg_count_; ++index) {
921 ok = ReadImageData(feature->args_[index].filename_, 1,
922 &data, &size);
923 if (!ok) goto Err2;
924 ok = ParseTileArgs(feature->args_[index].params_, &x_offset,
925 &y_offset);
926 if (!ok) {
927 free((void*)data);
928 ERROR_GOTO1("ERROR: Could not parse tile properties.\n", Err2);
929 }
930 err = WebPMuxAddTile(mux, 0, data, size, x_offset, y_offset, 1);
931 free((void*)data);
932 if (err != WEBP_MUX_OK) {
933 ERROR_GOTO3("ERROR#%d: Could not add a tile at index %d.\n",
934 err, index, Err2);
935 }
936 }
937 break;
938
939 case FEATURE_ICCP:
940 ok = ReadFile(config->input_, &mux);
941 if (!ok) goto Err2;
942 ok = ReadData(feature->args_[0].filename_, (void**)&data,
943 &size);
944 if (!ok) goto Err2;
945 err = WebPMuxSetColorProfile(mux, data, size, 1);
946 free((void*)data);
947 if (err != WEBP_MUX_OK) {
948 ERROR_GOTO2("ERROR#%d: Could not set color profile.\n", err, Err2);
949 }
950 break;
951
952 case FEATURE_XMP:
953 ok = ReadFile(config->input_, &mux);
954 if (!ok) goto Err2;
955 ok = ReadData(feature->args_[0].filename_, (void**)&data,
956 &size);
957 if (!ok) goto Err2;
958 err = WebPMuxSetMetadata(mux, data, size, 1);
959 free((void*)data);
960 if (err != WEBP_MUX_OK) {
961 ERROR_GOTO2("ERROR#%d: Could not set XMP metadata.\n", err, Err2);
962 }
963 break;
964
965 default:
966 ERROR_GOTO1("ERROR: Invalid feature for action 'set'.\n", Err2);
967 break;
968 }
969 ok = WriteWebP(mux, config->output_);
970 break;
971
972 case ACTION_STRIP:
973 ok = ReadFile(config->input_, &mux);
974 if (!ok) goto Err2;
975 switch(feature->type_) {
976 case FEATURE_ICCP:
977 err = WebPMuxDeleteColorProfile(mux);
978 if (err != WEBP_MUX_OK) {
979 ERROR_GOTO2("ERROR#%d: Could not delete color profile.\n", err,
980 Err2);
981 }
982 break;
983 case FEATURE_XMP:
984 err = WebPMuxDeleteMetadata(mux);
985 if (err != WEBP_MUX_OK) {
986 ERROR_GOTO2("ERROR#%d: Could not delete XMP metadata.\n", err,
987 Err2);
988 }
989 break;
990 default:
991 ERROR_GOTO1("ERROR: Invalid feature for action 'strip'.\n", Err2);
992 break;
993 }
994 ok = WriteWebP(mux, config->output_);
995 break;
996
997 case ACTION_INFO:
998 ok = ReadFile(config->input_, &mux);
999 if (!ok) goto Err2;
1000 ok = (DisplayInfo(mux) == WEBP_MUX_OK);
1001 break;
1002
1003 default:
1004 assert(0); // Invalid action.
1005 break;
1006 }
1007
1008 Err2:
1009 WebPMuxDelete(mux);
1010 return ok;
1011}
1012
1013//------------------------------------------------------------------------------
1014// Main.
1015
1016int main(int argc, const char* argv[]) {
1017 int ok = 1;
1018 WebPMuxConfig* config;
1019 ok = InitializeConfig(argc-1, argv+1, &config);
1020 if (ok) {
1021 Process(config);
1022 } else {
1023 PrintHelp();
1024 }
1025 DeleteConfig(config);
1026 return ok;
1027}
1028
1029//------------------------------------------------------------------------------
1030
1031#if defined(__cplusplus) || defined(c_plusplus)
1032} // extern "C"
1033#endif