@@ -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 | |||
"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 | |||
@@ -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 | |||
@@ -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<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, | |||
JsEngine js, | |||
@@ -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<String, Object> 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<String, Object> 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<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; | |||
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 height; | |||
@@ -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<KenBurnsOperation> { | |||
@@ -59,6 +62,7 @@ public class KenBurnsExec extends ExecBase<KenBurnsOperation> { | |||
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<KenBurnsOperation> { | |||
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); | |||
@@ -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": [{ | |||