@@ -133,20 +133,20 @@ Here is a complex example using multiple assets and operations: | |||
"creates": "overlay1", // asset it creates | |||
"perform": { | |||
"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(); } | |||
@JsonIgnore public BigDecimal getDuration () { return duration(); } | |||
public JAsset init(AssetManager assetManager, Toolbox toolbox) { | |||
final JAsset asset = initPath(assetManager); | |||
@@ -10,7 +10,6 @@ import lombok.Getter; | |||
import lombok.NoArgsConstructor; | |||
import lombok.Setter; | |||
import lombok.extern.slf4j.Slf4j; | |||
import org.cobbzilla.util.handlebars.HandlebarsUtil; | |||
import java.io.File; | |||
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.empty; | |||
import static org.cobbzilla.util.io.FileUtil.abs; | |||
import static org.cobbzilla.util.json.JsonUtil.json; | |||
import static org.cobbzilla.util.system.CommandShell.execScript; | |||
@Slf4j | |||
@@ -49,7 +47,7 @@ public class ConcatOperation implements JOperator { | |||
@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 | |||
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("sources", sources); | |||
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); | |||
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.Setter; | |||
import lombok.extern.slf4j.Slf4j; | |||
import org.cobbzilla.util.handlebars.HandlebarsUtil; | |||
import java.io.File; | |||
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.io.FileUtil.abs; | |||
import static org.cobbzilla.util.io.FileUtil.mkdirOrDie; | |||
import static org.cobbzilla.util.json.JsonUtil.json; | |||
import static org.cobbzilla.util.system.CommandShell.execScript; | |||
@Slf4j | |||
@@ -33,7 +31,7 @@ public class SplitOperation implements JOperator { | |||
= "{{ffmpeg}} -i {{{source.path}}} -ss {{startSeconds}} -t {{interval}} {{{output.path}}}"; | |||
@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()); | |||
@@ -85,7 +83,7 @@ public class SplitOperation implements JOperator { | |||
ctx.put("output", slice); | |||
ctx.put("startSeconds", i); | |||
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); | |||
final String scriptOutput = execScript(script); | |||
@@ -11,7 +11,6 @@ import lombok.Getter; | |||
import lombok.NoArgsConstructor; | |||
import lombok.Setter; | |||
import lombok.extern.slf4j.Slf4j; | |||
import org.cobbzilla.util.handlebars.HandlebarsUtil; | |||
import java.io.File; | |||
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.empty; | |||
import static org.cobbzilla.util.io.FileUtil.*; | |||
import static org.cobbzilla.util.json.JsonUtil.json; | |||
import static org.cobbzilla.util.system.CommandShell.execScript; | |||
@Slf4j | |||
@@ -34,7 +32,7 @@ public class TrimOperation implements JOperator { | |||
@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 output = json2asset(op.getCreates()); | |||
@@ -85,7 +83,7 @@ public class TrimOperation implements JOperator { | |||
final BigDecimal startTime = config.getStartTime(); | |||
ctx.put("startSeconds", 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); | |||
final String scriptOutput = execScript(script); | |||
@@ -1,9 +1,22 @@ | |||
package jvcl.service; | |||
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 { | |||
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", | |||
"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", | |||
"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": [ | |||