blob: e5cc4b54b34927f222a47e9ed466db1c5ed9ea08 [file] [log] [blame]
James Zernad1e1632012-01-06 14:49:06 -08001// Copyright 2011 Google Inc. All Rights Reserved.
Urvang Joshia4f32ca2011-09-30 11:07:01 +05302//
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) {
James Zern2f741d12011-12-02 16:00:51 -0800393 uint8_t* const data_mem = (uint8_t*)malloc(size);
394 uint8_t* const alpha_mem = (uint8_t*)malloc(alpha_size);
395 if ((data_mem != NULL) && (alpha_mem != NULL)) {
396 memcpy(data_mem, data, size);
397 memcpy(alpha_mem, alpha_data, alpha_size);
398 *data_ptr = data_mem;
399 *size_ptr = size;
400 *alpha_data_ptr = alpha_mem;
401 *alpha_size_ptr = alpha_size;
Urvang Joshicdf97aa2011-12-01 16:11:51 +0530402 ok = 1;
403 } else {
James Zern2f741d12011-12-02 16:00:51 -0800404 free(data_mem);
405 free(alpha_mem);
Urvang Joshicdf97aa2011-12-01 16:11:51 +0530406 err = WEBP_MUX_MEMORY_ERROR;
407 fprintf(stderr, "Failed to allocate %d bytes to extract image data from"
408 " file %s. Error: %d\n", size + alpha_size, filename, err);
409 }
410 } else {
411 fprintf(stderr, "Failed to extract image data from file %s. Error: %d\n",
412 filename, err);
413 }
414 WebPMuxDelete(mux);
415 return ok;
416}
417
Urvang Joshia4f32ca2011-09-30 11:07:01 +0530418static int WriteData(const char* filename, void* data, uint32_t size) {
419 int ok = 0;
James Zern04e84cf2011-11-04 15:20:08 -0700420 FILE* fout = strcmp(filename, "-") ? fopen(filename, "wb") : stdout;
Urvang Joshia4f32ca2011-09-30 11:07:01 +0530421 if (!fout) {
422 fprintf(stderr, "Error opening output WebP file %s!\n", filename);
423 return 0;
424 }
425 if (fwrite(data, size, 1, fout) != 1) {
426 fprintf(stderr, "Error writing file %s!\n", filename);
427 } else {
428 fprintf(stderr, "Saved file %s (%d bytes)\n", filename, size);
429 ok = 1;
430 }
431 if (fout != stdout) fclose(fout);
432 return ok;
433}
434
435static int WriteWebP(WebPMux* const mux, const char* filename) {
436 uint8_t* data = NULL;
437 uint32_t size = 0;
438 int ok;
439
440 WebPMuxError err = WebPMuxAssemble(mux, &data, &size);
441 if (err != WEBP_MUX_OK) {
442 fprintf(stderr, "Error (%d) assembling the WebP file.\n", err);
443 return 0;
444 }
445 ok = WriteData(filename, data, size);
446 free(data);
447 return ok;
448}
449
450static int ParseFrameArgs(const char* args, uint32_t* x_offset,
451 uint32_t* y_offset, uint32_t* duration) {
452 return (sscanf(args, "+%d+%d+%d", x_offset, y_offset, duration) == 3);
453}
454
455static int ParseTileArgs(const char* args, uint32_t* x_offset,
456 uint32_t* y_offset) {
457 return (sscanf(args, "+%d+%d", x_offset, y_offset) == 2);
458}
459
460//------------------------------------------------------------------------------
461// Clean-up.
462
463static void DeleteConfig(WebPMuxConfig* config) {
464 if (config != NULL) {
465 free(config->feature_.args_);
466 free(config);
467 }
468}
469
470//------------------------------------------------------------------------------
471// Parsing.
472
473// Basic syntactic checks on the command-line arguments.
474// Returns 1 on valid, 0 otherwise.
475// Also fills up num_feature_args to be number of feature arguments given.
476// (e.g. if there are 4 '-frame's and 1 '-loop', then num_feature_args = 5).
477static int ValidateCommandLine(int argc, const char* argv[],
478 int* num_feature_args) {
479 int num_frame_args;
480 int num_tile_args;
481 int num_loop_args;
482 int ok = 1;
483
484 assert(num_feature_args != NULL);
485 *num_feature_args = 0;
486
487 // Simple checks.
James Zern04e84cf2011-11-04 15:20:08 -0700488 if (CountOccurrences(argv, argc, "-get") > 1) {
Urvang Joshia4f32ca2011-09-30 11:07:01 +0530489 ERROR_GOTO1("ERROR: Multiple '-get' arguments specified.\n", ErrValidate);
490 }
James Zern04e84cf2011-11-04 15:20:08 -0700491 if (CountOccurrences(argv, argc, "-set") > 1) {
Urvang Joshia4f32ca2011-09-30 11:07:01 +0530492 ERROR_GOTO1("ERROR: Multiple '-set' arguments specified.\n", ErrValidate);
493 }
James Zern04e84cf2011-11-04 15:20:08 -0700494 if (CountOccurrences(argv, argc, "-strip") > 1) {
Urvang Joshia4f32ca2011-09-30 11:07:01 +0530495 ERROR_GOTO1("ERROR: Multiple '-strip' arguments specified.\n", ErrValidate);
496 }
James Zern04e84cf2011-11-04 15:20:08 -0700497 if (CountOccurrences(argv, argc, "-info") > 1) {
Urvang Joshia4f32ca2011-09-30 11:07:01 +0530498 ERROR_GOTO1("ERROR: Multiple '-info' arguments specified.\n", ErrValidate);
499 }
James Zern04e84cf2011-11-04 15:20:08 -0700500 if (CountOccurrences(argv, argc, "-o") > 1) {
Urvang Joshia4f32ca2011-09-30 11:07:01 +0530501 ERROR_GOTO1("ERROR: Multiple output files specified.\n", ErrValidate);
502 }
503
504 // Compound checks.
James Zern04e84cf2011-11-04 15:20:08 -0700505 num_frame_args = CountOccurrences(argv, argc, "-frame");
506 num_tile_args = CountOccurrences(argv, argc, "-tile");
507 num_loop_args = CountOccurrences(argv, argc, "-loop");
Urvang Joshia4f32ca2011-09-30 11:07:01 +0530508
509 if (num_loop_args > 1) {
510 ERROR_GOTO1("ERROR: Multiple loop counts specified.\n", ErrValidate);
511 }
512
513 if (IsNotCompatible(num_frame_args, num_loop_args)) {
514 ERROR_GOTO1("ERROR: Both frames and loop count have to be specified.\n",
515 ErrValidate);
516 }
517 if (num_frame_args > 0 && num_tile_args > 0) {
518 ERROR_GOTO1("ERROR: Only one of frames & tiles can be specified at a time."
519 "\n", ErrValidate);
520 }
521
522 assert(ok == 1);
523 if (num_frame_args == 0 && num_tile_args == 0) {
524 // Single argument ('set' action for XMP or ICCP, OR a 'get' action).
525 *num_feature_args = 1;
526 } else {
527 // Multiple arguments ('set' action for animation or tiling).
528 if (num_frame_args > 0) {
529 *num_feature_args = num_frame_args + num_loop_args;
530 } else {
531 *num_feature_args = num_tile_args;
532 }
533 }
534
535 ErrValidate:
536 return ok;
537}
538
539#define ACTION_IS_NIL (config->action_type_ == NIL_ACTION)
540
541#define FEATURETYPE_IS_NIL (feature->type_ == NIL_FEATURE)
542
543#define CHECK_NUM_ARGS_LESS(NUM, LABEL) \
544 if (argc < i + (NUM)) { \
545 fprintf(stderr, "ERROR: Too few arguments for '%s'.\n", argv[i]); \
546 goto LABEL; \
547 }
548
549#define CHECK_NUM_ARGS_NOT_EQUAL(NUM, LABEL) \
550 if (argc != i + (NUM)) { \
551 fprintf(stderr, "ERROR: Too many arguments for '%s'.\n", argv[i]); \
552 goto LABEL; \
553 }
554
555// Parses command-line arguments to fill up config object. Also performs some
556// semantic checks.
557static int ParseCommandLine(int argc, const char* argv[],
558 WebPMuxConfig* config) {
559 int i = 0;
560 int feature_arg_index = 0;
561 int ok = 1;
562
563 while (i < argc) {
564 Feature* const feature = &config->feature_;
James Zern04e84cf2011-11-04 15:20:08 -0700565 FeatureArg* const arg = &feature->args_[feature_arg_index];
Urvang Joshia4f32ca2011-09-30 11:07:01 +0530566 if (argv[i][0] == '-') { // One of the action types or output.
567 if (!strcmp(argv[i], "-set")) {
568 if (ACTION_IS_NIL) {
569 config->action_type_ = ACTION_SET;
570 } else {
571 ERROR_GOTO1("ERROR: Multiple actions specified.\n", ErrParse);
572 }
573 ++i;
574 } else if (!strcmp(argv[i], "-get")) {
575 if (ACTION_IS_NIL) {
576 config->action_type_ = ACTION_GET;
577 } else {
578 ERROR_GOTO1("ERROR: Multiple actions specified.\n", ErrParse);
579 }
580 ++i;
581 } else if (!strcmp(argv[i], "-strip")) {
582 if (ACTION_IS_NIL) {
583 config->action_type_ = ACTION_STRIP;
584 feature->arg_count_ = 0;
585 } else {
586 ERROR_GOTO1("ERROR: Multiple actions specified.\n", ErrParse);
587 }
588 ++i;
589 } else if (!strcmp(argv[i], "-frame")) {
590 CHECK_NUM_ARGS_LESS(3, ErrParse);
591 if (ACTION_IS_NIL || config->action_type_ == ACTION_SET) {
592 config->action_type_ = ACTION_SET;
593 } else {
594 ERROR_GOTO1("ERROR: Multiple actions specified.\n", ErrParse);
595 }
596 if (FEATURETYPE_IS_NIL || feature->type_ == FEATURE_FRM) {
597 feature->type_ = FEATURE_FRM;
598 } else {
599 ERROR_GOTO1("ERROR: Multiple features specified.\n", ErrParse);
600 }
601 arg->subtype_ = SUBTYPE_FRM;
602 arg->filename_ = argv[i + 1];
603 arg->params_ = argv[i + 2];
604 ++feature_arg_index;
605 i += 3;
606 } else if (!strcmp(argv[i], "-loop")) {
607 CHECK_NUM_ARGS_LESS(2, ErrParse);
608 if (ACTION_IS_NIL || config->action_type_ == ACTION_SET) {
609 config->action_type_ = ACTION_SET;
610 } else {
611 ERROR_GOTO1("ERROR: Multiple actions specified.\n", ErrParse);
612 }
613 if (FEATURETYPE_IS_NIL || feature->type_ == FEATURE_FRM) {
614 feature->type_ = FEATURE_FRM;
615 } else {
616 ERROR_GOTO1("ERROR: Multiple features specified.\n", ErrParse);
617 }
618 arg->subtype_ = SUBTYPE_LOOP;
619 arg->params_ = argv[i + 1];
620 ++feature_arg_index;
621 i += 2;
622 } else if (!strcmp(argv[i], "-tile")) {
623 CHECK_NUM_ARGS_LESS(3, ErrParse);
624 if (ACTION_IS_NIL || config->action_type_ == ACTION_SET) {
625 config->action_type_ = ACTION_SET;
626 } else {
627 ERROR_GOTO1("ERROR: Multiple actions specified.\n", ErrParse);
628 }
629 if (FEATURETYPE_IS_NIL || feature->type_ == FEATURE_TILE) {
630 feature->type_ = FEATURE_TILE;
631 } else {
632 ERROR_GOTO1("ERROR: Multiple features specified.\n", ErrParse);
633 }
634 arg->filename_ = argv[i + 1];
635 arg->params_ = argv[i + 2];
636 ++feature_arg_index;
637 i += 3;
638 } else if (!strcmp(argv[i], "-o")) {
639 CHECK_NUM_ARGS_LESS(2, ErrParse);
640 config->output_ = argv[i + 1];
641 i += 2;
642 } else if (!strcmp(argv[i], "-info")) {
643 CHECK_NUM_ARGS_NOT_EQUAL(2, ErrParse);
644 if (config->action_type_ != NIL_ACTION) {
645 ERROR_GOTO1("ERROR: Multiple actions specified.\n", ErrParse);
646 } else {
647 config->action_type_ = ACTION_INFO;
648 feature->arg_count_ = 0;
649 config->input_ = argv[i + 1];
650 }
651 i += 2;
652 } else if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "-help")) {
653 PrintHelp();
654 DeleteConfig(config);
655 exit(1);
656 } else {
657 ERROR_GOTO2("ERROR: Unknown option: '%s'.\n", argv[i], ErrParse);
658 }
659 } else { // One of the feature types or input.
660 if (ACTION_IS_NIL) {
661 ERROR_GOTO1("ERROR: Action must be specified before other arguments.\n",
662 ErrParse);
663 }
664 if (!strcmp(argv[i], "icc") || !strcmp(argv[i], "xmp")) {
665 if (FEATURETYPE_IS_NIL) {
666 feature->type_ = (!strcmp(argv[i], "icc")) ? FEATURE_ICCP :
667 FEATURE_XMP;
668 } else {
669 ERROR_GOTO1("ERROR: Multiple features specified.\n", ErrParse);
670 }
671 if (config->action_type_ == ACTION_SET) {
672 CHECK_NUM_ARGS_LESS(2, ErrParse);
673 arg->filename_ = argv[i + 1];
674 ++feature_arg_index;
675 i += 2;
676 } else {
677 ++i;
678 }
679 } else if ((!strcmp(argv[i], "frame") ||
680 !strcmp(argv[i], "tile")) &&
681 (config->action_type_ == ACTION_GET)) {
682 CHECK_NUM_ARGS_LESS(2, ErrParse);
683 feature->type_ = (!strcmp(argv[i], "frame")) ? FEATURE_FRM :
684 FEATURE_TILE;
685 arg->params_ = argv[i + 1];
686 ++feature_arg_index;
687 i += 2;
James Zern04e84cf2011-11-04 15:20:08 -0700688 } else { // Assume input file.
Urvang Joshia4f32ca2011-09-30 11:07:01 +0530689 if (config->input_ == NULL) {
690 config->input_ = argv[i];
691 } else {
692 ERROR_GOTO2("ERROR at '%s': Multiple input files specified.\n",
693 argv[i], ErrParse);
694 }
695 ++i;
696 }
697 }
698 }
699 ErrParse:
700 return ok;
701}
702
James Zern04e84cf2011-11-04 15:20:08 -0700703// Additional checks after config is filled.
Urvang Joshia4f32ca2011-09-30 11:07:01 +0530704static int ValidateConfig(WebPMuxConfig* config) {
705 int ok = 1;
706 Feature* const feature = &config->feature_;
707
708 // Action.
709 if (ACTION_IS_NIL) {
710 ERROR_GOTO1("ERROR: No action specified.\n", ErrValidate2);
711 }
712
713 // Feature type.
714 if (FEATURETYPE_IS_NIL && config->action_type_ != ACTION_INFO) {
715 ERROR_GOTO1("ERROR: No feature specified.\n", ErrValidate2);
716 }
717
718 // Input file.
719 if (config->input_ == NULL) {
720 if (config->action_type_ != ACTION_SET) {
721 ERROR_GOTO1("ERROR: No input file specified.\n", ErrValidate2);
722 } else if (feature->type_ != FEATURE_FRM &&
723 feature->type_ != FEATURE_TILE) {
724 ERROR_GOTO1("ERROR: No input file specified.\n", ErrValidate2);
725 }
726 }
727
728 // Output file.
729 if (config->output_ == NULL && config->action_type_ != ACTION_INFO) {
730 ERROR_GOTO1("ERROR: No output file specified.\n", ErrValidate2);
731 }
732
733 ErrValidate2:
734 return ok;
735}
736
737// Create config object from command-line arguments.
738static int InitializeConfig(int argc, const char* argv[],
739 WebPMuxConfig** config) {
740 int num_feature_args = 0;
741 int ok = 1;
742
743 assert(config != NULL);
744 *config = NULL;
745
746 // Validate command-line arguments.
747 if (!ValidateCommandLine(argc, argv, &num_feature_args)) {
748 ERROR_GOTO1("Exiting due to command-line parsing error.\n", Err1);
749 }
750
751 // Allocate memory.
752 *config = (WebPMuxConfig*)calloc(1, sizeof(**config));
753 if (*config == NULL) {
754 ERROR_GOTO1("ERROR: Memory allocation error.\n", Err1);
755 }
756 (*config)->feature_.arg_count_ = num_feature_args;
757 (*config)->feature_.args_ =
758 (FeatureArg*)calloc(num_feature_args, sizeof(FeatureArg));
759 if ((*config)->feature_.args_ == NULL) {
760 ERROR_GOTO1("ERROR: Memory allocation error.\n", Err1);
761 }
762
763 // Parse command-line.
James Zern04e84cf2011-11-04 15:20:08 -0700764 if (!ParseCommandLine(argc, argv, *config) ||
765 !ValidateConfig(*config)) {
Urvang Joshia4f32ca2011-09-30 11:07:01 +0530766 ERROR_GOTO1("Exiting due to command-line parsing error.\n", Err1);
767 }
768
769 Err1:
770 return ok;
771}
772
773#undef ACTION_IS_NIL
774#undef FEATURETYPE_IS_NIL
775#undef CHECK_NUM_ARGS_LESS
776#undef CHECK_NUM_ARGS_MORE
777
778//------------------------------------------------------------------------------
779// Processing.
780
James Zern04e84cf2011-11-04 15:20:08 -0700781static int GetFrameTile(const WebPMux* mux,
782 const WebPMuxConfig* config, int isFrame) {
Urvang Joshia4f32ca2011-09-30 11:07:01 +0530783 const uint8_t* data = NULL;
784 uint32_t size = 0;
Urvang Joshicdf97aa2011-12-01 16:11:51 +0530785 const uint8_t* alpha_data = NULL;
786 uint32_t alpha_size = 0;
Urvang Joshia4f32ca2011-09-30 11:07:01 +0530787 uint32_t x_offset = 0;
788 uint32_t y_offset = 0;
789 uint32_t duration = 0;
790 WebPMuxError err = WEBP_MUX_OK;
791 WebPMux* mux_single = NULL;
792 long num = 0;
793 int ok = 1;
794
795 num = strtol(config->feature_.args_[0].params_, NULL, 10);
796 if (num < 0) {
797 ERROR_GOTO1("ERROR: Frame/Tile index must be non-negative.\n", ErrGet);
798 }
799
800 if (isFrame) {
Urvang Joshicdf97aa2011-12-01 16:11:51 +0530801 err = WebPMuxGetFrame(mux, num, &data, &size, &alpha_data, &alpha_size,
Urvang Joshic398f592011-11-22 14:40:41 +0530802 &x_offset, &y_offset, &duration);
Urvang Joshia4f32ca2011-09-30 11:07:01 +0530803 if (err != WEBP_MUX_OK) {
804 ERROR_GOTO3("ERROR#%d: Could not get frame %ld.\n", err, num, ErrGet);
805 }
806 } else {
Urvang Joshicdf97aa2011-12-01 16:11:51 +0530807 err = WebPMuxGetTile(mux, num, &data, &size, &alpha_data, &alpha_size,
Urvang Joshic398f592011-11-22 14:40:41 +0530808 &x_offset, &y_offset);
Urvang Joshia4f32ca2011-09-30 11:07:01 +0530809 if (err != WEBP_MUX_OK) {
810 ERROR_GOTO3("ERROR#%d: Could not get frame %ld.\n", err, num, ErrGet);
811 }
812 }
813
814 mux_single = WebPMuxNew();
815 if (mux_single == NULL) {
816 err = WEBP_MUX_MEMORY_ERROR;
817 ERROR_GOTO2("ERROR#%d: Could not allocate a mux object.\n", err, ErrGet);
818 }
Urvang Joshicdf97aa2011-12-01 16:11:51 +0530819 err = WebPMuxSetImage(mux_single, data, size, alpha_data, alpha_size, 1);
Urvang Joshia4f32ca2011-09-30 11:07:01 +0530820 if (err != WEBP_MUX_OK) {
821 ERROR_GOTO2("ERROR#%d: Could not create single image mux object.\n", err,
822 ErrGet);
823 }
824 ok = WriteWebP(mux_single, config->output_);
825
826 ErrGet:
827 WebPMuxDelete(mux_single);
828 return ok;
829}
830
831// Read and process config.
James Zern04e84cf2011-11-04 15:20:08 -0700832static int Process(const WebPMuxConfig* config) {
Urvang Joshia4f32ca2011-09-30 11:07:01 +0530833 WebPMux* mux = NULL;
834 const uint8_t* data = NULL;
835 uint32_t size = 0;
Urvang Joshicdf97aa2011-12-01 16:11:51 +0530836 const uint8_t* alpha_data = NULL;
837 uint32_t alpha_size = 0;
Urvang Joshia4f32ca2011-09-30 11:07:01 +0530838 uint32_t x_offset = 0;
839 uint32_t y_offset = 0;
840 uint32_t duration = 0;
841 uint32_t loop_count = 0;
842 WebPMuxError err = WEBP_MUX_OK;
843 long num;
844 int index = 0;
845 int ok = 1;
James Zern04e84cf2011-11-04 15:20:08 -0700846 const Feature* const feature = &config->feature_;
Urvang Joshia4f32ca2011-09-30 11:07:01 +0530847
James Zern04e84cf2011-11-04 15:20:08 -0700848 switch (config->action_type_) {
Urvang Joshia4f32ca2011-09-30 11:07:01 +0530849 case ACTION_GET:
850 ok = ReadFile(config->input_, &mux);
851 if (!ok) goto Err2;
James Zern04e84cf2011-11-04 15:20:08 -0700852 switch (feature->type_) {
Urvang Joshia4f32ca2011-09-30 11:07:01 +0530853 case FEATURE_FRM:
854 ok = GetFrameTile(mux, config, 1);
855 break;
856
857 case FEATURE_TILE:
858 ok = GetFrameTile(mux, config, 0);
859 break;
860
861 case FEATURE_ICCP:
862 err = WebPMuxGetColorProfile(mux, &data, &size);
863 if (err != WEBP_MUX_OK) {
864 ERROR_GOTO2("ERROR#%d: Could not get color profile.\n", err, Err2);
865 }
866 ok = WriteData(config->output_, (void*)data, size);
867 break;
868
869 case FEATURE_XMP:
870 err = WebPMuxGetMetadata(mux, &data, &size);
871 if (err != WEBP_MUX_OK) {
872 ERROR_GOTO2("ERROR#%d: Could not get XMP metadata.\n", err, Err2);
873 }
874 ok = WriteData(config->output_, (void*)data, size);
875 break;
876
877 default:
878 ERROR_GOTO1("ERROR: Invalid feature for action 'get'.\n", Err2);
879 break;
880 }
881 break;
882
883 case ACTION_SET:
James Zern04e84cf2011-11-04 15:20:08 -0700884 switch (feature->type_) {
Urvang Joshia4f32ca2011-09-30 11:07:01 +0530885 case FEATURE_FRM:
886 mux = WebPMuxNew();
887 if (mux == NULL) {
888 ERROR_GOTO2("ERROR#%d: Could not allocate a mux object.\n",
889 WEBP_MUX_MEMORY_ERROR, Err2);
890 }
891 for (index = 0; index < feature->arg_count_; ++index) {
892 if (feature->args_[index].subtype_ == SUBTYPE_LOOP) {
893 num = strtol(feature->args_[index].params_, NULL, 10);
894 if (num < 0) {
895 ERROR_GOTO1("ERROR: Loop count must be non-negative.\n", Err2);
896 } else {
897 loop_count = num;
898 }
899 err = WebPMuxSetLoopCount(mux, loop_count);
900 if (err != WEBP_MUX_OK) {
901 ERROR_GOTO2("ERROR#%d: Could not set loop count.\n", err, Err2);
902 }
903 } else if (feature->args_[index].subtype_ == SUBTYPE_FRM) {
Urvang Joshicdf97aa2011-12-01 16:11:51 +0530904 ok = ReadImage(feature->args_[index].filename_,
905 &data, &size, &alpha_data, &alpha_size);
Urvang Joshia4f32ca2011-09-30 11:07:01 +0530906 if (!ok) goto Err2;
907 ok = ParseFrameArgs(feature->args_[index].params_,
908 &x_offset, &y_offset, &duration);
909 if (!ok) {
910 free((void*)data);
Urvang Joshicdf97aa2011-12-01 16:11:51 +0530911 free((void*)alpha_data);
Urvang Joshia4f32ca2011-09-30 11:07:01 +0530912 ERROR_GOTO1("ERROR: Could not parse frame properties.\n", Err2);
913 }
Urvang Joshicdf97aa2011-12-01 16:11:51 +0530914 err = WebPMuxAddFrame(mux, 0, data, size, alpha_data, alpha_size,
Urvang Joshic398f592011-11-22 14:40:41 +0530915 x_offset, y_offset, duration, 1);
Urvang Joshia4f32ca2011-09-30 11:07:01 +0530916 free((void*)data);
Urvang Joshicdf97aa2011-12-01 16:11:51 +0530917 free((void*)alpha_data);
Urvang Joshia4f32ca2011-09-30 11:07:01 +0530918 if (err != WEBP_MUX_OK) {
919 ERROR_GOTO3("ERROR#%d: Could not add a frame at index %d.\n",
920 err, index, Err2);
921 }
922 } else {
923 ERROR_GOTO1("ERROR: Invalid subtype for 'frame'", Err2);
924 }
925 }
926 break;
927
928 case FEATURE_TILE:
929 mux = WebPMuxNew();
930 if (mux == NULL) {
931 ERROR_GOTO2("ERROR#%d: Could not allocate a mux object.\n",
932 WEBP_MUX_MEMORY_ERROR, Err2);
933 }
934 for (index = 0; index < feature->arg_count_; ++index) {
Urvang Joshicdf97aa2011-12-01 16:11:51 +0530935 ok = ReadImage(feature->args_[index].filename_,
936 &data, &size, &alpha_data, &alpha_size);
Urvang Joshia4f32ca2011-09-30 11:07:01 +0530937 if (!ok) goto Err2;
938 ok = ParseTileArgs(feature->args_[index].params_, &x_offset,
939 &y_offset);
940 if (!ok) {
941 free((void*)data);
Urvang Joshicdf97aa2011-12-01 16:11:51 +0530942 free((void*)alpha_data);
Urvang Joshia4f32ca2011-09-30 11:07:01 +0530943 ERROR_GOTO1("ERROR: Could not parse tile properties.\n", Err2);
944 }
Urvang Joshicdf97aa2011-12-01 16:11:51 +0530945 err = WebPMuxAddTile(mux, 0, data, size, alpha_data, alpha_size,
Urvang Joshic398f592011-11-22 14:40:41 +0530946 x_offset, y_offset, 1);
Urvang Joshia4f32ca2011-09-30 11:07:01 +0530947 free((void*)data);
Urvang Joshicdf97aa2011-12-01 16:11:51 +0530948 free((void*)alpha_data);
Urvang Joshia4f32ca2011-09-30 11:07:01 +0530949 if (err != WEBP_MUX_OK) {
950 ERROR_GOTO3("ERROR#%d: Could not add a tile at index %d.\n",
951 err, index, Err2);
952 }
953 }
954 break;
955
956 case FEATURE_ICCP:
957 ok = ReadFile(config->input_, &mux);
958 if (!ok) goto Err2;
James Zern04e84cf2011-11-04 15:20:08 -0700959 ok = ReadData(feature->args_[0].filename_, (void**)&data, &size);
Urvang Joshia4f32ca2011-09-30 11:07:01 +0530960 if (!ok) goto Err2;
961 err = WebPMuxSetColorProfile(mux, data, size, 1);
962 free((void*)data);
963 if (err != WEBP_MUX_OK) {
964 ERROR_GOTO2("ERROR#%d: Could not set color profile.\n", err, Err2);
965 }
966 break;
967
968 case FEATURE_XMP:
969 ok = ReadFile(config->input_, &mux);
970 if (!ok) goto Err2;
James Zern04e84cf2011-11-04 15:20:08 -0700971 ok = ReadData(feature->args_[0].filename_, (void**)&data, &size);
Urvang Joshia4f32ca2011-09-30 11:07:01 +0530972 if (!ok) goto Err2;
973 err = WebPMuxSetMetadata(mux, data, size, 1);
974 free((void*)data);
975 if (err != WEBP_MUX_OK) {
976 ERROR_GOTO2("ERROR#%d: Could not set XMP metadata.\n", err, Err2);
977 }
978 break;
979
980 default:
981 ERROR_GOTO1("ERROR: Invalid feature for action 'set'.\n", Err2);
982 break;
983 }
984 ok = WriteWebP(mux, config->output_);
985 break;
986
987 case ACTION_STRIP:
988 ok = ReadFile(config->input_, &mux);
989 if (!ok) goto Err2;
James Zern04e84cf2011-11-04 15:20:08 -0700990 switch (feature->type_) {
Urvang Joshia4f32ca2011-09-30 11:07:01 +0530991 case FEATURE_ICCP:
992 err = WebPMuxDeleteColorProfile(mux);
993 if (err != WEBP_MUX_OK) {
994 ERROR_GOTO2("ERROR#%d: Could not delete color profile.\n", err,
995 Err2);
996 }
997 break;
998 case FEATURE_XMP:
999 err = WebPMuxDeleteMetadata(mux);
1000 if (err != WEBP_MUX_OK) {
1001 ERROR_GOTO2("ERROR#%d: Could not delete XMP metadata.\n", err,
1002 Err2);
1003 }
1004 break;
1005 default:
1006 ERROR_GOTO1("ERROR: Invalid feature for action 'strip'.\n", Err2);
1007 break;
1008 }
1009 ok = WriteWebP(mux, config->output_);
1010 break;
1011
1012 case ACTION_INFO:
1013 ok = ReadFile(config->input_, &mux);
1014 if (!ok) goto Err2;
1015 ok = (DisplayInfo(mux) == WEBP_MUX_OK);
1016 break;
1017
1018 default:
1019 assert(0); // Invalid action.
1020 break;
1021 }
1022
1023 Err2:
1024 WebPMuxDelete(mux);
1025 return ok;
1026}
1027
1028//------------------------------------------------------------------------------
1029// Main.
1030
1031int main(int argc, const char* argv[]) {
1032 int ok = 1;
1033 WebPMuxConfig* config;
1034 ok = InitializeConfig(argc-1, argv+1, &config);
1035 if (ok) {
1036 Process(config);
1037 } else {
1038 PrintHelp();
1039 }
1040 DeleteConfig(config);
1041 return ok;
1042}
1043
1044//------------------------------------------------------------------------------