From 3f182d36f4b560791a3ba314d81dc81f72cde150 Mon Sep 17 00:00:00 2001 From: Pascal Massimino Date: Fri, 21 Oct 2016 06:14:45 -0700 Subject: [PATCH] add a "-duration duration,start,end" option to webpmux this will force a constant duration for an interval of frames in an animation. Notes: a) '-duration [...]' can be repeated as many times as needed. b) intervals are taken into account in option order. If they overlap, values will be overwritten. c) 'start' and 'end' can be omitted, but not the duration value. d) 'end' can be equal to '0', in which case it means 'last frame' e) single-image files are untouched (ie. not turned into an animation file). Some example usage: 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 Change-Id: I9b595dafa77f9221bacd080be7858b1457f54636 --- README.mux | 10 +++ examples/example_util.c | 13 ++++ examples/example_util.h | 6 ++ examples/webpmux.c | 160 +++++++++++++++++++++++++++++++++++++++- man/webpmux.1 | 23 +++++- 5 files changed, 209 insertions(+), 3 deletions(-) diff --git a/README.mux b/README.mux index 1380c914..5d301e6b 100644 --- a/README.mux +++ b/README.mux @@ -25,6 +25,8 @@ A list of options is available using the -help command line flag: > webpmux -help Usage: webpmux -get GET_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 -frame FRAME_OPTIONS [-frame...] [-loop LOOP_COUNT] [-bgcolor BACKGROUND_COLOR] -o OUTPUT @@ -48,6 +50,14 @@ SET_OPTIONS: 'file.exif' contains the EXIF 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 color profile/metadata: icc strip ICC profile diff --git a/examples/example_util.c b/examples/example_util.c index d215b06f..8b408ce2 100644 --- a/examples/example_util.c +++ b/examples/example_util.c @@ -14,6 +14,7 @@ #include #include +#include //------------------------------------------------------------------------------ // String parsing @@ -33,6 +34,18 @@ int ExUtilGetInt(const char* const v, int base, int* const 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) { char* end = NULL; const float f = (v != NULL) ? (float)strtod(v, &end) : 0.f; diff --git a/examples/example_util.h b/examples/example_util.h index 23dcfabf..4bb42eb7 100644 --- a/examples/example_util.h +++ b/examples/example_util.h @@ -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); 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 } // extern "C" #endif diff --git a/examples/webpmux.c b/examples/webpmux.c index 21f47752..39f73290 100644 --- a/examples/webpmux.c +++ b/examples/webpmux.c @@ -38,6 +38,11 @@ webpmux -strip exif 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: webpmux -info in.webp webpmux [ -h | -help ] @@ -66,7 +71,8 @@ typedef enum { ACTION_SET, ACTION_STRIP, ACTION_INFO, - ACTION_HELP + ACTION_HELP, + ACTION_DURATION } ActionType; typedef enum { @@ -88,6 +94,7 @@ typedef enum { FEATURE_XMP, FEATURE_ICCP, FEATURE_ANMF, + FEATURE_DURATION, LAST_FEATURE } FeatureType; @@ -285,6 +292,8 @@ static WebPMuxError DisplayInfo(const WebPMux* mux) { 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 -duration DURATION_OPTIONS [-duration ...]\n"); + printf(" INPUT -o OUTPUT\n"); printf(" webpmux -strip STRIP_OPTIONS INPUT -o OUTPUT\n"); printf(" webpmux -frame FRAME_OPTIONS [-frame...] [-loop LOOP_COUNT]" "\n"); @@ -311,6 +320,15 @@ static void PrintHelp(void) { 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("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("STRIP_OPTIONS:\n"); printf(" Strip color profile/metadata:\n"); @@ -411,6 +429,45 @@ static int WriteWebP(WebPMux* const mux, const char* filename) { 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) { int dispose_method, dummy; char plus_minus, blend_method; @@ -476,6 +533,7 @@ static int ValidateCommandLine(int argc, const char* argv[], int num_frame_args; int num_loop_args; int num_bgcolor_args; + int num_durations_args; int ok = 1; 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_loop_args = CountOccurrences(argv, argc, "-loop"); num_bgcolor_args = CountOccurrences(argv, argc, "-bgcolor"); + num_durations_args = CountOccurrences(argv, argc, "-duration"); if (num_loop_args > 1) { 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 " "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); - 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). *num_feature_args = 1; } else { @@ -563,6 +628,21 @@ static int ParseCommandLine(int argc, const char* argv[], ERROR_GOTO1("ERROR: Multiple actions specified.\n", ErrParse); } ++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")) { if (ACTION_IS_NIL) { config->action_type_ = ACTION_GET; @@ -936,6 +1016,82 @@ static int Process(const WebPMuxConfig* config) { ok = WriteWebP(mux, config->output_); 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: { ok = CreateMux(config->input_, &mux); if (!ok) goto Err2; diff --git a/man/webpmux.1 b/man/webpmux.1 index 0407ef4a..cea2b3c2 100644 --- a/man/webpmux.1 +++ b/man/webpmux.1 @@ -1,5 +1,5 @@ .\" Hey, EMACS: -*- nroff -*- -.TH WEBPMUX 1 "June 23, 2016" +.TH WEBPMUX 1 "November 8, 2016" .SH NAME webpmux \- create animated WebP files from non\-animated WebP images, extract 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 .RE .br +.B webpmux \-duration +.I DURATION OPTIONS +.B [ \-duration ... ] +.I INPUT +.B \-o +.I OUTPUT +.br .B webpmux \-info .I INPUT .br @@ -91,6 +98,20 @@ Strip EXIF metadata. .B xmp 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) Create an animated WebP file from multiple (non\-animated) WebP images. .TP