mirror of
https://github.com/webmproject/libwebp.git
synced 2024-12-27 06:08:21 +01:00
Merge "add a "-duration duration,start,end" option to webpmux"
This commit is contained in:
commit
f90c60d129
10
README.mux
10
README.mux
@ -25,6 +25,8 @@ A list of options is available using the -help command line flag:
|
|||||||
> webpmux -help
|
> webpmux -help
|
||||||
Usage: webpmux -get GET_OPTIONS INPUT -o OUTPUT
|
Usage: webpmux -get GET_OPTIONS INPUT -o OUTPUT
|
||||||
webpmux -set SET_OPTIONS INPUT -o OUTPUT
|
webpmux -set SET_OPTIONS INPUT -o OUTPUT
|
||||||
|
webpmux -duration DURATION_OPTIONS [-duration ...]
|
||||||
|
INPUT -o OUTPUT
|
||||||
webpmux -strip STRIP_OPTIONS INPUT -o OUTPUT
|
webpmux -strip STRIP_OPTIONS INPUT -o OUTPUT
|
||||||
webpmux -frame FRAME_OPTIONS [-frame...] [-loop LOOP_COUNT]
|
webpmux -frame FRAME_OPTIONS [-frame...] [-loop LOOP_COUNT]
|
||||||
[-bgcolor BACKGROUND_COLOR] -o OUTPUT
|
[-bgcolor BACKGROUND_COLOR] -o OUTPUT
|
||||||
@ -48,6 +50,14 @@ SET_OPTIONS:
|
|||||||
'file.exif' contains the EXIF metadata to be set
|
'file.exif' contains the EXIF metadata to be set
|
||||||
'file.xmp' contains the XMP metadata to be set
|
'file.xmp' contains the XMP metadata to be set
|
||||||
|
|
||||||
|
DURATION_OPTIONS:
|
||||||
|
Set constant duration of frames:
|
||||||
|
duration[,start[,end]]
|
||||||
|
where: 'duration' is the duration in milliseconds,
|
||||||
|
'start' is the start frame index (optional)(default=1),
|
||||||
|
'end' is the inclusive end frame index (optional).
|
||||||
|
The special value '0' means: last frame (default=0).
|
||||||
|
|
||||||
STRIP_OPTIONS:
|
STRIP_OPTIONS:
|
||||||
Strip color profile/metadata:
|
Strip color profile/metadata:
|
||||||
icc strip ICC profile
|
icc strip ICC profile
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
// String parsing
|
// String parsing
|
||||||
@ -33,6 +34,18 @@ int ExUtilGetInt(const char* const v, int base, int* const error) {
|
|||||||
return (int)ExUtilGetUInt(v, base, error);
|
return (int)ExUtilGetUInt(v, base, error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int ExUtilGetInts(const char* v, int base, int max_output, int output[]) {
|
||||||
|
int n, error = 0;
|
||||||
|
for (n = 0; v != NULL && n < max_output; ++n) {
|
||||||
|
const int value = ExUtilGetInt(v, base, &error);
|
||||||
|
if (error) return -1;
|
||||||
|
output[n] = value;
|
||||||
|
v = strchr(v, ',');
|
||||||
|
if (v != NULL) ++v; // skip over the trailing ','
|
||||||
|
}
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
float ExUtilGetFloat(const char* const v, int* const error) {
|
float ExUtilGetFloat(const char* const v, int* const error) {
|
||||||
char* end = NULL;
|
char* end = NULL;
|
||||||
const float f = (v != NULL) ? (float)strtod(v, &end) : 0.f;
|
const float f = (v != NULL) ? (float)strtod(v, &end) : 0.f;
|
||||||
|
@ -29,6 +29,12 @@ uint32_t ExUtilGetUInt(const char* const v, int base, int* const error);
|
|||||||
int ExUtilGetInt(const char* const v, int base, int* const error);
|
int ExUtilGetInt(const char* const v, int base, int* const error);
|
||||||
float ExUtilGetFloat(const char* const v, int* const error);
|
float ExUtilGetFloat(const char* const v, int* const error);
|
||||||
|
|
||||||
|
// This variant of ExUtilGetInt() will parse multiple integers from a
|
||||||
|
// comma-separated list. Up to 'max_output' integers are parsed.
|
||||||
|
// The result is placed in the output[] array, and the number of integers
|
||||||
|
// actually parsed is returned, or -1 if an error occurred.
|
||||||
|
int ExUtilGetInts(const char* v, int base, int max_output, int output[]);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
} // extern "C"
|
} // extern "C"
|
||||||
#endif
|
#endif
|
||||||
|
@ -38,6 +38,11 @@
|
|||||||
webpmux -strip exif in.webp -o out.webp
|
webpmux -strip exif in.webp -o out.webp
|
||||||
webpmux -strip xmp in.webp -o out.webp
|
webpmux -strip xmp in.webp -o out.webp
|
||||||
|
|
||||||
|
Change duration of frame intervals:
|
||||||
|
webpmux -duration 150 in.webp -o out.webp
|
||||||
|
webpmux -duration 33,10,0 in.webp -o out.webp
|
||||||
|
webpmux -duration 200,2 -duration 150,0,50 in.webp -o out.webp
|
||||||
|
|
||||||
Misc:
|
Misc:
|
||||||
webpmux -info in.webp
|
webpmux -info in.webp
|
||||||
webpmux [ -h | -help ]
|
webpmux [ -h | -help ]
|
||||||
@ -66,7 +71,8 @@ typedef enum {
|
|||||||
ACTION_SET,
|
ACTION_SET,
|
||||||
ACTION_STRIP,
|
ACTION_STRIP,
|
||||||
ACTION_INFO,
|
ACTION_INFO,
|
||||||
ACTION_HELP
|
ACTION_HELP,
|
||||||
|
ACTION_DURATION
|
||||||
} ActionType;
|
} ActionType;
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
@ -88,6 +94,7 @@ typedef enum {
|
|||||||
FEATURE_XMP,
|
FEATURE_XMP,
|
||||||
FEATURE_ICCP,
|
FEATURE_ICCP,
|
||||||
FEATURE_ANMF,
|
FEATURE_ANMF,
|
||||||
|
FEATURE_DURATION,
|
||||||
LAST_FEATURE
|
LAST_FEATURE
|
||||||
} FeatureType;
|
} FeatureType;
|
||||||
|
|
||||||
@ -285,6 +292,8 @@ static WebPMuxError DisplayInfo(const WebPMux* mux) {
|
|||||||
static void PrintHelp(void) {
|
static void PrintHelp(void) {
|
||||||
printf("Usage: webpmux -get GET_OPTIONS INPUT -o OUTPUT\n");
|
printf("Usage: webpmux -get GET_OPTIONS INPUT -o OUTPUT\n");
|
||||||
printf(" webpmux -set SET_OPTIONS INPUT -o OUTPUT\n");
|
printf(" webpmux -set SET_OPTIONS INPUT -o OUTPUT\n");
|
||||||
|
printf(" webpmux -duration DURATION_OPTIONS [-duration ...]\n");
|
||||||
|
printf(" INPUT -o OUTPUT\n");
|
||||||
printf(" webpmux -strip STRIP_OPTIONS INPUT -o OUTPUT\n");
|
printf(" webpmux -strip STRIP_OPTIONS INPUT -o OUTPUT\n");
|
||||||
printf(" webpmux -frame FRAME_OPTIONS [-frame...] [-loop LOOP_COUNT]"
|
printf(" webpmux -frame FRAME_OPTIONS [-frame...] [-loop LOOP_COUNT]"
|
||||||
"\n");
|
"\n");
|
||||||
@ -311,6 +320,15 @@ static void PrintHelp(void) {
|
|||||||
printf(" 'file.exif' contains the EXIF metadata 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(" 'file.xmp' contains the XMP metadata to be set\n");
|
||||||
|
|
||||||
|
printf("\n");
|
||||||
|
printf("DURATION_OPTIONS:\n");
|
||||||
|
printf(" Set constant duration of frames:\n");
|
||||||
|
printf(" duration[,start[,end]]\n");
|
||||||
|
printf(" where: 'duration' is the duration in milliseconds,\n");
|
||||||
|
printf(" 'start' is the start frame index (optional)(default=1),\n");
|
||||||
|
printf(" 'end' is the inclusive end frame index (optional).\n");
|
||||||
|
printf(" The special value '0' means: last frame (default=0).\n");
|
||||||
|
|
||||||
printf("\n");
|
printf("\n");
|
||||||
printf("STRIP_OPTIONS:\n");
|
printf("STRIP_OPTIONS:\n");
|
||||||
printf(" Strip color profile/metadata:\n");
|
printf(" Strip color profile/metadata:\n");
|
||||||
@ -411,6 +429,45 @@ static int WriteWebP(WebPMux* const mux, const char* filename) {
|
|||||||
return ok;
|
return ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static WebPMux* DuplicateMuxHeader(const WebPMux* const mux) {
|
||||||
|
WebPMux* new_mux = WebPMuxNew();
|
||||||
|
WebPMuxAnimParams p;
|
||||||
|
WebPMuxError err;
|
||||||
|
int i;
|
||||||
|
int ok = 1;
|
||||||
|
|
||||||
|
if (new_mux == NULL) return NULL;
|
||||||
|
|
||||||
|
err = WebPMuxGetAnimationParams(mux, &p);
|
||||||
|
if (err == WEBP_MUX_OK) {
|
||||||
|
err = WebPMuxSetAnimationParams(new_mux, &p);
|
||||||
|
if (err != WEBP_MUX_OK) {
|
||||||
|
ERROR_GOTO2("Error (%s) handling animation params.\n",
|
||||||
|
ErrorString(err), End);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
/* it might not be an animation. Just keep moving. */
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 1; i <= 3; ++i) {
|
||||||
|
WebPData metadata;
|
||||||
|
err = WebPMuxGetChunk(mux, kFourccList[i], &metadata);
|
||||||
|
if (err == WEBP_MUX_OK && metadata.size > 0) {
|
||||||
|
err = WebPMuxSetChunk(new_mux, kFourccList[i], &metadata, 1);
|
||||||
|
if (err != WEBP_MUX_OK) {
|
||||||
|
ERROR_GOTO1("Error transferring metadata in DuplicateMux().", End);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
End:
|
||||||
|
if (!ok) {
|
||||||
|
WebPMuxDelete(new_mux);
|
||||||
|
new_mux = NULL;
|
||||||
|
}
|
||||||
|
return new_mux;
|
||||||
|
}
|
||||||
|
|
||||||
static int ParseFrameArgs(const char* args, WebPMuxFrameInfo* const info) {
|
static int ParseFrameArgs(const char* args, WebPMuxFrameInfo* const info) {
|
||||||
int dispose_method, dummy;
|
int dispose_method, dummy;
|
||||||
char plus_minus, blend_method;
|
char plus_minus, blend_method;
|
||||||
@ -476,6 +533,7 @@ static int ValidateCommandLine(int argc, const char* argv[],
|
|||||||
int num_frame_args;
|
int num_frame_args;
|
||||||
int num_loop_args;
|
int num_loop_args;
|
||||||
int num_bgcolor_args;
|
int num_bgcolor_args;
|
||||||
|
int num_durations_args;
|
||||||
int ok = 1;
|
int ok = 1;
|
||||||
|
|
||||||
assert(num_feature_args != NULL);
|
assert(num_feature_args != NULL);
|
||||||
@ -502,6 +560,7 @@ static int ValidateCommandLine(int argc, const char* argv[],
|
|||||||
num_frame_args = CountOccurrences(argv, argc, "-frame");
|
num_frame_args = CountOccurrences(argv, argc, "-frame");
|
||||||
num_loop_args = CountOccurrences(argv, argc, "-loop");
|
num_loop_args = CountOccurrences(argv, argc, "-loop");
|
||||||
num_bgcolor_args = CountOccurrences(argv, argc, "-bgcolor");
|
num_bgcolor_args = CountOccurrences(argv, argc, "-bgcolor");
|
||||||
|
num_durations_args = CountOccurrences(argv, argc, "-duration");
|
||||||
|
|
||||||
if (num_loop_args > 1) {
|
if (num_loop_args > 1) {
|
||||||
ERROR_GOTO1("ERROR: Multiple loop counts specified.\n", ErrValidate);
|
ERROR_GOTO1("ERROR: Multiple loop counts specified.\n", ErrValidate);
|
||||||
@ -514,9 +573,15 @@ static int ValidateCommandLine(int argc, const char* argv[],
|
|||||||
ERROR_GOTO1("ERROR: Loop count and background color are relevant only in "
|
ERROR_GOTO1("ERROR: Loop count and background color are relevant only in "
|
||||||
"case of animation.\n", ErrValidate);
|
"case of animation.\n", ErrValidate);
|
||||||
}
|
}
|
||||||
|
if (num_durations_args > 0 && num_frame_args != 0) {
|
||||||
|
ERROR_GOTO1("ERROR: Can not combine -duration and -frame commands.\n",
|
||||||
|
ErrValidate);
|
||||||
|
}
|
||||||
|
|
||||||
assert(ok == 1);
|
assert(ok == 1);
|
||||||
if (num_frame_args == 0) {
|
if (num_durations_args > 0) {
|
||||||
|
*num_feature_args = num_durations_args;
|
||||||
|
} else if (num_frame_args == 0) {
|
||||||
// Single argument ('set' action for ICCP/EXIF/XMP, OR a 'get' action).
|
// Single argument ('set' action for ICCP/EXIF/XMP, OR a 'get' action).
|
||||||
*num_feature_args = 1;
|
*num_feature_args = 1;
|
||||||
} else {
|
} else {
|
||||||
@ -563,6 +628,21 @@ static int ParseCommandLine(int argc, const char* argv[],
|
|||||||
ERROR_GOTO1("ERROR: Multiple actions specified.\n", ErrParse);
|
ERROR_GOTO1("ERROR: Multiple actions specified.\n", ErrParse);
|
||||||
}
|
}
|
||||||
++i;
|
++i;
|
||||||
|
} else if (!strcmp(argv[i], "-duration")) {
|
||||||
|
CHECK_NUM_ARGS_LESS(2, ErrParse);
|
||||||
|
if (ACTION_IS_NIL || config->action_type_ == ACTION_DURATION) {
|
||||||
|
config->action_type_ = ACTION_DURATION;
|
||||||
|
} else {
|
||||||
|
ERROR_GOTO1("ERROR: Multiple actions specified.\n", ErrParse);
|
||||||
|
}
|
||||||
|
if (FEATURETYPE_IS_NIL || feature->type_ == FEATURE_DURATION) {
|
||||||
|
feature->type_ = FEATURE_DURATION;
|
||||||
|
} else {
|
||||||
|
ERROR_GOTO1("ERROR: Multiple features specified.\n", ErrParse);
|
||||||
|
}
|
||||||
|
arg->params_ = argv[i + 1];
|
||||||
|
++feature_arg_index;
|
||||||
|
i += 2;
|
||||||
} else if (!strcmp(argv[i], "-get")) {
|
} else if (!strcmp(argv[i], "-get")) {
|
||||||
if (ACTION_IS_NIL) {
|
if (ACTION_IS_NIL) {
|
||||||
config->action_type_ = ACTION_GET;
|
config->action_type_ = ACTION_GET;
|
||||||
@ -936,6 +1016,82 @@ static int Process(const WebPMuxConfig* config) {
|
|||||||
ok = WriteWebP(mux, config->output_);
|
ok = WriteWebP(mux, config->output_);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case ACTION_DURATION: {
|
||||||
|
int num_frames;
|
||||||
|
ok = CreateMux(config->input_, &mux);
|
||||||
|
if (!ok) goto Err2;
|
||||||
|
err = WebPMuxNumChunks(mux, WEBP_CHUNK_ANMF, &num_frames);
|
||||||
|
ok = (err == WEBP_MUX_OK);
|
||||||
|
if (!ok) {
|
||||||
|
ERROR_GOTO1("ERROR: can not parse the number of frames.\n", Err2);
|
||||||
|
}
|
||||||
|
if (num_frames == 0) {
|
||||||
|
fprintf(stderr, "Doesn't look like the source is animated. "
|
||||||
|
"Skipping duration setting.\n");
|
||||||
|
ok = WriteWebP(mux, config->output_);
|
||||||
|
if (!ok) goto Err2;
|
||||||
|
} else {
|
||||||
|
int i;
|
||||||
|
int* durations = NULL;
|
||||||
|
WebPMux* new_mux = DuplicateMuxHeader(mux);
|
||||||
|
if (new_mux == NULL) goto Err2;
|
||||||
|
durations = (int*)malloc((size_t)num_frames * sizeof(*durations));
|
||||||
|
if (durations == NULL) goto Err2;
|
||||||
|
for (i = 0; i < num_frames; ++i) durations[i] = -1;
|
||||||
|
|
||||||
|
// Parse intervals to process.
|
||||||
|
for (i = 0; i < feature->arg_count_; ++i) {
|
||||||
|
int k;
|
||||||
|
int args[3];
|
||||||
|
int duration, start, end;
|
||||||
|
const int nb_args = ExUtilGetInts(feature->args_[i].params_,
|
||||||
|
10, 3, args);
|
||||||
|
ok = (nb_args >= 1);
|
||||||
|
if (!ok) goto Err3;
|
||||||
|
duration = args[0];
|
||||||
|
if (duration < 0) {
|
||||||
|
ERROR_GOTO1("ERROR: duration must be strictly positive.\n", Err3);
|
||||||
|
}
|
||||||
|
|
||||||
|
start = (nb_args >= 2) ? args[1] : 1;
|
||||||
|
if (start <= 0) start = 1;
|
||||||
|
|
||||||
|
end = (nb_args >= 3) ? args[2] : num_frames;
|
||||||
|
if (end == 0) end = num_frames;
|
||||||
|
if (end > num_frames) end = num_frames;
|
||||||
|
|
||||||
|
for (k = start; k <= end; ++k) {
|
||||||
|
assert(k >= 1 && k <= num_frames);
|
||||||
|
durations[k - 1] = duration;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply non-negative durations to their destination frames.
|
||||||
|
for (i = 1; i <= num_frames; ++i) {
|
||||||
|
WebPMuxFrameInfo frame;
|
||||||
|
err = WebPMuxGetFrame(mux, i, &frame);
|
||||||
|
if (err != WEBP_MUX_OK || frame.id != WEBP_CHUNK_ANMF) {
|
||||||
|
ERROR_GOTO2("ERROR: can not retrieve frame #%d.\n", i, Err3);
|
||||||
|
}
|
||||||
|
if (durations[i - 1] >= 0) frame.duration = durations[i - 1];
|
||||||
|
err = WebPMuxPushFrame(new_mux, &frame, 1);
|
||||||
|
if (err != WEBP_MUX_OK) {
|
||||||
|
ERROR_GOTO2("ERROR: error push frame data #%d\n", i, Err3);
|
||||||
|
}
|
||||||
|
WebPDataClear(&frame.bitstream);
|
||||||
|
}
|
||||||
|
WebPMuxDelete(mux);
|
||||||
|
ok = WriteWebP(new_mux, config->output_);
|
||||||
|
mux = new_mux; // transfer for the WebPMuxDelete() call
|
||||||
|
new_mux = NULL;
|
||||||
|
|
||||||
|
Err3:
|
||||||
|
free(durations);
|
||||||
|
WebPMuxDelete(new_mux);
|
||||||
|
if (!ok) goto Err2;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
case ACTION_STRIP: {
|
case ACTION_STRIP: {
|
||||||
ok = CreateMux(config->input_, &mux);
|
ok = CreateMux(config->input_, &mux);
|
||||||
if (!ok) goto Err2;
|
if (!ok) goto Err2;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
.\" Hey, EMACS: -*- nroff -*-
|
.\" Hey, EMACS: -*- nroff -*-
|
||||||
.TH WEBPMUX 1 "June 23, 2016"
|
.TH WEBPMUX 1 "November 8, 2016"
|
||||||
.SH NAME
|
.SH NAME
|
||||||
webpmux \- create animated WebP files from non\-animated WebP images, extract
|
webpmux \- create animated WebP files from non\-animated WebP images, extract
|
||||||
frames from animated WebP images, and manage XMP/EXIF metadata and ICC profile.
|
frames from animated WebP images, and manage XMP/EXIF metadata and ICC profile.
|
||||||
@ -35,6 +35,13 @@ frames from animated WebP images, and manage XMP/EXIF metadata and ICC profile.
|
|||||||
.I OUTPUT
|
.I OUTPUT
|
||||||
.RE
|
.RE
|
||||||
.br
|
.br
|
||||||
|
.B webpmux \-duration
|
||||||
|
.I DURATION OPTIONS
|
||||||
|
.B [ \-duration ... ]
|
||||||
|
.I INPUT
|
||||||
|
.B \-o
|
||||||
|
.I OUTPUT
|
||||||
|
.br
|
||||||
.B webpmux \-info
|
.B webpmux \-info
|
||||||
.I INPUT
|
.I INPUT
|
||||||
.br
|
.br
|
||||||
@ -91,6 +98,20 @@ Strip EXIF metadata.
|
|||||||
.B xmp
|
.B xmp
|
||||||
Strip XMP metadata.
|
Strip XMP metadata.
|
||||||
|
|
||||||
|
.SS DURATION_OPTIONS (\-duration)
|
||||||
|
Amend the duration of a specific interval of frames.
|
||||||
|
.TP
|
||||||
|
.I duration[,start[,end]]
|
||||||
|
Where:
|
||||||
|
'duration' is the duration for the interval (mandatory). Must be non-negative.
|
||||||
|
'start' is the starting frame index of the interval (optional). If 'start'
|
||||||
|
is less or equal to '1', its value will be set to '1'.
|
||||||
|
'end' is the ending frame index (inclusive) of the interval (optional). The
|
||||||
|
value '0' has the special meaning 'last frame of the animation'.
|
||||||
|
Note that the frames outside of the [start, end] interval will remain untouched.
|
||||||
|
.I Reminder: frame indexing starts at '1'.
|
||||||
|
.br
|
||||||
|
|
||||||
.SS FRAME_OPTIONS (\-frame)
|
.SS FRAME_OPTIONS (\-frame)
|
||||||
Create an animated WebP file from multiple (non\-animated) WebP images.
|
Create an animated WebP file from multiple (non\-animated) WebP images.
|
||||||
.TP
|
.TP
|
||||||
|
Loading…
Reference in New Issue
Block a user