mirror of
https://github.com/webmproject/libwebp.git
synced 2024-12-30 15:28:23 +01:00
b4fa0a47ea
this enables webpmux to accept input files starting with '-' when using -get/-set/-strip; -info & -frame expect an input file as one of their parameters so no changes are necessary there. Change-Id: I154eb6dc388258a7fb743ec76ba869bf9589be1b
1070 lines
36 KiB
C
1070 lines
36 KiB
C
// Copyright 2011 Google Inc. All Rights Reserved.
|
|
//
|
|
// Use of this source code is governed by a BSD-style license
|
|
// that can be found in the COPYING file in the root of the source
|
|
// tree. An additional intellectual property rights grant can be found
|
|
// in the file PATENTS. All contributing project authors may
|
|
// be found in the AUTHORS file in the root of the source tree.
|
|
// -----------------------------------------------------------------------------
|
|
//
|
|
// Simple command-line to create a WebP container file and to extract or strip
|
|
// relevant data from the container file.
|
|
//
|
|
// Authors: Vikas (vikaas.arora@gmail.com),
|
|
// Urvang (urvang@google.com)
|
|
|
|
/* Usage examples:
|
|
|
|
Create container WebP file:
|
|
webpmux -frame anim_1.webp +100+10+10 \
|
|
-frame anim_2.webp +100+25+25+1 \
|
|
-frame anim_3.webp +100+50+50+1 \
|
|
-frame anim_4.webp +100 \
|
|
-loop 10 -bgcolor 128,255,255,255 \
|
|
-o out_animation_container.webp
|
|
|
|
webpmux -set icc image_profile.icc in.webp -o out_icc_container.webp
|
|
webpmux -set exif image_metadata.exif in.webp -o out_exif_container.webp
|
|
webpmux -set xmp image_metadata.xmp in.webp -o out_xmp_container.webp
|
|
|
|
Extract relevant data from WebP container file:
|
|
webpmux -get frgm n in.webp -o out_fragment.webp
|
|
webpmux -get frame n in.webp -o out_frame.webp
|
|
webpmux -get icc in.webp -o image_profile.icc
|
|
webpmux -get exif in.webp -o image_metadata.exif
|
|
webpmux -get xmp in.webp -o image_metadata.xmp
|
|
|
|
Strip data from WebP Container file:
|
|
webpmux -strip icc in.webp -o out.webp
|
|
webpmux -strip exif in.webp -o out.webp
|
|
webpmux -strip xmp in.webp -o out.webp
|
|
|
|
Misc:
|
|
webpmux -info in.webp
|
|
webpmux [ -h | -help ]
|
|
webpmux -version
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include <assert.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include "webp/decode.h"
|
|
#include "webp/mux.h"
|
|
#include "./example_util.h"
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Config object to parse command-line arguments.
|
|
|
|
typedef enum {
|
|
NIL_ACTION = 0,
|
|
ACTION_GET,
|
|
ACTION_SET,
|
|
ACTION_STRIP,
|
|
ACTION_INFO,
|
|
ACTION_HELP
|
|
} ActionType;
|
|
|
|
typedef enum {
|
|
NIL_SUBTYPE = 0,
|
|
SUBTYPE_ANMF,
|
|
SUBTYPE_LOOP,
|
|
SUBTYPE_BGCOLOR
|
|
} FeatureSubType;
|
|
|
|
typedef struct {
|
|
FeatureSubType subtype_;
|
|
const char* filename_;
|
|
const char* params_;
|
|
} FeatureArg;
|
|
|
|
typedef enum {
|
|
NIL_FEATURE = 0,
|
|
FEATURE_EXIF,
|
|
FEATURE_XMP,
|
|
FEATURE_ICCP,
|
|
FEATURE_ANMF,
|
|
FEATURE_FRGM,
|
|
LAST_FEATURE
|
|
} FeatureType;
|
|
|
|
static const char* const kFourccList[LAST_FEATURE] = {
|
|
NULL, "EXIF", "XMP ", "ICCP", "ANMF", "FRGM"
|
|
};
|
|
|
|
static const char* const kDescriptions[LAST_FEATURE] = {
|
|
NULL, "EXIF metadata", "XMP metadata", "ICC profile",
|
|
"Animation frame", "Image fragment"
|
|
};
|
|
|
|
typedef struct {
|
|
FeatureType type_;
|
|
FeatureArg* args_;
|
|
int arg_count_;
|
|
} Feature;
|
|
|
|
typedef struct {
|
|
ActionType action_type_;
|
|
const char* input_;
|
|
const char* output_;
|
|
Feature feature_;
|
|
} WebPMuxConfig;
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Helper functions.
|
|
|
|
static int CountOccurrences(const char* arglist[], int list_length,
|
|
const char* arg) {
|
|
int i;
|
|
int num_occurences = 0;
|
|
|
|
for (i = 0; i < list_length; ++i) {
|
|
if (!strcmp(arglist[i], arg)) {
|
|
++num_occurences;
|
|
}
|
|
}
|
|
return num_occurences;
|
|
}
|
|
|
|
static const char* const kErrorMessages[] = {
|
|
"WEBP_MUX_NOT_FOUND", "WEBP_MUX_INVALID_ARGUMENT", "WEBP_MUX_BAD_DATA",
|
|
"WEBP_MUX_MEMORY_ERROR", "WEBP_MUX_NOT_ENOUGH_DATA"
|
|
};
|
|
|
|
static const char* ErrorString(WebPMuxError err) {
|
|
assert(err <= WEBP_MUX_NOT_FOUND && err >= WEBP_MUX_NOT_ENOUGH_DATA);
|
|
return kErrorMessages[-err];
|
|
}
|
|
|
|
#define RETURN_IF_ERROR(ERR_MSG) \
|
|
if (err != WEBP_MUX_OK) { \
|
|
fprintf(stderr, ERR_MSG); \
|
|
return err; \
|
|
}
|
|
|
|
#define RETURN_IF_ERROR3(ERR_MSG, FORMAT_STR1, FORMAT_STR2) \
|
|
if (err != WEBP_MUX_OK) { \
|
|
fprintf(stderr, ERR_MSG, FORMAT_STR1, FORMAT_STR2); \
|
|
return err; \
|
|
}
|
|
|
|
#define ERROR_GOTO1(ERR_MSG, LABEL) \
|
|
do { \
|
|
fprintf(stderr, ERR_MSG); \
|
|
ok = 0; \
|
|
goto LABEL; \
|
|
} while (0)
|
|
|
|
#define ERROR_GOTO2(ERR_MSG, FORMAT_STR, LABEL) \
|
|
do { \
|
|
fprintf(stderr, ERR_MSG, FORMAT_STR); \
|
|
ok = 0; \
|
|
goto LABEL; \
|
|
} while (0)
|
|
|
|
#define ERROR_GOTO3(ERR_MSG, FORMAT_STR1, FORMAT_STR2, LABEL) \
|
|
do { \
|
|
fprintf(stderr, ERR_MSG, FORMAT_STR1, FORMAT_STR2); \
|
|
ok = 0; \
|
|
goto LABEL; \
|
|
} while (0)
|
|
|
|
static WebPMuxError DisplayInfo(const WebPMux* mux) {
|
|
int width, height;
|
|
uint32_t flag;
|
|
|
|
WebPMuxError err = WebPMuxGetCanvasSize(mux, &width, &height);
|
|
assert(err == WEBP_MUX_OK); // As WebPMuxCreate() was successful earlier.
|
|
printf("Canvas size: %d x %d\n", width, height);
|
|
|
|
err = WebPMuxGetFeatures(mux, &flag);
|
|
#ifndef WEBP_EXPERIMENTAL_FEATURES
|
|
if (flag & FRAGMENTS_FLAG) err = WEBP_MUX_INVALID_ARGUMENT;
|
|
#endif
|
|
RETURN_IF_ERROR("Failed to retrieve features\n");
|
|
|
|
if (flag == 0) {
|
|
fprintf(stderr, "No features present.\n");
|
|
return err;
|
|
}
|
|
|
|
// Print the features present.
|
|
printf("Features present:");
|
|
if (flag & ANIMATION_FLAG) printf(" animation");
|
|
if (flag & FRAGMENTS_FLAG) printf(" image fragments");
|
|
if (flag & ICCP_FLAG) printf(" ICC profile");
|
|
if (flag & EXIF_FLAG) printf(" EXIF metadata");
|
|
if (flag & XMP_FLAG) printf(" XMP metadata");
|
|
if (flag & ALPHA_FLAG) printf(" transparency");
|
|
printf("\n");
|
|
|
|
if ((flag & ANIMATION_FLAG) || (flag & FRAGMENTS_FLAG)) {
|
|
const int is_anim = !!(flag & ANIMATION_FLAG);
|
|
const WebPChunkId id = is_anim ? WEBP_CHUNK_ANMF : WEBP_CHUNK_FRGM;
|
|
const char* const type_str = is_anim ? "frame" : "fragment";
|
|
int nFrames;
|
|
|
|
if (is_anim) {
|
|
WebPMuxAnimParams params;
|
|
err = WebPMuxGetAnimationParams(mux, ¶ms);
|
|
assert(err == WEBP_MUX_OK);
|
|
printf("Background color : 0x%.8X Loop Count : %d\n",
|
|
params.bgcolor, params.loop_count);
|
|
}
|
|
|
|
err = WebPMuxNumChunks(mux, id, &nFrames);
|
|
assert(err == WEBP_MUX_OK);
|
|
|
|
printf("Number of %ss: %d\n", type_str, nFrames);
|
|
if (nFrames > 0) {
|
|
int i;
|
|
printf("No.: width height alpha x_offset y_offset ");
|
|
if (is_anim) printf("duration dispose blend ");
|
|
printf("image_size\n");
|
|
for (i = 1; i <= nFrames; i++) {
|
|
WebPMuxFrameInfo frame;
|
|
err = WebPMuxGetFrame(mux, i, &frame);
|
|
if (err == WEBP_MUX_OK) {
|
|
WebPBitstreamFeatures features;
|
|
const VP8StatusCode status = WebPGetFeatures(
|
|
frame.bitstream.bytes, frame.bitstream.size, &features);
|
|
assert(status == VP8_STATUS_OK); // Checked by WebPMuxCreate().
|
|
(void)status;
|
|
printf("%3d: %5d %5d %5s %8d %8d ", i, features.width,
|
|
features.height, features.has_alpha ? "yes" : "no",
|
|
frame.x_offset, frame.y_offset);
|
|
if (is_anim) {
|
|
const char* const dispose =
|
|
(frame.dispose_method == WEBP_MUX_DISPOSE_NONE) ? "none"
|
|
: "background";
|
|
const char* const blend =
|
|
(frame.blend_method == WEBP_MUX_BLEND) ? "yes" : "no";
|
|
printf("%8d %10s %5s ", frame.duration, dispose, blend);
|
|
}
|
|
printf("%10d\n", (int)frame.bitstream.size);
|
|
}
|
|
WebPDataClear(&frame.bitstream);
|
|
RETURN_IF_ERROR3("Failed to retrieve %s#%d\n", type_str, i);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (flag & ICCP_FLAG) {
|
|
WebPData icc_profile;
|
|
err = WebPMuxGetChunk(mux, "ICCP", &icc_profile);
|
|
assert(err == WEBP_MUX_OK);
|
|
printf("Size of the ICC profile data: %d\n", (int)icc_profile.size);
|
|
}
|
|
|
|
if (flag & EXIF_FLAG) {
|
|
WebPData exif;
|
|
err = WebPMuxGetChunk(mux, "EXIF", &exif);
|
|
assert(err == WEBP_MUX_OK);
|
|
printf("Size of the EXIF metadata: %d\n", (int)exif.size);
|
|
}
|
|
|
|
if (flag & XMP_FLAG) {
|
|
WebPData xmp;
|
|
err = WebPMuxGetChunk(mux, "XMP ", &xmp);
|
|
assert(err == WEBP_MUX_OK);
|
|
printf("Size of the XMP metadata: %d\n", (int)xmp.size);
|
|
}
|
|
|
|
if ((flag & ALPHA_FLAG) && !(flag & (ANIMATION_FLAG | FRAGMENTS_FLAG))) {
|
|
WebPMuxFrameInfo image;
|
|
err = WebPMuxGetFrame(mux, 1, &image);
|
|
if (err == WEBP_MUX_OK) {
|
|
printf("Size of the image (with alpha): %d\n", (int)image.bitstream.size);
|
|
}
|
|
WebPDataClear(&image.bitstream);
|
|
RETURN_IF_ERROR("Failed to retrieve the image\n");
|
|
}
|
|
|
|
return WEBP_MUX_OK;
|
|
}
|
|
|
|
static void PrintHelp(void) {
|
|
printf("Usage: webpmux -get GET_OPTIONS INPUT -o OUTPUT\n");
|
|
printf(" webpmux -set SET_OPTIONS INPUT -o OUTPUT\n");
|
|
printf(" webpmux -strip STRIP_OPTIONS INPUT -o OUTPUT\n");
|
|
#ifdef WEBP_EXPERIMENTAL_FEATURES
|
|
printf(" webpmux -frgm FRAGMENT_OPTIONS [-frgm...] -o OUTPUT\n");
|
|
#endif
|
|
printf(" webpmux -frame FRAME_OPTIONS [-frame...] [-loop LOOP_COUNT]"
|
|
"\n");
|
|
printf(" [-bgcolor BACKGROUND_COLOR] -o OUTPUT\n");
|
|
printf(" webpmux -info INPUT\n");
|
|
printf(" webpmux [-h|-help]\n");
|
|
printf(" webpmux -version\n");
|
|
|
|
printf("\n");
|
|
printf("GET_OPTIONS:\n");
|
|
printf(" Extract relevant data.\n");
|
|
printf(" icc Get ICC profile.\n");
|
|
printf(" exif Get EXIF metadata.\n");
|
|
printf(" xmp Get XMP metadata.\n");
|
|
#ifdef WEBP_EXPERIMENTAL_FEATURES
|
|
printf(" frgm n Get nth fragment.\n");
|
|
#endif
|
|
printf(" frame n Get nth frame.\n");
|
|
|
|
printf("\n");
|
|
printf("SET_OPTIONS:\n");
|
|
printf(" Set color profile/metadata.\n");
|
|
printf(" icc file.icc Set ICC profile.\n");
|
|
printf(" exif file.exif Set EXIF metadata.\n");
|
|
printf(" xmp file.xmp Set XMP metadata.\n");
|
|
printf(" where: 'file.icc' contains the ICC profile to be set,\n");
|
|
printf(" 'file.exif' contains the EXIF metadata to be set\n");
|
|
printf(" 'file.xmp' contains the XMP metadata to be set\n");
|
|
|
|
printf("\n");
|
|
printf("STRIP_OPTIONS:\n");
|
|
printf(" Strip color profile/metadata.\n");
|
|
printf(" icc Strip ICC profile.\n");
|
|
printf(" exif Strip EXIF metadata.\n");
|
|
printf(" xmp Strip XMP metadata.\n");
|
|
|
|
#ifdef WEBP_EXPERIMENTAL_FEATURES
|
|
printf("\n");
|
|
printf("FRAGMENT_OPTIONS(i):\n");
|
|
printf(" Create fragmented image.\n");
|
|
printf(" file_i +xi+yi\n");
|
|
printf(" where: 'file_i' is the i'th fragment (WebP format),\n");
|
|
printf(" 'xi','yi' specify the image offset for this fragment."
|
|
"\n");
|
|
#endif
|
|
|
|
printf("\n");
|
|
printf("FRAME_OPTIONS(i):\n");
|
|
printf(" Create animation.\n");
|
|
printf(" file_i +di+[xi+yi[+mi[bi]]]\n");
|
|
printf(" where: 'file_i' is the i'th animation frame (WebP format),\n");
|
|
printf(" 'di' is the pause duration before next frame.\n");
|
|
printf(" 'xi','yi' specify the image offset for this frame.\n");
|
|
printf(" 'mi' is the dispose method for this frame (0 or 1).\n");
|
|
printf(" 'bi' is the blending method for this frame (+b or -b)."
|
|
"\n");
|
|
|
|
printf("\n");
|
|
printf("LOOP_COUNT:\n");
|
|
printf(" Number of times to repeat the animation.\n");
|
|
printf(" Valid range is 0 to 65535 [Default: 0 (infinite)].\n");
|
|
|
|
printf("\n");
|
|
printf("BACKGROUND_COLOR:\n");
|
|
printf(" Background color of the canvas.\n");
|
|
printf(" A,R,G,B\n");
|
|
printf(" where: 'A', 'R', 'G' and 'B' are integers in the range 0 to 255 "
|
|
"specifying\n");
|
|
printf(" the Alpha, Red, Green and Blue component values "
|
|
"respectively\n");
|
|
printf(" [Default: 255,255,255,255].\n");
|
|
|
|
printf("\nINPUT & OUTPUT are in WebP format.\n");
|
|
|
|
printf("\nNote: The nature of EXIF, XMP and ICC data is not checked");
|
|
printf(" and is assumed to be\nvalid.\n");
|
|
}
|
|
|
|
static int ReadFileToWebPData(const char* const filename,
|
|
WebPData* const webp_data) {
|
|
const uint8_t* data;
|
|
size_t size;
|
|
if (!ExUtilReadFile(filename, &data, &size)) return 0;
|
|
webp_data->bytes = data;
|
|
webp_data->size = size;
|
|
return 1;
|
|
}
|
|
|
|
static int CreateMux(const char* const filename, WebPMux** mux) {
|
|
WebPData bitstream;
|
|
assert(mux != NULL);
|
|
if (!ReadFileToWebPData(filename, &bitstream)) return 0;
|
|
*mux = WebPMuxCreate(&bitstream, 1);
|
|
free((void*)bitstream.bytes);
|
|
if (*mux != NULL) return 1;
|
|
fprintf(stderr, "Failed to create mux object from file %s.\n", filename);
|
|
return 0;
|
|
}
|
|
|
|
static int WriteData(const char* filename, const WebPData* const webpdata) {
|
|
int ok = 0;
|
|
FILE* fout = strcmp(filename, "-") ? fopen(filename, "wb") : stdout;
|
|
if (!fout) {
|
|
fprintf(stderr, "Error opening output WebP file %s!\n", filename);
|
|
return 0;
|
|
}
|
|
if (fwrite(webpdata->bytes, webpdata->size, 1, fout) != 1) {
|
|
fprintf(stderr, "Error writing file %s!\n", filename);
|
|
} else {
|
|
fprintf(stderr, "Saved file %s (%d bytes)\n",
|
|
filename, (int)webpdata->size);
|
|
ok = 1;
|
|
}
|
|
if (fout != stdout) fclose(fout);
|
|
return ok;
|
|
}
|
|
|
|
static int WriteWebP(WebPMux* const mux, const char* filename) {
|
|
int ok;
|
|
WebPData webp_data;
|
|
const WebPMuxError err = WebPMuxAssemble(mux, &webp_data);
|
|
if (err != WEBP_MUX_OK) {
|
|
fprintf(stderr, "Error (%s) assembling the WebP file.\n", ErrorString(err));
|
|
return 0;
|
|
}
|
|
ok = WriteData(filename, &webp_data);
|
|
WebPDataClear(&webp_data);
|
|
return ok;
|
|
}
|
|
|
|
static int ParseFrameArgs(const char* args, WebPMuxFrameInfo* const info) {
|
|
int dispose_method, dummy;
|
|
char plus_minus, blend_method;
|
|
const int num_args = sscanf(args, "+%d+%d+%d+%d%c%c+%d", &info->duration,
|
|
&info->x_offset, &info->y_offset, &dispose_method,
|
|
&plus_minus, &blend_method, &dummy);
|
|
switch (num_args) {
|
|
case 1:
|
|
info->x_offset = info->y_offset = 0; // fall through
|
|
case 3:
|
|
dispose_method = 0; // fall through
|
|
case 4:
|
|
plus_minus = '+';
|
|
blend_method = 'b'; // fall through
|
|
case 6:
|
|
break;
|
|
case 2:
|
|
case 5:
|
|
default:
|
|
return 0;
|
|
}
|
|
// Note: The sanity of the following conversion is checked by
|
|
// WebPMuxPushFrame().
|
|
info->dispose_method = (WebPMuxAnimDispose)dispose_method;
|
|
|
|
if (blend_method != 'b') return 0;
|
|
if (plus_minus != '-' && plus_minus != '+') return 0;
|
|
info->blend_method =
|
|
(plus_minus == '+') ? WEBP_MUX_BLEND : WEBP_MUX_NO_BLEND;
|
|
return 1;
|
|
}
|
|
|
|
static int ParseFragmentArgs(const char* args, WebPMuxFrameInfo* const info) {
|
|
return (sscanf(args, "+%d+%d", &info->x_offset, &info->y_offset) == 2);
|
|
}
|
|
|
|
static int ParseBgcolorArgs(const char* args, uint32_t* const bgcolor) {
|
|
uint32_t a, r, g, b;
|
|
if (sscanf(args, "%u,%u,%u,%u", &a, &r, &g, &b) != 4) return 0;
|
|
if (a >= 256 || r >= 256 || g >= 256 || b >= 256) return 0;
|
|
*bgcolor = (a << 24) | (r << 16) | (g << 8) | (b << 0);
|
|
return 1;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Clean-up.
|
|
|
|
static void DeleteConfig(WebPMuxConfig* config) {
|
|
if (config != NULL) {
|
|
free(config->feature_.args_);
|
|
free(config);
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Parsing.
|
|
|
|
// Basic syntactic checks on the command-line arguments.
|
|
// Returns 1 on valid, 0 otherwise.
|
|
// Also fills up num_feature_args to be number of feature arguments given.
|
|
// (e.g. if there are 4 '-frame's and 1 '-loop', then num_feature_args = 5).
|
|
static int ValidateCommandLine(int argc, const char* argv[],
|
|
int* num_feature_args) {
|
|
int num_frame_args;
|
|
int num_frgm_args;
|
|
int num_loop_args;
|
|
int num_bgcolor_args;
|
|
int ok = 1;
|
|
|
|
assert(num_feature_args != NULL);
|
|
*num_feature_args = 0;
|
|
|
|
// Simple checks.
|
|
if (CountOccurrences(argv, argc, "-get") > 1) {
|
|
ERROR_GOTO1("ERROR: Multiple '-get' arguments specified.\n", ErrValidate);
|
|
}
|
|
if (CountOccurrences(argv, argc, "-set") > 1) {
|
|
ERROR_GOTO1("ERROR: Multiple '-set' arguments specified.\n", ErrValidate);
|
|
}
|
|
if (CountOccurrences(argv, argc, "-strip") > 1) {
|
|
ERROR_GOTO1("ERROR: Multiple '-strip' arguments specified.\n", ErrValidate);
|
|
}
|
|
if (CountOccurrences(argv, argc, "-info") > 1) {
|
|
ERROR_GOTO1("ERROR: Multiple '-info' arguments specified.\n", ErrValidate);
|
|
}
|
|
if (CountOccurrences(argv, argc, "-o") > 1) {
|
|
ERROR_GOTO1("ERROR: Multiple output files specified.\n", ErrValidate);
|
|
}
|
|
|
|
// Compound checks.
|
|
num_frame_args = CountOccurrences(argv, argc, "-frame");
|
|
num_frgm_args = CountOccurrences(argv, argc, "-frgm");
|
|
num_loop_args = CountOccurrences(argv, argc, "-loop");
|
|
num_bgcolor_args = CountOccurrences(argv, argc, "-bgcolor");
|
|
|
|
if (num_loop_args > 1) {
|
|
ERROR_GOTO1("ERROR: Multiple loop counts specified.\n", ErrValidate);
|
|
}
|
|
if (num_bgcolor_args > 1) {
|
|
ERROR_GOTO1("ERROR: Multiple background colors specified.\n", ErrValidate);
|
|
}
|
|
|
|
if ((num_frame_args == 0) && (num_loop_args + num_bgcolor_args > 0)) {
|
|
ERROR_GOTO1("ERROR: Loop count and background color are relevant only in "
|
|
"case of animation.\n", ErrValidate);
|
|
}
|
|
if (num_frame_args > 0 && num_frgm_args > 0) {
|
|
ERROR_GOTO1("ERROR: Only one of frames & fragments can be specified at a "
|
|
"time.\n", ErrValidate);
|
|
}
|
|
|
|
assert(ok == 1);
|
|
if (num_frame_args == 0 && num_frgm_args == 0) {
|
|
// Single argument ('set' action for ICCP/EXIF/XMP, OR a 'get' action).
|
|
*num_feature_args = 1;
|
|
} else {
|
|
// Multiple arguments ('set' action for animation or fragmented image).
|
|
if (num_frame_args > 0) {
|
|
*num_feature_args = num_frame_args + num_loop_args + num_bgcolor_args;
|
|
} else {
|
|
*num_feature_args = num_frgm_args;
|
|
}
|
|
}
|
|
|
|
ErrValidate:
|
|
return ok;
|
|
}
|
|
|
|
#define ACTION_IS_NIL (config->action_type_ == NIL_ACTION)
|
|
|
|
#define FEATURETYPE_IS_NIL (feature->type_ == NIL_FEATURE)
|
|
|
|
#define CHECK_NUM_ARGS_LESS(NUM, LABEL) \
|
|
if (argc < i + (NUM)) { \
|
|
fprintf(stderr, "ERROR: Too few arguments for '%s'.\n", argv[i]); \
|
|
goto LABEL; \
|
|
}
|
|
|
|
#define CHECK_NUM_ARGS_NOT_EQUAL(NUM, LABEL) \
|
|
if (argc != i + (NUM)) { \
|
|
fprintf(stderr, "ERROR: Too many arguments for '%s'.\n", argv[i]); \
|
|
goto LABEL; \
|
|
}
|
|
|
|
// Parses command-line arguments to fill up config object. Also performs some
|
|
// semantic checks.
|
|
static int ParseCommandLine(int argc, const char* argv[],
|
|
WebPMuxConfig* config) {
|
|
int i = 0;
|
|
int feature_arg_index = 0;
|
|
int ok = 1;
|
|
|
|
while (i < argc) {
|
|
Feature* const feature = &config->feature_;
|
|
FeatureArg* const arg = &feature->args_[feature_arg_index];
|
|
if (argv[i][0] == '-') { // One of the action types or output.
|
|
if (!strcmp(argv[i], "-set")) {
|
|
if (ACTION_IS_NIL) {
|
|
config->action_type_ = ACTION_SET;
|
|
} else {
|
|
ERROR_GOTO1("ERROR: Multiple actions specified.\n", ErrParse);
|
|
}
|
|
++i;
|
|
} else if (!strcmp(argv[i], "-get")) {
|
|
if (ACTION_IS_NIL) {
|
|
config->action_type_ = ACTION_GET;
|
|
} else {
|
|
ERROR_GOTO1("ERROR: Multiple actions specified.\n", ErrParse);
|
|
}
|
|
++i;
|
|
} else if (!strcmp(argv[i], "-strip")) {
|
|
if (ACTION_IS_NIL) {
|
|
config->action_type_ = ACTION_STRIP;
|
|
feature->arg_count_ = 0;
|
|
} else {
|
|
ERROR_GOTO1("ERROR: Multiple actions specified.\n", ErrParse);
|
|
}
|
|
++i;
|
|
} else if (!strcmp(argv[i], "-frame")) {
|
|
CHECK_NUM_ARGS_LESS(3, ErrParse);
|
|
if (ACTION_IS_NIL || config->action_type_ == ACTION_SET) {
|
|
config->action_type_ = ACTION_SET;
|
|
} else {
|
|
ERROR_GOTO1("ERROR: Multiple actions specified.\n", ErrParse);
|
|
}
|
|
if (FEATURETYPE_IS_NIL || feature->type_ == FEATURE_ANMF) {
|
|
feature->type_ = FEATURE_ANMF;
|
|
} else {
|
|
ERROR_GOTO1("ERROR: Multiple features specified.\n", ErrParse);
|
|
}
|
|
arg->subtype_ = SUBTYPE_ANMF;
|
|
arg->filename_ = argv[i + 1];
|
|
arg->params_ = argv[i + 2];
|
|
++feature_arg_index;
|
|
i += 3;
|
|
} else if (!strcmp(argv[i], "-loop") || !strcmp(argv[i], "-bgcolor")) {
|
|
CHECK_NUM_ARGS_LESS(2, ErrParse);
|
|
if (ACTION_IS_NIL || config->action_type_ == ACTION_SET) {
|
|
config->action_type_ = ACTION_SET;
|
|
} else {
|
|
ERROR_GOTO1("ERROR: Multiple actions specified.\n", ErrParse);
|
|
}
|
|
if (FEATURETYPE_IS_NIL || feature->type_ == FEATURE_ANMF) {
|
|
feature->type_ = FEATURE_ANMF;
|
|
} else {
|
|
ERROR_GOTO1("ERROR: Multiple features specified.\n", ErrParse);
|
|
}
|
|
arg->subtype_ =
|
|
!strcmp(argv[i], "-loop") ? SUBTYPE_LOOP : SUBTYPE_BGCOLOR;
|
|
arg->params_ = argv[i + 1];
|
|
++feature_arg_index;
|
|
i += 2;
|
|
#ifdef WEBP_EXPERIMENTAL_FEATURES
|
|
} else if (!strcmp(argv[i], "-frgm")) {
|
|
CHECK_NUM_ARGS_LESS(3, ErrParse);
|
|
if (ACTION_IS_NIL || config->action_type_ == ACTION_SET) {
|
|
config->action_type_ = ACTION_SET;
|
|
} else {
|
|
ERROR_GOTO1("ERROR: Multiple actions specified.\n", ErrParse);
|
|
}
|
|
if (FEATURETYPE_IS_NIL || feature->type_ == FEATURE_FRGM) {
|
|
feature->type_ = FEATURE_FRGM;
|
|
} else {
|
|
ERROR_GOTO1("ERROR: Multiple features specified.\n", ErrParse);
|
|
}
|
|
arg->filename_ = argv[i + 1];
|
|
arg->params_ = argv[i + 2];
|
|
++feature_arg_index;
|
|
i += 3;
|
|
#endif
|
|
} else if (!strcmp(argv[i], "-o")) {
|
|
CHECK_NUM_ARGS_LESS(2, ErrParse);
|
|
config->output_ = argv[i + 1];
|
|
i += 2;
|
|
} else if (!strcmp(argv[i], "-info")) {
|
|
CHECK_NUM_ARGS_NOT_EQUAL(2, ErrParse);
|
|
if (config->action_type_ != NIL_ACTION) {
|
|
ERROR_GOTO1("ERROR: Multiple actions specified.\n", ErrParse);
|
|
} else {
|
|
config->action_type_ = ACTION_INFO;
|
|
feature->arg_count_ = 0;
|
|
config->input_ = argv[i + 1];
|
|
}
|
|
i += 2;
|
|
} else if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "-help")) {
|
|
PrintHelp();
|
|
DeleteConfig(config);
|
|
exit(0);
|
|
} else if (!strcmp(argv[i], "-version")) {
|
|
const int version = WebPGetMuxVersion();
|
|
printf("%d.%d.%d\n",
|
|
(version >> 16) & 0xff, (version >> 8) & 0xff, version & 0xff);
|
|
DeleteConfig(config);
|
|
exit(0);
|
|
} else if (!strcmp(argv[i], "--")) {
|
|
if (i < argc - 1) {
|
|
++i;
|
|
if (config->input_ == NULL) {
|
|
config->input_ = argv[i];
|
|
} else {
|
|
ERROR_GOTO2("ERROR at '%s': Multiple input files specified.\n",
|
|
argv[i], ErrParse);
|
|
}
|
|
}
|
|
break;
|
|
} else {
|
|
ERROR_GOTO2("ERROR: Unknown option: '%s'.\n", argv[i], ErrParse);
|
|
}
|
|
} else { // One of the feature types or input.
|
|
if (ACTION_IS_NIL) {
|
|
ERROR_GOTO1("ERROR: Action must be specified before other arguments.\n",
|
|
ErrParse);
|
|
}
|
|
if (!strcmp(argv[i], "icc") || !strcmp(argv[i], "exif") ||
|
|
!strcmp(argv[i], "xmp")) {
|
|
if (FEATURETYPE_IS_NIL) {
|
|
feature->type_ = (!strcmp(argv[i], "icc")) ? FEATURE_ICCP :
|
|
(!strcmp(argv[i], "exif")) ? FEATURE_EXIF : FEATURE_XMP;
|
|
} else {
|
|
ERROR_GOTO1("ERROR: Multiple features specified.\n", ErrParse);
|
|
}
|
|
if (config->action_type_ == ACTION_SET) {
|
|
CHECK_NUM_ARGS_LESS(2, ErrParse);
|
|
arg->filename_ = argv[i + 1];
|
|
++feature_arg_index;
|
|
i += 2;
|
|
} else {
|
|
++i;
|
|
}
|
|
#ifdef WEBP_EXPERIMENTAL_FEATURES
|
|
} else if ((!strcmp(argv[i], "frame") ||
|
|
!strcmp(argv[i], "frgm")) &&
|
|
#else
|
|
} else if (!strcmp(argv[i], "frame") &&
|
|
#endif
|
|
(config->action_type_ == ACTION_GET)) {
|
|
CHECK_NUM_ARGS_LESS(2, ErrParse);
|
|
feature->type_ = (!strcmp(argv[i], "frame")) ? FEATURE_ANMF :
|
|
FEATURE_FRGM;
|
|
arg->params_ = argv[i + 1];
|
|
++feature_arg_index;
|
|
i += 2;
|
|
} else { // Assume input file.
|
|
if (config->input_ == NULL) {
|
|
config->input_ = argv[i];
|
|
} else {
|
|
ERROR_GOTO2("ERROR at '%s': Multiple input files specified.\n",
|
|
argv[i], ErrParse);
|
|
}
|
|
++i;
|
|
}
|
|
}
|
|
}
|
|
ErrParse:
|
|
return ok;
|
|
}
|
|
|
|
// Additional checks after config is filled.
|
|
static int ValidateConfig(WebPMuxConfig* config) {
|
|
int ok = 1;
|
|
Feature* const feature = &config->feature_;
|
|
|
|
// Action.
|
|
if (ACTION_IS_NIL) {
|
|
ERROR_GOTO1("ERROR: No action specified.\n", ErrValidate2);
|
|
}
|
|
|
|
// Feature type.
|
|
if (FEATURETYPE_IS_NIL && config->action_type_ != ACTION_INFO) {
|
|
ERROR_GOTO1("ERROR: No feature specified.\n", ErrValidate2);
|
|
}
|
|
|
|
// Input file.
|
|
if (config->input_ == NULL) {
|
|
if (config->action_type_ != ACTION_SET) {
|
|
ERROR_GOTO1("ERROR: No input file specified.\n", ErrValidate2);
|
|
} else if (feature->type_ != FEATURE_ANMF &&
|
|
feature->type_ != FEATURE_FRGM) {
|
|
ERROR_GOTO1("ERROR: No input file specified.\n", ErrValidate2);
|
|
}
|
|
}
|
|
|
|
// Output file.
|
|
if (config->output_ == NULL && config->action_type_ != ACTION_INFO) {
|
|
ERROR_GOTO1("ERROR: No output file specified.\n", ErrValidate2);
|
|
}
|
|
|
|
ErrValidate2:
|
|
return ok;
|
|
}
|
|
|
|
// Create config object from command-line arguments.
|
|
static int InitializeConfig(int argc, const char* argv[],
|
|
WebPMuxConfig** config) {
|
|
int num_feature_args = 0;
|
|
int ok = 1;
|
|
|
|
assert(config != NULL);
|
|
*config = NULL;
|
|
|
|
// Validate command-line arguments.
|
|
if (!ValidateCommandLine(argc, argv, &num_feature_args)) {
|
|
ERROR_GOTO1("Exiting due to command-line parsing error.\n", Err1);
|
|
}
|
|
|
|
// Allocate memory.
|
|
*config = (WebPMuxConfig*)calloc(1, sizeof(**config));
|
|
if (*config == NULL) {
|
|
ERROR_GOTO1("ERROR: Memory allocation error.\n", Err1);
|
|
}
|
|
(*config)->feature_.arg_count_ = num_feature_args;
|
|
(*config)->feature_.args_ =
|
|
(FeatureArg*)calloc(num_feature_args, sizeof(FeatureArg));
|
|
if ((*config)->feature_.args_ == NULL) {
|
|
ERROR_GOTO1("ERROR: Memory allocation error.\n", Err1);
|
|
}
|
|
|
|
// Parse command-line.
|
|
if (!ParseCommandLine(argc, argv, *config) ||
|
|
!ValidateConfig(*config)) {
|
|
ERROR_GOTO1("Exiting due to command-line parsing error.\n", Err1);
|
|
}
|
|
|
|
Err1:
|
|
return ok;
|
|
}
|
|
|
|
#undef ACTION_IS_NIL
|
|
#undef FEATURETYPE_IS_NIL
|
|
#undef CHECK_NUM_ARGS_LESS
|
|
#undef CHECK_NUM_ARGS_MORE
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Processing.
|
|
|
|
static int GetFrameFragment(const WebPMux* mux,
|
|
const WebPMuxConfig* config, int is_frame) {
|
|
WebPMuxError err = WEBP_MUX_OK;
|
|
WebPMux* mux_single = NULL;
|
|
long num = 0;
|
|
int ok = 1;
|
|
const WebPChunkId id = is_frame ? WEBP_CHUNK_ANMF : WEBP_CHUNK_FRGM;
|
|
WebPMuxFrameInfo info;
|
|
WebPDataInit(&info.bitstream);
|
|
|
|
num = strtol(config->feature_.args_[0].params_, NULL, 10);
|
|
if (num < 0) {
|
|
ERROR_GOTO1("ERROR: Frame/Fragment index must be non-negative.\n", ErrGet);
|
|
}
|
|
|
|
err = WebPMuxGetFrame(mux, num, &info);
|
|
if (err == WEBP_MUX_OK && info.id != id) err = WEBP_MUX_NOT_FOUND;
|
|
if (err != WEBP_MUX_OK) {
|
|
ERROR_GOTO3("ERROR (%s): Could not get frame %ld.\n",
|
|
ErrorString(err), num, ErrGet);
|
|
}
|
|
|
|
mux_single = WebPMuxNew();
|
|
if (mux_single == NULL) {
|
|
err = WEBP_MUX_MEMORY_ERROR;
|
|
ERROR_GOTO2("ERROR (%s): Could not allocate a mux object.\n",
|
|
ErrorString(err), ErrGet);
|
|
}
|
|
err = WebPMuxSetImage(mux_single, &info.bitstream, 1);
|
|
if (err != WEBP_MUX_OK) {
|
|
ERROR_GOTO2("ERROR (%s): Could not create single image mux object.\n",
|
|
ErrorString(err), ErrGet);
|
|
}
|
|
|
|
ok = WriteWebP(mux_single, config->output_);
|
|
|
|
ErrGet:
|
|
WebPDataClear(&info.bitstream);
|
|
WebPMuxDelete(mux_single);
|
|
return ok;
|
|
}
|
|
|
|
// Read and process config.
|
|
static int Process(const WebPMuxConfig* config) {
|
|
WebPMux* mux = NULL;
|
|
WebPData chunk;
|
|
WebPMuxError err = WEBP_MUX_OK;
|
|
int ok = 1;
|
|
const Feature* const feature = &config->feature_;
|
|
|
|
switch (config->action_type_) {
|
|
case ACTION_GET: {
|
|
ok = CreateMux(config->input_, &mux);
|
|
if (!ok) goto Err2;
|
|
switch (feature->type_) {
|
|
case FEATURE_ANMF:
|
|
case FEATURE_FRGM:
|
|
ok = GetFrameFragment(mux, config,
|
|
(feature->type_ == FEATURE_ANMF) ? 1 : 0);
|
|
break;
|
|
|
|
case FEATURE_ICCP:
|
|
case FEATURE_EXIF:
|
|
case FEATURE_XMP:
|
|
err = WebPMuxGetChunk(mux, kFourccList[feature->type_], &chunk);
|
|
if (err != WEBP_MUX_OK) {
|
|
ERROR_GOTO3("ERROR (%s): Could not get the %s.\n",
|
|
ErrorString(err), kDescriptions[feature->type_], Err2);
|
|
}
|
|
ok = WriteData(config->output_, &chunk);
|
|
break;
|
|
|
|
default:
|
|
ERROR_GOTO1("ERROR: Invalid feature for action 'get'.\n", Err2);
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
case ACTION_SET: {
|
|
switch (feature->type_) {
|
|
case FEATURE_ANMF: {
|
|
int i;
|
|
WebPMuxAnimParams params = { 0xFFFFFFFF, 0 };
|
|
mux = WebPMuxNew();
|
|
if (mux == NULL) {
|
|
ERROR_GOTO2("ERROR (%s): Could not allocate a mux object.\n",
|
|
ErrorString(WEBP_MUX_MEMORY_ERROR), Err2);
|
|
}
|
|
for (i = 0; i < feature->arg_count_; ++i) {
|
|
switch (feature->args_[i].subtype_) {
|
|
case SUBTYPE_BGCOLOR: {
|
|
uint32_t bgcolor;
|
|
ok = ParseBgcolorArgs(feature->args_[i].params_, &bgcolor);
|
|
if (!ok) {
|
|
ERROR_GOTO1("ERROR: Could not parse the background color \n",
|
|
Err2);
|
|
}
|
|
params.bgcolor = bgcolor;
|
|
break;
|
|
}
|
|
case SUBTYPE_LOOP: {
|
|
const long loop_count =
|
|
strtol(feature->args_[i].params_, NULL, 10);
|
|
if (loop_count != (int)loop_count) {
|
|
// Note: This is only a 'necessary' condition for loop_count
|
|
// to be valid. The 'sufficient' conditioned in checked in
|
|
// WebPMuxSetAnimationParams() method called later.
|
|
ERROR_GOTO1("ERROR: Loop count must be in the range 0 to "
|
|
"65535.\n", Err2);
|
|
}
|
|
params.loop_count = (int)loop_count;
|
|
break;
|
|
}
|
|
case SUBTYPE_ANMF: {
|
|
WebPMuxFrameInfo frame;
|
|
frame.id = WEBP_CHUNK_ANMF;
|
|
ok = ReadFileToWebPData(feature->args_[i].filename_,
|
|
&frame.bitstream);
|
|
if (!ok) goto Err2;
|
|
ok = ParseFrameArgs(feature->args_[i].params_, &frame);
|
|
if (!ok) {
|
|
WebPDataClear(&frame.bitstream);
|
|
ERROR_GOTO1("ERROR: Could not parse frame properties.\n",
|
|
Err2);
|
|
}
|
|
err = WebPMuxPushFrame(mux, &frame, 1);
|
|
WebPDataClear(&frame.bitstream);
|
|
if (err != WEBP_MUX_OK) {
|
|
ERROR_GOTO3("ERROR (%s): Could not add a frame at index %d."
|
|
"\n", ErrorString(err), i, Err2);
|
|
}
|
|
break;
|
|
}
|
|
default: {
|
|
ERROR_GOTO1("ERROR: Invalid subtype for 'frame'", Err2);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
err = WebPMuxSetAnimationParams(mux, ¶ms);
|
|
if (err != WEBP_MUX_OK) {
|
|
ERROR_GOTO2("ERROR (%s): Could not set animation parameters.\n",
|
|
ErrorString(err), Err2);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case FEATURE_FRGM: {
|
|
int i;
|
|
mux = WebPMuxNew();
|
|
if (mux == NULL) {
|
|
ERROR_GOTO2("ERROR (%s): Could not allocate a mux object.\n",
|
|
ErrorString(WEBP_MUX_MEMORY_ERROR), Err2);
|
|
}
|
|
for (i = 0; i < feature->arg_count_; ++i) {
|
|
WebPMuxFrameInfo frgm;
|
|
frgm.id = WEBP_CHUNK_FRGM;
|
|
ok = ReadFileToWebPData(feature->args_[i].filename_,
|
|
&frgm.bitstream);
|
|
if (!ok) goto Err2;
|
|
ok = ParseFragmentArgs(feature->args_[i].params_, &frgm);
|
|
if (!ok) {
|
|
WebPDataClear(&frgm.bitstream);
|
|
ERROR_GOTO1("ERROR: Could not parse fragment properties.\n",
|
|
Err2);
|
|
}
|
|
err = WebPMuxPushFrame(mux, &frgm, 1);
|
|
WebPDataClear(&frgm.bitstream);
|
|
if (err != WEBP_MUX_OK) {
|
|
ERROR_GOTO3("ERROR (%s): Could not add a fragment at index %d.\n",
|
|
ErrorString(err), i, Err2);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case FEATURE_ICCP:
|
|
case FEATURE_EXIF:
|
|
case FEATURE_XMP: {
|
|
ok = CreateMux(config->input_, &mux);
|
|
if (!ok) goto Err2;
|
|
ok = ReadFileToWebPData(feature->args_[0].filename_, &chunk);
|
|
if (!ok) goto Err2;
|
|
err = WebPMuxSetChunk(mux, kFourccList[feature->type_], &chunk, 1);
|
|
free((void*)chunk.bytes);
|
|
if (err != WEBP_MUX_OK) {
|
|
ERROR_GOTO3("ERROR (%s): Could not set the %s.\n",
|
|
ErrorString(err), kDescriptions[feature->type_], Err2);
|
|
}
|
|
break;
|
|
}
|
|
default: {
|
|
ERROR_GOTO1("ERROR: Invalid feature for action 'set'.\n", Err2);
|
|
break;
|
|
}
|
|
}
|
|
ok = WriteWebP(mux, config->output_);
|
|
break;
|
|
}
|
|
case ACTION_STRIP: {
|
|
ok = CreateMux(config->input_, &mux);
|
|
if (!ok) goto Err2;
|
|
if (feature->type_ == FEATURE_ICCP || feature->type_ == FEATURE_EXIF ||
|
|
feature->type_ == FEATURE_XMP) {
|
|
err = WebPMuxDeleteChunk(mux, kFourccList[feature->type_]);
|
|
if (err != WEBP_MUX_OK) {
|
|
ERROR_GOTO3("ERROR (%s): Could not strip the %s.\n",
|
|
ErrorString(err), kDescriptions[feature->type_], Err2);
|
|
}
|
|
} else {
|
|
ERROR_GOTO1("ERROR: Invalid feature for action 'strip'.\n", Err2);
|
|
break;
|
|
}
|
|
ok = WriteWebP(mux, config->output_);
|
|
break;
|
|
}
|
|
case ACTION_INFO: {
|
|
ok = CreateMux(config->input_, &mux);
|
|
if (!ok) goto Err2;
|
|
ok = (DisplayInfo(mux) == WEBP_MUX_OK);
|
|
break;
|
|
}
|
|
default: {
|
|
assert(0); // Invalid action.
|
|
break;
|
|
}
|
|
}
|
|
|
|
Err2:
|
|
WebPMuxDelete(mux);
|
|
return ok;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Main.
|
|
|
|
int main(int argc, const char* argv[]) {
|
|
WebPMuxConfig* config;
|
|
int ok = InitializeConfig(argc - 1, argv + 1, &config);
|
|
if (ok) {
|
|
ok = Process(config);
|
|
} else {
|
|
PrintHelp();
|
|
}
|
|
DeleteConfig(config);
|
|
return !ok;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|