@@ -231,6 +231,7 @@ Here is a complex example using multiple assets and operations. | |||||
"end": "duration", // when to end zooming, default is duration | "end": "duration", // when to end zooming, default is duration | ||||
"x": "source.width * 0.6", // pan to this x-position | "x": "source.width * 0.6", // pan to this x-position | ||||
"y": "source.height * 0.4", // pan to this y-position | "y": "source.height * 0.4", // pan to this y-position | ||||
"upscale": "8", // upscale factor. upscaling the image results in a smoother pan, but a longer encode, default is 8 | |||||
"width": "1024", // width of output video | "width": "1024", // width of output video | ||||
"height": "768" // height of output video | "height": "768" // height of output video | ||||
} | } | ||||
@@ -21,8 +21,8 @@ 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 jvcl.service.Toolbox.divideBig; | |||||
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; | ||||
import static org.cobbzilla.util.io.FileUtil.*; | import static org.cobbzilla.util.io.FileUtil.*; | ||||
@@ -156,7 +156,7 @@ public class JAsset implements JsObjectView { | |||||
public BigDecimal aspectRatio() { | public BigDecimal aspectRatio() { | ||||
final BigDecimal width = width(); | final BigDecimal width = width(); | ||||
final BigDecimal height = height(); | final BigDecimal height = height(); | ||||
return width == null || height == null ? null : width.divide(height, HALF_EVEN); | |||||
return width == null || height == null ? null : divideBig(width, height); | |||||
} | } | ||||
public JAsset init(AssetManager assetManager, Toolbox toolbox) { | public JAsset init(AssetManager assetManager, Toolbox toolbox) { | ||||
@@ -176,14 +176,23 @@ public class JAsset implements JsObjectView { | |||||
// if dest already exists, use that | // if dest already exists, use that | ||||
if (hasDest()) { | if (hasDest()) { | ||||
if (destExists()) { | |||||
if (destExists() && !destIsDirectory()) { | |||||
setOriginalPath(path); | setOriginalPath(path); | ||||
setPath(destPath()); | setPath(destPath()); | ||||
return this; | return this; | ||||
} | } | ||||
} | } | ||||
final File sourcePath = hasDest() ? new File(getDest()) : assetManager.sourcePath(getName()); | |||||
final File sourcePath; | |||||
if (hasDest()) { | |||||
if (destIsDirectory()) { | |||||
sourcePath = new File(getDest(), basename(getName())); | |||||
} else { | |||||
sourcePath = new File(getDest()); | |||||
} | |||||
} else { | |||||
sourcePath = assetManager.sourcePath(getName()); | |||||
} | |||||
if (path.startsWith(PREFIX_CLASSPATH)) { | if (path.startsWith(PREFIX_CLASSPATH)) { | ||||
// it's a classpath resource | // it's a classpath resource | ||||
final String resource = path.substring(PREFIX_CLASSPATH.length()); | final String resource = path.substring(PREFIX_CLASSPATH.length()); | ||||
@@ -1,14 +1,22 @@ | |||||
package jvcl.model; | package jvcl.model; | ||||
import com.fasterxml.jackson.annotation.JsonCreator; | import com.fasterxml.jackson.annotation.JsonCreator; | ||||
import jvcl.model.info.JTrackType; | |||||
import lombok.AllArgsConstructor; | import lombok.AllArgsConstructor; | ||||
import static jvcl.model.info.JTrackType.*; | |||||
@AllArgsConstructor | @AllArgsConstructor | ||||
public enum JFileExtension { | public enum JFileExtension { | ||||
mp4 (".mp4"), | |||||
mkv (".mkv"), | |||||
raw (".yuv"); | |||||
mp4 (".mp4", video), | |||||
mkv (".mkv", video), | |||||
mp3 (".mp3", audio), | |||||
aac (".aac", audio), | |||||
flac (".flac", audio), | |||||
png (".png", image), | |||||
jpg (".jpg", image), | |||||
jpeg (".jpeg", image); | |||||
@JsonCreator public static JFileExtension fromString(String v) { return valueOf(v.toLowerCase()); } | @JsonCreator public static JFileExtension fromString(String v) { return valueOf(v.toLowerCase()); } | ||||
@@ -20,4 +28,7 @@ public enum JFileExtension { | |||||
private final String ext; | private final String ext; | ||||
public String ext() { return ext; } | public String ext() { return ext; } | ||||
private final JTrackType mediaType; | |||||
public JTrackType mediaType() { return mediaType; } | |||||
} | } |
@@ -1,17 +1,10 @@ | |||||
package jvcl.model; | package jvcl.model; | ||||
import com.fasterxml.jackson.databind.JsonNode; | |||||
import lombok.Getter; | import lombok.Getter; | ||||
import lombok.NoArgsConstructor; | import lombok.NoArgsConstructor; | ||||
import lombok.Setter; | import lombok.Setter; | ||||
import lombok.experimental.Accessors; | import lombok.experimental.Accessors; | ||||
import java.util.Arrays; | |||||
import java.util.Optional; | |||||
import static org.cobbzilla.util.daemon.ZillaRuntime.die; | |||||
import static org.cobbzilla.util.daemon.ZillaRuntime.empty; | |||||
import static org.cobbzilla.util.json.JsonUtil.json; | |||||
import static org.cobbzilla.util.reflect.ReflectionUtil.copy; | import static org.cobbzilla.util.reflect.ReflectionUtil.copy; | ||||
@NoArgsConstructor @Accessors(chain=true) | @NoArgsConstructor @Accessors(chain=true) | ||||
@@ -34,36 +27,4 @@ public class JFormat { | |||||
if (!hasFileExtension()) setFileExtension(other.getFileExtension()); | if (!hasFileExtension()) setFileExtension(other.getFileExtension()); | ||||
} | } | ||||
public static JFormat getFormat(JsonNode formatNode, JAsset[] sources) { | |||||
if (formatNode == null) { | |||||
// no format supplied, use format from first source | |||||
return new JFormat().setFileExtension(sources[0].getFormat().getFileExtension()); | |||||
} | |||||
if (formatNode.isObject()) { | |||||
return json(formatNode, JFormat.class); | |||||
} else if (formatNode.isTextual()) { | |||||
final JFileExtension formatType; | |||||
final String formatTypeString = formatNode.textValue(); | |||||
if (!empty(formatTypeString)) { | |||||
// is the format the name of an input? | |||||
final Optional<JAsset> asset = Arrays.stream(sources).filter(s -> s.getName().equals(formatTypeString)).findFirst(); | |||||
if (asset.isEmpty()) { | |||||
// not the name of an asset, must be the name of a format | |||||
formatType = JFileExtension.valueOf(formatTypeString); | |||||
} else { | |||||
// it's the name of an asset, use that asset's format | |||||
formatType = asset.get().getFormat().getFileExtension(); | |||||
} | |||||
return new JFormat().setFileExtension(formatType); | |||||
} else { | |||||
// is the format a valid format type? | |||||
if (JFileExtension.isValid(formatTypeString)) return new JFormat().setFileExtension(JFileExtension.valueOf(formatTypeString)); | |||||
return die("getFormat: invalid format type: "+formatTypeString); | |||||
} | |||||
} else { | |||||
return die("getFormat: invalid format node: "+json(formatNode)); | |||||
} | |||||
} | |||||
} | } |
@@ -8,8 +8,8 @@ import lombok.extern.slf4j.Slf4j; | |||||
import java.math.BigDecimal; | import java.math.BigDecimal; | ||||
import static org.cobbzilla.util.daemon.ZillaRuntime.big; | |||||
import static org.cobbzilla.util.daemon.ZillaRuntime.empty; | |||||
import static java.math.BigDecimal.ZERO; | |||||
import static org.cobbzilla.util.daemon.ZillaRuntime.*; | |||||
@Slf4j | @Slf4j | ||||
public class JMediaInfo { | public class JMediaInfo { | ||||
@@ -23,6 +23,7 @@ public class JMediaInfo { | |||||
JTrack general = null; | JTrack general = null; | ||||
JTrack video = null; | JTrack video = null; | ||||
JTrack audio = null; | JTrack audio = null; | ||||
JTrack image = null; | |||||
for (int i=0; i<media.getTrack().length; i++) { | for (int i=0; i<media.getTrack().length; i++) { | ||||
final JTrack t = media.getTrack()[i]; | final JTrack t = media.getTrack()[i]; | ||||
if (t.video()) { | if (t.video()) { | ||||
@@ -37,28 +38,45 @@ public class JMediaInfo { | |||||
} else { | } else { | ||||
log.warn("initFormat: multiple audio tracks found, only using the first one"); | log.warn("initFormat: multiple audio tracks found, only using the first one"); | ||||
} | } | ||||
} else if (t.image()) { | |||||
if (image == null) { | |||||
image = t; | |||||
} else { | |||||
log.warn("initFormat: multiple image tracks found, only using the first one"); | |||||
} | |||||
} else if (t.getType().equals("General") && general == null) { | } else if (t.getType().equals("General") && general == null) { | ||||
general = t; | general = t; | ||||
} | } | ||||
} | } | ||||
if (general == null) return die("initFormat: no general track found"); | |||||
final JFormat format = new JFormat(); | final JFormat format = new JFormat(); | ||||
if (video != null) { | if (video != null) { | ||||
format.setFileExtension(JFileExtension.fromString(general.getFileExtension())) | format.setFileExtension(JFileExtension.fromString(general.getFileExtension())) | ||||
.setHeight(video.height()) | .setHeight(video.height()) | ||||
.setWidth(video.width()); | .setWidth(video.width()); | ||||
} else if (audio != null) { | } else if (audio != null) { | ||||
format.setFileExtension(JFileExtension.fromString(audio.getFileExtension())); | |||||
format.setFileExtension(JFileExtension.fromString(general.getFileExtension())); | |||||
} else if (image != null) { | |||||
format.setFileExtension(JFileExtension.fromString(general.getFileExtension())) | |||||
.setHeight(image.height()) | |||||
.setWidth(image.width()); | |||||
} else { | |||||
return die("initFormat: no media tracks could be found in file"); | |||||
} | } | ||||
return format; | return format; | ||||
} | } | ||||
public BigDecimal duration() { | public BigDecimal duration() { | ||||
if (media == null || empty(media.getTrack())) return BigDecimal.ZERO; | |||||
if (media == null || empty(media.getTrack())) return ZERO; | |||||
// find the longest media track | // find the longest media track | ||||
BigDecimal longest = null; | BigDecimal longest = null; | ||||
for (JTrack t : media.getTrack()) { | for (JTrack t : media.getTrack()) { | ||||
if (!t.media()) continue; | |||||
if (!t.audioOrVideo()) continue; | |||||
if (!t.hasDuration()) continue; | if (!t.hasDuration()) continue; | ||||
final BigDecimal d = big(t.getDuration()); | final BigDecimal d = big(t.getDuration()); | ||||
if (longest == null || longest.compareTo(d) < 0) longest = d; | if (longest == null || longest.compareTo(d) < 0) longest = d; | ||||
@@ -67,10 +85,10 @@ public class JMediaInfo { | |||||
} | } | ||||
public BigDecimal width() { | public BigDecimal width() { | ||||
if (media == null || empty(media.getTrack())) return BigDecimal.ZERO; | |||||
if (media == null || empty(media.getTrack())) return ZERO; | |||||
// find the first video track | // find the first video track | ||||
for (JTrack t : media.getTrack()) { | for (JTrack t : media.getTrack()) { | ||||
if (!t.video()) continue; | |||||
if (!t.imageOrVideo()) continue; | |||||
if (!t.hasWidth()) continue; | if (!t.hasWidth()) continue; | ||||
return big(t.getWidth()); | return big(t.getWidth()); | ||||
} | } | ||||
@@ -78,10 +96,10 @@ public class JMediaInfo { | |||||
} | } | ||||
public BigDecimal height() { | public BigDecimal height() { | ||||
if (media == null || empty(media.getTrack())) return BigDecimal.ZERO; | |||||
if (media == null || empty(media.getTrack())) return ZERO; | |||||
// find the first video track | // find the first video track | ||||
for (JTrack t : media.getTrack()) { | for (JTrack t : media.getTrack()) { | ||||
if (!t.video()) continue; | |||||
if (!t.imageOrVideo()) continue; | |||||
if (!t.hasHeight()) continue; | if (!t.hasHeight()) continue; | ||||
return big(t.getHeight()); | return big(t.getHeight()); | ||||
} | } | ||||
@@ -20,13 +20,18 @@ public class JTrack { | |||||
} | } | ||||
public boolean audio() { return type() == JTrackType.audio; } | public boolean audio() { return type() == JTrackType.audio; } | ||||
public boolean video() { return type() == JTrackType.video; } | public boolean video() { return type() == JTrackType.video; } | ||||
public boolean media() { return audio() || video(); } | |||||
public boolean image() { return type() == JTrackType.image; } | |||||
public boolean audioOrVideo() { return audio() || video(); } | |||||
public boolean imageOrVideo() { return image() || video(); } | |||||
@JsonProperty("ID") @Getter @Setter private String id; | @JsonProperty("ID") @Getter @Setter private String id; | ||||
@JsonProperty("StreamOrder") @Getter @Setter private String streamOrder; | @JsonProperty("StreamOrder") @Getter @Setter private String streamOrder; | ||||
@JsonProperty("VideoCount") @Getter @Setter private String videoCount; | @JsonProperty("VideoCount") @Getter @Setter private String videoCount; | ||||
@JsonProperty("AudioCount") @Getter @Setter private String audioCount; | @JsonProperty("AudioCount") @Getter @Setter private String audioCount; | ||||
@JsonProperty("FileExtension") @Getter @Setter private String fileExtension; | @JsonProperty("FileExtension") @Getter @Setter private String fileExtension; | ||||
public boolean hasFileExtension () { return !empty(fileExtension); } | |||||
@JsonProperty("Format") @Getter @Setter private String format; | @JsonProperty("Format") @Getter @Setter private String format; | ||||
@JsonProperty("Format_AdditionalFeatures") @Getter @Setter private String formatAdditionalFeatures; | @JsonProperty("Format_AdditionalFeatures") @Getter @Setter private String formatAdditionalFeatures; | ||||
@JsonProperty("Format_Profile") @Getter @Setter private String formatProfile; | @JsonProperty("Format_Profile") @Getter @Setter private String formatProfile; | ||||
@@ -1,11 +1,22 @@ | |||||
package jvcl.model.info; | package jvcl.model.info; | ||||
import com.fasterxml.jackson.annotation.JsonCreator; | import com.fasterxml.jackson.annotation.JsonCreator; | ||||
import jvcl.model.JFileExtension; | |||||
import lombok.AllArgsConstructor; | |||||
@AllArgsConstructor | |||||
public enum JTrackType { | public enum JTrackType { | ||||
general, audio, video, other; | |||||
general (null), | |||||
audio (JFileExtension.flac), | |||||
video (JFileExtension.mp4), | |||||
image (JFileExtension.png), | |||||
other (null); | |||||
@JsonCreator public static JTrackType fromString(String val) { return valueOf(val.toLowerCase()); } | @JsonCreator public static JTrackType fromString(String val) { return valueOf(val.toLowerCase()); } | ||||
private final JFileExtension ext; | |||||
public JFileExtension ext() { return ext; } | |||||
} | } |
@@ -2,23 +2,44 @@ package jvcl.model.operation; | |||||
import jvcl.model.JAsset; | import jvcl.model.JAsset; | ||||
import jvcl.model.JFileExtension; | import jvcl.model.JFileExtension; | ||||
import jvcl.model.JFormat; | |||||
import jvcl.model.info.JTrackType; | |||||
import jvcl.service.AssetManager; | import jvcl.service.AssetManager; | ||||
import lombok.Getter; | import lombok.Getter; | ||||
import lombok.Setter; | import lombok.Setter; | ||||
import static jvcl.model.JAsset.json2asset; | import static jvcl.model.JAsset.json2asset; | ||||
import static jvcl.model.info.JTrackType.video; | |||||
import static org.cobbzilla.util.daemon.ZillaRuntime.die; | |||||
public class JSingleSourceOperation extends JOperation { | public class JSingleSourceOperation extends JOperation { | ||||
@Getter @Setter private String source; | @Getter @Setter private String source; | ||||
protected JTrackType outputMediaType() { return video; } | |||||
public JSingleOperationContext getSingleInputContext(AssetManager assetManager) { | public JSingleOperationContext getSingleInputContext(AssetManager assetManager) { | ||||
final JAsset source = assetManager.resolve(getSource()); | final JAsset source = assetManager.resolve(getSource()); | ||||
final JAsset output = json2asset(getCreates()); | final JAsset output = json2asset(getCreates()); | ||||
output.mergeFormat(source.getFormat()); | output.mergeFormat(source.getFormat()); | ||||
final JFileExtension formatType = output.getFormat().getFileExtension(); | |||||
// ensure output is in the correct fprmat | |||||
final JFormat format = output.getFormat(); | |||||
final JTrackType type = outputMediaType(); | |||||
if (!format.hasFileExtension() || format.getFileExtension().mediaType() != type) { | |||||
final JFileExtension ext = type.ext(); | |||||
if (ext == null) { | |||||
return die("getSingleInputContext: no file extension found for output media type: " + type); | |||||
} | |||||
format.setFileExtension(ext); | |||||
} | |||||
final JFileExtension formatType = getFileExtension(output); | |||||
return new JSingleOperationContext(source, output, formatType); | return new JSingleOperationContext(source, output, formatType); | ||||
} | } | ||||
protected JFileExtension getFileExtension(JAsset output) { | |||||
return output.getFormat().getFileExtension(); | |||||
} | |||||
} | } |
@@ -3,16 +3,65 @@ package jvcl.operation; | |||||
import jvcl.model.operation.JSingleSourceOperation; | import jvcl.model.operation.JSingleSourceOperation; | ||||
import lombok.Getter; | import lombok.Getter; | ||||
import lombok.Setter; | import lombok.Setter; | ||||
import org.cobbzilla.util.javascript.JsEngine; | |||||
import java.math.BigDecimal; | |||||
import java.util.Map; | |||||
import static java.math.BigDecimal.ZERO; | |||||
import static jvcl.service.Toolbox.evalBig; | |||||
import static org.cobbzilla.util.daemon.ZillaRuntime.big; | |||||
import static org.cobbzilla.util.daemon.ZillaRuntime.empty; | |||||
public class KenBurnsOperation extends JSingleSourceOperation { | public class KenBurnsOperation extends JSingleSourceOperation { | ||||
public static final BigDecimal DEFAULT_FPS = big(25); | |||||
public static final BigDecimal DEFAULT_UPSCALE = big(8); | |||||
@Getter @Setter private String zoom; | @Getter @Setter private String zoom; | ||||
public BigDecimal getZoom(Map<String, Object> ctx, JsEngine js) { | |||||
return evalBig(zoom, ctx, js); | |||||
} | |||||
@Getter @Setter private String duration; | @Getter @Setter private String duration; | ||||
@Getter @Setter private String width; | |||||
@Getter @Setter private String height; | |||||
@Getter @Setter private String x; | |||||
@Getter @Setter private String y; | |||||
public BigDecimal getDuration(Map<String, Object> ctx, JsEngine js) { | |||||
return evalBig(duration, ctx, js); | |||||
} | |||||
@Getter @Setter private String start; | @Getter @Setter private String start; | ||||
public boolean hasStart () { return !empty(start); } | |||||
public BigDecimal getStartTime(Map<String, Object> ctx, JsEngine js) { | |||||
return evalBig(start, ctx, js, ZERO); | |||||
} | |||||
@Getter @Setter private String end; | @Getter @Setter private String end; | ||||
public boolean hasEndTime () { return !empty(end); } | |||||
public BigDecimal getEndTime(Map<String, Object> ctx, JsEngine js, BigDecimal defaultValue) { | |||||
return evalBig(end, ctx, js, defaultValue); | |||||
} | |||||
@Getter @Setter private String x; | |||||
public boolean hasX () { return !empty(x); } | |||||
public BigDecimal getX(Map<String, Object> ctx, JsEngine js) { return evalBig(x, ctx, js); } | |||||
@Getter @Setter private String y; | |||||
public boolean hasY () { return !empty(y); } | |||||
public BigDecimal getY(Map<String, Object> ctx, JsEngine js) { return evalBig(y, ctx, js); } | |||||
@Getter @Setter private String width; | |||||
public boolean hasWidth () { return !empty(width); } | |||||
public BigDecimal getWidth(Map<String, Object> ctx, JsEngine js) { return evalBig(width, ctx, js); } | |||||
@Getter @Setter private String height; | |||||
public boolean hasHeight () { return !empty(height); } | |||||
public BigDecimal getHeight(Map<String, Object> ctx, JsEngine js) { return evalBig(height, ctx, js); } | |||||
@Getter @Setter private String fps; | |||||
public boolean hasFps () { return !empty(fps); } | |||||
public BigDecimal getFps(Map<String, Object> ctx, JsEngine js) { return evalBig(fps, ctx, js, DEFAULT_FPS); } | |||||
@Getter @Setter private String upscale; | |||||
public boolean hasUpscale () { return !empty(upscale); } | |||||
public BigDecimal getUpscale(Map<String, Object> ctx, JsEngine js) { return evalBig(fps, ctx, js, DEFAULT_UPSCALE); } | |||||
} | } |
@@ -7,12 +7,10 @@ import lombok.extern.slf4j.Slf4j; | |||||
import org.cobbzilla.util.javascript.JsEngine; | import org.cobbzilla.util.javascript.JsEngine; | ||||
import java.math.BigDecimal; | import java.math.BigDecimal; | ||||
import java.math.RoundingMode; | |||||
import java.util.Map; | import java.util.Map; | ||||
import static jvcl.service.Toolbox.eval; | |||||
import static jvcl.service.Toolbox.getDuration; | |||||
import static org.cobbzilla.util.daemon.ZillaRuntime.big; | |||||
import static java.math.BigDecimal.ZERO; | |||||
import static jvcl.service.Toolbox.evalBig; | |||||
import static org.cobbzilla.util.daemon.ZillaRuntime.empty; | import static org.cobbzilla.util.daemon.ZillaRuntime.empty; | ||||
@Slf4j | @Slf4j | ||||
@@ -22,12 +20,12 @@ public class OverlayOperation extends JSingleSourceOperation { | |||||
@Getter @Setter private String start; | @Getter @Setter private String start; | ||||
public BigDecimal getStartTime(Map<String, Object> ctx, JsEngine js) { | public BigDecimal getStartTime(Map<String, Object> ctx, JsEngine js) { | ||||
return empty(start) ? BigDecimal.ZERO : getDuration(eval(start, ctx, js)); | |||||
return evalBig(start, ctx, js, ZERO); | |||||
} | } | ||||
@Getter @Setter private String end; | @Getter @Setter private String end; | ||||
public BigDecimal getEndTime(Map<String, Object> ctx, JsEngine js) { | public BigDecimal getEndTime(Map<String, Object> ctx, JsEngine js) { | ||||
return empty(end) ? BigDecimal.ZERO : getDuration(eval(end, ctx, js)); | |||||
return evalBig(end, ctx, js); | |||||
} | } | ||||
public static class OverlayConfig { | public static class OverlayConfig { | ||||
@@ -35,33 +33,30 @@ public class OverlayOperation extends JSingleSourceOperation { | |||||
@Getter @Setter private String start; | @Getter @Setter private String start; | ||||
public BigDecimal getStartTime(Map<String, Object> ctx, JsEngine js) { | public BigDecimal getStartTime(Map<String, Object> ctx, JsEngine js) { | ||||
return empty(start) ? BigDecimal.ZERO : getDuration(eval(start, ctx, js)); | |||||
return evalBig(start, ctx, js, ZERO); | |||||
} | } | ||||
@Getter @Setter private String end; | @Getter @Setter private String end; | ||||
public boolean hasEndTime () { return !empty(end); } | public boolean hasEndTime () { return !empty(end); } | ||||
public BigDecimal getEndTime(Map<String, Object> ctx, JsEngine js) { | public BigDecimal getEndTime(Map<String, Object> ctx, JsEngine js) { | ||||
return getDuration(eval(end, ctx, js)); | |||||
return evalBig(end, 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); } | |||||
public BigDecimal getWidth(Map<String, Object> ctx, JsEngine js) { return evalBig(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); } | |||||
public BigDecimal getHeight(Map<String, Object> ctx, JsEngine js) { return evalBig(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); } | |||||
public BigDecimal getX(Map<String, Object> ctx, JsEngine js) { return evalBig(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); } | |||||
public BigDecimal getY(Map<String, Object> ctx, JsEngine js) { return evalBig(y, ctx, js); } | |||||
public BigDecimal aspectRatio () { | |||||
return big(getWidth()).divide(big(getHeight()), RoundingMode.HALF_EVEN); | |||||
} | |||||
} | } | ||||
} | } |
@@ -5,22 +5,30 @@ import jvcl.model.operation.JSingleSourceOperation; | |||||
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.math.BigDecimal; | import java.math.BigDecimal; | ||||
import java.util.Map; | |||||
import static jvcl.service.Toolbox.getDuration; | |||||
import static org.cobbzilla.util.daemon.ZillaRuntime.empty; | |||||
import static java.math.BigDecimal.ZERO; | |||||
import static jvcl.service.Toolbox.evalBig; | |||||
@Slf4j | @Slf4j | ||||
public class SplitOperation extends JSingleSourceOperation { | public class SplitOperation extends JSingleSourceOperation { | ||||
@Getter @Setter private String interval; | @Getter @Setter private String interval; | ||||
public BigDecimal getIntervalIncr() { return getDuration(interval); } | |||||
public BigDecimal getIntervalIncr(Map<String, Object> ctx, JsEngine js) { | |||||
return evalBig(this.interval, ctx, js); | |||||
} | |||||
@Getter @Setter private String start; | @Getter @Setter private String start; | ||||
public BigDecimal getStartTime() { return empty(start) ? BigDecimal.ZERO : getDuration(start); } | |||||
public BigDecimal getStartTime(Map<String, Object> ctx, JsEngine js) { | |||||
return evalBig(start, ctx, js, ZERO); | |||||
} | |||||
@Getter @Setter private String end; | @Getter @Setter private String end; | ||||
public BigDecimal getEndTime(JAsset source) { return empty(end) ? source.duration() : getDuration(end); } | |||||
public BigDecimal getEndTime(JAsset asset, Map<String, Object> ctx, JsEngine js) { | |||||
return evalBig(end, ctx, js, asset.duration()); | |||||
} | |||||
} | } |
@@ -4,21 +4,24 @@ import jvcl.model.operation.JSingleSourceOperation; | |||||
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.math.BigDecimal; | import java.math.BigDecimal; | ||||
import java.util.Map; | |||||
import static jvcl.service.Toolbox.getDuration; | |||||
import static java.math.BigDecimal.ZERO; | |||||
import static jvcl.service.Toolbox.evalBig; | |||||
import static org.cobbzilla.util.daemon.ZillaRuntime.empty; | import static org.cobbzilla.util.daemon.ZillaRuntime.empty; | ||||
@Slf4j | @Slf4j | ||||
public class TrimOperation extends JSingleSourceOperation { | public class TrimOperation extends JSingleSourceOperation { | ||||
@Getter @Setter private String start; | @Getter @Setter private String start; | ||||
public BigDecimal getStartTime() { return empty(start) ? BigDecimal.ZERO : getDuration(start); } | |||||
public BigDecimal getStartTime(Map<String, Object> ctx, JsEngine js) { return evalBig(start, ctx, js, ZERO); } | |||||
@Getter @Setter private String end; | @Getter @Setter private String end; | ||||
public boolean hasEnd() { return !empty(end); } | public boolean hasEnd() { return !empty(end); } | ||||
public BigDecimal getEndTime() { return getDuration(end); } | |||||
public BigDecimal getEndTime(Map<String, Object> ctx, JsEngine js) { return evalBig(end, ctx, js); } | |||||
public String shortString() { return "trim_"+getStart()+(hasEnd() ? "_"+getEnd() : ""); } | public String shortString() { return "trim_"+getStart()+(hasEnd() ? "_"+getEnd() : ""); } | ||||
public String toString() { return getSource()+"_"+getStart()+(hasEnd() ? "_"+getEnd() : ""); } | public String toString() { return getSource()+"_"+getStart()+(hasEnd() ? "_"+getEnd() : ""); } | ||||
@@ -8,18 +8,36 @@ import jvcl.operation.KenBurnsOperation; | |||||
import jvcl.service.AssetManager; | import jvcl.service.AssetManager; | ||||
import jvcl.service.Toolbox; | import jvcl.service.Toolbox; | ||||
import lombok.extern.slf4j.Slf4j; | import lombok.extern.slf4j.Slf4j; | ||||
import org.cobbzilla.util.javascript.StandardJsEngine; | |||||
import java.io.File; | |||||
import java.math.BigDecimal; | |||||
import java.util.HashMap; | |||||
import java.util.Map; | |||||
import static java.math.BigDecimal.ONE; | |||||
import static java.math.BigDecimal.ZERO; | |||||
import static jvcl.service.Toolbox.TWO; | |||||
import static jvcl.service.Toolbox.divideBig; | |||||
import static org.cobbzilla.util.io.FileUtil.abs; | |||||
import static org.cobbzilla.util.system.CommandShell.execScript; | |||||
@Slf4j | @Slf4j | ||||
public class KenBurnsExec extends ExecBase<KenBurnsOperation> { | public class KenBurnsExec extends ExecBase<KenBurnsOperation> { | ||||
public static final String KEN_BURNS_TEMPLATE | public static final String KEN_BURNS_TEMPLATE | ||||
= "{{{ffmpeg}}} -i {{{source.path}}} -filter_complex \"" | = "{{{ffmpeg}}} -i {{{source.path}}} -filter_complex \"" | ||||
+ "scale={{expr width '*' 16}}x{{expr height '*' 16}}, " | |||||
+ "scale={{expr width '*' upscale}}x{{expr height '*' upscale}}, " | |||||
+ "zoompan=" | + "zoompan=" | ||||
+ "z='min(zoom*{{zoomIncrementFactor}},{{zoom}})':" | |||||
+ "d={{duration}}:" | |||||
+ "x='if(gte(zoom,{{zoom}}),x,x+{{deltaX}}/a)':" | |||||
+ "y='if(gte(zoom,{{zoom}}),y,y+{{deltaY}})':" | |||||
+ "z='{{#exists startFrame}}" | |||||
+ "if(between(in,{{startFrame}},{{endFrame}}),min(zoom+{{zoomIncrementFactor}},{{zoom}}),{{zoom}})" | |||||
+ "{{else}}" | |||||
+ "z='min(zoom+{{zoomIncrementFactor}},{{zoom}})':" | |||||
+ "{{/exists}}':" | |||||
+ "d={{totalFrames}}:" | |||||
+ "fps={{fps}}:" | |||||
+ "x='if(gte(zoom,{{zoom}}),x,x{{deltaXSign}}{{deltaX}}/a)':" | |||||
+ "y='if(gte(zoom,{{zoom}}),y,y{{deltaYSign}}{{deltaY}})':" | |||||
+ "s={{width}}x{{height}}" | + "s={{width}}x{{height}}" | ||||
+ "\" -y {{{output.path}}}"; | + "\" -y {{{output.path}}}"; | ||||
@@ -30,7 +48,58 @@ public class KenBurnsExec extends ExecBase<KenBurnsOperation> { | |||||
final JAsset output = opCtx.output; | final JAsset output = opCtx.output; | ||||
final JFileExtension formatType = opCtx.formatType; | final JFileExtension formatType = opCtx.formatType; | ||||
final File defaultOutfile = assetManager.assetPath(op, source, formatType); | |||||
final File path = resolveOutputPath(output, defaultOutfile); | |||||
if (path == null) return; | |||||
output.setPath(abs(path)); | |||||
final StandardJsEngine js = toolbox.getJs(); | |||||
final Map<String, Object> ctx = new HashMap<>(); | |||||
ctx.put("ffmpeg", toolbox.getFfmpeg()); | |||||
ctx.put("source", source); | |||||
ctx.put("output", output); | |||||
ctx.put("width", op.getWidth(ctx, js)); | |||||
ctx.put("height", op.getHeight(ctx, js)); | |||||
ctx.put("upscale", op.getUpscale(ctx, js)); | |||||
final BigDecimal fps = op.getFps(ctx, js); | |||||
final BigDecimal duration = op.getDuration(ctx, js); | |||||
ctx.put("duration", duration); | |||||
final BigDecimal start = op.getStartTime(ctx, js); | |||||
final BigDecimal end = op.getEndTime(ctx, js, duration.subtract(start)); | |||||
if (op.hasStart() || op.hasEndTime()) { | |||||
ctx.put("startFrame", start.multiply(fps).intValue()); | |||||
ctx.put("endFrame", end.multiply(fps).intValue()); | |||||
} | |||||
final BigDecimal zoom = op.getZoom(ctx, js); | |||||
final BigDecimal totalFrames = duration.multiply(fps); | |||||
final BigDecimal zoomIncrementFactor = divideBig(zoom.subtract(ONE), totalFrames); | |||||
ctx.put("zoom", zoom); | |||||
ctx.put("fps", fps.intValue()); | |||||
ctx.put("totalFrames", totalFrames.intValue()); | |||||
ctx.put("zoomIncrementFactor", zoomIncrementFactor); | |||||
final BigDecimal midX = divideBig(source.getWidth(), TWO); | |||||
final BigDecimal midY = divideBig(source.getHeight(), TWO); | |||||
final BigDecimal destX = op.hasX() ? op.getX(ctx, js) : midX; | |||||
final BigDecimal destY = op.hasY() ? op.getY(ctx, js) : midY; | |||||
final BigDecimal deltaX = divideBig(destX.subtract(midX), totalFrames); | |||||
final BigDecimal deltaY = divideBig(destY.subtract(midY), totalFrames); | |||||
ctx.put("deltaXSign", deltaX.compareTo(ZERO) < 0 ? "-" : "+"); | |||||
ctx.put("deltaX", deltaX.abs()); | |||||
ctx.put("deltaYSign", deltaY.compareTo(ZERO) < 0 ? "-" : "+"); | |||||
ctx.put("deltaY", deltaY.abs()); | |||||
final String script = renderScript(toolbox, ctx, KEN_BURNS_TEMPLATE); | |||||
log.debug("operate: running script: "+script); | |||||
final String scriptOutput = execScript(script); | |||||
log.debug("operate: command output: "+scriptOutput); | |||||
assetManager.addOperationAsset(output); | |||||
} | } | ||||
} | } |
@@ -14,8 +14,7 @@ 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 org.cobbzilla.util.daemon.ZillaRuntime.big; | |||||
import static jvcl.service.Toolbox.divideBig; | |||||
import static org.cobbzilla.util.io.FileUtil.abs; | import static org.cobbzilla.util.io.FileUtil.abs; | ||||
import static org.cobbzilla.util.system.CommandShell.execScript; | import static org.cobbzilla.util.system.CommandShell.execScript; | ||||
@@ -57,18 +56,20 @@ public class OverlayExec extends ExecBase<OverlayOperation> { | |||||
ctx.put("output", output); | ctx.put("output", output); | ||||
if (overlay.hasWidth()) { | if (overlay.hasWidth()) { | ||||
final String width = overlay.getWidth(ctx, js); | |||||
ctx.put("width", width); | |||||
final BigDecimal width = overlay.getWidth(ctx, js); | |||||
ctx.put("width", width.intValue()); | |||||
if (!overlay.hasHeight()) { | if (!overlay.hasHeight()) { | ||||
final int height = big(width).divide(overlay.aspectRatio(), HALF_EVEN).intValue(); | |||||
final BigDecimal aspectRatio = overlaySource.aspectRatio(); | |||||
final int height = divideBig(width, aspectRatio).intValue(); | |||||
ctx.put("height", height); | ctx.put("height", height); | ||||
} | } | ||||
} | } | ||||
if (overlay.hasHeight()) { | if (overlay.hasHeight()) { | ||||
final String height = overlay.getHeight(ctx, js); | |||||
ctx.put("height", height); | |||||
final BigDecimal height = overlay.getHeight(ctx, js); | |||||
ctx.put("height", height.intValue()); | |||||
if (!overlay.hasWidth()) { | if (!overlay.hasWidth()) { | ||||
final int width = big(height).multiply(overlay.aspectRatio()).intValue(); | |||||
final BigDecimal aspectRatio = overlaySource.aspectRatio(); | |||||
final int width = height.multiply(aspectRatio).intValue(); | |||||
ctx.put("width", width); | ctx.put("width", width); | ||||
} | } | ||||
} | } | ||||
@@ -7,6 +7,7 @@ import jvcl.operation.SplitOperation; | |||||
import jvcl.service.AssetManager; | import jvcl.service.AssetManager; | ||||
import jvcl.service.Toolbox; | import jvcl.service.Toolbox; | ||||
import lombok.extern.slf4j.Slf4j; | import lombok.extern.slf4j.Slf4j; | ||||
import org.cobbzilla.util.javascript.JsEngine; | |||||
import java.io.File; | import java.io.File; | ||||
import java.math.BigDecimal; | import java.math.BigDecimal; | ||||
@@ -31,12 +32,14 @@ public class SplitExec extends ExecBase<SplitOperation> { | |||||
final JAsset output = opCtx.output; | final JAsset output = opCtx.output; | ||||
final JFileExtension formatType = opCtx.formatType; | final JFileExtension formatType = opCtx.formatType; | ||||
final JsEngine js = toolbox.getJs(); | |||||
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); | ||||
final BigDecimal incr = op.getIntervalIncr(); | |||||
final BigDecimal endTime = op.getEndTime(source); | |||||
for (BigDecimal i = op.getStartTime(); | |||||
final BigDecimal incr = op.getIntervalIncr(ctx, js); | |||||
final BigDecimal endTime = op.getEndTime(source, ctx, js); | |||||
for (BigDecimal i = op.getStartTime(ctx, js); | |||||
i.compareTo(endTime) < 0; | i.compareTo(endTime) < 0; | ||||
i = i.add(incr)) { | i = i.add(incr)) { | ||||
@@ -7,6 +7,7 @@ import jvcl.operation.TrimOperation; | |||||
import jvcl.service.AssetManager; | import jvcl.service.AssetManager; | ||||
import jvcl.service.Toolbox; | import jvcl.service.Toolbox; | ||||
import lombok.extern.slf4j.Slf4j; | import lombok.extern.slf4j.Slf4j; | ||||
import org.cobbzilla.util.javascript.StandardJsEngine; | |||||
import java.io.File; | import java.io.File; | ||||
import java.math.BigDecimal; | import java.math.BigDecimal; | ||||
@@ -69,14 +70,16 @@ public class TrimExec extends ExecBase<TrimOperation> { | |||||
JAsset subOutput, | JAsset subOutput, | ||||
Toolbox toolbox, | Toolbox toolbox, | ||||
AssetManager assetManager) { | AssetManager assetManager) { | ||||
final StandardJsEngine js = toolbox.getJs(); | |||||
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", subOutput); | ctx.put("output", subOutput); | ||||
final BigDecimal startTime = op.getStartTime(); | |||||
final BigDecimal startTime = op.getStartTime(ctx, js); | |||||
ctx.put("startSeconds", startTime); | ctx.put("startSeconds", startTime); | ||||
if (op.hasEnd()) ctx.put("interval", op.getEndTime().subtract(startTime)); | |||||
if (op.hasEnd()) ctx.put("interval", op.getEndTime(ctx, js).subtract(startTime)); | |||||
final String script = renderScript(toolbox, ctx, TRIM_TEMPLATE); | final String script = renderScript(toolbox, ctx, TRIM_TEMPLATE); | ||||
log.debug("operate: running script: "+script); | log.debug("operate: running script: "+script); | ||||
@@ -18,6 +18,7 @@ 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.io.FileUtil.*; | import static org.cobbzilla.util.io.FileUtil.*; | ||||
import static org.cobbzilla.util.json.JsonUtil.*; | import static org.cobbzilla.util.json.JsonUtil.*; | ||||
@@ -28,7 +29,11 @@ public class Toolbox { | |||||
public static final Toolbox DEFAULT_TOOLBOX = new Toolbox(); | public static final Toolbox DEFAULT_TOOLBOX = new Toolbox(); | ||||
public static final BigDecimal TWO = big(2); | |||||
public static final int DIVISION_SCALE = 12; | |||||
public static final ObjectMapper JSON_MAPPER = FULL_MAPPER_ALLOW_COMMENTS; | public static final ObjectMapper JSON_MAPPER = FULL_MAPPER_ALLOW_COMMENTS; | ||||
static { JSON_MAPPER.registerModule(new JOperationModule()); } | static { JSON_MAPPER.registerModule(new JOperationModule()); } | ||||
@Getter(lazy=true) private final Handlebars handlebars = initHandlebars(); | @Getter(lazy=true) private final Handlebars handlebars = initHandlebars(); | ||||
@@ -45,10 +50,12 @@ public class Toolbox { | |||||
} | } | ||||
} | } | ||||
public static BigDecimal getDuration(String t) { | |||||
// we may want to support other time formats. | |||||
// for now everything is in seconds | |||||
return big(t); | |||||
public static BigDecimal evalBig(String val, Map<String, Object> ctx, JsEngine js) { | |||||
return big(eval(val, ctx, js)); | |||||
} | |||||
public static BigDecimal evalBig(String val, Map<String, Object> ctx, JsEngine js, BigDecimal defaultValue) { | |||||
return empty(val) ? defaultValue : evalBig(val, ctx, js); | |||||
} | } | ||||
public static Map<String, Object> jsContext(Map<String, Object> ctx) { | public static Map<String, Object> jsContext(Map<String, Object> ctx) { | ||||
@@ -64,6 +71,10 @@ public class Toolbox { | |||||
return jsCtx; | return jsCtx; | ||||
} | } | ||||
public static BigDecimal divideBig(BigDecimal numerator, BigDecimal denominator) { | |||||
return numerator.divide(denominator, DIVISION_SCALE, HALF_EVEN); | |||||
} | |||||
private Handlebars initHandlebars() { | private Handlebars initHandlebars() { | ||||
final Handlebars hbs = new Handlebars(new HandlebarsUtil(Toolbox.class.getSimpleName())); | final Handlebars hbs = new Handlebars(new HandlebarsUtil(Toolbox.class.getSimpleName())); | ||||
HandlebarsUtil.registerUtilityHelpers(hbs); | HandlebarsUtil.registerUtilityHelpers(hbs); | ||||
@@ -8,6 +8,8 @@ import jvcl.model.operation.JOperation; | |||||
import jvcl.operation.exec.ExecBase; | import jvcl.operation.exec.ExecBase; | ||||
import java.io.IOException; | import java.io.IOException; | ||||
import java.util.HashMap; | |||||
import java.util.Map; | |||||
import java.util.StringTokenizer; | import java.util.StringTokenizer; | ||||
import static com.fasterxml.jackson.databind.type.TypeBindings.emptyBindings; | import static com.fasterxml.jackson.databind.type.TypeBindings.emptyBindings; | ||||
@@ -18,13 +20,32 @@ import static org.cobbzilla.util.reflect.ReflectionUtil.forName; | |||||
public class JOperationFactory extends DeserializationProblemHandler { | public class JOperationFactory extends DeserializationProblemHandler { | ||||
@Override public JavaType handleUnknownTypeId(DeserializationContext ctxt, | |||||
private final Map<Class<? extends JOperation>, JavaType> typeCache = new HashMap<>(); | |||||
@Override public JavaType handleUnknownTypeId(DeserializationContext ctx, | |||||
JavaType baseType, | JavaType baseType, | ||||
String subTypeId, | String subTypeId, | ||||
TypeIdResolver idResolver, | TypeIdResolver idResolver, | ||||
String failureMsg) throws IOException { | String failureMsg) throws IOException { | ||||
try { | try { | ||||
return new JOperationType(getOperationClass(subTypeId), emptyBindings(), baseType, null); | |||||
final Class<? extends JOperation> opClass = (Class<? extends JOperation>) getOperationClass(subTypeId); | |||||
Class<? extends JOperation> superclass = (Class<? extends JOperation>) opClass.getSuperclass(); | |||||
JavaType base = null; | |||||
while (!superclass.equals(JOperation.class)) { | |||||
if (superclass.equals(Object.class)) { | |||||
return die("Invalid operation class, not a subclass of JOperation: "+opClass.getName()); | |||||
} | |||||
base = typeCache.computeIfAbsent(superclass, c -> JOperationType.create(c, baseType)); | |||||
superclass = (Class<? extends JOperation>) superclass.getSuperclass(); | |||||
} | |||||
if (base == null) return die("Invalid operation class, not a subclass of JOperation: "+opClass.getName()); | |||||
typeCache.computeIfAbsent(superclass, c -> JOperationType.create(c, baseType)); | |||||
return new JOperationType(opClass, emptyBindings(), base, null); | |||||
} catch (Exception e) { | } catch (Exception e) { | ||||
throw new IOException("handleUnknownTypeId: '"+subTypeId+"' is not a valid operation type: "+shortError(e)); | throw new IOException("handleUnknownTypeId: '"+subTypeId+"' is not a valid operation type: "+shortError(e)); | ||||
} | } | ||||
@@ -5,6 +5,8 @@ import com.fasterxml.jackson.databind.type.SimpleType; | |||||
import com.fasterxml.jackson.databind.type.TypeBase; | import com.fasterxml.jackson.databind.type.TypeBase; | ||||
import com.fasterxml.jackson.databind.type.TypeBindings; | import com.fasterxml.jackson.databind.type.TypeBindings; | ||||
import static com.fasterxml.jackson.databind.type.TypeBindings.emptyBindings; | |||||
public class JOperationType extends SimpleType { | public class JOperationType extends SimpleType { | ||||
protected JOperationType(Class<?> cls) { super(cls); } | protected JOperationType(Class<?> cls) { super(cls); } | ||||
@@ -23,4 +25,7 @@ public class JOperationType extends SimpleType { | |||||
super(cls, bindings, superClass, superInts, extraHash, valueHandler, typeHandler, asStatic); | super(cls, bindings, superClass, superInts, extraHash, valueHandler, typeHandler, asStatic); | ||||
} | } | ||||
public static JavaType create(Class c, JavaType baseType) { | |||||
return new JOperationType(c, emptyBindings(), baseType, null); | |||||
} | |||||
} | } |
@@ -25,6 +25,7 @@ public class BasicTest { | |||||
} | } | ||||
@Test public void testOverlay() { runSpec("tests/test_overlay.jvcl"); } | @Test public void testOverlay() { runSpec("tests/test_overlay.jvcl"); } | ||||
@Test public void testKenBurns() { runSpec("tests/test_ken_burns.jvcl"); } | |||||
private void runSpec(String specPath) { | private void runSpec(String specPath) { | ||||
try { | try { | ||||
@@ -1,2 +1,9 @@ | |||||
*.mp4 | *.mp4 | ||||
*.mkv | |||||
*.mp3 | |||||
*.aac | |||||
*.flac | |||||
*.json | *.json | ||||
*.jpg | |||||
*.jpeg | |||||
*.png |
@@ -1,7 +1,7 @@ | |||||
{ | { | ||||
"assets": [ | "assets": [ | ||||
{ | { | ||||
"name": "img1", | |||||
"name": "javelin.jpg", | |||||
"path": "https://live.staticflickr.com/65535/48159911972_01efa0e5ea_b.jpg", | "path": "https://live.staticflickr.com/65535/48159911972_01efa0e5ea_b.jpg", | ||||
"dest": "src/test/resources/sources/" | "dest": "src/test/resources/sources/" | ||||
} | } | ||||
@@ -10,7 +10,7 @@ | |||||
{ | { | ||||
"operation": "ken-burns", // name of the operation | "operation": "ken-burns", // name of the operation | ||||
"creates": "ken1", // asset it creates | "creates": "ken1", // asset it creates | ||||
"source": "img1", // source image | |||||
"source": "javelin.jpg", // source image | |||||
"zoom": "1.3", // zoom level, from 1 to 10 | "zoom": "1.3", // zoom level, from 1 to 10 | ||||
"duration": "5", // how long the resulting video will be | "duration": "5", // how long the resulting video will be | ||||
"start": "0", // when to start zooming, default is 0 | "start": "0", // when to start zooming, default is 0 | ||||
@@ -16,14 +16,14 @@ | |||||
{ | { | ||||
"operation": "trim", | "operation": "trim", | ||||
"creates": "v1", | "creates": "v1", | ||||
"trim": "vid1", | |||||
"source": "vid1", | |||||
"start": "0", | "start": "0", | ||||
"end": "60" | "end": "60" | ||||
}, | }, | ||||
{ | { | ||||
"operation": "trim", | "operation": "trim", | ||||
"creates": "v2", | "creates": "v2", | ||||
"trim": "vid2", | |||||
"source": "vid2", | |||||
"start": "10", | "start": "10", | ||||
"end": "20" | "end": "20" | ||||
}, | }, | ||||