blob: 7d1fcb77c63f8266be3d115fa15d96149b281ee2 [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;
Urvang Joshicdf97aa2011-12-01 16:11:51 +0530160 const uint8_t* alpha_data;
161 uint32_t alpha_size;
Urvang Joshia4f32ca2011-09-30 11:07:01 +0530162 uint32_t flag;
163
164 WebPMuxError err = WebPMuxGetFeatures(mux, &flag);
165 RETURN_IF_ERROR("Failed to retrieve features\n");
166
167 if (flag == 0) {
168 fprintf(stderr, "No features present.\n");
169 return err;
170 }
171
172 // Print the features present.
173 fprintf(stderr, "Features present:");
174 if (flag & ANIMATION_FLAG) fprintf(stderr, " animation");
175 if (flag & TILE_FLAG) fprintf(stderr, " tiling");
176 if (flag & ICCP_FLAG) fprintf(stderr, " icc profile");
177 if (flag & META_FLAG) fprintf(stderr, " metadata");
Urvang Joshicdf97aa2011-12-01 16:11:51 +0530178 if (flag & ALPHA_FLAG) fprintf(stderr, " transparency");
Urvang Joshia4f32ca2011-09-30 11:07:01 +0530179 fprintf(stderr, "\n");
180
181 if (flag & ANIMATION_FLAG) {
182 uint32_t loop_count;
183 err = WebPMuxGetLoopCount(mux, &loop_count);
184 RETURN_IF_ERROR("Failed to retrieve loop count\n");
185 fprintf(stderr, "Loop Count : %d\n", loop_count);
186
187 err = WebPMuxNumNamedElements(mux, "frame", &nFrames);
188 RETURN_IF_ERROR("Failed to retrieve number of frames\n");
189
190 fprintf(stderr, "Number of frames: %d\n", nFrames);
191 if (nFrames > 0) {
192 int i;
193 uint32_t x_offset, y_offset, duration;
Urvang Joshicdf97aa2011-12-01 16:11:51 +0530194 fprintf(stderr, "No.: x_offset y_offset duration");
195 if (flag & ALPHA_FLAG) fprintf(stderr, " alpha_size");
196 fprintf(stderr, "\n");
Urvang Joshia4f32ca2011-09-30 11:07:01 +0530197 for (i = 1; i <= nFrames; i++) {
Urvang Joshicdf97aa2011-12-01 16:11:51 +0530198 err = WebPMuxGetFrame(mux, i, &data, &size, &alpha_data, &alpha_size,
Urvang Joshic398f592011-11-22 14:40:41 +0530199 &x_offset, &y_offset, &duration);
Urvang Joshicdf97aa2011-12-01 16:11:51 +0530200 RETURN_IF_ERROR2("Failed to retrieve frame#%d\n", i);
201 fprintf(stderr, "%3d: %8d %8d %8d", i, x_offset, y_offset, duration);
202 if (flag & ALPHA_FLAG) fprintf(stderr, " %10d", alpha_size);
203 fprintf(stderr, "\n");
Urvang Joshia4f32ca2011-09-30 11:07:01 +0530204 }
205 }
206 }
207
208 if (flag & TILE_FLAG) {
209 err = WebPMuxNumNamedElements(mux, "tile", &nTiles);
210 RETURN_IF_ERROR("Failed to retrieve number of tiles\n");
211
212 fprintf(stderr, "Number of tiles: %d\n", nTiles);
213 if (nTiles > 0) {
214 int i;
215 uint32_t x_offset, y_offset;
Urvang Joshicdf97aa2011-12-01 16:11:51 +0530216 fprintf(stderr, "No.: x_offset y_offset");
217 if (flag & ALPHA_FLAG) fprintf(stderr, " alpha_size");
218 fprintf(stderr, "\n");
Urvang Joshia4f32ca2011-09-30 11:07:01 +0530219 for (i = 1; i <= nTiles; i++) {
Urvang Joshicdf97aa2011-12-01 16:11:51 +0530220 err = WebPMuxGetTile(mux, i, &data, &size, &alpha_data, &alpha_size,
Urvang Joshic398f592011-11-22 14:40:41 +0530221 &x_offset, &y_offset);
Urvang Joshicdf97aa2011-12-01 16:11:51 +0530222 RETURN_IF_ERROR2("Failed to retrieve tile#%d\n", i);
223 fprintf(stderr, "%3d: %8d %8d", i, x_offset, y_offset);
224 if (flag & ALPHA_FLAG) fprintf(stderr, " %10d", alpha_size);
225 fprintf(stderr, "\n");
Urvang Joshia4f32ca2011-09-30 11:07:01 +0530226 }
227 }
228 }
229
230 if (flag & ICCP_FLAG) {
231 err = WebPMuxGetColorProfile(mux, &data, &size);
232 RETURN_IF_ERROR("Failed to retrieve the color profile\n");
233 fprintf(stderr, "Size of the color profile data: %d\n", size);
234 }
235
236 if (flag & META_FLAG) {
237 err = WebPMuxGetMetadata(mux, &data, &size);
238 RETURN_IF_ERROR("Failed to retrieve the XMP metadata\n");
239 fprintf(stderr, "Size of the XMP metadata: %d\n", size);
240 }
241
Urvang Joshicdf97aa2011-12-01 16:11:51 +0530242 if ((flag & ALPHA_FLAG) && !(flag & (ANIMATION_FLAG | TILE_FLAG))) {
243 err = WebPMuxGetImage(mux, &data, &size, &alpha_data, &alpha_size);
244 RETURN_IF_ERROR("Failed to retrieve the image\n");
245 fprintf(stderr, "Size of the alpha data: %d\n", alpha_size);
246 }
247
Urvang Joshia4f32ca2011-09-30 11:07:01 +0530248 return WEBP_MUX_OK;
249}
250
Pascal Massiminoabd030b2011-11-01 06:24:34 -0700251static void PrintHelp(void) {
Urvang Joshia4f32ca2011-09-30 11:07:01 +0530252 fprintf(stderr, "Usage: webpmux -get GET_OPTIONS INPUT -o OUTPUT "
253 " Extract relevant data.\n");
254 fprintf(stderr, " or: webpmux -set SET_OPTIONS INPUT -o OUTPUT "
255 " Set color profile/metadata.\n");
256 fprintf(stderr, " or: webpmux -strip STRIP_OPTIONS INPUT -o OUTPUT "
257 " Strip color profile/metadata.\n");
258 fprintf(stderr, " or: webpmux [-tile TILE_OPTIONS]... -o OUTPUT "
259 " Create tiled image.\n");
260 fprintf(stderr, " or: webpmux [-frame FRAME_OPTIONS]... -loop LOOP_COUNT"
261 " -o OUTPUT Create animation.\n");
262 fprintf(stderr, " or: webpmux -info INPUT "
263 " Print info about given webp file.\n");
264 fprintf(stderr, " or: webpmux -help OR -h "
265 " Print this help.\n");
266
267 fprintf(stderr, "\n");
268 fprintf(stderr, "GET_OPTIONS:\n");
269 fprintf(stderr, " icc Get ICCP Color profile.\n");
270 fprintf(stderr, " xmp Get XMP metadata.\n");
271 fprintf(stderr, " tile n Get nth tile.\n");
272 fprintf(stderr, " frame n Get nth frame.\n");
273
274 fprintf(stderr, "\n");
275 fprintf(stderr, "SET_OPTIONS:\n");
276 fprintf(stderr, " icc Set ICC Color profile.\n");
277 fprintf(stderr, " xmp Set XMP metadata.\n");
278
279 fprintf(stderr, "\n");
280 fprintf(stderr, "STRIP_OPTIONS:\n");
281 fprintf(stderr, " icc Strip ICCP color profile.\n");
282 fprintf(stderr, " xmp Strip XMP metadata.\n");
283
284 fprintf(stderr, "\n");
285 fprintf(stderr, "TILE_OPTIONS(i):\n");
286 fprintf(stderr, " file_i +xi+yi\n");
287 fprintf(stderr, " where: 'file_i' is the i'th tile (webp format),\n");
288 fprintf(stderr, " 'xi','yi' specify the image offset for this "
289 "tile.\n");
290
291 fprintf(stderr, "\n");
292 fprintf(stderr, "FRAME_OPTIONS(i):\n");
293 fprintf(stderr, " file_i +xi+yi+di\n");
294 fprintf(stderr, " where: 'file_i' is the i'th animation frame (webp "
295 "format),\n");
296 fprintf(stderr, " 'xi','yi' specify the image offset for this "
297 "frame.\n");
298 fprintf(stderr, " 'di' is the pause duration before next frame."
299 "\n");
300
301 fprintf(stderr, "\n");
302 fprintf(stderr, "INPUT & OUTPUT are in webp format.");
303 fprintf(stderr, "\n");
304}
305
306static int ReadData(const char* filename, void** data_ptr, uint32_t* size_ptr) {
307 void* data = NULL;
308 long size = 0;
309 int ok = 0;
310 FILE* in;
311
312 *size_ptr = 0;
313 in = fopen(filename, "rb");
314 if (!in) {
315 fprintf(stderr, "Failed to open file %s\n", filename);
316 return 0;
317 }
318 fseek(in, 0, SEEK_END);
319 size = ftell(in);
320 fseek(in, 0, SEEK_SET);
321 if (size > 0xffffffffu) {
322 fprintf(stderr, "Size (%ld bytes) is out of range for file %s\n",
323 size, filename);
324 size = 0;
325 goto Err;
326 }
327 if (size < 0) {
328 size = 0;
329 goto Err;
330 }
331 data = malloc(size);
332 if (data) {
333 if (fread(data, size, 1, in) != 1) {
334 free(data);
335 data = NULL;
336 size = 0;
337 fprintf(stderr, "Failed to read %ld bytes from file %s\n",
338 size, filename);
339 goto Err;
340 }
341 ok = 1;
342 } else {
343 fprintf(stderr, "Failed to allocate %ld bytes for reading file %s\n",
344 size, filename);
345 size = 0;
346 }
347
348 Err:
349 if (in != stdin) fclose(in);
350 *size_ptr = (uint32_t)size;
351 *data_ptr = data;
352 return ok;
353}
354
355static int ReadFile(const char* const filename, WebPMux** mux) {
356 uint32_t size = 0;
357 void* data = NULL;
358
359 assert(mux != NULL);
360
361 if (!ReadData(filename, &data, &size)) return 0;
362 *mux = WebPMuxCreate((const uint8_t*)data, size, 1);
363 free(data);
364 if (*mux != NULL) return 1;
365 fprintf(stderr, "Failed to create mux object from file %s.\n",
366 filename);
367 return 0;
368}
369
Urvang Joshicdf97aa2011-12-01 16:11:51 +0530370static int ReadImage(const char* filename,
371 const uint8_t** data_ptr, uint32_t* size_ptr,
372 const uint8_t** alpha_data_ptr, uint32_t* alpha_size_ptr) {
373 void* data = NULL;
374 uint32_t size = 0;
375 const uint8_t* alpha_data = NULL;
376 uint32_t alpha_size = 0;
377 WebPMux* mux;
378 WebPMuxError err;
379 int ok = 0;
380
381 if (!ReadData(filename, &data, &size)) return 0;
382
383 mux = WebPMuxCreate((const uint8_t*)data, size, 1);
384 free(data);
385 if (mux == NULL) {
386 fprintf(stderr, "Failed to create mux object from file %s.\n",
387 filename);
388 return 0;
389 }
390 err = WebPMuxGetImage(mux, (const uint8_t**)&data, &size,
391 &alpha_data, &alpha_size);
392 if (err == WEBP_MUX_OK) {
393 *size_ptr = size;
394 *alpha_size_ptr = alpha_size;
395 *data_ptr = (uint8_t*)malloc(*size_ptr);
396 *alpha_data_ptr = (uint8_t*)malloc(*alpha_size_ptr);
397 if ((*data_ptr != NULL) && (*alpha_data_ptr != NULL)) {
398 memcpy((void*)*data_ptr, data, (size_t)size);
399 memcpy((void*)*alpha_data_ptr, alpha_data, (size_t)alpha_size);
400 ok = 1;
401 } else {
402 free(data_ptr);
403 free(alpha_data_ptr);
404 err = WEBP_MUX_MEMORY_ERROR;
405 fprintf(stderr, "Failed to allocate %d bytes to extract image data from"
406 " file %s. Error: %d\n", size + alpha_size, filename, err);
407 }
408 } else {
409 fprintf(stderr, "Failed to extract image data from file %s. Error: %d\n",
410 filename, err);
411 }
412 WebPMuxDelete(mux);
413 return ok;
414}
415
Urvang Joshia4f32ca2011-09-30 11:07:01 +0530416static int WriteData(const char* filename, void* data, uint32_t size) {
417 int ok = 0;
James Zern04e84cf2011-11-04 15:20:08 -0700418 FILE* fout = strcmp(filename, "-") ? fopen(filename, "wb") : stdout;
Urvang Joshia4f32ca2011-09-30 11:07:01 +0530419 if (!fout) {
420 fprintf(stderr, "Error opening output WebP file %s!\n", filename);
421 return 0;
422 }
423 if (fwrite(data, size, 1, fout) != 1) {
424 fprintf(stderr, "Error writing file %s!\n", filename);
425 } else {
426 fprintf(stderr, "Saved file %s (%d bytes)\n", filename, size);
427 ok = 1;
428 }
429 if (fout != stdout) fclose(fout);
430 return ok;
431}
432
433static int WriteWebP(WebPMux* const mux, const char* filename) {
434 uint8_t* data = NULL;
435 uint32_t size = 0;
436 int ok;
437
438 WebPMuxError err = WebPMuxAssemble(mux, &data, &size);
439 if (err != WEBP_MUX_OK) {
440 fprintf(stderr, "Error (%d) assembling the WebP file.\n", err);
441 return 0;
442 }
443 ok = WriteData(filename, data, size);
444 free(data);
445 return ok;
446}
447
448static int ParseFrameArgs(const char* args, uint32_t* x_offset,
449 uint32_t* y_offset, uint32_t* duration) {
450 return (sscanf(args, "+%d+%d+%d", x_offset, y_offset, duration) == 3);
451}
452
453static int ParseTileArgs(const char* args, uint32_t* x_offset,
454 uint32_t* y_offset) {
455 return (sscanf(args, "+%d+%d", x_offset, y_offset) == 2);
456}
457
458//------------------------------------------------------------------------------
459// Clean-up.
460
461static void DeleteConfig(WebPMuxConfig* config) {
462 if (config != NULL) {
463 free(config->feature_.args_);
464 free(config);
465 }
466}
467
468//------------------------------------------------------------------------------
469// Parsing.
470
471// Basic syntactic checks on the command-line arguments.
472// Returns 1 on valid, 0 otherwise.
473// Also fills up num_feature_args to be number of feature arguments given.
474// (e.g. if there are 4 '-frame's and 1 '-loop', then num_feature_args = 5).
475static int ValidateCommandLine(int argc, const char* argv[],
476 int* num_feature_args) {
477 int num_frame_args;
478 int num_tile_args;
479 int num_loop_args;
480 int ok = 1;
481
482 assert(num_feature_args != NULL);
483 *num_feature_args = 0;
484
485 // Simple checks.
James Zern04e84cf2011-11-04 15:20:08 -0700486 if (CountOccurrences(argv, argc, "-get") > 1) {
Urvang Joshia4f32ca2011-09-30 11:07:01 +0530487 ERROR_GOTO1("ERROR: Multiple '-get' arguments specified.\n", ErrValidate);
488 }
James Zern04e84cf2011-11-04 15:20:08 -0700489 if (CountOccurrences(argv, argc, "-set") > 1) {
Urvang Joshia4f32ca2011-09-30 11:07:01 +0530490 ERROR_GOTO1("ERROR: Multiple '-set' arguments specified.\n", ErrValidate);
491 }
James Zern04e84cf2011-11-04 15:20:08 -0700492 if (CountOccurrences(argv, argc, "-strip") > 1) {
Urvang Joshia4f32ca2011-09-30 11:07:01 +0530493 ERROR_GOTO1("ERROR: Multiple '-strip' arguments specified.\n", ErrValidate);
494 }
James Zern04e84cf2011-11-04 15:20:08 -0700495 if (CountOccurrences(argv, argc, "-info") > 1) {
Urvang Joshia4f32ca2011-09-30 11:07:01 +0530496 ERROR_GOTO1("ERROR: Multiple '-info' arguments specified.\n", ErrValidate);
497 }
James Zern04e84cf2011-11-04 15:20:08 -0700498 if (CountOccurrences(argv, argc, "-o") > 1) {
Urvang Joshia4f32ca2011-09-30 11:07:01 +0530499 ERROR_GOTO1("ERROR: Multiple output files specified.\n", ErrValidate);
500 }
501
502 // Compound checks.
James Zern04e84cf2011-11-04 15:20:08 -0700503 num_frame_args = CountOccurrences(argv, argc, "-frame");
504 num_tile_args = CountOccurrences(argv, argc, "-tile");
505 num_loop_args = CountOccurrences(argv, argc, "-loop");
Urvang Joshia4f32ca2011-09-30 11:07:01 +0530506
507 if (num_loop_args > 1) {
508 ERROR_GOTO1("ERROR: Multiple loop counts specified.\n", ErrValidate);
509 }
510
511 if (IsNotCompatible(num_frame_args, num_loop_args)) {
512 ERROR_GOTO1("ERROR: Both frames and loop count have to be specified.\n",
513 ErrValidate);
514 }
515 if (num_frame_args > 0 && num_tile_args > 0) {
516 ERROR_GOTO1("ERROR: Only one of frames & tiles can be specified at a time."
517 "\n", ErrValidate);
518 }
519
520 assert(ok == 1);
521 if (num_frame_args == 0 && num_tile_args == 0) {
522 // Single argument ('set' action for XMP or ICCP, OR a 'get' action).
523 *num_feature_args = 1;
524 } else {
525 // Multiple arguments ('set' action for animation or tiling).
526 if (num_frame_args > 0) {
527 *num_feature_args = num_frame_args + num_loop_args;
528 } else {
529 *num_feature_args = num_tile_args;
530 }
531 }
532
533 ErrValidate:
534 return ok;
535}
536
537#define ACTION_IS_NIL (config->action_type_ == NIL_ACTION)
538
539#define FEATURETYPE_IS_NIL (feature->type_ == NIL_FEATURE)
540
541#define CHECK_NUM_ARGS_LESS(NUM, LABEL) \
542 if (argc < i + (NUM)) { \
543 fprintf(stderr, "ERROR: Too few arguments for '%s'.\n", argv[i]); \
544 goto LABEL; \
545 }
546
547#define CHECK_NUM_ARGS_NOT_EQUAL(NUM, LABEL) \
548 if (argc != i + (NUM)) { \
549 fprintf(stderr, "ERROR: Too many arguments for '%s'.\n", argv[i]); \
550 goto LABEL; \
551 }
552
553// Parses command-line arguments to fill up config object. Also performs some
554// semantic checks.
555static int ParseCommandLine(int argc, const char* argv[],
556 WebPMuxConfig* config) {
557 int i = 0;
558 int feature_arg_index = 0;
559 int ok = 1;
560
561 while (i < argc) {
562 Feature* const feature = &config->feature_;
James Zern04e84cf2011-11-04 15:20:08 -0700563 FeatureArg* const arg = &feature->args_[feature_arg_index];
Urvang Joshia4f32ca2011-09-30 11:07:01 +0530564 if (argv[i][0] == '-') { // One of the action types or output.
565 if (!strcmp(argv[i], "-set")) {
566 if (ACTION_IS_NIL) {
567 config->action_type_ = ACTION_SET;
568 } else {
569 ERROR_GOTO1("ERROR: Multiple actions specified.\n", ErrParse);
570 }
571 ++i;
572 } else if (!strcmp(argv[i], "-get")) {
573 if (ACTION_IS_NIL) {
574 config->action_type_ = ACTION_GET;
575 } else {
576 ERROR_GOTO1("ERROR: Multiple actions specified.\n", ErrParse);
577 }
578 ++i;
579 } else if (!strcmp(argv[i], "-strip")) {
580 if (ACTION_IS_NIL) {
581 config->action_type_ = ACTION_STRIP;
582 feature->arg_count_ = 0;
583 } else {
584 ERROR_GOTO1("ERROR: Multiple actions specified.\n", ErrParse);
585 }
586 ++i;
587 } else if (!strcmp(argv[i], "-frame")) {
588 CHECK_NUM_ARGS_LESS(3, ErrParse);
589 if (ACTION_IS_NIL || config->action_type_ == ACTION_SET) {
590 config->action_type_ = ACTION_SET;
591 } else {
592 ERROR_GOTO1("ERROR: Multiple actions specified.\n", ErrParse);
593 }
594 if (FEATURETYPE_IS_NIL || feature->type_ == FEATURE_FRM) {
595 feature->type_ = FEATURE_FRM;
596 } else {
597 ERROR_GOTO1("ERROR: Multiple features specified.\n", ErrParse);
598 }
599 arg->subtype_ = SUBTYPE_FRM;
600 arg->filename_ = argv[i + 1];
601 arg->params_ = argv[i + 2];
602 ++feature_arg_index;
603 i += 3;
604 } else if (!strcmp(argv[i], "-loop")) {
605 CHECK_NUM_ARGS_LESS(2, ErrParse);
606 if (ACTION_IS_NIL || config->action_type_ == ACTION_SET) {
607 config->action_type_ = ACTION_SET;
608 } else {
609 ERROR_GOTO1("ERROR: Multiple actions specified.\n", ErrParse);
610 }
611 if (FEATURETYPE_IS_NIL || feature->type_ == FEATURE_FRM) {
612 feature->type_ = FEATURE_FRM;
613 } else {
614 ERROR_GOTO1("ERROR: Multiple features specified.\n", ErrParse);
615 }
616 arg->subtype_ = SUBTYPE_LOOP;
617 arg->params_ = argv[i + 1];
618 ++feature_arg_index;
619 i += 2;
620 } else if (!strcmp(argv[i], "-tile")) {
621 CHECK_NUM_ARGS_LESS(3, ErrParse);
622 if (ACTION_IS_NIL || config->action_type_ == ACTION_SET) {
623 config->action_type_ = ACTION_SET;
624 } else {
625 ERROR_GOTO1("ERROR: Multiple actions specified.\n", ErrParse);
626 }
627 if (FEATURETYPE_IS_NIL || feature->type_ == FEATURE_TILE) {
628 feature->type_ = FEATURE_TILE;
629 } else {
630 ERROR_GOTO1("ERROR: Multiple features specified.\n", ErrParse);
631 }
632 arg->filename_ = argv[i + 1];
633 arg->params_ = argv[i + 2];
634 ++feature_arg_index;
635 i += 3;
636 } else if (!strcmp(argv[i], "-o")) {
637 CHECK_NUM_ARGS_LESS(2, ErrParse);
638 config->output_ = argv[i + 1];
639 i += 2;
640 } else if (!strcmp(argv[i], "-info")) {
641 CHECK_NUM_ARGS_NOT_EQUAL(2, ErrParse);
642 if (config->action_type_ != NIL_ACTION) {
643 ERROR_GOTO1("ERROR: Multiple actions specified.\n", ErrParse);
644 } else {
645 config->action_type_ = ACTION_INFO;
646 feature->arg_count_ = 0;
647 config->input_ = argv[i + 1];
648 }
649 i += 2;
650 } else if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "-help")) {
651 PrintHelp();
652 DeleteConfig(config);
653 exit(1);
654 } else {
655 ERROR_GOTO2("ERROR: Unknown option: '%s'.\n", argv[i], ErrParse);
656 }
657 } else { // One of the feature types or input.
658 if (ACTION_IS_NIL) {
659 ERROR_GOTO1("ERROR: Action must be specified before other arguments.\n",
660 ErrParse);
661 }
662 if (!strcmp(argv[i], "icc") || !strcmp(argv[i], "xmp")) {
663 if (FEATURETYPE_IS_NIL) {
664 feature->type_ = (!strcmp(argv[i], "icc")) ? FEATURE_ICCP :
665 FEATURE_XMP;
666 } else {
667 ERROR_GOTO1("ERROR: Multiple features specified.\n", ErrParse);
668 }
669 if (config->action_type_ == ACTION_SET) {
670 CHECK_NUM_ARGS_LESS(2, ErrParse);
671 arg->filename_ = argv[i + 1];
672 ++feature_arg_index;
673 i += 2;
674 } else {
675 ++i;
676 }
677 } else if ((!strcmp(argv[i], "frame") ||
678 !strcmp(argv[i], "tile")) &&
679 (config->action_type_ == ACTION_GET)) {
680 CHECK_NUM_ARGS_LESS(2, ErrParse);
681 feature->type_ = (!strcmp(argv[i], "frame")) ? FEATURE_FRM :
682 FEATURE_TILE;
683 arg->params_ = argv[i + 1];
684 ++feature_arg_index;
685 i += 2;
James Zern04e84cf2011-11-04 15:20:08 -0700686 } else { // Assume input file.
Urvang Joshia4f32ca2011-09-30 11:07:01 +0530687 if (config->input_ == NULL) {
688 config->input_ = argv[i];
689 } else {
690 ERROR_GOTO2("ERROR at '%s': Multiple input files specified.\n",
691 argv[i], ErrParse);
692 }
693 ++i;
694 }
695 }
696 }
697 ErrParse:
698 return ok;
699}
700
James Zern04e84cf2011-11-04 15:20:08 -0700701// Additional checks after config is filled.
Urvang Joshia4f32ca2011-09-30 11:07:01 +0530702static int ValidateConfig(WebPMuxConfig* config) {
703 int ok = 1;
704 Feature* const feature = &config->feature_;
705
706 // Action.
707 if (ACTION_IS_NIL) {
708 ERROR_GOTO1("ERROR: No action specified.\n", ErrValidate2);
709 }
710
711 // Feature type.
712 if (FEATURETYPE_IS_NIL && config->action_type_ != ACTION_INFO) {
713 ERROR_GOTO1("ERROR: No feature specified.\n", ErrValidate2);
714 }
715
716 // Input file.
717 if (config->input_ == NULL) {
718 if (config->action_type_ != ACTION_SET) {
719 ERROR_GOTO1("ERROR: No input file specified.\n", ErrValidate2);
720 } else if (feature->type_ != FEATURE_FRM &&
721 feature->type_ != FEATURE_TILE) {
722 ERROR_GOTO1("ERROR: No input file specified.\n", ErrValidate2);
723 }
724 }
725
726 // Output file.
727 if (config->output_ == NULL && config->action_type_ != ACTION_INFO) {
728 ERROR_GOTO1("ERROR: No output file specified.\n", ErrValidate2);
729 }
730
731 ErrValidate2:
732 return ok;
733}
734
735// Create config object from command-line arguments.
736static int InitializeConfig(int argc, const char* argv[],
737 WebPMuxConfig** config) {
738 int num_feature_args = 0;
739 int ok = 1;
740
741 assert(config != NULL);
742 *config = NULL;
743
744 // Validate command-line arguments.
745 if (!ValidateCommandLine(argc, argv, &num_feature_args)) {
746 ERROR_GOTO1("Exiting due to command-line parsing error.\n", Err1);
747 }
748
749 // Allocate memory.
750 *config = (WebPMuxConfig*)calloc(1, sizeof(**config));
751 if (*config == NULL) {
752 ERROR_GOTO1("ERROR: Memory allocation error.\n", Err1);
753 }
754 (*config)->feature_.arg_count_ = num_feature_args;
755 (*config)->feature_.args_ =
756 (FeatureArg*)calloc(num_feature_args, sizeof(FeatureArg));
757 if ((*config)->feature_.args_ == NULL) {
758 ERROR_GOTO1("ERROR: Memory allocation error.\n", Err1);
759 }
760
761 // Parse command-line.
James Zern04e84cf2011-11-04 15:20:08 -0700762 if (!ParseCommandLine(argc, argv, *config) ||
763 !ValidateConfig(*config)) {
Urvang Joshia4f32ca2011-09-30 11:07:01 +0530764 ERROR_GOTO1("Exiting due to command-line parsing error.\n", Err1);
765 }
766
767 Err1:
768 return ok;
769}
770
771#undef ACTION_IS_NIL
772#undef FEATURETYPE_IS_NIL
773#undef CHECK_NUM_ARGS_LESS
774#undef CHECK_NUM_ARGS_MORE
775
776//------------------------------------------------------------------------------
777// Processing.
778
James Zern04e84cf2011-11-04 15:20:08 -0700779static int GetFrameTile(const WebPMux* mux,
780 const WebPMuxConfig* config, int isFrame) {
Urvang Joshia4f32ca2011-09-30 11:07:01 +0530781 const uint8_t* data = NULL;
782 uint32_t size = 0;
Urvang Joshicdf97aa2011-12-01 16:11:51 +0530783 const uint8_t* alpha_data = NULL;
784 uint32_t alpha_size = 0;
Urvang Joshia4f32ca2011-09-30 11:07:01 +0530785 uint32_t x_offset = 0;
786 uint32_t y_offset = 0;
787 uint32_t duration = 0;
788 WebPMuxError err = WEBP_MUX_OK;
789 WebPMux* mux_single = NULL;
790 long num = 0;
791 int ok = 1;
792
793 num = strtol(config->feature_.args_[0].params_, NULL, 10);
794 if (num < 0) {
795 ERROR_GOTO1("ERROR: Frame/Tile index must be non-negative.\n", ErrGet);
796 }
797
798 if (isFrame) {
Urvang Joshicdf97aa2011-12-01 16:11:51 +0530799 err = WebPMuxGetFrame(mux, num, &data, &size, &alpha_data, &alpha_size,
Urvang Joshic398f592011-11-22 14:40:41 +0530800 &x_offset, &y_offset, &duration);
Urvang Joshia4f32ca2011-09-30 11:07:01 +0530801 if (err != WEBP_MUX_OK) {
802 ERROR_GOTO3("ERROR#%d: Could not get frame %ld.\n", err, num, ErrGet);
803 }
804 } else {
Urvang Joshicdf97aa2011-12-01 16:11:51 +0530805 err = WebPMuxGetTile(mux, num, &data, &size, &alpha_data, &alpha_size,
Urvang Joshic398f592011-11-22 14:40:41 +0530806 &x_offset, &y_offset);
Urvang Joshia4f32ca2011-09-30 11:07:01 +0530807 if (err != WEBP_MUX_OK) {
808 ERROR_GOTO3("ERROR#%d: Could not get frame %ld.\n", err, num, ErrGet);
809 }
810 }
811
812 mux_single = WebPMuxNew();
813 if (mux_single == NULL) {
814 err = WEBP_MUX_MEMORY_ERROR;
815 ERROR_GOTO2("ERROR#%d: Could not allocate a mux object.\n", err, ErrGet);
816 }
Urvang Joshicdf97aa2011-12-01 16:11:51 +0530817 err = WebPMuxSetImage(mux_single, data, size, alpha_data, alpha_size, 1);
Urvang Joshia4f32ca2011-09-30 11:07:01 +0530818 if (err != WEBP_MUX_OK) {
819 ERROR_GOTO2("ERROR#%d: Could not create single image mux object.\n", err,
820 ErrGet);
821 }
822 ok = WriteWebP(mux_single, config->output_);
823
824 ErrGet:
825 WebPMuxDelete(mux_single);
826 return ok;
827}
828
829// Read and process config.
James Zern04e84cf2011-11-04 15:20:08 -0700830static int Process(const WebPMuxConfig* config) {
Urvang Joshia4f32ca2011-09-30 11:07:01 +0530831 WebPMux* mux = NULL;
832 const uint8_t* data = NULL;
833 uint32_t size = 0;
Urvang Joshicdf97aa2011-12-01 16:11:51 +0530834 const uint8_t* alpha_data = NULL;
835 uint32_t alpha_size = 0;
Urvang Joshia4f32ca2011-09-30 11:07:01 +0530836 uint32_t x_offset = 0;
837 uint32_t y_offset = 0;
838 uint32_t duration = 0;
839 uint32_t loop_count = 0;
840 WebPMuxError err = WEBP_MUX_OK;
841 long num;
842 int index = 0;
843 int ok = 1;
James Zern04e84cf2011-11-04 15:20:08 -0700844 const Feature* const feature = &config->feature_;
Urvang Joshia4f32ca2011-09-30 11:07:01 +0530845
James Zern04e84cf2011-11-04 15:20:08 -0700846 switch (config->action_type_) {
Urvang Joshia4f32ca2011-09-30 11:07:01 +0530847 case ACTION_GET:
848 ok = ReadFile(config->input_, &mux);
849 if (!ok) goto Err2;
James Zern04e84cf2011-11-04 15:20:08 -0700850 switch (feature->type_) {
Urvang Joshia4f32ca2011-09-30 11:07:01 +0530851 case FEATURE_FRM:
852 ok = GetFrameTile(mux, config, 1);
853 break;
854
855 case FEATURE_TILE:
856 ok = GetFrameTile(mux, config, 0);
857 break;
858
859 case FEATURE_ICCP:
860 err = WebPMuxGetColorProfile(mux, &data, &size);
861 if (err != WEBP_MUX_OK) {
862 ERROR_GOTO2("ERROR#%d: Could not get color profile.\n", err, Err2);
863 }
864 ok = WriteData(config->output_, (void*)data, size);
865 break;
866
867 case FEATURE_XMP:
868 err = WebPMuxGetMetadata(mux, &data, &size);
869 if (err != WEBP_MUX_OK) {
870 ERROR_GOTO2("ERROR#%d: Could not get XMP metadata.\n", err, Err2);
871 }
872 ok = WriteData(config->output_, (void*)data, size);
873 break;
874
875 default:
876 ERROR_GOTO1("ERROR: Invalid feature for action 'get'.\n", Err2);
877 break;
878 }
879 break;
880
881 case ACTION_SET:
James Zern04e84cf2011-11-04 15:20:08 -0700882 switch (feature->type_) {
Urvang Joshia4f32ca2011-09-30 11:07:01 +0530883 case FEATURE_FRM:
884 mux = WebPMuxNew();
885 if (mux == NULL) {
886 ERROR_GOTO2("ERROR#%d: Could not allocate a mux object.\n",
887 WEBP_MUX_MEMORY_ERROR, Err2);
888 }
889 for (index = 0; index < feature->arg_count_; ++index) {
890 if (feature->args_[index].subtype_ == SUBTYPE_LOOP) {
891 num = strtol(feature->args_[index].params_, NULL, 10);
892 if (num < 0) {
893 ERROR_GOTO1("ERROR: Loop count must be non-negative.\n", Err2);
894 } else {
895 loop_count = num;
896 }
897 err = WebPMuxSetLoopCount(mux, loop_count);
898 if (err != WEBP_MUX_OK) {
899 ERROR_GOTO2("ERROR#%d: Could not set loop count.\n", err, Err2);
900 }
901 } else if (feature->args_[index].subtype_ == SUBTYPE_FRM) {
Urvang Joshicdf97aa2011-12-01 16:11:51 +0530902 ok = ReadImage(feature->args_[index].filename_,
903 &data, &size, &alpha_data, &alpha_size);
Urvang Joshia4f32ca2011-09-30 11:07:01 +0530904 if (!ok) goto Err2;
905 ok = ParseFrameArgs(feature->args_[index].params_,
906 &x_offset, &y_offset, &duration);
907 if (!ok) {
908 free((void*)data);
Urvang Joshicdf97aa2011-12-01 16:11:51 +0530909 free((void*)alpha_data);
Urvang Joshia4f32ca2011-09-30 11:07:01 +0530910 ERROR_GOTO1("ERROR: Could not parse frame properties.\n", Err2);
911 }
Urvang Joshicdf97aa2011-12-01 16:11:51 +0530912 err = WebPMuxAddFrame(mux, 0, data, size, alpha_data, alpha_size,
Urvang Joshic398f592011-11-22 14:40:41 +0530913 x_offset, y_offset, duration, 1);
Urvang Joshia4f32ca2011-09-30 11:07:01 +0530914 free((void*)data);
Urvang Joshicdf97aa2011-12-01 16:11:51 +0530915 free((void*)alpha_data);
Urvang Joshia4f32ca2011-09-30 11:07:01 +0530916 if (err != WEBP_MUX_OK) {
917 ERROR_GOTO3("ERROR#%d: Could not add a frame at index %d.\n",
918 err, index, Err2);
919 }
920 } else {
921 ERROR_GOTO1("ERROR: Invalid subtype for 'frame'", Err2);
922 }
923 }
924 break;
925
926 case FEATURE_TILE:
927 mux = WebPMuxNew();
928 if (mux == NULL) {
929 ERROR_GOTO2("ERROR#%d: Could not allocate a mux object.\n",
930 WEBP_MUX_MEMORY_ERROR, Err2);
931 }
932 for (index = 0; index < feature->arg_count_; ++index) {
Urvang Joshicdf97aa2011-12-01 16:11:51 +0530933 ok = ReadImage(feature->args_[index].filename_,
934 &data, &size, &alpha_data, &alpha_size);
Urvang Joshia4f32ca2011-09-30 11:07:01 +0530935 if (!ok) goto Err2;
936 ok = ParseTileArgs(feature->args_[index].params_, &x_offset,
937 &y_offset);
938 if (!ok) {
939 free((void*)data);
Urvang Joshicdf97aa2011-12-01 16:11:51 +0530940 free((void*)alpha_data);
Urvang Joshia4f32ca2011-09-30 11:07:01 +0530941 ERROR_GOTO1("ERROR: Could not parse tile properties.\n", Err2);
942 }
Urvang Joshicdf97aa2011-12-01 16:11:51 +0530943 err = WebPMuxAddTile(mux, 0, data, size, alpha_data, alpha_size,
Urvang Joshic398f592011-11-22 14:40:41 +0530944 x_offset, y_offset, 1);
Urvang Joshia4f32ca2011-09-30 11:07:01 +0530945 free((void*)data);
Urvang Joshicdf97aa2011-12-01 16:11:51 +0530946 free((void*)alpha_data);
Urvang Joshia4f32ca2011-09-30 11:07:01 +0530947 if (err != WEBP_MUX_OK) {
948 ERROR_GOTO3("ERROR#%d: Could not add a tile at index %d.\n",
949 err, index, Err2);
950 }
951 }
952 break;
953
954 case FEATURE_ICCP:
955 ok = ReadFile(config->input_, &mux);
956 if (!ok) goto Err2;
James Zern04e84cf2011-11-04 15:20:08 -0700957 ok = ReadData(feature->args_[0].filename_, (void**)&data, &size);
Urvang Joshia4f32ca2011-09-30 11:07:01 +0530958 if (!ok) goto Err2;
959 err = WebPMuxSetColorProfile(mux, data, size, 1);
960 free((void*)data);
961 if (err != WEBP_MUX_OK) {
962 ERROR_GOTO2("ERROR#%d: Could not set color profile.\n", err, Err2);
963 }
964 break;
965
966 case FEATURE_XMP:
967 ok = ReadFile(config->input_, &mux);
968 if (!ok) goto Err2;
James Zern04e84cf2011-11-04 15:20:08 -0700969 ok = ReadData(feature->args_[0].filename_, (void**)&data, &size);
Urvang Joshia4f32ca2011-09-30 11:07:01 +0530970 if (!ok) goto Err2;
971 err = WebPMuxSetMetadata(mux, data, size, 1);
972 free((void*)data);
973 if (err != WEBP_MUX_OK) {
974 ERROR_GOTO2("ERROR#%d: Could not set XMP metadata.\n", err, Err2);
975 }
976 break;
977
978 default:
979 ERROR_GOTO1("ERROR: Invalid feature for action 'set'.\n", Err2);
980 break;
981 }
982 ok = WriteWebP(mux, config->output_);
983 break;
984
985 case ACTION_STRIP:
986 ok = ReadFile(config->input_, &mux);
987 if (!ok) goto Err2;
James Zern04e84cf2011-11-04 15:20:08 -0700988 switch (feature->type_) {
Urvang Joshia4f32ca2011-09-30 11:07:01 +0530989 case FEATURE_ICCP:
990 err = WebPMuxDeleteColorProfile(mux);
991 if (err != WEBP_MUX_OK) {
992 ERROR_GOTO2("ERROR#%d: Could not delete color profile.\n", err,
993 Err2);
994 }
995 break;
996 case FEATURE_XMP:
997 err = WebPMuxDeleteMetadata(mux);
998 if (err != WEBP_MUX_OK) {
999 ERROR_GOTO2("ERROR#%d: Could not delete XMP metadata.\n", err,
1000 Err2);
1001 }
1002 break;
1003 default:
1004 ERROR_GOTO1("ERROR: Invalid feature for action 'strip'.\n", Err2);
1005 break;
1006 }
1007 ok = WriteWebP(mux, config->output_);
1008 break;
1009
1010 case ACTION_INFO:
1011 ok = ReadFile(config->input_, &mux);
1012 if (!ok) goto Err2;
1013 ok = (DisplayInfo(mux) == WEBP_MUX_OK);
1014 break;
1015
1016 default:
1017 assert(0); // Invalid action.
1018 break;
1019 }
1020
1021 Err2:
1022 WebPMuxDelete(mux);
1023 return ok;
1024}
1025
1026//------------------------------------------------------------------------------
1027// Main.
1028
1029int main(int argc, const char* argv[]) {
1030 int ok = 1;
1031 WebPMuxConfig* config;
1032 ok = InitializeConfig(argc-1, argv+1, &config);
1033 if (ok) {
1034 Process(config);
1035 } else {
1036 PrintHelp();
1037 }
1038 DeleteConfig(config);
1039 return ok;
1040}
1041
1042//------------------------------------------------------------------------------