blob: f4bc8597a884c44437758f9558a0c7b5353d2c1e [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++) {
194 err = WebPMuxGetFrame(mux, i, &data, &size, &x_offset, &y_offset,
195 &duration);
196 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++) {
212 err = WebPMuxGetTile(mux, i, &data, &size, &x_offset, &y_offset);
213 assert(err == WEBP_MUX_OK);
214 fprintf(stderr, "%3d: %8d %8d\n", i, x_offset, y_offset);
215 }
216 }
217 }
218
219 if (flag & ICCP_FLAG) {
220 err = WebPMuxGetColorProfile(mux, &data, &size);
221 RETURN_IF_ERROR("Failed to retrieve the color profile\n");
222 fprintf(stderr, "Size of the color profile data: %d\n", size);
223 }
224
225 if (flag & META_FLAG) {
226 err = WebPMuxGetMetadata(mux, &data, &size);
227 RETURN_IF_ERROR("Failed to retrieve the XMP metadata\n");
228 fprintf(stderr, "Size of the XMP metadata: %d\n", size);
229 }
230
231 return WEBP_MUX_OK;
232}
233
Pascal Massiminoabd030b2011-11-01 06:24:34 -0700234static void PrintHelp(void) {
Urvang Joshia4f32ca2011-09-30 11:07:01 +0530235 fprintf(stderr, "Usage: webpmux -get GET_OPTIONS INPUT -o OUTPUT "
236 " Extract relevant data.\n");
237 fprintf(stderr, " or: webpmux -set SET_OPTIONS INPUT -o OUTPUT "
238 " Set color profile/metadata.\n");
239 fprintf(stderr, " or: webpmux -strip STRIP_OPTIONS INPUT -o OUTPUT "
240 " Strip color profile/metadata.\n");
241 fprintf(stderr, " or: webpmux [-tile TILE_OPTIONS]... -o OUTPUT "
242 " Create tiled image.\n");
243 fprintf(stderr, " or: webpmux [-frame FRAME_OPTIONS]... -loop LOOP_COUNT"
244 " -o OUTPUT Create animation.\n");
245 fprintf(stderr, " or: webpmux -info INPUT "
246 " Print info about given webp file.\n");
247 fprintf(stderr, " or: webpmux -help OR -h "
248 " Print this help.\n");
249
250 fprintf(stderr, "\n");
251 fprintf(stderr, "GET_OPTIONS:\n");
252 fprintf(stderr, " icc Get ICCP Color profile.\n");
253 fprintf(stderr, " xmp Get XMP metadata.\n");
254 fprintf(stderr, " tile n Get nth tile.\n");
255 fprintf(stderr, " frame n Get nth frame.\n");
256
257 fprintf(stderr, "\n");
258 fprintf(stderr, "SET_OPTIONS:\n");
259 fprintf(stderr, " icc Set ICC Color profile.\n");
260 fprintf(stderr, " xmp Set XMP metadata.\n");
261
262 fprintf(stderr, "\n");
263 fprintf(stderr, "STRIP_OPTIONS:\n");
264 fprintf(stderr, " icc Strip ICCP color profile.\n");
265 fprintf(stderr, " xmp Strip XMP metadata.\n");
266
267 fprintf(stderr, "\n");
268 fprintf(stderr, "TILE_OPTIONS(i):\n");
269 fprintf(stderr, " file_i +xi+yi\n");
270 fprintf(stderr, " where: 'file_i' is the i'th tile (webp format),\n");
271 fprintf(stderr, " 'xi','yi' specify the image offset for this "
272 "tile.\n");
273
274 fprintf(stderr, "\n");
275 fprintf(stderr, "FRAME_OPTIONS(i):\n");
276 fprintf(stderr, " file_i +xi+yi+di\n");
277 fprintf(stderr, " where: 'file_i' is the i'th animation frame (webp "
278 "format),\n");
279 fprintf(stderr, " 'xi','yi' specify the image offset for this "
280 "frame.\n");
281 fprintf(stderr, " 'di' is the pause duration before next frame."
282 "\n");
283
284 fprintf(stderr, "\n");
285 fprintf(stderr, "INPUT & OUTPUT are in webp format.");
286 fprintf(stderr, "\n");
287}
288
289static int ReadData(const char* filename, void** data_ptr, uint32_t* size_ptr) {
290 void* data = NULL;
291 long size = 0;
292 int ok = 0;
293 FILE* in;
294
295 *size_ptr = 0;
296 in = fopen(filename, "rb");
297 if (!in) {
298 fprintf(stderr, "Failed to open file %s\n", filename);
299 return 0;
300 }
301 fseek(in, 0, SEEK_END);
302 size = ftell(in);
303 fseek(in, 0, SEEK_SET);
304 if (size > 0xffffffffu) {
305 fprintf(stderr, "Size (%ld bytes) is out of range for file %s\n",
306 size, filename);
307 size = 0;
308 goto Err;
309 }
310 if (size < 0) {
311 size = 0;
312 goto Err;
313 }
314 data = malloc(size);
315 if (data) {
316 if (fread(data, size, 1, in) != 1) {
317 free(data);
318 data = NULL;
319 size = 0;
320 fprintf(stderr, "Failed to read %ld bytes from file %s\n",
321 size, filename);
322 goto Err;
323 }
324 ok = 1;
325 } else {
326 fprintf(stderr, "Failed to allocate %ld bytes for reading file %s\n",
327 size, filename);
328 size = 0;
329 }
330
331 Err:
332 if (in != stdin) fclose(in);
333 *size_ptr = (uint32_t)size;
334 *data_ptr = data;
335 return ok;
336}
337
338static int ReadFile(const char* const filename, WebPMux** mux) {
339 uint32_t size = 0;
340 void* data = NULL;
341
342 assert(mux != NULL);
343
344 if (!ReadData(filename, &data, &size)) return 0;
345 *mux = WebPMuxCreate((const uint8_t*)data, size, 1);
346 free(data);
347 if (*mux != NULL) return 1;
348 fprintf(stderr, "Failed to create mux object from file %s.\n",
349 filename);
350 return 0;
351}
352
353static int ReadImageData(const char* filename, int image_index,
354 const uint8_t** data_ptr, uint32_t* size_ptr) {
355 uint32_t size = 0;
356 void* data = NULL;
357 WebPMux* mux;
358 WebPMuxError err;
359 int ok = 1;
360
361 if (!ReadData(filename, &data, &size)) return 0;
362
363 mux = WebPMuxCreate((const uint8_t*)data, size, 1);
364 free(data);
365 if (mux == NULL) {
366 fprintf(stderr, "Failed to create mux object from file %s.\n",
367 filename);
368 return 0;
369 }
370 err = WebPMuxGetNamedData(mux, "image", image_index, (const uint8_t**)&data,
371 &size);
372 if (err == WEBP_MUX_OK) {
373 *size_ptr = size;
374 *data_ptr = (uint8_t*)malloc(*size_ptr);
375 if (*data_ptr != NULL) {
376 memcpy((void*)*data_ptr, data, (size_t)size);
377 } else {
378 err = WEBP_MUX_MEMORY_ERROR;
379 fprintf(stderr, "Failed to allocate %d bytes to extract image data from"
380 " file %s. Error: %d\n", size, filename, err);
381 ok = 0;
382 }
383 } else {
384 fprintf(stderr, "Failed to extract image data from file %s. Error: %d\n",
James Zern04e84cf2011-11-04 15:20:08 -0700385 filename, err);
Urvang Joshia4f32ca2011-09-30 11:07:01 +0530386 ok = 0;
387 }
388 WebPMuxDelete(mux);
389 return ok;
390}
391
392static int WriteData(const char* filename, void* data, uint32_t size) {
393 int ok = 0;
James Zern04e84cf2011-11-04 15:20:08 -0700394 FILE* fout = strcmp(filename, "-") ? fopen(filename, "wb") : stdout;
Urvang Joshia4f32ca2011-09-30 11:07:01 +0530395 if (!fout) {
396 fprintf(stderr, "Error opening output WebP file %s!\n", filename);
397 return 0;
398 }
399 if (fwrite(data, size, 1, fout) != 1) {
400 fprintf(stderr, "Error writing file %s!\n", filename);
401 } else {
402 fprintf(stderr, "Saved file %s (%d bytes)\n", filename, size);
403 ok = 1;
404 }
405 if (fout != stdout) fclose(fout);
406 return ok;
407}
408
409static int WriteWebP(WebPMux* const mux, const char* filename) {
410 uint8_t* data = NULL;
411 uint32_t size = 0;
412 int ok;
413
414 WebPMuxError err = WebPMuxAssemble(mux, &data, &size);
415 if (err != WEBP_MUX_OK) {
416 fprintf(stderr, "Error (%d) assembling the WebP file.\n", err);
417 return 0;
418 }
419 ok = WriteData(filename, data, size);
420 free(data);
421 return ok;
422}
423
424static int ParseFrameArgs(const char* args, uint32_t* x_offset,
425 uint32_t* y_offset, uint32_t* duration) {
426 return (sscanf(args, "+%d+%d+%d", x_offset, y_offset, duration) == 3);
427}
428
429static int ParseTileArgs(const char* args, uint32_t* x_offset,
430 uint32_t* y_offset) {
431 return (sscanf(args, "+%d+%d", x_offset, y_offset) == 2);
432}
433
434//------------------------------------------------------------------------------
435// Clean-up.
436
437static void DeleteConfig(WebPMuxConfig* config) {
438 if (config != NULL) {
439 free(config->feature_.args_);
440 free(config);
441 }
442}
443
444//------------------------------------------------------------------------------
445// Parsing.
446
447// Basic syntactic checks on the command-line arguments.
448// Returns 1 on valid, 0 otherwise.
449// Also fills up num_feature_args to be number of feature arguments given.
450// (e.g. if there are 4 '-frame's and 1 '-loop', then num_feature_args = 5).
451static int ValidateCommandLine(int argc, const char* argv[],
452 int* num_feature_args) {
453 int num_frame_args;
454 int num_tile_args;
455 int num_loop_args;
456 int ok = 1;
457
458 assert(num_feature_args != NULL);
459 *num_feature_args = 0;
460
461 // Simple checks.
James Zern04e84cf2011-11-04 15:20:08 -0700462 if (CountOccurrences(argv, argc, "-get") > 1) {
Urvang Joshia4f32ca2011-09-30 11:07:01 +0530463 ERROR_GOTO1("ERROR: Multiple '-get' arguments specified.\n", ErrValidate);
464 }
James Zern04e84cf2011-11-04 15:20:08 -0700465 if (CountOccurrences(argv, argc, "-set") > 1) {
Urvang Joshia4f32ca2011-09-30 11:07:01 +0530466 ERROR_GOTO1("ERROR: Multiple '-set' arguments specified.\n", ErrValidate);
467 }
James Zern04e84cf2011-11-04 15:20:08 -0700468 if (CountOccurrences(argv, argc, "-strip") > 1) {
Urvang Joshia4f32ca2011-09-30 11:07:01 +0530469 ERROR_GOTO1("ERROR: Multiple '-strip' arguments specified.\n", ErrValidate);
470 }
James Zern04e84cf2011-11-04 15:20:08 -0700471 if (CountOccurrences(argv, argc, "-info") > 1) {
Urvang Joshia4f32ca2011-09-30 11:07:01 +0530472 ERROR_GOTO1("ERROR: Multiple '-info' arguments specified.\n", ErrValidate);
473 }
James Zern04e84cf2011-11-04 15:20:08 -0700474 if (CountOccurrences(argv, argc, "-o") > 1) {
Urvang Joshia4f32ca2011-09-30 11:07:01 +0530475 ERROR_GOTO1("ERROR: Multiple output files specified.\n", ErrValidate);
476 }
477
478 // Compound checks.
James Zern04e84cf2011-11-04 15:20:08 -0700479 num_frame_args = CountOccurrences(argv, argc, "-frame");
480 num_tile_args = CountOccurrences(argv, argc, "-tile");
481 num_loop_args = CountOccurrences(argv, argc, "-loop");
Urvang Joshia4f32ca2011-09-30 11:07:01 +0530482
483 if (num_loop_args > 1) {
484 ERROR_GOTO1("ERROR: Multiple loop counts specified.\n", ErrValidate);
485 }
486
487 if (IsNotCompatible(num_frame_args, num_loop_args)) {
488 ERROR_GOTO1("ERROR: Both frames and loop count have to be specified.\n",
489 ErrValidate);
490 }
491 if (num_frame_args > 0 && num_tile_args > 0) {
492 ERROR_GOTO1("ERROR: Only one of frames & tiles can be specified at a time."
493 "\n", ErrValidate);
494 }
495
496 assert(ok == 1);
497 if (num_frame_args == 0 && num_tile_args == 0) {
498 // Single argument ('set' action for XMP or ICCP, OR a 'get' action).
499 *num_feature_args = 1;
500 } else {
501 // Multiple arguments ('set' action for animation or tiling).
502 if (num_frame_args > 0) {
503 *num_feature_args = num_frame_args + num_loop_args;
504 } else {
505 *num_feature_args = num_tile_args;
506 }
507 }
508
509 ErrValidate:
510 return ok;
511}
512
513#define ACTION_IS_NIL (config->action_type_ == NIL_ACTION)
514
515#define FEATURETYPE_IS_NIL (feature->type_ == NIL_FEATURE)
516
517#define CHECK_NUM_ARGS_LESS(NUM, LABEL) \
518 if (argc < i + (NUM)) { \
519 fprintf(stderr, "ERROR: Too few arguments for '%s'.\n", argv[i]); \
520 goto LABEL; \
521 }
522
523#define CHECK_NUM_ARGS_NOT_EQUAL(NUM, LABEL) \
524 if (argc != i + (NUM)) { \
525 fprintf(stderr, "ERROR: Too many arguments for '%s'.\n", argv[i]); \
526 goto LABEL; \
527 }
528
529// Parses command-line arguments to fill up config object. Also performs some
530// semantic checks.
531static int ParseCommandLine(int argc, const char* argv[],
532 WebPMuxConfig* config) {
533 int i = 0;
534 int feature_arg_index = 0;
535 int ok = 1;
536
537 while (i < argc) {
538 Feature* const feature = &config->feature_;
James Zern04e84cf2011-11-04 15:20:08 -0700539 FeatureArg* const arg = &feature->args_[feature_arg_index];
Urvang Joshia4f32ca2011-09-30 11:07:01 +0530540 if (argv[i][0] == '-') { // One of the action types or output.
541 if (!strcmp(argv[i], "-set")) {
542 if (ACTION_IS_NIL) {
543 config->action_type_ = ACTION_SET;
544 } else {
545 ERROR_GOTO1("ERROR: Multiple actions specified.\n", ErrParse);
546 }
547 ++i;
548 } else if (!strcmp(argv[i], "-get")) {
549 if (ACTION_IS_NIL) {
550 config->action_type_ = ACTION_GET;
551 } else {
552 ERROR_GOTO1("ERROR: Multiple actions specified.\n", ErrParse);
553 }
554 ++i;
555 } else if (!strcmp(argv[i], "-strip")) {
556 if (ACTION_IS_NIL) {
557 config->action_type_ = ACTION_STRIP;
558 feature->arg_count_ = 0;
559 } else {
560 ERROR_GOTO1("ERROR: Multiple actions specified.\n", ErrParse);
561 }
562 ++i;
563 } else if (!strcmp(argv[i], "-frame")) {
564 CHECK_NUM_ARGS_LESS(3, ErrParse);
565 if (ACTION_IS_NIL || config->action_type_ == ACTION_SET) {
566 config->action_type_ = ACTION_SET;
567 } else {
568 ERROR_GOTO1("ERROR: Multiple actions specified.\n", ErrParse);
569 }
570 if (FEATURETYPE_IS_NIL || feature->type_ == FEATURE_FRM) {
571 feature->type_ = FEATURE_FRM;
572 } else {
573 ERROR_GOTO1("ERROR: Multiple features specified.\n", ErrParse);
574 }
575 arg->subtype_ = SUBTYPE_FRM;
576 arg->filename_ = argv[i + 1];
577 arg->params_ = argv[i + 2];
578 ++feature_arg_index;
579 i += 3;
580 } else if (!strcmp(argv[i], "-loop")) {
581 CHECK_NUM_ARGS_LESS(2, ErrParse);
582 if (ACTION_IS_NIL || config->action_type_ == ACTION_SET) {
583 config->action_type_ = ACTION_SET;
584 } else {
585 ERROR_GOTO1("ERROR: Multiple actions specified.\n", ErrParse);
586 }
587 if (FEATURETYPE_IS_NIL || feature->type_ == FEATURE_FRM) {
588 feature->type_ = FEATURE_FRM;
589 } else {
590 ERROR_GOTO1("ERROR: Multiple features specified.\n", ErrParse);
591 }
592 arg->subtype_ = SUBTYPE_LOOP;
593 arg->params_ = argv[i + 1];
594 ++feature_arg_index;
595 i += 2;
596 } else if (!strcmp(argv[i], "-tile")) {
597 CHECK_NUM_ARGS_LESS(3, 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_TILE) {
604 feature->type_ = FEATURE_TILE;
605 } else {
606 ERROR_GOTO1("ERROR: Multiple features specified.\n", ErrParse);
607 }
608 arg->filename_ = argv[i + 1];
609 arg->params_ = argv[i + 2];
610 ++feature_arg_index;
611 i += 3;
612 } else if (!strcmp(argv[i], "-o")) {
613 CHECK_NUM_ARGS_LESS(2, ErrParse);
614 config->output_ = argv[i + 1];
615 i += 2;
616 } else if (!strcmp(argv[i], "-info")) {
617 CHECK_NUM_ARGS_NOT_EQUAL(2, ErrParse);
618 if (config->action_type_ != NIL_ACTION) {
619 ERROR_GOTO1("ERROR: Multiple actions specified.\n", ErrParse);
620 } else {
621 config->action_type_ = ACTION_INFO;
622 feature->arg_count_ = 0;
623 config->input_ = argv[i + 1];
624 }
625 i += 2;
626 } else if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "-help")) {
627 PrintHelp();
628 DeleteConfig(config);
629 exit(1);
630 } else {
631 ERROR_GOTO2("ERROR: Unknown option: '%s'.\n", argv[i], ErrParse);
632 }
633 } else { // One of the feature types or input.
634 if (ACTION_IS_NIL) {
635 ERROR_GOTO1("ERROR: Action must be specified before other arguments.\n",
636 ErrParse);
637 }
638 if (!strcmp(argv[i], "icc") || !strcmp(argv[i], "xmp")) {
639 if (FEATURETYPE_IS_NIL) {
640 feature->type_ = (!strcmp(argv[i], "icc")) ? FEATURE_ICCP :
641 FEATURE_XMP;
642 } else {
643 ERROR_GOTO1("ERROR: Multiple features specified.\n", ErrParse);
644 }
645 if (config->action_type_ == ACTION_SET) {
646 CHECK_NUM_ARGS_LESS(2, ErrParse);
647 arg->filename_ = argv[i + 1];
648 ++feature_arg_index;
649 i += 2;
650 } else {
651 ++i;
652 }
653 } else if ((!strcmp(argv[i], "frame") ||
654 !strcmp(argv[i], "tile")) &&
655 (config->action_type_ == ACTION_GET)) {
656 CHECK_NUM_ARGS_LESS(2, ErrParse);
657 feature->type_ = (!strcmp(argv[i], "frame")) ? FEATURE_FRM :
658 FEATURE_TILE;
659 arg->params_ = argv[i + 1];
660 ++feature_arg_index;
661 i += 2;
James Zern04e84cf2011-11-04 15:20:08 -0700662 } else { // Assume input file.
Urvang Joshia4f32ca2011-09-30 11:07:01 +0530663 if (config->input_ == NULL) {
664 config->input_ = argv[i];
665 } else {
666 ERROR_GOTO2("ERROR at '%s': Multiple input files specified.\n",
667 argv[i], ErrParse);
668 }
669 ++i;
670 }
671 }
672 }
673 ErrParse:
674 return ok;
675}
676
James Zern04e84cf2011-11-04 15:20:08 -0700677// Additional checks after config is filled.
Urvang Joshia4f32ca2011-09-30 11:07:01 +0530678static int ValidateConfig(WebPMuxConfig* config) {
679 int ok = 1;
680 Feature* const feature = &config->feature_;
681
682 // Action.
683 if (ACTION_IS_NIL) {
684 ERROR_GOTO1("ERROR: No action specified.\n", ErrValidate2);
685 }
686
687 // Feature type.
688 if (FEATURETYPE_IS_NIL && config->action_type_ != ACTION_INFO) {
689 ERROR_GOTO1("ERROR: No feature specified.\n", ErrValidate2);
690 }
691
692 // Input file.
693 if (config->input_ == NULL) {
694 if (config->action_type_ != ACTION_SET) {
695 ERROR_GOTO1("ERROR: No input file specified.\n", ErrValidate2);
696 } else if (feature->type_ != FEATURE_FRM &&
697 feature->type_ != FEATURE_TILE) {
698 ERROR_GOTO1("ERROR: No input file specified.\n", ErrValidate2);
699 }
700 }
701
702 // Output file.
703 if (config->output_ == NULL && config->action_type_ != ACTION_INFO) {
704 ERROR_GOTO1("ERROR: No output file specified.\n", ErrValidate2);
705 }
706
707 ErrValidate2:
708 return ok;
709}
710
711// Create config object from command-line arguments.
712static int InitializeConfig(int argc, const char* argv[],
713 WebPMuxConfig** config) {
714 int num_feature_args = 0;
715 int ok = 1;
716
717 assert(config != NULL);
718 *config = NULL;
719
720 // Validate command-line arguments.
721 if (!ValidateCommandLine(argc, argv, &num_feature_args)) {
722 ERROR_GOTO1("Exiting due to command-line parsing error.\n", Err1);
723 }
724
725 // Allocate memory.
726 *config = (WebPMuxConfig*)calloc(1, sizeof(**config));
727 if (*config == NULL) {
728 ERROR_GOTO1("ERROR: Memory allocation error.\n", Err1);
729 }
730 (*config)->feature_.arg_count_ = num_feature_args;
731 (*config)->feature_.args_ =
732 (FeatureArg*)calloc(num_feature_args, sizeof(FeatureArg));
733 if ((*config)->feature_.args_ == NULL) {
734 ERROR_GOTO1("ERROR: Memory allocation error.\n", Err1);
735 }
736
737 // Parse command-line.
James Zern04e84cf2011-11-04 15:20:08 -0700738 if (!ParseCommandLine(argc, argv, *config) ||
739 !ValidateConfig(*config)) {
Urvang Joshia4f32ca2011-09-30 11:07:01 +0530740 ERROR_GOTO1("Exiting due to command-line parsing error.\n", Err1);
741 }
742
743 Err1:
744 return ok;
745}
746
747#undef ACTION_IS_NIL
748#undef FEATURETYPE_IS_NIL
749#undef CHECK_NUM_ARGS_LESS
750#undef CHECK_NUM_ARGS_MORE
751
752//------------------------------------------------------------------------------
753// Processing.
754
James Zern04e84cf2011-11-04 15:20:08 -0700755static int GetFrameTile(const WebPMux* mux,
756 const WebPMuxConfig* config, int isFrame) {
Urvang Joshia4f32ca2011-09-30 11:07:01 +0530757 const uint8_t* data = NULL;
758 uint32_t size = 0;
759 uint32_t x_offset = 0;
760 uint32_t y_offset = 0;
761 uint32_t duration = 0;
762 WebPMuxError err = WEBP_MUX_OK;
763 WebPMux* mux_single = NULL;
764 long num = 0;
765 int ok = 1;
766
767 num = strtol(config->feature_.args_[0].params_, NULL, 10);
768 if (num < 0) {
769 ERROR_GOTO1("ERROR: Frame/Tile index must be non-negative.\n", ErrGet);
770 }
771
772 if (isFrame) {
773 err = WebPMuxGetFrame(mux, num, &data, &size, &x_offset, &y_offset,
774 &duration);
775 if (err != WEBP_MUX_OK) {
776 ERROR_GOTO3("ERROR#%d: Could not get frame %ld.\n", err, num, ErrGet);
777 }
778 } else {
779 err = WebPMuxGetTile(mux, num, &data, &size, &x_offset, &y_offset);
780 if (err != WEBP_MUX_OK) {
781 ERROR_GOTO3("ERROR#%d: Could not get frame %ld.\n", err, num, ErrGet);
782 }
783 }
784
785 mux_single = WebPMuxNew();
786 if (mux_single == NULL) {
787 err = WEBP_MUX_MEMORY_ERROR;
788 ERROR_GOTO2("ERROR#%d: Could not allocate a mux object.\n", err, ErrGet);
789 }
790 err = WebPMuxAddNamedData(mux_single, 0, "image", data, size, 1);
791 if (err != WEBP_MUX_OK) {
792 ERROR_GOTO2("ERROR#%d: Could not create single image mux object.\n", err,
793 ErrGet);
794 }
795 ok = WriteWebP(mux_single, config->output_);
796
797 ErrGet:
798 WebPMuxDelete(mux_single);
799 return ok;
800}
801
802// Read and process config.
James Zern04e84cf2011-11-04 15:20:08 -0700803static int Process(const WebPMuxConfig* config) {
Urvang Joshia4f32ca2011-09-30 11:07:01 +0530804 WebPMux* mux = NULL;
805 const uint8_t* data = NULL;
806 uint32_t size = 0;
807 uint32_t x_offset = 0;
808 uint32_t y_offset = 0;
809 uint32_t duration = 0;
810 uint32_t loop_count = 0;
811 WebPMuxError err = WEBP_MUX_OK;
812 long num;
813 int index = 0;
814 int ok = 1;
James Zern04e84cf2011-11-04 15:20:08 -0700815 const Feature* const feature = &config->feature_;
Urvang Joshia4f32ca2011-09-30 11:07:01 +0530816
James Zern04e84cf2011-11-04 15:20:08 -0700817 switch (config->action_type_) {
Urvang Joshia4f32ca2011-09-30 11:07:01 +0530818 case ACTION_GET:
819 ok = ReadFile(config->input_, &mux);
820 if (!ok) goto Err2;
James Zern04e84cf2011-11-04 15:20:08 -0700821 switch (feature->type_) {
Urvang Joshia4f32ca2011-09-30 11:07:01 +0530822 case FEATURE_FRM:
823 ok = GetFrameTile(mux, config, 1);
824 break;
825
826 case FEATURE_TILE:
827 ok = GetFrameTile(mux, config, 0);
828 break;
829
830 case FEATURE_ICCP:
831 err = WebPMuxGetColorProfile(mux, &data, &size);
832 if (err != WEBP_MUX_OK) {
833 ERROR_GOTO2("ERROR#%d: Could not get color profile.\n", err, Err2);
834 }
835 ok = WriteData(config->output_, (void*)data, size);
836 break;
837
838 case FEATURE_XMP:
839 err = WebPMuxGetMetadata(mux, &data, &size);
840 if (err != WEBP_MUX_OK) {
841 ERROR_GOTO2("ERROR#%d: Could not get XMP metadata.\n", err, Err2);
842 }
843 ok = WriteData(config->output_, (void*)data, size);
844 break;
845
846 default:
847 ERROR_GOTO1("ERROR: Invalid feature for action 'get'.\n", Err2);
848 break;
849 }
850 break;
851
852 case ACTION_SET:
James Zern04e84cf2011-11-04 15:20:08 -0700853 switch (feature->type_) {
Urvang Joshia4f32ca2011-09-30 11:07:01 +0530854 case FEATURE_FRM:
855 mux = WebPMuxNew();
856 if (mux == NULL) {
857 ERROR_GOTO2("ERROR#%d: Could not allocate a mux object.\n",
858 WEBP_MUX_MEMORY_ERROR, Err2);
859 }
860 for (index = 0; index < feature->arg_count_; ++index) {
861 if (feature->args_[index].subtype_ == SUBTYPE_LOOP) {
862 num = strtol(feature->args_[index].params_, NULL, 10);
863 if (num < 0) {
864 ERROR_GOTO1("ERROR: Loop count must be non-negative.\n", Err2);
865 } else {
866 loop_count = num;
867 }
868 err = WebPMuxSetLoopCount(mux, loop_count);
869 if (err != WEBP_MUX_OK) {
870 ERROR_GOTO2("ERROR#%d: Could not set loop count.\n", err, Err2);
871 }
872 } else if (feature->args_[index].subtype_ == SUBTYPE_FRM) {
873 ok = ReadImageData(feature->args_[index].filename_, 1,
874 &data, &size);
875 if (!ok) goto Err2;
876 ok = ParseFrameArgs(feature->args_[index].params_,
877 &x_offset, &y_offset, &duration);
878 if (!ok) {
879 free((void*)data);
880 ERROR_GOTO1("ERROR: Could not parse frame properties.\n", Err2);
881 }
882 err = WebPMuxAddFrame(mux, 0, data, size, x_offset, y_offset,
883 duration, 1);
884 free((void*)data);
885 if (err != WEBP_MUX_OK) {
886 ERROR_GOTO3("ERROR#%d: Could not add a frame at index %d.\n",
887 err, index, Err2);
888 }
889 } else {
890 ERROR_GOTO1("ERROR: Invalid subtype for 'frame'", Err2);
891 }
892 }
893 break;
894
895 case FEATURE_TILE:
896 mux = WebPMuxNew();
897 if (mux == NULL) {
898 ERROR_GOTO2("ERROR#%d: Could not allocate a mux object.\n",
899 WEBP_MUX_MEMORY_ERROR, Err2);
900 }
901 for (index = 0; index < feature->arg_count_; ++index) {
902 ok = ReadImageData(feature->args_[index].filename_, 1,
903 &data, &size);
904 if (!ok) goto Err2;
905 ok = ParseTileArgs(feature->args_[index].params_, &x_offset,
906 &y_offset);
907 if (!ok) {
908 free((void*)data);
909 ERROR_GOTO1("ERROR: Could not parse tile properties.\n", Err2);
910 }
911 err = WebPMuxAddTile(mux, 0, data, size, x_offset, y_offset, 1);
912 free((void*)data);
913 if (err != WEBP_MUX_OK) {
914 ERROR_GOTO3("ERROR#%d: Could not add a tile at index %d.\n",
915 err, index, Err2);
916 }
917 }
918 break;
919
920 case FEATURE_ICCP:
921 ok = ReadFile(config->input_, &mux);
922 if (!ok) goto Err2;
James Zern04e84cf2011-11-04 15:20:08 -0700923 ok = ReadData(feature->args_[0].filename_, (void**)&data, &size);
Urvang Joshia4f32ca2011-09-30 11:07:01 +0530924 if (!ok) goto Err2;
925 err = WebPMuxSetColorProfile(mux, data, size, 1);
926 free((void*)data);
927 if (err != WEBP_MUX_OK) {
928 ERROR_GOTO2("ERROR#%d: Could not set color profile.\n", err, Err2);
929 }
930 break;
931
932 case FEATURE_XMP:
933 ok = ReadFile(config->input_, &mux);
934 if (!ok) goto Err2;
James Zern04e84cf2011-11-04 15:20:08 -0700935 ok = ReadData(feature->args_[0].filename_, (void**)&data, &size);
Urvang Joshia4f32ca2011-09-30 11:07:01 +0530936 if (!ok) goto Err2;
937 err = WebPMuxSetMetadata(mux, data, size, 1);
938 free((void*)data);
939 if (err != WEBP_MUX_OK) {
940 ERROR_GOTO2("ERROR#%d: Could not set XMP metadata.\n", err, Err2);
941 }
942 break;
943
944 default:
945 ERROR_GOTO1("ERROR: Invalid feature for action 'set'.\n", Err2);
946 break;
947 }
948 ok = WriteWebP(mux, config->output_);
949 break;
950
951 case ACTION_STRIP:
952 ok = ReadFile(config->input_, &mux);
953 if (!ok) goto Err2;
James Zern04e84cf2011-11-04 15:20:08 -0700954 switch (feature->type_) {
Urvang Joshia4f32ca2011-09-30 11:07:01 +0530955 case FEATURE_ICCP:
956 err = WebPMuxDeleteColorProfile(mux);
957 if (err != WEBP_MUX_OK) {
958 ERROR_GOTO2("ERROR#%d: Could not delete color profile.\n", err,
959 Err2);
960 }
961 break;
962 case FEATURE_XMP:
963 err = WebPMuxDeleteMetadata(mux);
964 if (err != WEBP_MUX_OK) {
965 ERROR_GOTO2("ERROR#%d: Could not delete XMP metadata.\n", err,
966 Err2);
967 }
968 break;
969 default:
970 ERROR_GOTO1("ERROR: Invalid feature for action 'strip'.\n", Err2);
971 break;
972 }
973 ok = WriteWebP(mux, config->output_);
974 break;
975
976 case ACTION_INFO:
977 ok = ReadFile(config->input_, &mux);
978 if (!ok) goto Err2;
979 ok = (DisplayInfo(mux) == WEBP_MUX_OK);
980 break;
981
982 default:
983 assert(0); // Invalid action.
984 break;
985 }
986
987 Err2:
988 WebPMuxDelete(mux);
989 return ok;
990}
991
992//------------------------------------------------------------------------------
993// Main.
994
995int main(int argc, const char* argv[]) {
996 int ok = 1;
997 WebPMuxConfig* config;
998 ok = InitializeConfig(argc-1, argv+1, &config);
999 if (ok) {
1000 Process(config);
1001 } else {
1002 PrintHelp();
1003 }
1004 DeleteConfig(config);
1005 return ok;
1006}
1007
1008//------------------------------------------------------------------------------