@@ -21,6 +21,7 @@ import java.util.*; | |||||
import java.util.regex.Pattern; | import java.util.regex.Pattern; | ||||
import java.util.stream.Collectors; | import java.util.stream.Collectors; | ||||
import static java.math.RoundingMode.HALF_EVEN; | |||||
import static java.util.Comparator.comparing; | import static java.util.Comparator.comparing; | ||||
import static org.cobbzilla.util.daemon.ZillaRuntime.*; | import static org.cobbzilla.util.daemon.ZillaRuntime.*; | ||||
import static org.cobbzilla.util.http.HttpSchemes.isHttpOrHttps; | import static org.cobbzilla.util.http.HttpSchemes.isHttpOrHttps; | ||||
@@ -31,7 +32,7 @@ import static org.cobbzilla.util.reflect.ReflectionUtil.copy; | |||||
import static org.cobbzilla.util.system.CommandShell.execScript; | import static org.cobbzilla.util.system.CommandShell.execScript; | ||||
@NoArgsConstructor @AllArgsConstructor @Accessors(chain=true) @Slf4j | @NoArgsConstructor @AllArgsConstructor @Accessors(chain=true) @Slf4j | ||||
public class JAsset { | |||||
public class JAsset implements JsObjectView { | |||||
public static final JAsset NULL_ASSET = new JAsset().setName("~null asset~").setPath("/dev/null"); | public static final JAsset NULL_ASSET = new JAsset().setName("~null asset~").setPath("/dev/null"); | ||||
public static final String PREFIX_CLASSPATH = "classpath:"; | public static final String PREFIX_CLASSPATH = "classpath:"; | ||||
@@ -86,7 +87,8 @@ public class JAsset { | |||||
return hasDest() && (dest.endsWith("/") || new File(dest).isDirectory()); | return hasDest() && (dest.endsWith("/") || new File(dest).isDirectory()); | ||||
} | } | ||||
public File destDirectory() { | public File destDirectory() { | ||||
return mkdirOrDie(new File(dest.endsWith("/") ? dest.substring(0, dest.length()-1) : dest)); | |||||
final String dir = destIsDirectory() ? dest : dirname(dest); | |||||
return mkdirOrDie(new File(dir.endsWith("/") ? dir.substring(0, dir.length()-1) : dir)); | |||||
} | } | ||||
// if path was not a file, it got resolved to a file | // if path was not a file, it got resolved to a file | ||||
@@ -142,9 +144,21 @@ public class JAsset { | |||||
} | } | ||||
} | } | ||||
public BigDecimal duration() { return getInfo().duration(); } | |||||
public BigDecimal duration() { return hasInfo() ? getInfo().duration() : null; } | |||||
@JsonIgnore public BigDecimal getDuration () { return duration(); } | @JsonIgnore public BigDecimal getDuration () { return duration(); } | ||||
public BigDecimal width() { return hasInfo() ? getInfo().width() : null; } | |||||
@JsonIgnore public BigDecimal getWidth () { return width(); } | |||||
public BigDecimal height() { return hasInfo() ? getInfo().height() : null; } | |||||
@JsonIgnore public BigDecimal getHeight () { return height(); } | |||||
public BigDecimal aspectRatio() { | |||||
final BigDecimal width = width(); | |||||
final BigDecimal height = height(); | |||||
return width == null || height == null ? null : width.divide(height, HALF_EVEN); | |||||
} | |||||
public JAsset init(AssetManager assetManager, Toolbox toolbox) { | public JAsset init(AssetManager assetManager, Toolbox toolbox) { | ||||
final JAsset asset = initPath(assetManager); | final JAsset asset = initPath(assetManager); | ||||
if (!asset.hasListAssets()) { | if (!asset.hasListAssets()) { | ||||
@@ -210,12 +224,16 @@ public class JAsset { | |||||
}); | }); | ||||
final Pattern regex = Pattern.compile(b.toString()); | final Pattern regex = Pattern.compile(b.toString()); | ||||
final File dir = new File(path.substring(0, lastSlash)); | final File dir = new File(path.substring(0, lastSlash)); | ||||
final String filesInDir = execScript("find " + dir + " -type f"); | |||||
final Set<String> matches = Arrays.stream(filesInDir.split("\n")) | |||||
.filter(f -> regex.matcher(f).matches()) | |||||
.collect(Collectors.toCollection(() -> new TreeSet<>(comparing(String::toString)))); | |||||
for (String f : matches) { | |||||
addAsset(new JAsset(this).setPath(f)); | |||||
if (dir.exists()) { | |||||
final String filesInDir = execScript("find " + dir + " -type f"); | |||||
final Set<String> matches = Arrays.stream(filesInDir.split("\n")) | |||||
.filter(f -> regex.matcher(f).matches()) | |||||
.collect(Collectors.toCollection(() -> new TreeSet<>(comparing(String::toString)))); | |||||
for (String f : matches) { | |||||
addAsset(new JAsset(this).setPath(f)); | |||||
} | |||||
} else { | |||||
return die("initPath: no files matched: "+path); | |||||
} | } | ||||
} else { | } else { | ||||
@@ -227,4 +245,21 @@ public class JAsset { | |||||
return this; | return this; | ||||
} | } | ||||
@Override public Object toJs() { return new JAssetJs(this); } | |||||
public static class JAssetJs { | |||||
public Integer duration; | |||||
public Integer width; | |||||
public Integer height; | |||||
public JAssetJs (JAsset asset) { | |||||
final BigDecimal d = asset.duration(); | |||||
this.duration = d == null ? null : d.intValue(); | |||||
final BigDecimal w = asset.width(); | |||||
this.width = w == null ? null : w.intValue(); | |||||
final BigDecimal h = asset.height(); | |||||
this.height = h == null ? null : h.intValue(); | |||||
} | |||||
} | |||||
} | } |
@@ -2,6 +2,7 @@ package jvcl.model; | |||||
import com.fasterxml.jackson.annotation.JsonCreator; | import com.fasterxml.jackson.annotation.JsonCreator; | ||||
import jvcl.op.ConcatOperation; | import jvcl.op.ConcatOperation; | ||||
import jvcl.op.OverlayOperation; | |||||
import jvcl.op.SplitOperation; | import jvcl.op.SplitOperation; | ||||
import jvcl.op.TrimOperation; | import jvcl.op.TrimOperation; | ||||
import jvcl.service.AssetManager; | import jvcl.service.AssetManager; | ||||
@@ -15,7 +16,7 @@ public enum JOperationType { | |||||
concat (new ConcatOperation()), | concat (new ConcatOperation()), | ||||
split (new SplitOperation()), | split (new SplitOperation()), | ||||
trim (new TrimOperation()), | trim (new TrimOperation()), | ||||
overlay (null), | |||||
overlay (new OverlayOperation()), | |||||
ken_burns (null), | ken_burns (null), | ||||
letterbox (null), | letterbox (null), | ||||
split_silence (null); | split_silence (null); | ||||
@@ -0,0 +1,7 @@ | |||||
package jvcl.model; | |||||
public interface JsObjectView { | |||||
Object toJs(); | |||||
} |
@@ -66,4 +66,26 @@ public class JMediaInfo { | |||||
return longest; | return longest; | ||||
} | } | ||||
public BigDecimal width() { | |||||
if (media == null || empty(media.getTrack())) return BigDecimal.ZERO; | |||||
// find the first video track | |||||
for (JTrack t : media.getTrack()) { | |||||
if (!t.video()) continue; | |||||
if (!t.hasWidth()) continue; | |||||
return big(t.getWidth()); | |||||
} | |||||
return null; | |||||
} | |||||
public BigDecimal height() { | |||||
if (media == null || empty(media.getTrack())) return BigDecimal.ZERO; | |||||
// find the first video track | |||||
for (JTrack t : media.getTrack()) { | |||||
if (!t.video()) continue; | |||||
if (!t.hasHeight()) continue; | |||||
return big(t.getHeight()); | |||||
} | |||||
return null; | |||||
} | |||||
} | } |
@@ -42,9 +42,11 @@ public class JTrack { | |||||
@JsonProperty("Width") @Getter @Setter private String width; | @JsonProperty("Width") @Getter @Setter private String width; | ||||
public Integer width () { return parseInt(width); } | public Integer width () { return parseInt(width); } | ||||
public boolean hasWidth () { return !empty(width); } | |||||
@JsonProperty("Height") @Getter @Setter private String height; | @JsonProperty("Height") @Getter @Setter private String height; | ||||
public Integer height () { return parseInt(height); } | public Integer height () { return parseInt(height); } | ||||
public boolean hasHeight () { return !empty(height); } | |||||
@JsonProperty("Sampled_Width") @Getter @Setter private String sampledWidth; | @JsonProperty("Sampled_Width") @Getter @Setter private String sampledWidth; | ||||
@JsonProperty("Sampled_Height") @Getter @Setter private String sampledHeight; | @JsonProperty("Sampled_Height") @Getter @Setter private String sampledHeight; | ||||
@@ -9,21 +9,32 @@ import jvcl.service.Toolbox; | |||||
import lombok.Getter; | import lombok.Getter; | ||||
import lombok.Setter; | import lombok.Setter; | ||||
import lombok.extern.slf4j.Slf4j; | import lombok.extern.slf4j.Slf4j; | ||||
import org.cobbzilla.util.javascript.JsEngine; | |||||
import java.io.File; | |||||
import java.math.BigDecimal; | import java.math.BigDecimal; | ||||
import java.util.HashMap; | import java.util.HashMap; | ||||
import java.util.Map; | import java.util.Map; | ||||
import static java.math.RoundingMode.HALF_EVEN; | |||||
import static jvcl.model.JAsset.json2asset; | import static jvcl.model.JAsset.json2asset; | ||||
import static jvcl.service.Toolbox.getDuration; | import static jvcl.service.Toolbox.getDuration; | ||||
import static org.cobbzilla.util.daemon.ZillaRuntime.big; | |||||
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.basename; | |||||
import static org.cobbzilla.util.system.CommandShell.execScript; | import static org.cobbzilla.util.system.CommandShell.execScript; | ||||
@Slf4j | @Slf4j | ||||
public class OverlayOperation implements JOperator { | public class OverlayOperation implements JOperator { | ||||
public static final String OVERLAY_TEMPLATE | public static final String OVERLAY_TEMPLATE | ||||
= "{{ffmpeg}} "; | |||||
= "{{ffmpeg}} -i {{{source.path}}} -filter_complex \"" + | |||||
"movie={{{overlay.path}}}{{#exists overlayStart}}:seek_point={{overlayStart}}{{/exists}} [ovl]; " + | |||||
"[v:0] setpts=PTS-(STARTPTS+{{#exists offset}}{{offset}}{{else}}0{{/exists}}) [main]; " + | |||||
"[ovl] setpts=PTS-STARTPTS{{#exists width}}, scale={{width}}x{{height}}{{/exists}} ; " + | |||||
"[main][ovl] overlay=shortest=1{{#exists x}}:x={{x}}{{/exists}}{{#exists y}}:y={{y}}{{/exists}} " + | |||||
"\" {{{output.path}}}"; | |||||
@Override public void operate(JOperation op, Toolbox toolbox, AssetManager assetManager) { | @Override public void operate(JOperation op, Toolbox toolbox, AssetManager assetManager) { | ||||
final OverlayConfig config = loadConfig(op, OverlayConfig.class); | final OverlayConfig config = loadConfig(op, OverlayConfig.class); | ||||
@@ -34,16 +45,44 @@ public class OverlayOperation implements JOperator { | |||||
output.mergeFormat(source.getFormat()); | output.mergeFormat(source.getFormat()); | ||||
final JFileExtension formatType = output.getFormat().getFileExtension(); | final JFileExtension formatType = output.getFormat().getFileExtension(); | ||||
if (output.hasDest()) { | |||||
if (output.destExists() && !output.destIsDirectory()) { | |||||
log.info("operate: dest exists, not trimming: " + output.getDest()); | |||||
return; | |||||
} else if (output.destIsDirectory()) { | |||||
final File defaultFile = assetManager.assetPath(op, source, formatType, new Object[]{config}); | |||||
output.setPath(abs(new File(output.destDirectory(), basename(abs(defaultFile))))); | |||||
} else { | |||||
output.setPath(output.destPath()); | |||||
} | |||||
} | |||||
final Map<String, Object> ctx = new HashMap<>(); | final Map<String, Object> ctx = new HashMap<>(); | ||||
ctx.put("ffmpeg", toolbox.getFfmpeg()); | |||||
ctx.put("source", source); | ctx.put("source", source); | ||||
ctx.put("overlay", overlay); | ctx.put("overlay", overlay); | ||||
ctx.put("output", output); | 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()); | |||||
ctx.put("offset", config.getOffsetSeconds(ctx, toolbox.getJs())); | |||||
ctx.put("overlayStart", config.getOverlayStartSeconds(ctx, toolbox.getJs())); | |||||
if (config.hasOverlayEnd()) ctx.put("overlayEnd", config.getOverlayEndSeconds(ctx, toolbox.getJs())); | |||||
if (config.hasWidth()) { | |||||
final String width = config.getWidth(ctx, toolbox.getJs()); | |||||
ctx.put("width", width); | |||||
if (!config.hasHeight()) { | |||||
final int height = big(width).divide(overlay.aspectRatio(), HALF_EVEN).intValue(); | |||||
ctx.put("height", height); | |||||
} | |||||
} | |||||
if (config.hasHeight()) { | |||||
final String height = config.getHeight(ctx, toolbox.getJs()); | |||||
ctx.put("height", height); | |||||
if (!config.hasWidth()) { | |||||
final int width = big(height).multiply(overlay.aspectRatio()).intValue(); | |||||
ctx.put("width", width); | |||||
} | |||||
} | |||||
if (config.hasX()) ctx.put("x", config.getX(ctx, toolbox.getJs())); | |||||
if (config.hasY()) ctx.put("y", config.getY(ctx, toolbox.getJs())); | |||||
final String script = renderScript(toolbox, ctx, OVERLAY_TEMPLATE); | final String script = renderScript(toolbox, ctx, OVERLAY_TEMPLATE); | ||||
@@ -57,32 +96,50 @@ public class OverlayOperation implements JOperator { | |||||
@Getter @Setter private String source; | @Getter @Setter private String source; | ||||
@Getter @Setter private String overlay; | @Getter @Setter private String overlay; | ||||
private String eval(String val, Map<String, Object> ctx, JsEngine js) { | |||||
final Map<String, Object> jsCtx = Toolbox.jsContext(ctx); | |||||
final Object result = js.evaluate(val, jsCtx); | |||||
return result == null ? null : result.toString(); | |||||
} | |||||
@Getter @Setter private String offset; | @Getter @Setter private String offset; | ||||
public BigDecimal getOffsetSeconds () { return empty(offset) ? BigDecimal.ZERO : getDuration(offset); } | |||||
public BigDecimal getOffsetSeconds (Map<String, Object> ctx, JsEngine js) { | |||||
return empty(offset) ? BigDecimal.ZERO : getDuration(eval(offset, ctx, js)); | |||||
} | |||||
@Getter @Setter private String overlayStart; | @Getter @Setter private String overlayStart; | ||||
public BigDecimal getOverlayStartSeconds () { return empty(overlayStart) ? BigDecimal.ZERO : getDuration(overlayStart); } | |||||
public BigDecimal getOverlayStartSeconds (Map<String, Object> ctx, JsEngine js) { | |||||
return empty(overlayStart) ? BigDecimal.ZERO : getDuration(eval(overlayStart, ctx, js)); | |||||
} | |||||
@Getter @Setter private String overlayEnd; | @Getter @Setter private String overlayEnd; | ||||
public boolean hasOverlayEnd () { return !empty(overlayEnd); } | public boolean hasOverlayEnd () { return !empty(overlayEnd); } | ||||
public BigDecimal getOverlayEndSeconds () { return getDuration(overlayEnd); } | |||||
public BigDecimal getOverlayEndSeconds (Map<String, Object> ctx, JsEngine js) { | |||||
return getDuration(eval(overlayEnd, ctx, js)); | |||||
} | |||||
@Getter @Setter private String width; | @Getter @Setter private String width; | ||||
public boolean hasWidth () { return !empty(width); } | public boolean hasWidth () { return !empty(width); } | ||||
public String getWidth(Map<String, Object> ctx, JsEngine js) { return eval(width, ctx, js); } | |||||
@Getter @Setter private String height; | @Getter @Setter private String height; | ||||
public boolean hasHeight () { return !empty(height); } | public boolean hasHeight () { return !empty(height); } | ||||
public String getHeight(Map<String, Object> ctx, JsEngine js) { return eval(height, ctx, js); } | |||||
@Getter @Setter private String x; | @Getter @Setter private String x; | ||||
public boolean hasX () { return !empty(x); } | public boolean hasX () { return !empty(x); } | ||||
public String getX(Map<String, Object> ctx, JsEngine js) { return eval(x, ctx, js); } | |||||
@Getter @Setter private String y; | @Getter @Setter private String y; | ||||
public boolean hasY () { return !empty(y); } | public boolean hasY () { return !empty(y); } | ||||
public String getY(Map<String, Object> ctx, JsEngine js) { return eval(y, ctx, js); } | |||||
@Getter @Setter private String outputWidth; | @Getter @Setter private String outputWidth; | ||||
public boolean hasOutputWidth () { return !empty(outputWidth); } | public boolean hasOutputWidth () { return !empty(outputWidth); } | ||||
public String getOutputWidth(Map<String, Object> ctx, JsEngine js) { return eval(outputWidth, ctx, js); } | |||||
@Getter @Setter private String outputHeight; | @Getter @Setter private String outputHeight; | ||||
public boolean hasOutputHeight () { return !empty(outputHeight); } | public boolean hasOutputHeight () { return !empty(outputHeight); } | ||||
public String getOutputHeight(Map<String, Object> ctx, JsEngine js) { return eval(outputHeight, ctx, js); } | |||||
} | } | ||||
} | } |
@@ -51,34 +51,44 @@ public class TrimOperation implements JOperator { | |||||
final File outfile; | final File outfile; | ||||
if (output.hasDest()) { | if (output.hasDest()) { | ||||
outfile = new File(output.destDirectory(), basename(appendToFileNameBeforeExt(asset.getPath(), "_"+config.shortString()))); | outfile = new File(output.destDirectory(), basename(appendToFileNameBeforeExt(asset.getPath(), "_"+config.shortString()))); | ||||
if (outfile.exists()) { | |||||
log.info("operate: dest exists: "+abs(outfile)); | |||||
return; | |||||
} | |||||
} else { | } else { | ||||
outfile = defaultOutfile; | outfile = defaultOutfile; | ||||
} | } | ||||
subOutput.setPath(abs(outfile)); | subOutput.setPath(abs(outfile)); | ||||
trim(config, asset, subOutput, toolbox, assetManager); | |||||
trim(config, asset, output, subOutput, toolbox, assetManager); | |||||
} | } | ||||
} else { | } else { | ||||
if (output.hasDest() && output.destExists()) { | |||||
log.info("operate: dest exists, not trimming: "+output.getDest()); | |||||
final File defaultOutfile = assetManager.assetPath(op, source, formatType, new Object[]{config}); | |||||
if (output.hasDest()) { | |||||
if (output.destExists() && !output.destIsDirectory()) { | |||||
log.info("operate: dest exists, not trimming: " + output.getDest()); | |||||
return; | |||||
} else if (output.destIsDirectory()) { | |||||
output.setPath(abs(new File(output.destDirectory(), basename(abs(defaultOutfile))))); | |||||
} else { | |||||
output.setPath(output.destPath()); | |||||
} | |||||
} else { | } else { | ||||
trim(config, source, output, toolbox, assetManager); | |||||
output.setPath(abs(defaultOutfile)); | |||||
} | } | ||||
trim(config, source, output, output, toolbox, assetManager); | |||||
} | } | ||||
} | } | ||||
private void trim(TrimConfig config, | private void trim(TrimConfig config, | ||||
JAsset source, | JAsset source, | ||||
JAsset output, | JAsset output, | ||||
JAsset subOutput, | |||||
Toolbox toolbox, | Toolbox toolbox, | ||||
AssetManager assetManager) { | AssetManager assetManager) { | ||||
if (output.destExists()) { | |||||
log.info("trim: dest exists: "+output.getDest()); | |||||
return; | |||||
} | |||||
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); | ||||
ctx.put("output", output); | |||||
ctx.put("output", subOutput); | |||||
final BigDecimal startTime = config.getStartTime(); | final BigDecimal startTime = config.getStartTime(); | ||||
ctx.put("startSeconds", startTime); | ctx.put("startSeconds", startTime); | ||||
@@ -88,7 +98,11 @@ public class TrimOperation implements JOperator { | |||||
log.debug("operate: running script: "+script); | log.debug("operate: running script: "+script); | ||||
final String scriptOutput = execScript(script); | final String scriptOutput = execScript(script); | ||||
log.debug("operate: command output: "+scriptOutput); | log.debug("operate: command output: "+scriptOutput); | ||||
assetManager.addOperationAssetSlice(output, output); | |||||
if (output == subOutput) { | |||||
assetManager.addOperationAsset(output); | |||||
} else { | |||||
assetManager.addOperationAssetSlice(output, subOutput); | |||||
} | |||||
} | } | ||||
@NoArgsConstructor @EqualsAndHashCode | @NoArgsConstructor @EqualsAndHashCode | ||||
@@ -2,6 +2,7 @@ package jvcl.service; | |||||
import com.github.jknack.handlebars.Handlebars; | import com.github.jknack.handlebars.Handlebars; | ||||
import jvcl.model.JAsset; | import jvcl.model.JAsset; | ||||
import jvcl.model.JsObjectView; | |||||
import jvcl.model.info.JMediaInfo; | import jvcl.model.info.JMediaInfo; | ||||
import lombok.Getter; | import lombok.Getter; | ||||
import lombok.extern.slf4j.Slf4j; | import lombok.extern.slf4j.Slf4j; | ||||
@@ -11,12 +12,12 @@ import org.cobbzilla.util.javascript.StandardJsEngine; | |||||
import java.io.File; | import java.io.File; | ||||
import java.math.BigDecimal; | import java.math.BigDecimal; | ||||
import java.math.RoundingMode; | |||||
import java.util.HashMap; | |||||
import java.util.Map; | import java.util.Map; | ||||
import java.util.concurrent.ConcurrentHashMap; | import java.util.concurrent.ConcurrentHashMap; | ||||
import static java.math.RoundingMode.HALF_EVEN; | |||||
import static org.cobbzilla.util.daemon.ZillaRuntime.*; | import static org.cobbzilla.util.daemon.ZillaRuntime.*; | ||||
import static org.cobbzilla.util.daemon.ZillaRuntime.big; | |||||
import static org.cobbzilla.util.io.FileUtil.abs; | import static org.cobbzilla.util.io.FileUtil.abs; | ||||
import static org.cobbzilla.util.io.FileUtil.replaceExt; | import static org.cobbzilla.util.io.FileUtil.replaceExt; | ||||
import static org.cobbzilla.util.json.JsonUtil.FULL_MAPPER_ALLOW_UNKNOWN_FIELDS; | import static org.cobbzilla.util.json.JsonUtil.FULL_MAPPER_ALLOW_UNKNOWN_FIELDS; | ||||
@@ -31,8 +32,23 @@ public class Toolbox { | |||||
@Getter(lazy=true) private final Handlebars handlebars = initHandlebars(); | @Getter(lazy=true) private final Handlebars handlebars = initHandlebars(); | ||||
@Getter(lazy=true) private final StandardJsEngine js = new StandardJsEngine(); | |||||
public static BigDecimal getDuration(String t) { | public static BigDecimal getDuration(String t) { | ||||
return big(parseDuration(t)).divide(big(1000), RoundingMode.UNNECESSARY); | |||||
return big(parseDuration(t)).divide(big(1000), HALF_EVEN); | |||||
} | |||||
public static Map<String, Object> jsContext(Map<String, Object> ctx) { | |||||
final Map<String, Object> jsCtx = new HashMap<>(); | |||||
for (Map.Entry<String, Object> entry : ctx.entrySet()) { | |||||
final Object value = entry.getValue(); | |||||
if (value instanceof JsObjectView) { | |||||
jsCtx.put(entry.getKey(), ((JsObjectView) value).toJs()); | |||||
} else { | |||||
jsCtx.put(entry.getKey(), value); | |||||
} | |||||
} | |||||
return jsCtx; | |||||
} | } | ||||
private Handlebars initHandlebars() { | private Handlebars initHandlebars() { | ||||
@@ -16,6 +16,7 @@ public class BasicTest { | |||||
@Test public void testSplit () { runSpec("tests/test_split.json"); } | @Test public void testSplit () { runSpec("tests/test_split.json"); } | ||||
@Test public void testConcat () { runSpec("tests/test_concat.json"); } | @Test public void testConcat () { runSpec("tests/test_concat.json"); } | ||||
@Test public void testTrim () { runSpec("tests/test_trim.json"); } | @Test public void testTrim () { runSpec("tests/test_trim.json"); } | ||||
@Test public void testOverlay() { runSpec("tests/test_overlay.json"); } | |||||
private void runSpec(String specPath) { | private void runSpec(String specPath) { | ||||
@Cleanup("delete") final File specFile = stream2file(loadResourceAsStream(specPath)); | @Cleanup("delete") final File specFile = stream2file(loadResourceAsStream(specPath)); | ||||
@@ -35,5 +35,6 @@ | |||||
"outputWidth": "1920", // output width in pixels. default is source width | "outputWidth": "1920", // output width in pixels. default is source width | ||||
"outputHeight": "1024" // output height in pixes. default is source height | "outputHeight": "1024" // output height in pixes. default is source height | ||||
} | } | ||||
} | |||||
] | ] | ||||
} | } |