diff --git a/src/main/java/jvc/model/JSpec.java b/src/main/java/jvc/model/JSpec.java index 78ab09b..cf000a9 100644 --- a/src/main/java/jvc/model/JSpec.java +++ b/src/main/java/jvc/model/JSpec.java @@ -5,10 +5,12 @@ import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import lombok.experimental.Accessors; +import org.cobbzilla.util.collection.NameAndValue; @NoArgsConstructor @Accessors(chain=true) public class JSpec { + @Getter @Setter private NameAndValue[] vars; @Getter @Setter private JAsset[] assets; @Getter @Setter private JOperation[] operations; @Getter @Setter private JArtifact[] artifacts; diff --git a/src/main/java/jvc/model/JsObjectView.java b/src/main/java/jvc/model/JsObjectView.java index 995ba38..c1964e5 100644 --- a/src/main/java/jvc/model/JsObjectView.java +++ b/src/main/java/jvc/model/JsObjectView.java @@ -1,10 +1,12 @@ package jvc.model; +import jvc.model.js.JAssetJs; + import java.util.Collection; import java.util.stream.Collectors; import static java.util.Collections.emptyList; -import static org.cobbzilla.util.daemon.ZillaRuntime.empty; +import static org.cobbzilla.util.daemon.ZillaRuntime.*; public interface JsObjectView { @@ -16,13 +18,17 @@ public interface JsObjectView { && (((Collection) value).iterator().next() instanceof JsObjectView); } - static Collection toJs(Collection values) { + static Collection toJs(Collection values) { if (empty(values)) { return emptyList(); } else { - return values.stream() - .map(v -> (T) v.toJs()) - .collect(Collectors.toList()); + try { + return values.stream() + .map(v -> (JAssetJs) v.toJs()) + .collect(Collectors.toList()); + } catch (Exception e) { + return die("toJs: "+shortError(e)); + } } } diff --git a/src/main/java/jvc/model/operation/JOperation.java b/src/main/java/jvc/model/operation/JOperation.java index 9d5ec21..1a534fb 100644 --- a/src/main/java/jvc/model/operation/JOperation.java +++ b/src/main/java/jvc/model/operation/JOperation.java @@ -4,6 +4,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.databind.JsonNode; import jvc.model.JAsset; +import jvc.model.JSpec; import jvc.operation.exec.ExecBase; import lombok.Getter; import lombok.NoArgsConstructor; @@ -50,9 +51,11 @@ public abstract class JOperation { return hashOf(operation, json(this), sources, args); } - private static final Map, ExecBase> execMap = new HashMap<>(); - public ExecBase getExec() { - return (ExecBase) execMap.computeIfAbsent(getClass(), c -> instantiate(getOperationExecClass(getClass()))); + private static final Map> execMap = new HashMap<>(); + public ExecBase getExec(JSpec spec) { + final String cacheKey = hashOf(getClass().getName(), spec.getVars()); + return (ExecBase) execMap.computeIfAbsent(cacheKey, + c -> ((ExecBase) instantiate(getOperationExecClass(getClass()))).setSpec(spec)); } public String shortString() { return safeShellArg(operation+"_"+sha256_hex(json(this))); } diff --git a/src/main/java/jvc/operation/exec/AdjustSpeedExec.java b/src/main/java/jvc/operation/exec/AdjustSpeedExec.java index c72983b..e5ec14b 100644 --- a/src/main/java/jvc/operation/exec/AdjustSpeedExec.java +++ b/src/main/java/jvc/operation/exec/AdjustSpeedExec.java @@ -1,5 +1,6 @@ package jvc.operation.exec; +import jvc.model.JSpec; import jvc.model.operation.JSingleOperationContext; import jvc.operation.AdjustSpeedOperation; import lombok.extern.slf4j.Slf4j; diff --git a/src/main/java/jvc/operation/exec/ExecBase.java b/src/main/java/jvc/operation/exec/ExecBase.java index 86556ab..9db62a9 100644 --- a/src/main/java/jvc/operation/exec/ExecBase.java +++ b/src/main/java/jvc/operation/exec/ExecBase.java @@ -1,11 +1,16 @@ package jvc.operation.exec; import jvc.model.JAsset; +import jvc.model.JSpec; import jvc.model.JStreamType; import jvc.model.operation.JOperation; import jvc.service.AssetManager; import jvc.service.Toolbox; +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; import lombok.extern.slf4j.Slf4j; +import org.cobbzilla.util.collection.NameAndValue; import org.cobbzilla.util.handlebars.HandlebarsUtil; import java.io.File; @@ -15,13 +20,22 @@ import java.util.Map; import static jvc.service.Toolbox.jsContext; 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.basename; import static org.cobbzilla.util.system.CommandShell.execScript; -@Slf4j +@Slf4j @Accessors(chain=true) public abstract class ExecBase { + @Getter @Setter private JSpec spec; + + public NameAndValue[] getVars () { + return spec == null || empty(spec.getVars()) + ? NameAndValue.EMPTY_ARRAY + : spec.getVars(); + } + public abstract Map operate(OP operation, Toolbox toolbox, AssetManager assetManager); protected String renderScript(Toolbox toolbox, Map ctx, String template) { @@ -43,10 +57,19 @@ public abstract class ExecBase { } } - protected Map initialContext(Toolbox toolbox, JAsset source) { + protected Map initialContext(Toolbox toolbox, JAsset source, NameAndValue[] vars) { final Map ctx = new HashMap<>(); ctx.put("ffmpeg", toolbox.getFfmpeg()); ctx.put("source", source); + if (!empty(vars)) { + for (NameAndValue var : vars) { + final String name = var.getName(); + if (ctx.containsKey(name)) { + log.warn("initialContext: spec variable "+ name +" will mask existing value: "+ctx.get(name)); + } + ctx.put(name, var.getValue()); + } + } return ctx; } diff --git a/src/main/java/jvc/operation/exec/KenBurnsExec.java b/src/main/java/jvc/operation/exec/KenBurnsExec.java index bd9fa01..4e97f01 100644 --- a/src/main/java/jvc/operation/exec/KenBurnsExec.java +++ b/src/main/java/jvc/operation/exec/KenBurnsExec.java @@ -57,7 +57,7 @@ public class KenBurnsExec extends ExecBase { output.setPath(abs(path)); final JsEngine js = toolbox.getJs(); - final Map ctx = initialContext(toolbox, source); + final Map ctx = initialContext(toolbox, source, getVars()); ctx.put("output", output); ctx.put("width", op.getWidth(ctx, js)); ctx.put("height", op.getHeight(ctx, js)); diff --git a/src/main/java/jvc/operation/exec/OverlayExec.java b/src/main/java/jvc/operation/exec/OverlayExec.java index a7d68fe..01294b6 100644 --- a/src/main/java/jvc/operation/exec/OverlayExec.java +++ b/src/main/java/jvc/operation/exec/OverlayExec.java @@ -40,7 +40,7 @@ public class OverlayExec extends ExecBase { output.setPath(abs(path)); final JsEngine js = toolbox.getJs(); - final Map ctx = initialContext(toolbox, source); + final Map ctx = initialContext(toolbox, source, getVars()); ctx.put("overlay", overlaySource); ctx.put("mainStart", op.getStartTime(ctx, js)); diff --git a/src/main/java/jvc/operation/exec/SingleOrMultiSourceExecBase.java b/src/main/java/jvc/operation/exec/SingleOrMultiSourceExecBase.java index 4a70b4e..ee8568b 100644 --- a/src/main/java/jvc/operation/exec/SingleOrMultiSourceExecBase.java +++ b/src/main/java/jvc/operation/exec/SingleOrMultiSourceExecBase.java @@ -22,7 +22,7 @@ public abstract class SingleOrMultiSourceExecBase ctx = initialContext(toolbox, source); + final Map ctx = initialContext(toolbox, source, getVars()); addCommandContext(op, opCtx, ctx); return operate(op, toolbox, assetManager, source, output, streamType, ctx); } diff --git a/src/main/java/jvc/operation/exec/SplitExec.java b/src/main/java/jvc/operation/exec/SplitExec.java index ad0d7c7..0833e2b 100644 --- a/src/main/java/jvc/operation/exec/SplitExec.java +++ b/src/main/java/jvc/operation/exec/SplitExec.java @@ -31,7 +31,7 @@ public class SplitExec extends ExecBase { final JStreamType streamType = opCtx.streamType; final JsEngine js = toolbox.getJs(); - final Map ctx = initialContext(toolbox, source); + final Map ctx = initialContext(toolbox, source, getVars()); assetManager.addOperationArrayAsset(output); final BigDecimal incr = op.getIntervalIncr(ctx, js); diff --git a/src/main/java/jvc/service/JvcEngine.java b/src/main/java/jvc/service/JvcEngine.java index 264e324..b755450 100644 --- a/src/main/java/jvc/service/JvcEngine.java +++ b/src/main/java/jvc/service/JvcEngine.java @@ -31,15 +31,15 @@ public class JvcEngine { public void runSpec(JSpec spec) { Arrays.stream(spec.getAssets()).forEach(assetManager::defineAsset); - Arrays.stream(spec.getOperations()).forEach(this::runOp); + Arrays.stream(spec.getOperations()).forEach(op -> runOp(spec, op)); } - private void runOp(JOperation op) { + private void runOp(JSpec spec, JOperation op) { final ExecBase exec = op .setExecIndex(completed.size()) .setNoExec(noExec) - .getExec(); + .getExec(spec); final Map ctx = exec.operate(op, toolbox, assetManager); if (ctx == null) { diff --git a/src/test/resources/tests/test_overlay.jvc b/src/test/resources/tests/test_overlay.jvc index 9562269..1ea00ad 100644 --- a/src/test/resources/tests/test_overlay.jvc +++ b/src/test/resources/tests/test_overlay.jvc @@ -42,8 +42,6 @@ "operation": "overlay", // name of the operation "creates": { "name": "overlay1", // name of the output asset - "width": "1920", // output width in pixels. default is source width - "height": "1024", // output height in pixes. default is source height "dest": "src/test/resources/outputs/overlay/" }, "source": "v1", // main video asset diff --git a/src/test/resources/tests/test_scale.jvc b/src/test/resources/tests/test_scale.jvc index 113b24c..aee0e0c 100644 --- a/src/test/resources/tests/test_scale.jvc +++ b/src/test/resources/tests/test_scale.jvc @@ -1,4 +1,8 @@ { + "vars": [ + {"name": "out_width", "value": "1024"}, + {"name": "out_height", "value": "768"} + ], "assets": [ // wildcard matches multiple files, vid1_splits becomes a "list" asset. resolution is 320x240 { "name": "vid1_splits", "path": "src/test/resources/outputs/vid1_splits_*.mp4" } @@ -8,8 +12,10 @@ "operation": "scale", // name of the operation "creates": "scaled_test1", // output asset name "source": "vid1_splits[3]", // scale this source asset - "width": "1024", // width of scaled asset. if omitted and height is present, width will be proportional - "height": "768", // height of scaled asset. if omitted and width is present, height will be proportional + "width": "out_width", // width of scaled asset. if omitted and height is present, width will be proportional + // here we reference the `out_width` variable defined above in the `vars` array + "height": "out_height", // height of scaled asset. if omitted and width is present, height will be proportional + // here we reference the `out_height` variable defined above in the `vars` array "validate": [{ "comment": "expect output resolution of 1024x768", "test": "output.width === 1024 && output.height === 768"