@@ -5,10 +5,12 @@ import lombok.Getter; | |||||
import lombok.NoArgsConstructor; | import lombok.NoArgsConstructor; | ||||
import lombok.Setter; | import lombok.Setter; | ||||
import lombok.experimental.Accessors; | import lombok.experimental.Accessors; | ||||
import org.cobbzilla.util.collection.NameAndValue; | |||||
@NoArgsConstructor @Accessors(chain=true) | @NoArgsConstructor @Accessors(chain=true) | ||||
public class JSpec { | public class JSpec { | ||||
@Getter @Setter private NameAndValue[] vars; | |||||
@Getter @Setter private JAsset[] assets; | @Getter @Setter private JAsset[] assets; | ||||
@Getter @Setter private JOperation[] operations; | @Getter @Setter private JOperation[] operations; | ||||
@Getter @Setter private JArtifact[] artifacts; | @Getter @Setter private JArtifact[] artifacts; | ||||
@@ -1,10 +1,12 @@ | |||||
package jvc.model; | package jvc.model; | ||||
import jvc.model.js.JAssetJs; | |||||
import java.util.Collection; | import java.util.Collection; | ||||
import java.util.stream.Collectors; | import java.util.stream.Collectors; | ||||
import static java.util.Collections.emptyList; | import static java.util.Collections.emptyList; | ||||
import static org.cobbzilla.util.daemon.ZillaRuntime.empty; | |||||
import static org.cobbzilla.util.daemon.ZillaRuntime.*; | |||||
public interface JsObjectView { | public interface JsObjectView { | ||||
@@ -16,13 +18,17 @@ public interface JsObjectView { | |||||
&& (((Collection) value).iterator().next() instanceof JsObjectView); | && (((Collection) value).iterator().next() instanceof JsObjectView); | ||||
} | } | ||||
static <T extends JsObjectView> Collection<T> toJs(Collection<T> values) { | |||||
static <T extends JsObjectView> Collection<JAssetJs> toJs(Collection<T> values) { | |||||
if (empty(values)) { | if (empty(values)) { | ||||
return emptyList(); | return emptyList(); | ||||
} else { | } 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)); | |||||
} | |||||
} | } | ||||
} | } | ||||
@@ -4,6 +4,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore; | |||||
import com.fasterxml.jackson.annotation.JsonTypeInfo; | import com.fasterxml.jackson.annotation.JsonTypeInfo; | ||||
import com.fasterxml.jackson.databind.JsonNode; | import com.fasterxml.jackson.databind.JsonNode; | ||||
import jvc.model.JAsset; | import jvc.model.JAsset; | ||||
import jvc.model.JSpec; | |||||
import jvc.operation.exec.ExecBase; | import jvc.operation.exec.ExecBase; | ||||
import lombok.Getter; | import lombok.Getter; | ||||
import lombok.NoArgsConstructor; | import lombok.NoArgsConstructor; | ||||
@@ -50,9 +51,11 @@ public abstract class JOperation { | |||||
return hashOf(operation, json(this), sources, args); | return hashOf(operation, json(this), sources, args); | ||||
} | } | ||||
private static final Map<Class<? extends JOperation>, ExecBase<?>> execMap = new HashMap<>(); | |||||
public <OP extends JOperation> ExecBase<OP> getExec() { | |||||
return (ExecBase<OP>) execMap.computeIfAbsent(getClass(), c -> instantiate(getOperationExecClass(getClass()))); | |||||
private static final Map<String, ExecBase<?>> execMap = new HashMap<>(); | |||||
public <OP extends JOperation> ExecBase<OP> getExec(JSpec spec) { | |||||
final String cacheKey = hashOf(getClass().getName(), spec.getVars()); | |||||
return (ExecBase<OP>) execMap.computeIfAbsent(cacheKey, | |||||
c -> ((ExecBase<?>) instantiate(getOperationExecClass(getClass()))).setSpec(spec)); | |||||
} | } | ||||
public String shortString() { return safeShellArg(operation+"_"+sha256_hex(json(this))); } | public String shortString() { return safeShellArg(operation+"_"+sha256_hex(json(this))); } | ||||
@@ -1,5 +1,6 @@ | |||||
package jvc.operation.exec; | package jvc.operation.exec; | ||||
import jvc.model.JSpec; | |||||
import jvc.model.operation.JSingleOperationContext; | import jvc.model.operation.JSingleOperationContext; | ||||
import jvc.operation.AdjustSpeedOperation; | import jvc.operation.AdjustSpeedOperation; | ||||
import lombok.extern.slf4j.Slf4j; | import lombok.extern.slf4j.Slf4j; | ||||
@@ -1,11 +1,16 @@ | |||||
package jvc.operation.exec; | package jvc.operation.exec; | ||||
import jvc.model.JAsset; | import jvc.model.JAsset; | ||||
import jvc.model.JSpec; | |||||
import jvc.model.JStreamType; | import jvc.model.JStreamType; | ||||
import jvc.model.operation.JOperation; | import jvc.model.operation.JOperation; | ||||
import jvc.service.AssetManager; | import jvc.service.AssetManager; | ||||
import jvc.service.Toolbox; | import jvc.service.Toolbox; | ||||
import lombok.Getter; | |||||
import lombok.Setter; | |||||
import lombok.experimental.Accessors; | |||||
import lombok.extern.slf4j.Slf4j; | import lombok.extern.slf4j.Slf4j; | ||||
import org.cobbzilla.util.collection.NameAndValue; | |||||
import org.cobbzilla.util.handlebars.HandlebarsUtil; | import org.cobbzilla.util.handlebars.HandlebarsUtil; | ||||
import java.io.File; | import java.io.File; | ||||
@@ -15,13 +20,22 @@ import java.util.Map; | |||||
import static jvc.service.Toolbox.jsContext; | import static jvc.service.Toolbox.jsContext; | ||||
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.io.FileUtil.abs; | import static org.cobbzilla.util.io.FileUtil.abs; | ||||
import static org.cobbzilla.util.io.FileUtil.basename; | import static org.cobbzilla.util.io.FileUtil.basename; | ||||
import static org.cobbzilla.util.system.CommandShell.execScript; | import static org.cobbzilla.util.system.CommandShell.execScript; | ||||
@Slf4j | |||||
@Slf4j @Accessors(chain=true) | |||||
public abstract class ExecBase<OP extends JOperation> { | public abstract class ExecBase<OP extends JOperation> { | ||||
@Getter @Setter private JSpec spec; | |||||
public NameAndValue[] getVars () { | |||||
return spec == null || empty(spec.getVars()) | |||||
? NameAndValue.EMPTY_ARRAY | |||||
: spec.getVars(); | |||||
} | |||||
public abstract Map<String, Object> operate(OP operation, Toolbox toolbox, AssetManager assetManager); | public abstract Map<String, Object> operate(OP operation, Toolbox toolbox, AssetManager assetManager); | ||||
protected String renderScript(Toolbox toolbox, Map<String, Object> ctx, String template) { | protected String renderScript(Toolbox toolbox, Map<String, Object> ctx, String template) { | ||||
@@ -43,10 +57,19 @@ public abstract class ExecBase<OP extends JOperation> { | |||||
} | } | ||||
} | } | ||||
protected Map<String, Object> initialContext(Toolbox toolbox, JAsset source) { | |||||
protected Map<String, Object> initialContext(Toolbox toolbox, JAsset source, NameAndValue[] vars) { | |||||
final Map<String, Object> ctx = new HashMap<>(); | final Map<String, Object> ctx = new HashMap<>(); | ||||
ctx.put("ffmpeg", toolbox.getFfmpeg()); | ctx.put("ffmpeg", toolbox.getFfmpeg()); | ||||
ctx.put("source", source); | 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; | return ctx; | ||||
} | } | ||||
@@ -57,7 +57,7 @@ public class KenBurnsExec extends ExecBase<KenBurnsOperation> { | |||||
output.setPath(abs(path)); | output.setPath(abs(path)); | ||||
final JsEngine js = toolbox.getJs(); | final JsEngine js = toolbox.getJs(); | ||||
final Map<String, Object> ctx = initialContext(toolbox, source); | |||||
final Map<String, Object> ctx = initialContext(toolbox, source, getVars()); | |||||
ctx.put("output", output); | ctx.put("output", output); | ||||
ctx.put("width", op.getWidth(ctx, js)); | ctx.put("width", op.getWidth(ctx, js)); | ||||
ctx.put("height", op.getHeight(ctx, js)); | ctx.put("height", op.getHeight(ctx, js)); | ||||
@@ -40,7 +40,7 @@ public class OverlayExec extends ExecBase<OverlayOperation> { | |||||
output.setPath(abs(path)); | output.setPath(abs(path)); | ||||
final JsEngine js = toolbox.getJs(); | final JsEngine js = toolbox.getJs(); | ||||
final Map<String, Object> ctx = initialContext(toolbox, source); | |||||
final Map<String, Object> ctx = initialContext(toolbox, source, getVars()); | |||||
ctx.put("overlay", overlaySource); | ctx.put("overlay", overlaySource); | ||||
ctx.put("mainStart", op.getStartTime(ctx, js)); | ctx.put("mainStart", op.getStartTime(ctx, js)); | ||||
@@ -22,7 +22,7 @@ public abstract class SingleOrMultiSourceExecBase<OP extends JSingleSourceOperat | |||||
final JAsset source = opCtx.source; | final JAsset source = opCtx.source; | ||||
final JAsset output = opCtx.output; | final JAsset output = opCtx.output; | ||||
final JStreamType streamType = opCtx.streamType; | final JStreamType streamType = opCtx.streamType; | ||||
final Map<String, Object> ctx = initialContext(toolbox, source); | |||||
final Map<String, Object> ctx = initialContext(toolbox, source, getVars()); | |||||
addCommandContext(op, opCtx, ctx); | addCommandContext(op, opCtx, ctx); | ||||
return operate(op, toolbox, assetManager, source, output, streamType, ctx); | return operate(op, toolbox, assetManager, source, output, streamType, ctx); | ||||
} | } | ||||
@@ -31,7 +31,7 @@ public class SplitExec extends ExecBase<SplitOperation> { | |||||
final JStreamType streamType = opCtx.streamType; | final JStreamType streamType = opCtx.streamType; | ||||
final JsEngine js = toolbox.getJs(); | final JsEngine js = toolbox.getJs(); | ||||
final Map<String, Object> ctx = initialContext(toolbox, source); | |||||
final Map<String, Object> ctx = initialContext(toolbox, source, getVars()); | |||||
assetManager.addOperationArrayAsset(output); | assetManager.addOperationArrayAsset(output); | ||||
final BigDecimal incr = op.getIntervalIncr(ctx, js); | final BigDecimal incr = op.getIntervalIncr(ctx, js); | ||||
@@ -31,15 +31,15 @@ public class JvcEngine { | |||||
public void runSpec(JSpec spec) { | public void runSpec(JSpec spec) { | ||||
Arrays.stream(spec.getAssets()).forEach(assetManager::defineAsset); | 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<JOperation> exec = op | final ExecBase<JOperation> exec = op | ||||
.setExecIndex(completed.size()) | .setExecIndex(completed.size()) | ||||
.setNoExec(noExec) | .setNoExec(noExec) | ||||
.getExec(); | |||||
.getExec(spec); | |||||
final Map<String, Object> ctx = exec.operate(op, toolbox, assetManager); | final Map<String, Object> ctx = exec.operate(op, toolbox, assetManager); | ||||
if (ctx == null) { | if (ctx == null) { | ||||
@@ -42,8 +42,6 @@ | |||||
"operation": "overlay", // name of the operation | "operation": "overlay", // name of the operation | ||||
"creates": { | "creates": { | ||||
"name": "overlay1", // name of the output asset | "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/" | "dest": "src/test/resources/outputs/overlay/" | ||||
}, | }, | ||||
"source": "v1", // main video asset | "source": "v1", // main video asset | ||||
@@ -1,4 +1,8 @@ | |||||
{ | { | ||||
"vars": [ | |||||
{"name": "out_width", "value": "1024"}, | |||||
{"name": "out_height", "value": "768"} | |||||
], | |||||
"assets": [ | "assets": [ | ||||
// wildcard matches multiple files, vid1_splits becomes a "list" asset. resolution is 320x240 | // wildcard matches multiple files, vid1_splits becomes a "list" asset. resolution is 320x240 | ||||
{ "name": "vid1_splits", "path": "src/test/resources/outputs/vid1_splits_*.mp4" } | { "name": "vid1_splits", "path": "src/test/resources/outputs/vid1_splits_*.mp4" } | ||||
@@ -8,8 +12,10 @@ | |||||
"operation": "scale", // name of the operation | "operation": "scale", // name of the operation | ||||
"creates": "scaled_test1", // output asset name | "creates": "scaled_test1", // output asset name | ||||
"source": "vid1_splits[3]", // scale this source asset | "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": [{ | "validate": [{ | ||||
"comment": "expect output resolution of 1024x768", | "comment": "expect output resolution of 1024x768", | ||||
"test": "output.width === 1024 && output.height === 768" | "test": "output.width === 1024 && output.height === 768" | ||||