@@ -133,20 +133,20 @@ Here is a complex example using multiple assets and operations: | |||||
"creates": "overlay1", // asset it creates | "creates": "overlay1", // asset it creates | ||||
"perform": { | "perform": { | ||||
"source": "combined_vid1", // main video asset | "source": "combined_vid1", // main video asset | ||||
"overlay": "vid1", // overlay this video on the main video | |||||
"overlay": "vid2", // overlay this video on the main video | |||||
"start": "vid1.end_ts", // when (on the main video timeline) to start the overlay. default is 0 (beginning) | |||||
"duration": "vid1.duration", // how long to play the overlay. default is to play the entire overlay asset | |||||
"offset": "30", // when (on the main video timeline) to begin showing the overlay. default is 0 (beginning) | |||||
"overlayStart": "0", // when (on the overlay video timeline) to begin playback on the overlay. default is 0 (beginning) | |||||
"overlayEnd": "0", // when (on the overlay video timeline) to end playback on the overlay. default is to play the whole overlay | |||||
"width": 400, // how wide the overlay will be, in pixels. default is "overlay.width" | |||||
"height": 300, // how tall the overlay will be, in pixels. default is "overlay.height" | |||||
"width": "overlay.width / 2", // how wide the overlay will be, in pixels. default is the full overlay width, or maintain aspect ratio if height was set | |||||
"height": "", // how tall the overlay will be, in pixels. default is the full overlay height, or maintain aspect ratio if width was set | |||||
"x": "source.width / 2", // horizontal overlay position. default is 0 | |||||
"y": "source.height / 2", // vertical overlay position. default is 0 | |||||
"out": "1080p", // this is a shortcut to the two lines below, and is the preferred way of specifying the output resolution | |||||
"out_width": 1920, // output width in pixels. default is source width | |||||
"out_height": 1024 // output height in pixes. default is source height | |||||
"x": "source.width/2", // horizontal overlay position on main video. default is 0 | |||||
"y": "source.height/2", // vertical overlay position on main video. default is 0 | |||||
"outputWidth": "1920", // output width in pixels. default is source width | |||||
"outputHeight": "1024" // output height in pixes. default is source height | |||||
} | } | ||||
} | } | ||||
] | ] | ||||
@@ -143,6 +143,7 @@ public class JAsset { | |||||
} | } | ||||
public BigDecimal duration() { return getInfo().duration(); } | public BigDecimal duration() { return getInfo().duration(); } | ||||
@JsonIgnore public BigDecimal getDuration () { return duration(); } | |||||
public JAsset init(AssetManager assetManager, Toolbox toolbox) { | public JAsset init(AssetManager assetManager, Toolbox toolbox) { | ||||
final JAsset asset = initPath(assetManager); | final JAsset asset = initPath(assetManager); | ||||
@@ -10,7 +10,6 @@ import lombok.Getter; | |||||
import lombok.NoArgsConstructor; | import lombok.NoArgsConstructor; | ||||
import lombok.Setter; | import lombok.Setter; | ||||
import lombok.extern.slf4j.Slf4j; | import lombok.extern.slf4j.Slf4j; | ||||
import org.cobbzilla.util.handlebars.HandlebarsUtil; | |||||
import java.io.File; | import java.io.File; | ||||
import java.util.HashMap; | import java.util.HashMap; | ||||
@@ -22,7 +21,6 @@ import static jvcl.model.JAsset.json2asset; | |||||
import static org.cobbzilla.util.daemon.ZillaRuntime.die; | import static org.cobbzilla.util.daemon.ZillaRuntime.die; | ||||
import static org.cobbzilla.util.daemon.ZillaRuntime.empty; | import static org.cobbzilla.util.daemon.ZillaRuntime.empty; | ||||
import static org.cobbzilla.util.io.FileUtil.abs; | import static org.cobbzilla.util.io.FileUtil.abs; | ||||
import static org.cobbzilla.util.json.JsonUtil.json; | |||||
import static org.cobbzilla.util.system.CommandShell.execScript; | import static org.cobbzilla.util.system.CommandShell.execScript; | ||||
@Slf4j | @Slf4j | ||||
@@ -49,7 +47,7 @@ public class ConcatOperation implements JOperator { | |||||
@Override public void operate(JOperation op, Toolbox toolbox, AssetManager assetManager) { | @Override public void operate(JOperation op, Toolbox toolbox, AssetManager assetManager) { | ||||
final ConcatConfig config = json(json(op.getPerform()), ConcatConfig.class); | |||||
final ConcatConfig config = loadConfig(op, ConcatConfig.class); | |||||
// validate sources | // validate sources | ||||
final List<JAsset> sources = flattenAssetList(assetManager.resolve(config.getConcat())); | final List<JAsset> sources = flattenAssetList(assetManager.resolve(config.getConcat())); | ||||
@@ -82,7 +80,7 @@ public class ConcatOperation implements JOperator { | |||||
ctx.put("ffmpeg", toolbox.getFfmpeg()); | ctx.put("ffmpeg", toolbox.getFfmpeg()); | ||||
ctx.put("sources", sources); | ctx.put("sources", sources); | ||||
ctx.put("output", output); | ctx.put("output", output); | ||||
final String script = HandlebarsUtil.apply(toolbox.getHandlebars(), CONCAT_RECODE_TEMPLATE_1, ctx); | |||||
final String script = renderScript(toolbox, ctx, CONCAT_RECODE_TEMPLATE_1); | |||||
log.debug("operate: running script: "+script); | log.debug("operate: running script: "+script); | ||||
final String scriptOutput = execScript(script); | final String scriptOutput = execScript(script); | ||||
@@ -0,0 +1,88 @@ | |||||
package jvcl.op; | |||||
import jvcl.model.JAsset; | |||||
import jvcl.model.JFileExtension; | |||||
import jvcl.model.JOperation; | |||||
import jvcl.service.AssetManager; | |||||
import jvcl.service.JOperator; | |||||
import jvcl.service.Toolbox; | |||||
import lombok.Getter; | |||||
import lombok.Setter; | |||||
import lombok.extern.slf4j.Slf4j; | |||||
import java.math.BigDecimal; | |||||
import java.util.HashMap; | |||||
import java.util.Map; | |||||
import static jvcl.model.JAsset.json2asset; | |||||
import static jvcl.service.Toolbox.getDuration; | |||||
import static org.cobbzilla.util.daemon.ZillaRuntime.empty; | |||||
import static org.cobbzilla.util.system.CommandShell.execScript; | |||||
@Slf4j | |||||
public class OverlayOperation implements JOperator { | |||||
public static final String OVERLAY_TEMPLATE | |||||
= "{{ffmpeg}} "; | |||||
@Override public void operate(JOperation op, Toolbox toolbox, AssetManager assetManager) { | |||||
final OverlayConfig config = loadConfig(op, OverlayConfig.class); | |||||
final JAsset source = assetManager.resolve(config.getSource()); | |||||
final JAsset overlay = assetManager.resolve(config.getOverlay()); | |||||
final JAsset output = json2asset(op.getCreates()); | |||||
output.mergeFormat(source.getFormat()); | |||||
final JFileExtension formatType = output.getFormat().getFileExtension(); | |||||
final Map<String, Object> ctx = new HashMap<>(); | |||||
ctx.put("source", source); | |||||
ctx.put("overlay", overlay); | |||||
ctx.put("output", output); | |||||
ctx.put("offset", config.getOffsetSeconds()); | |||||
ctx.put("overlayStart", config.getOverlayStartSeconds()); | |||||
if (config.hasOverlayEnd()) ctx.put("overlayEnd", config.getOverlayEndSeconds()); | |||||
if (config.hasWidth()) ctx.put("width", config.getWidth()); | |||||
if (config.hasHeight()) ctx.put("height", config.getHeight()); | |||||
final String script = renderScript(toolbox, ctx, OVERLAY_TEMPLATE); | |||||
log.debug("operate: running script: "+script); | |||||
final String scriptOutput = execScript(script); | |||||
log.debug("operate: command output: "+scriptOutput); | |||||
assetManager.addOperationAsset(output); | |||||
} | |||||
private static class OverlayConfig { | |||||
@Getter @Setter private String source; | |||||
@Getter @Setter private String overlay; | |||||
@Getter @Setter private String offset; | |||||
public BigDecimal getOffsetSeconds () { return empty(offset) ? BigDecimal.ZERO : getDuration(offset); } | |||||
@Getter @Setter private String overlayStart; | |||||
public BigDecimal getOverlayStartSeconds () { return empty(overlayStart) ? BigDecimal.ZERO : getDuration(overlayStart); } | |||||
@Getter @Setter private String overlayEnd; | |||||
public boolean hasOverlayEnd () { return !empty(overlayEnd); } | |||||
public BigDecimal getOverlayEndSeconds () { return getDuration(overlayEnd); } | |||||
@Getter @Setter private String width; | |||||
public boolean hasWidth () { return !empty(width); } | |||||
@Getter @Setter private String height; | |||||
public boolean hasHeight () { return !empty(height); } | |||||
@Getter @Setter private String x; | |||||
public boolean hasX () { return !empty(x); } | |||||
@Getter @Setter private String y; | |||||
public boolean hasY () { return !empty(y); } | |||||
@Getter @Setter private String outputWidth; | |||||
public boolean hasOutputWidth () { return !empty(outputWidth); } | |||||
@Getter @Setter private String outputHeight; | |||||
public boolean hasOutputHeight () { return !empty(outputHeight); } | |||||
} | |||||
} |
@@ -10,7 +10,6 @@ import lombok.Getter; | |||||
import lombok.NoArgsConstructor; | import lombok.NoArgsConstructor; | ||||
import lombok.Setter; | import lombok.Setter; | ||||
import lombok.extern.slf4j.Slf4j; | import lombok.extern.slf4j.Slf4j; | ||||
import org.cobbzilla.util.handlebars.HandlebarsUtil; | |||||
import java.io.File; | import java.io.File; | ||||
import java.math.BigDecimal; | import java.math.BigDecimal; | ||||
@@ -23,7 +22,6 @@ import static org.cobbzilla.util.daemon.ZillaRuntime.die; | |||||
import static org.cobbzilla.util.daemon.ZillaRuntime.empty; | import static org.cobbzilla.util.daemon.ZillaRuntime.empty; | ||||
import static org.cobbzilla.util.io.FileUtil.abs; | import static org.cobbzilla.util.io.FileUtil.abs; | ||||
import static org.cobbzilla.util.io.FileUtil.mkdirOrDie; | import static org.cobbzilla.util.io.FileUtil.mkdirOrDie; | ||||
import static org.cobbzilla.util.json.JsonUtil.json; | |||||
import static org.cobbzilla.util.system.CommandShell.execScript; | import static org.cobbzilla.util.system.CommandShell.execScript; | ||||
@Slf4j | @Slf4j | ||||
@@ -33,7 +31,7 @@ public class SplitOperation implements JOperator { | |||||
= "{{ffmpeg}} -i {{{source.path}}} -ss {{startSeconds}} -t {{interval}} {{{output.path}}}"; | = "{{ffmpeg}} -i {{{source.path}}} -ss {{startSeconds}} -t {{interval}} {{{output.path}}}"; | ||||
@Override public void operate(JOperation op, Toolbox toolbox, AssetManager assetManager) { | @Override public void operate(JOperation op, Toolbox toolbox, AssetManager assetManager) { | ||||
final SplitConfig config = json(json(op.getPerform()), SplitConfig.class); | |||||
final SplitConfig config = loadConfig(op, SplitConfig.class); | |||||
final JAsset source = assetManager.resolve(config.getSplit()); | final JAsset source = assetManager.resolve(config.getSplit()); | ||||
@@ -85,7 +83,7 @@ public class SplitOperation implements JOperator { | |||||
ctx.put("output", slice); | ctx.put("output", slice); | ||||
ctx.put("startSeconds", i); | ctx.put("startSeconds", i); | ||||
ctx.put("interval", incr); | ctx.put("interval", incr); | ||||
final String script = HandlebarsUtil.apply(toolbox.getHandlebars(), SPLIT_TEMPLATE, ctx); | |||||
final String script = renderScript(toolbox, ctx, SPLIT_TEMPLATE); | |||||
log.debug("operate: running script: "+script); | log.debug("operate: running script: "+script); | ||||
final String scriptOutput = execScript(script); | final String scriptOutput = execScript(script); | ||||
@@ -11,7 +11,6 @@ import lombok.Getter; | |||||
import lombok.NoArgsConstructor; | import lombok.NoArgsConstructor; | ||||
import lombok.Setter; | import lombok.Setter; | ||||
import lombok.extern.slf4j.Slf4j; | import lombok.extern.slf4j.Slf4j; | ||||
import org.cobbzilla.util.handlebars.HandlebarsUtil; | |||||
import java.io.File; | import java.io.File; | ||||
import java.math.BigDecimal; | import java.math.BigDecimal; | ||||
@@ -23,7 +22,6 @@ import static jvcl.service.Toolbox.getDuration; | |||||
import static org.cobbzilla.util.daemon.ZillaRuntime.die; | import static org.cobbzilla.util.daemon.ZillaRuntime.die; | ||||
import static org.cobbzilla.util.daemon.ZillaRuntime.empty; | import static org.cobbzilla.util.daemon.ZillaRuntime.empty; | ||||
import static org.cobbzilla.util.io.FileUtil.*; | import static org.cobbzilla.util.io.FileUtil.*; | ||||
import static org.cobbzilla.util.json.JsonUtil.json; | |||||
import static org.cobbzilla.util.system.CommandShell.execScript; | import static org.cobbzilla.util.system.CommandShell.execScript; | ||||
@Slf4j | @Slf4j | ||||
@@ -34,7 +32,7 @@ public class TrimOperation implements JOperator { | |||||
@Override public void operate(JOperation op, Toolbox toolbox, AssetManager assetManager) { | @Override public void operate(JOperation op, Toolbox toolbox, AssetManager assetManager) { | ||||
final TrimConfig config = json(json(op.getPerform()), TrimConfig.class); | |||||
final TrimConfig config = loadConfig(op, TrimConfig.class); | |||||
final JAsset source = assetManager.resolve(config.getTrim()); | final JAsset source = assetManager.resolve(config.getTrim()); | ||||
final JAsset output = json2asset(op.getCreates()); | final JAsset output = json2asset(op.getCreates()); | ||||
@@ -85,7 +83,7 @@ public class TrimOperation implements JOperator { | |||||
final BigDecimal startTime = config.getStartTime(); | final BigDecimal startTime = config.getStartTime(); | ||||
ctx.put("startSeconds", startTime); | ctx.put("startSeconds", startTime); | ||||
if (config.hasEnd()) ctx.put("interval", config.getEndTime().subtract(startTime)); | if (config.hasEnd()) ctx.put("interval", config.getEndTime().subtract(startTime)); | ||||
final String script = HandlebarsUtil.apply(toolbox.getHandlebars(), TRIM_TEMPLATE, ctx); | |||||
final String script = renderScript(toolbox, ctx, TRIM_TEMPLATE); | |||||
log.debug("operate: running script: "+script); | log.debug("operate: running script: "+script); | ||||
final String scriptOutput = execScript(script); | final String scriptOutput = execScript(script); | ||||
@@ -1,9 +1,22 @@ | |||||
package jvcl.service; | package jvcl.service; | ||||
import jvcl.model.JOperation; | import jvcl.model.JOperation; | ||||
import org.cobbzilla.util.handlebars.HandlebarsUtil; | |||||
import java.util.Map; | |||||
import static org.cobbzilla.util.json.JsonUtil.json; | |||||
public interface JOperator { | public interface JOperator { | ||||
void operate(JOperation op, Toolbox toolbox, AssetManager assetManager); | void operate(JOperation op, Toolbox toolbox, AssetManager assetManager); | ||||
default <T> T loadConfig(JOperation op, Class<T> configClass) { | |||||
return json(json(op.getPerform()), configClass); | |||||
} | |||||
default String renderScript(Toolbox toolbox, Map<String, Object> ctx, String template) { | |||||
return HandlebarsUtil.apply(toolbox.getHandlebars(), template, ctx); | |||||
} | |||||
} | } |
@@ -0,0 +1,39 @@ | |||||
{ | |||||
"assets": [ | |||||
{ | |||||
"name": "vid1", | |||||
"path": "https://archive.org/download/gov.archives.arc.1257628/gov.archives.arc.1257628_512kb.mp4", | |||||
"dest": "src/test/resources/sources/" | |||||
}, | |||||
{ | |||||
"name": "vid2", | |||||
"path": "https://archive.org/download/gov.archives.arc.49442/gov.archives.arc.49442_512kb.mp4", | |||||
"dest": "src/test/resources/sources/" | |||||
} | |||||
], | |||||
"operations": [ | |||||
{ | |||||
"operation": "overlay", // name of the operation | |||||
"creates": { | |||||
"name": "overlay1", | |||||
"dest": "src/test/resources/outputs/overlay/" | |||||
}, | |||||
"perform": { | |||||
"source": "vid1", // main video asset | |||||
"overlay": "vid2", // overlay this video on the main video | |||||
"offset": "30", // when (on the main video timeline) to begin showing the overlay. default is 0 (beginning) | |||||
"overlayStart": "0", // when (on the overlay video timeline) to begin playback on the overlay. default is 0 (beginning) | |||||
"overlayEnd": "0", // when (on the overlay video timeline) to end playback on the overlay. default is to play the whole overlay | |||||
"width": "overlay.width / 2", // how wide the overlay will be, in pixels. default is the full overlay width, or maintain aspect ratio if height was set | |||||
"height": "", // how tall the overlay will be, in pixels. default is the full overlay height, or maintain aspect ratio if width was set | |||||
"x": "source.width / 2", // horizontal overlay position on main video. default is 0 | |||||
"y": "source.height / 2", // vertical overlay position on main video. default is 0 | |||||
"outputWidth": "1920", // output width in pixels. default is source width | |||||
"outputHeight": "1024" // output height in pixes. default is source height | |||||
} | |||||
] | |||||
} |
@@ -3,12 +3,12 @@ | |||||
{ | { | ||||
"name": "vid1", | "name": "vid1", | ||||
"path": "https://archive.org/download/gov.archives.arc.1257628/gov.archives.arc.1257628_512kb.mp4", | "path": "https://archive.org/download/gov.archives.arc.1257628/gov.archives.arc.1257628_512kb.mp4", | ||||
"dest": "src/test/resources/sources/gov.archives.arc.1257628_512kb.mp4" | |||||
"dest": "src/test/resources/sources/" | |||||
}, | }, | ||||
{ | { | ||||
"name": "vid2", | "name": "vid2", | ||||
"path": "https://archive.org/download/gov.archives.arc.49442/gov.archives.arc.49442_512kb.mp4", | "path": "https://archive.org/download/gov.archives.arc.49442/gov.archives.arc.49442_512kb.mp4", | ||||
"dest": "src/test/resources/sources/gov.archives.arc.49442_512kb.mp4" | |||||
"dest": "src/test/resources/sources/" | |||||
} | } | ||||
], | ], | ||||
"operations": [ | "operations": [ | ||||