diff --git a/bin/jkenburns b/bin/jkenburns new file mode 100644 index 0000000..a1caa06 --- /dev/null +++ b/bin/jkenburns @@ -0,0 +1,62 @@ +#!/bin/bash +# +# Apply zoom-pan effect to a still image to create a video +# +# Usage: +# +# jkenburns in-file out-file duration [zoom] [x] [y] [start] [end] [fps] [width] [height] +# +# in-file : file to trim +# out-file : write scaled file here +# duration : how long the output video will be +# zoom : zoom factor, default is 1 (no zoom) +# x : zoom focus X point, default is center +# y : zoom focus Y point, default is center +# start : when to start zooming, default is beginning of video +# end : when to end zooming, default is end of video +# fps : frame per second for output video, default is 25 +# width : output width, default is in-file width +# height : output height, default is in-file height +# +SCRIPT="${0}" +SCRIPT_DIR="$(cd "$(dirname "${SCRIPT}")" && pwd)" +. "${SCRIPT_DIR}"/jvc_common + +IN_FILE="${1?no in-file provided}" +OUT_FILE="${2?no out-file provided}" +DURATION="${3:?no duration provided}" +ZOOM="${4}" +X_POS="${5}" +Y_POS="${6}" +T_START="${7}" +T_END="${8}" +FRAMES_PER_SEC="${9}" +WIDTH="${10}" +HEIGHT="${11}" + +echo " +{ + \"assets\": [ + { \"name\": \"input\", \"path\": \"${IN_FILE}\" } + ], + \"operations\": [ + { + \"operation\": \"ken-burns\", + \"creates\": { + \"name\": \"zoompan\", + \"dest\": \"${OUT_FILE}\" + }, + \"source\": \"input\", + \"duration: \"${DURATION}\"$(if [[ -n "${ZOOM}" ]] ; then echo ", + \"zoom\": \"${ZOOM}\""; fi)$(if [[ -n "${X_POS}" ]] ; then echo ", + \"x\": \"${X_POS}\""; fi)$(if [[ -n "${Y_POS}" ]] ; then echo ", + \"y\": \"${Y_POS}\""; fi)$(if [[ -n "${T_START}" ]] ; then echo ", + \"start\": \"${T_START}\""; fi)$(if [[ -n "${T_END}" ]] ; then echo ", + \"end\": \"${T_END}\""; fi)$(if [[ -n "${FRAMES_PER_SEC}" ]] ; then echo ", + \"fps\": \"${FRAMES_PER_SEC}\""; fi)$(if [[ -n "${WIDTH}" ]] ; then echo ", + \"width\": \"${WIDTH}\""; fi)$(if [[ -n "${HEIGHT}" ]] ; then echo ", + \"height\": \"${HEIGHT}\""; fi) + } + ] +} +" | "${SCRIPT_DIR}"/jvc ${JVC_OPTIONS} diff --git a/docs/complex_example.md b/docs/complex_example.md index 4d37ee4..fd971d5 100644 --- a/docs/complex_example.md +++ b/docs/complex_example.md @@ -137,11 +137,11 @@ support a `comment` field, which can be used as well. "duration": "5.5", // how long the resulting video will be "start": "0", // when to start zooming, default is 0 "end": "duration", // when to end zooming, default is duration - "x": "source.width * 0.6", // pan to this x-position - "y": "source.height * 0.4", // pan to this y-position + "x": "source.width * 0.6", // pan to this x-position, default is center (no horizontal pan) + "y": "source.height * 0.4", // pan to this y-position, default is center (no vertical pan) "upscale": "8", // upscale factor. upscaling the image results in a smoother pan, but a longer encode, default is 8 - "width": "1024", // width of output video - "height": "768" // height of output video + "width": "1024", // width of output video, default is source width + "height": "768" // height of output video, default is source height }, // letterbox example diff --git a/docs/running.md b/docs/running.md index c873708..b0454b2 100644 --- a/docs/running.md +++ b/docs/running.md @@ -19,8 +19,8 @@ For example, to extract the first 5 seconds of a video: jtrim /tmp/input-file.mp4 /tmp/output-5s.mp4 0 5 ``` -There is a command-line tool for every operation except for `overlay` and -`ken-burns`, which are more complex. Pull requests welcome. +There is a command-line tool for every operation except for `overlay`, +which is more complex. Pull requests welcome. ## Help All commands accept a `-h` / `--help` option, this will print information about diff --git a/src/main/java/jvc/operation/HasWidthAndHeight.java b/src/main/java/jvc/operation/HasWidthAndHeight.java index b7fbadd..ce4db0e 100644 --- a/src/main/java/jvc/operation/HasWidthAndHeight.java +++ b/src/main/java/jvc/operation/HasWidthAndHeight.java @@ -10,16 +10,23 @@ import static jvc.service.Toolbox.divideBig; import static jvc.service.Toolbox.evalBig; import static org.cobbzilla.util.daemon.ZillaRuntime.empty; +// todo: add support for default height/width. implement defaults in subclasses. public interface HasWidthAndHeight { + String DEFAULT_WIDTH = "source.width"; + String DEFAULT_HEIGHT = "source.height"; + String getWidth (); default boolean hasWidth () { return !empty(getWidth()); } String getHeight (); default boolean hasHeight () { return !empty(getHeight()); } - default BigDecimal getWidth(Map ctx, JsEngine js) { return evalBig(getWidth(), ctx, js); } - default BigDecimal getHeight(Map ctx, JsEngine js) { return evalBig(getHeight(), ctx, js); } + default BigDecimal defaultWidth(Map ctx, JsEngine js) { return evalBig(DEFAULT_WIDTH, ctx, js); } + default BigDecimal defaultHeight(Map ctx, JsEngine js) { return evalBig(DEFAULT_HEIGHT, ctx, js); } + + default BigDecimal getWidth(Map ctx, JsEngine js) { return evalBig(getWidth(), ctx, js, defaultWidth(ctx, js)); } + default BigDecimal getHeight(Map ctx, JsEngine js) { return evalBig(getHeight(), ctx, js, defaultHeight(ctx, js)); } default void setProportionalWidthAndHeight(Map ctx, JsEngine js, diff --git a/src/main/java/jvc/operation/KenBurnsOperation.java b/src/main/java/jvc/operation/KenBurnsOperation.java index 3e4f261..f9ff521 100644 --- a/src/main/java/jvc/operation/KenBurnsOperation.java +++ b/src/main/java/jvc/operation/KenBurnsOperation.java @@ -8,6 +8,7 @@ import org.cobbzilla.util.javascript.JsEngine; import java.math.BigDecimal; import java.util.Map; +import static java.math.BigDecimal.ONE; import static jvc.service.Toolbox.evalBig; import static org.cobbzilla.util.daemon.ZillaRuntime.big; import static org.cobbzilla.util.daemon.ZillaRuntime.empty; @@ -18,12 +19,16 @@ public class KenBurnsOperation extends JSingleSourceOperation public static final BigDecimal DEFAULT_FPS = big(25); public static final BigDecimal DEFAULT_UPSCALE = big(8); + public static final String DEFAULT_X = "source.width / 2"; + public static final String DEFAULT_Y = "source.height / 2"; + @Getter @Setter private String zoom; public BigDecimal getZoom(Map ctx, JsEngine js) { - return evalBig(zoom, ctx, js); + return evalBig(zoom, ctx, js, ONE); } @Getter @Setter private String duration; + public boolean hasDuration () { return !empty(duration); } public BigDecimal getDuration(Map ctx, JsEngine js) { return evalBig(duration, ctx, js); } @@ -32,12 +37,16 @@ public class KenBurnsOperation extends JSingleSourceOperation @Getter @Setter private String end; @Getter @Setter private String x; - public boolean hasX () { return !empty(x); } - public BigDecimal getX(Map ctx, JsEngine js) { return evalBig(x, ctx, js); } + public BigDecimal getX(Map ctx, JsEngine js) { + final BigDecimal defaultX = evalBig(DEFAULT_X, ctx, js); + return evalBig(x, ctx, js, defaultX); + } @Getter @Setter private String y; - public boolean hasY () { return !empty(y); } - public BigDecimal getY(Map ctx, JsEngine js) { return evalBig(y, ctx, js); } + public BigDecimal getY(Map ctx, JsEngine js) { + final BigDecimal defaultY = evalBig(DEFAULT_Y, ctx, js); + return evalBig(y, ctx, js, defaultY); + } @Getter @Setter private String width; @Getter @Setter private String height; diff --git a/src/main/java/jvc/operation/exec/KenBurnsExec.java b/src/main/java/jvc/operation/exec/KenBurnsExec.java index 8f4b6ad..b421aa0 100644 --- a/src/main/java/jvc/operation/exec/KenBurnsExec.java +++ b/src/main/java/jvc/operation/exec/KenBurnsExec.java @@ -18,7 +18,10 @@ import static java.math.BigDecimal.ONE; import static java.math.BigDecimal.ZERO; import static jvc.service.Toolbox.TWO; import static jvc.service.Toolbox.divideBig; +import static org.cobbzilla.util.daemon.ZillaRuntime.die; import static org.cobbzilla.util.io.FileUtil.abs; +import static org.cobbzilla.util.json.JsonUtil.COMPACT_MAPPER; +import static org.cobbzilla.util.json.JsonUtil.json; @Slf4j public class KenBurnsExec extends ExecBase { @@ -59,6 +62,7 @@ public class KenBurnsExec extends ExecBase { ctx.put("upscale", op.getUpscale(ctx, js)); final BigDecimal fps = op.getFps(ctx, js); + if (!op.hasDuration()) return die("operate: no duration defined: "+json(op, COMPACT_MAPPER)); final BigDecimal duration = op.getDuration(ctx, js); ctx.put("duration", duration); @@ -80,8 +84,8 @@ public class KenBurnsExec extends ExecBase { final BigDecimal midX = divideBig(source.getWidth(), TWO); final BigDecimal midY = divideBig(source.getHeight(), TWO); - final BigDecimal destX = op.hasX() ? op.getX(ctx, js) : midX; - final BigDecimal destY = op.hasY() ? op.getY(ctx, js) : midY; + final BigDecimal destX = op.getX(ctx, js); + final BigDecimal destY = op.getY(ctx, js); final BigDecimal deltaX = divideBig(destX.subtract(midX), totalFrames); final BigDecimal deltaY = divideBig(destY.subtract(midY), totalFrames); diff --git a/src/test/resources/tests/test_ken_burns.jvc b/src/test/resources/tests/test_ken_burns.jvc index 21b920e..d38829c 100644 --- a/src/test/resources/tests/test_ken_burns.jvc +++ b/src/test/resources/tests/test_ken_burns.jvc @@ -16,8 +16,8 @@ "duration": "5", // how long the resulting video will be "start": "0", // when to start zooming, default is 0 "end": "duration", // when to end zooming, default is duration - "x": "source.width * 0.6", // pan to this x-position - "y": "source.height * 0.4", // pan to this y-position + "x": "source.width * 0.6", // pan to this x-position, default is center + "y": "source.height * 0.4", // pan to this y-position, default is center "width": "1024", // width of output video "height": "768", // height of output video "validate": [{