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