@@ -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} |
@@ -137,11 +137,11 @@ support a `comment` field, which can be used as well. | |||||
"duration": "5.5", // how long the resulting video will be | "duration": "5.5", // how long the resulting video will be | ||||
"start": "0", // when to start zooming, default is 0 | "start": "0", // when to start zooming, default is 0 | ||||
"end": "duration", // when to end zooming, default is duration | "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 | "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 | // letterbox example | ||||
@@ -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 | 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 | ## Help | ||||
All commands accept a `-h` / `--help` option, this will print information about | All commands accept a `-h` / `--help` option, this will print information about | ||||
@@ -10,16 +10,23 @@ import static jvc.service.Toolbox.divideBig; | |||||
import static jvc.service.Toolbox.evalBig; | import static jvc.service.Toolbox.evalBig; | ||||
import static org.cobbzilla.util.daemon.ZillaRuntime.empty; | import static org.cobbzilla.util.daemon.ZillaRuntime.empty; | ||||
// todo: add support for default height/width. implement defaults in subclasses. | |||||
public interface HasWidthAndHeight { | public interface HasWidthAndHeight { | ||||
String DEFAULT_WIDTH = "source.width"; | |||||
String DEFAULT_HEIGHT = "source.height"; | |||||
String getWidth (); | String getWidth (); | ||||
default boolean hasWidth () { return !empty(getWidth()); } | default boolean hasWidth () { return !empty(getWidth()); } | ||||
String getHeight (); | String getHeight (); | ||||
default boolean hasHeight () { return !empty(getHeight()); } | default boolean hasHeight () { return !empty(getHeight()); } | ||||
default BigDecimal getWidth(Map<String, Object> ctx, JsEngine js) { return evalBig(getWidth(), ctx, js); } | |||||
default BigDecimal getHeight(Map<String, Object> ctx, JsEngine js) { return evalBig(getHeight(), ctx, js); } | |||||
default BigDecimal defaultWidth(Map<String, Object> ctx, JsEngine js) { return evalBig(DEFAULT_WIDTH, ctx, js); } | |||||
default BigDecimal defaultHeight(Map<String, Object> ctx, JsEngine js) { return evalBig(DEFAULT_HEIGHT, ctx, js); } | |||||
default BigDecimal getWidth(Map<String, Object> ctx, JsEngine js) { return evalBig(getWidth(), ctx, js, defaultWidth(ctx, js)); } | |||||
default BigDecimal getHeight(Map<String, Object> ctx, JsEngine js) { return evalBig(getHeight(), ctx, js, defaultHeight(ctx, js)); } | |||||
default void setProportionalWidthAndHeight(Map<String, Object> ctx, | default void setProportionalWidthAndHeight(Map<String, Object> ctx, | ||||
JsEngine js, | JsEngine js, | ||||
@@ -8,6 +8,7 @@ import org.cobbzilla.util.javascript.JsEngine; | |||||
import java.math.BigDecimal; | import java.math.BigDecimal; | ||||
import java.util.Map; | import java.util.Map; | ||||
import static java.math.BigDecimal.ONE; | |||||
import static jvc.service.Toolbox.evalBig; | import static jvc.service.Toolbox.evalBig; | ||||
import static org.cobbzilla.util.daemon.ZillaRuntime.big; | import static org.cobbzilla.util.daemon.ZillaRuntime.big; | ||||
import static org.cobbzilla.util.daemon.ZillaRuntime.empty; | 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_FPS = big(25); | ||||
public static final BigDecimal DEFAULT_UPSCALE = big(8); | 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; | @Getter @Setter private String zoom; | ||||
public BigDecimal getZoom(Map<String, Object> ctx, JsEngine js) { | public BigDecimal getZoom(Map<String, Object> ctx, JsEngine js) { | ||||
return evalBig(zoom, ctx, js); | |||||
return evalBig(zoom, ctx, js, ONE); | |||||
} | } | ||||
@Getter @Setter private String duration; | @Getter @Setter private String duration; | ||||
public boolean hasDuration () { return !empty(duration); } | |||||
public BigDecimal getDuration(Map<String, Object> ctx, JsEngine js) { | public BigDecimal getDuration(Map<String, Object> ctx, JsEngine js) { | ||||
return evalBig(duration, ctx, js); | return evalBig(duration, ctx, js); | ||||
} | } | ||||
@@ -32,12 +37,16 @@ public class KenBurnsOperation extends JSingleSourceOperation | |||||
@Getter @Setter private String end; | @Getter @Setter private String end; | ||||
@Getter @Setter private String x; | @Getter @Setter private String x; | ||||
public boolean hasX () { return !empty(x); } | |||||
public BigDecimal getX(Map<String, Object> ctx, JsEngine js) { return evalBig(x, ctx, js); } | |||||
public BigDecimal getX(Map<String, Object> ctx, JsEngine js) { | |||||
final BigDecimal defaultX = evalBig(DEFAULT_X, ctx, js); | |||||
return evalBig(x, ctx, js, defaultX); | |||||
} | |||||
@Getter @Setter private String y; | @Getter @Setter private String y; | ||||
public boolean hasY () { return !empty(y); } | |||||
public BigDecimal getY(Map<String, Object> ctx, JsEngine js) { return evalBig(y, ctx, js); } | |||||
public BigDecimal getY(Map<String, Object> 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 width; | ||||
@Getter @Setter private String height; | @Getter @Setter private String height; | ||||
@@ -18,7 +18,10 @@ import static java.math.BigDecimal.ONE; | |||||
import static java.math.BigDecimal.ZERO; | import static java.math.BigDecimal.ZERO; | ||||
import static jvc.service.Toolbox.TWO; | import static jvc.service.Toolbox.TWO; | ||||
import static jvc.service.Toolbox.divideBig; | 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.io.FileUtil.abs; | ||||
import static org.cobbzilla.util.json.JsonUtil.COMPACT_MAPPER; | |||||
import static org.cobbzilla.util.json.JsonUtil.json; | |||||
@Slf4j | @Slf4j | ||||
public class KenBurnsExec extends ExecBase<KenBurnsOperation> { | public class KenBurnsExec extends ExecBase<KenBurnsOperation> { | ||||
@@ -59,6 +62,7 @@ public class KenBurnsExec extends ExecBase<KenBurnsOperation> { | |||||
ctx.put("upscale", op.getUpscale(ctx, js)); | ctx.put("upscale", op.getUpscale(ctx, js)); | ||||
final BigDecimal fps = op.getFps(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); | final BigDecimal duration = op.getDuration(ctx, js); | ||||
ctx.put("duration", duration); | ctx.put("duration", duration); | ||||
@@ -80,8 +84,8 @@ public class KenBurnsExec extends ExecBase<KenBurnsOperation> { | |||||
final BigDecimal midX = divideBig(source.getWidth(), TWO); | final BigDecimal midX = divideBig(source.getWidth(), TWO); | ||||
final BigDecimal midY = divideBig(source.getHeight(), 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 deltaX = divideBig(destX.subtract(midX), totalFrames); | ||||
final BigDecimal deltaY = divideBig(destY.subtract(midY), totalFrames); | final BigDecimal deltaY = divideBig(destY.subtract(midY), totalFrames); | ||||
@@ -16,8 +16,8 @@ | |||||
"duration": "5", // how long the resulting video will be | "duration": "5", // how long the resulting video will be | ||||
"start": "0", // when to start zooming, default is 0 | "start": "0", // when to start zooming, default is 0 | ||||
"end": "duration", // when to end zooming, default is duration | "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 | "width": "1024", // width of output video | ||||
"height": "768", // height of output video | "height": "768", // height of output video | ||||
"validate": [{ | "validate": [{ | ||||