From 9bfc55abe2fd234d6910d5b06b0c36bd25365120 Mon Sep 17 00:00:00 2001 From: Jonathan Cobb Date: Thu, 17 Dec 2020 08:04:27 -0500 Subject: [PATCH] add remove-track operation, refactor a bit --- README.md | 71 +++++++++++++++++-- src/main/java/jvcl/model/JAsset.java | 9 +++ src/main/java/jvcl/model/JFileExtension.java | 26 ++++++- src/main/java/jvcl/model/JTrackId.java | 34 +++++++++ src/main/java/jvcl/model/info/JMediaInfo.java | 37 ++++++++-- src/main/java/jvcl/model/info/JTrack.java | 2 + src/main/java/jvcl/model/info/JTrackType.java | 17 +++-- .../java/jvcl/model/operation/JOperation.java | 4 ++ .../operation/JSingleSourceOperation.java | 6 +- .../jvcl/operation/RemoveTrackOperation.java | 58 +++++++++++++++ .../jvcl/operation/exec/LetterboxExec.java | 6 -- .../jvcl/operation/exec/RemoveTrackExec.java | 60 ++++++++++++++++ .../java/jvcl/operation/exec/ScaleExec.java | 5 -- .../exec/SingleOrMultiSourceExecBase.java | 3 +- .../java/jvcl/operation/exec/TrimExec.java | 5 -- src/test/java/javicle/test/BasicTest.java | 5 +- src/test/resources/tests/test_concat.jvcl | 2 +- .../resources/tests/test_remove_track.jvcl | 42 +++++++++++ 18 files changed, 353 insertions(+), 39 deletions(-) create mode 100644 src/main/java/jvcl/model/JTrackId.java create mode 100644 src/main/java/jvcl/operation/RemoveTrackOperation.java create mode 100644 src/main/java/jvcl/operation/exec/RemoveTrackExec.java create mode 100644 src/test/resources/tests/test_remove_track.jvcl diff --git a/README.md b/README.md index d7e7b87..9329d46 100644 --- a/README.md +++ b/README.md @@ -123,7 +123,7 @@ Most of the operation settings can be JavaScript expressions, for example: The above would set the `start` value to ten seconds before the end of `someAsset`. ### Supported Operations -Today, JVCL supports seven basic operations. +Today, JVCL supports several basic operations. For each operation listed below, the header links to an example from the JVCL test suite. @@ -149,26 +149,38 @@ For transforming still images into video via a fade-pan (aka Ken Burns) effect. Transform a video in one size to another size using black letterboxes on the sides or top/bottom. Handy for embedding mobile videos into other screen formats. +### [remove-track](src/test/resources/tests/test_remove_track.jvcl) +For transforming still images into video via a fade-pan (aka Ken Burns) effect. + # Complex Example Here is a complex example using multiple assets and operations. +Note that comments, which are not usually legal in JSON, are allowed in JVCL files. + +If you have other JSON-aware tools that need to read JVLC files, you may not want to +use this comment syntax. The `asset` and `operation` JSON objects also support a `comment` +field, which can be used as well. + ```json { "assets": [ // file -- will be referenced directory { + "comment": "first video, already local", "name": "vid1", "path": "/tmp/path/to/video1.mp4" }, // URL -- will be downloaded to scratch directory and referenced from there { + "comment": "second video, will be downloaded", "name": "vid2", "path": "https://archive.org/download/gov.archives.arc.1257628/gov.archives.arc.1257628_512kb.mp4" }, // URL -- will be downloaded to `dest` directory and referenced from there { + "comment": "third video, will be downloaded", "name": "vid3", "path": "https://archive.org/download/gov.archives.arc.49442/gov.archives.arc.49442_512kb.mp4", "dest": "src/test/resources/sources/" @@ -176,13 +188,16 @@ Here is a complex example using multiple assets and operations. // Image URL { + "comment": "JPEG image, will be downloaded", "name": "img1", "path": "https://live.staticflickr.com/65535/48159911972_01efa0e5ea_b.jpg", "dest": "src/test/resources/sources/" } ], "operations": [ + // scale examples { + "comment": "scale using explicity height x width", "operation": "scale", // name of the operation "creates": "vid2_scaled", // asset it creates "source": "vid2", // source asset @@ -190,33 +205,45 @@ Here is a complex example using multiple assets and operations. "height": "768" // height of scaled asset. if omitted and width is present, height will be proportional }, { + "comment": "scale proportionally by a scale factor", "operation": "scale", // name of the operation "creates": "vid2_big", // asset it creates "source": "vid2", // source asset "factor": "2.2" // scale factor. if factor is set, width and height are ignored. }, + + // split example { + "comment": "split one asset into many", "operation": "split", // name of the operation "creates": "vid1_split_%", // assets it creates, the '%' will be replaced with a counter "source": "vid1", // split this source asset "interval": "10" // split every ten seconds }, + + // concat examples { + "comment": "re-combine previously split assets back together", "operation": "concat", // name of the operation "creates": "recombined_vid1", // assets it creates, the '%' will be replaced with a counter "source": ["vid1_split"] // recombine all split assets }, { + "comment": "append vid2 to the end of vid1 and create a new asset", "operation": "concat", // name of the operation - "creates": "combined_vid", // asset it creates, can be referenced later + "creates": "combined_vid2", // asset it creates, can be referenced later "source": ["vid1", "vid2"] // operation-specific: this says, concatenate these named assets }, { + "comment": "re-combine only some of the previously split assets", "operation": "concat", // name of the operation - "creates": "combined_vid", // the asset it creates, can be referenced later - "source": ["vid1", "vid2"] // operation-specific: this says, concatenate these named assets + "sources": ["vid1_splits[1..2]"],// concatentate these sources -- the 2nd and 3rd files only + "creates": "combined_vid3" // name of the output asset, will be written to scratch directory }, + + // trim example { + "comment": "trim all of the assets that were split above", "operation": "trim", // name of the operation "creates": { // create multiple files, will be prefixed with `name`, store them in `dest` "name": "vid1_trims", @@ -226,7 +253,10 @@ Here is a complex example using multiple assets and operations. "start": "1", // cropped region starts here, default is zero "end": "6" // cropped region ends here, default is end of video }, + + // overlay example { + "comment": "overlay one video onto another", "operation": "overlay", // name of the operation "creates": "overlay1", // asset it creates "source": "combined_vid1", // main video asset @@ -242,7 +272,10 @@ Here is a complex example using multiple assets and operations. "y": "source.height / 2" // vertical overlay position on main video. default is 0 } }, + + // ken-burns example { + "comment": "apply zoom-pan effect to image, creates video", "operation": "ken-burns", // name of the operation "creates": "ken1", // asset it creates "source": "img1", // source image @@ -256,13 +289,43 @@ Here is a complex example using multiple assets and operations. "width": "1024", // width of output video "height": "768" // height of output video }, + + // letterbox example { + "comment": "increase video size without scaling, add letterboxes as needed", "operation": "letterbox", // name of the operation "creates": "boxed1", // asset it creates "source": "ken1", // source asset "width": "source.width * 1.5", // make it wider "height": "source.height * 0.9", // and shorter "color": "AliceBlue" // default is black. can be a hex value (0xff0000 for red) or a color name from here: https://ffmpeg.org/ffmpeg-utils.html#color-syntax + }, + + // remove-track examples + { + "comment": "remove all audio tracks", + "operation": "remove-track", // name of the operation + "creates": "vid2_video_only", // name of the output asset + "source": "vid2", // main video asset + "track": "audio" // remove all audio tracks + }, + { + "comment": "remove all video tracks", + "operation": "remove-track", // name of the operation + "creates": "vid2_audio_only", // name of the output asset + "source": "vid2", // main video asset + "track": "video" // remove all video tracks + }, + { + "comment": "remove a specific audio track", + "operation": "remove-track", // name of the operation + "creates": "vid2_video_only2", // name of the output asset + "source": "vid2", // main video asset + "track": { + // only remove the first audio track + "type": "audio", // track type to remove + "number": "0" // track number to remove + } } ] } diff --git a/src/main/java/jvcl/model/JAsset.java b/src/main/java/jvcl/model/JAsset.java index bcb89a5..d1f9d29 100644 --- a/src/main/java/jvcl/model/JAsset.java +++ b/src/main/java/jvcl/model/JAsset.java @@ -3,6 +3,8 @@ package jvcl.model; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.databind.JsonNode; import jvcl.model.info.JMediaInfo; +import jvcl.model.info.JTrack; +import jvcl.model.info.JTrackType; import jvcl.service.AssetManager; import jvcl.service.Toolbox; import lombok.*; @@ -113,6 +115,9 @@ public class JAsset implements JsObjectView { } public boolean hasInfo() { return info != null; } + @Getter @Setter private String comment; + public boolean hasComment () { return !empty(comment); } + @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; @@ -153,6 +158,10 @@ public class JAsset implements JsObjectView { public BigDecimal height() { return hasInfo() ? getInfo().height() : null; } @JsonIgnore public BigDecimal getHeight () { return height(); } + public int numTracks(JTrackType type) { return hasInfo() ? getInfo().numTracks(type) : 0; } + + public JTrack firstTrack(JTrackType type) { return hasInfo() ? getInfo().firstTrack(type) : null; } + public BigDecimal aspectRatio() { final BigDecimal width = width(); final BigDecimal height = height(); diff --git a/src/main/java/jvcl/model/JFileExtension.java b/src/main/java/jvcl/model/JFileExtension.java index 0bdd9f4..65197ad 100644 --- a/src/main/java/jvcl/model/JFileExtension.java +++ b/src/main/java/jvcl/model/JFileExtension.java @@ -1,12 +1,14 @@ package jvcl.model; import com.fasterxml.jackson.annotation.JsonCreator; +import jvcl.model.info.JTrack; import jvcl.model.info.JTrackType; import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; import static jvcl.model.info.JTrackType.*; -@AllArgsConstructor +@AllArgsConstructor @Slf4j public enum JFileExtension { mp4 (".mp4", video), @@ -16,7 +18,9 @@ public enum JFileExtension { flac (".flac", audio), png (".png", image), jpg (".jpg", image), - jpeg (".jpeg", image); + jpeg (".jpeg", image), + sub (".sub", subtitle), + dat (".dat", data); @JsonCreator public static JFileExtension fromString(String v) { return valueOf(v.toLowerCase()); } @@ -31,4 +35,22 @@ public enum JFileExtension { private final JTrackType mediaType; public JTrackType mediaType() { return mediaType; } + public static JFileExtension fromTrack(JTrack track) { + if (track.hasFileExtension()) { + try { + return fromString(track.getFileExtension()); + } catch (Exception e) { + log.warn("fromTrack: unrecognized file extension: "+track.getFileExtension()); + } + } + if (track.hasFormat()) { + try { + return fromString(track.getFormat()); + } catch (Exception e) { + log.warn("fromTrack: unrecognized format: "+track.getFormat()); + } + } + return null; + } + } diff --git a/src/main/java/jvcl/model/JTrackId.java b/src/main/java/jvcl/model/JTrackId.java new file mode 100644 index 0000000..7ef23d3 --- /dev/null +++ b/src/main/java/jvcl/model/JTrackId.java @@ -0,0 +1,34 @@ +package jvcl.model; + +import com.fasterxml.jackson.databind.JsonNode; +import jvcl.model.info.JTrackType; +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.Accessors; + +import static org.cobbzilla.util.daemon.ZillaRuntime.die; +import static org.cobbzilla.util.json.JsonUtil.json; + +@Accessors(chain=true) +public class JTrackId { + + @Getter @Setter private JTrackType type; + @Getter @Setter private Integer number; + public boolean hasNumber () { return number != null; } + + public static JTrackId createTrackId (JsonNode val) { + if (val == null) return die("createTrackId: constructor val was null"); + if (val.isObject()) { + return json(json(val), JTrackId.class); + } else { + final JTrackType trackType; + try { + trackType = JTrackType.fromString(val.asText()); + } catch (Exception e) { + return die("createTrackId: not a valid track type: "+val.asText()); + } + return new JTrackId().setType(trackType); + } + } + +} diff --git a/src/main/java/jvcl/model/info/JMediaInfo.java b/src/main/java/jvcl/model/info/JMediaInfo.java index cf9bd7a..0817740 100644 --- a/src/main/java/jvcl/model/info/JMediaInfo.java +++ b/src/main/java/jvcl/model/info/JMediaInfo.java @@ -1,22 +1,34 @@ package jvcl.model.info; -import jvcl.model.JFormat; import jvcl.model.JFileExtension; +import jvcl.model.JFormat; import lombok.Getter; +import lombok.NoArgsConstructor; import lombok.Setter; import lombok.extern.slf4j.Slf4j; import java.math.BigDecimal; +import java.util.concurrent.atomic.AtomicReference; import static java.math.BigDecimal.ZERO; import static org.cobbzilla.util.daemon.ZillaRuntime.*; -@Slf4j +@NoArgsConstructor @Slf4j public class JMediaInfo { + public JMediaInfo (JMediaInfo other, JFormat format) { + this.media = other.getMedia(); + this.formatRef.set(format); + } + @Getter @Setter private JMedia media; - @Getter(lazy=true) private final JFormat format = initFormat(); - public boolean hasFormat () { return getFormat() != null; } + + private final AtomicReference formatRef = new AtomicReference<>(); + + public JFormat getFormat () { + if (formatRef.get() == null) formatRef.set(initFormat()); + return formatRef.get(); + } private JFormat initFormat () { if (media == null || empty(media.getTrack())) return null; @@ -106,4 +118,21 @@ public class JMediaInfo { return null; } + public int numTracks(JTrackType type) { + if (media == null || empty(media.getTrack())) return 0; + int count = 0; + for (JTrack t : media.getTrack()) { + if (t.type() == type) count++; + } + return count; + } + + public JTrack firstTrack(JTrackType type) { + if (media == null || empty(media.getTrack())) return null; + for (JTrack t : media.getTrack()) { + if (t.type() == type) return t; + } + return null; + } + } diff --git a/src/main/java/jvcl/model/info/JTrack.java b/src/main/java/jvcl/model/info/JTrack.java index 0d45651..9515b60 100644 --- a/src/main/java/jvcl/model/info/JTrack.java +++ b/src/main/java/jvcl/model/info/JTrack.java @@ -33,6 +33,8 @@ public class JTrack { public boolean hasFileExtension () { return !empty(fileExtension); } @JsonProperty("Format") @Getter @Setter private String format; + public boolean hasFormat () { return !empty(format); } + @JsonProperty("Format_AdditionalFeatures") @Getter @Setter private String formatAdditionalFeatures; @JsonProperty("Format_Profile") @Getter @Setter private String formatProfile; @JsonProperty("Format_Level") @Getter @Setter private String formatLevel; diff --git a/src/main/java/jvcl/model/info/JTrackType.java b/src/main/java/jvcl/model/info/JTrackType.java index 381962b..e6c95c1 100644 --- a/src/main/java/jvcl/model/info/JTrackType.java +++ b/src/main/java/jvcl/model/info/JTrackType.java @@ -7,16 +7,21 @@ import lombok.AllArgsConstructor; @AllArgsConstructor public enum JTrackType { - general (null), - audio (JFileExtension.flac), - video (JFileExtension.mp4), - image (JFileExtension.png), - other (null); + general (null, null), + audio (JFileExtension.flac, "a"), + video (JFileExtension.mp4, "v"), + image (JFileExtension.png, null), + subtitle(JFileExtension.png, "s"), + data (JFileExtension.png, "d"), + other (null, null); @JsonCreator public static JTrackType fromString(String val) { return valueOf(val.toLowerCase()); } private final JFileExtension ext; - public JFileExtension ext() { return ext; } + private final String ffmpegType; + public String ffmpegType() { return ffmpegType; } + public boolean hasFfmpegType() { return ffmpegType != null; } + } diff --git a/src/main/java/jvcl/model/operation/JOperation.java b/src/main/java/jvcl/model/operation/JOperation.java index 36c5ff9..250b8b3 100644 --- a/src/main/java/jvcl/model/operation/JOperation.java +++ b/src/main/java/jvcl/model/operation/JOperation.java @@ -14,6 +14,7 @@ import java.util.HashMap; import java.util.Map; import static jvcl.service.json.JOperationFactory.getOperationExecClass; +import static org.cobbzilla.util.daemon.ZillaRuntime.empty; import static org.cobbzilla.util.daemon.ZillaRuntime.hashOf; import static org.cobbzilla.util.json.JsonUtil.json; import static org.cobbzilla.util.reflect.ReflectionUtil.instantiate; @@ -33,6 +34,9 @@ public abstract class JOperation { @Getter @Setter private JsonNode creates; @Getter @Setter private boolean noExec = false; + @Getter @Setter private String comment; + public boolean hasComment () { return !empty(comment); } + public String hash(JAsset[] sources) { return hash(sources, null); } public String hash(JAsset[] sources, Object[] args) { diff --git a/src/main/java/jvcl/model/operation/JSingleSourceOperation.java b/src/main/java/jvcl/model/operation/JSingleSourceOperation.java index 4919f5c..ee80dce 100644 --- a/src/main/java/jvcl/model/operation/JSingleSourceOperation.java +++ b/src/main/java/jvcl/model/operation/JSingleSourceOperation.java @@ -23,7 +23,7 @@ public class JSingleSourceOperation extends JOperation { final JAsset output = json2asset(getCreates()); output.mergeFormat(source.getFormat()); - // ensure output is in the correct fprmat + // ensure output is in the correct format final JFormat format = output.getFormat(); final JTrackType type = outputMediaType(); if (!format.hasFileExtension() || format.getFileExtension().mediaType() != type) { @@ -33,12 +33,12 @@ public class JSingleSourceOperation extends JOperation { } format.setFileExtension(ext); } - final JFileExtension formatType = getFileExtension(output); + final JFileExtension formatType = getFileExtension(source, output); return new JSingleOperationContext(source, output, formatType); } - protected JFileExtension getFileExtension(JAsset output) { + protected JFileExtension getFileExtension(JAsset source, JAsset output) { return output.getFormat().getFileExtension(); } diff --git a/src/main/java/jvcl/operation/RemoveTrackOperation.java b/src/main/java/jvcl/operation/RemoveTrackOperation.java new file mode 100644 index 0000000..9466574 --- /dev/null +++ b/src/main/java/jvcl/operation/RemoveTrackOperation.java @@ -0,0 +1,58 @@ +package jvcl.operation; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.databind.JsonNode; +import jvcl.model.JAsset; +import jvcl.model.JFileExtension; +import jvcl.model.JFormat; +import jvcl.model.JTrackId; +import jvcl.model.info.JMediaInfo; +import jvcl.model.info.JTrack; +import jvcl.model.info.JTrackType; +import jvcl.model.operation.JSingleSourceOperation; +import lombok.Getter; +import lombok.Setter; + +import static jvcl.model.JTrackId.createTrackId; +import static org.cobbzilla.util.daemon.ZillaRuntime.die; + +public class RemoveTrackOperation extends JSingleSourceOperation { + + @Getter @Setter private JsonNode track; + + @JsonIgnore @Getter(lazy=true) private final JTrackId trackId = initTrackId(); + private JTrackId initTrackId() { + final JTrackId trackId = createTrackId(getTrack()); + final JTrackType trackType = trackId.getType(); + if (!trackType.hasFfmpegType()) die("initTrackType: cannot remove tracks of type "+ trackType); + return trackId; + } + + @Override protected JFileExtension getFileExtension(JAsset source, JAsset output) { + + final JTrackId trackId = getTrackId(); + final JTrackType trackType = trackId.getType(); + + // if we are removing all video tracks, the output will be an audio asset + final int trackCount = source.numTracks(trackType); + if (trackCount == 0) return die("getFileExtension: no tracks of type "+ trackType +" found in source: "+source); + + if (wouldRemoveAllVideoTracks(trackId, trackCount)) { + // find the format of the first audio track + final JTrack audio = source.firstTrack(JTrackType.audio); + if (audio == null) return die("getFileExtension: no audio tracks found!"); + final JFileExtension ext = JFileExtension.fromTrack(audio); + source.setInfo(new JMediaInfo(source.getInfo(), new JFormat().setFileExtension(ext))); + return ext; + } + + return super.getFileExtension(source, output); + } + + private boolean wouldRemoveAllVideoTracks(JTrackId trackId, int trackCount) { + if (trackId.getType() != JTrackType.video) return false; + if (!trackId.hasNumber()) return true; + return trackCount == 1; + } + +} diff --git a/src/main/java/jvcl/operation/exec/LetterboxExec.java b/src/main/java/jvcl/operation/exec/LetterboxExec.java index fb7854c..bd19c3b 100644 --- a/src/main/java/jvcl/operation/exec/LetterboxExec.java +++ b/src/main/java/jvcl/operation/exec/LetterboxExec.java @@ -64,7 +64,6 @@ public class LetterboxExec extends SingleOrMultiSourceExecBase { + + public static final String REMOVE_TRACK_TEMPLATE + = "{{{ffmpeg}}} -i {{{source.path}}} " + + "-map 0 -map -0:{{trackType}}{{#exists trackNumber}}:{{trackNumber}}{{/exists}} " + + "-c copy " + + "-y {{{output.path}}}"; + + @Override public void operate(RemoveTrackOperation op, Toolbox toolbox, AssetManager assetManager) { + + final JSingleOperationContext opCtx = op.getSingleInputContext(assetManager); + final JAsset source = opCtx.source; + final JAsset output = opCtx.output; + final JFileExtension formatType = opCtx.formatType; + + final Map ctx = new HashMap<>(); + ctx.put("ffmpeg", toolbox.getFfmpeg()); + ctx.put("source", source); + + final JTrackId trackId = op.getTrackId(); + final JTrackType trackType = trackId.getType(); + ctx.put("trackType", trackType.ffmpegType()); + if (trackId.hasNumber()) ctx.put("trackNumber", trackId.getNumber()); + + operate(op, toolbox, assetManager, source, output, formatType, ctx); + } + + @Override protected void process(Map ctx, + RemoveTrackOperation op, + JAsset source, + JAsset output, + JAsset subOutput, + Toolbox toolbox, + AssetManager assetManager) { + ctx.put("source", source); + ctx.put("output", subOutput); + final String script = renderScript(toolbox, ctx, REMOVE_TRACK_TEMPLATE); + + log.debug("operate: running script: "+script); + final String scriptOutput = exec(script, op.isNoExec()); + log.debug("operate: command output: "+scriptOutput); + } + +} diff --git a/src/main/java/jvcl/operation/exec/ScaleExec.java b/src/main/java/jvcl/operation/exec/ScaleExec.java index dd6df28..5a12264 100644 --- a/src/main/java/jvcl/operation/exec/ScaleExec.java +++ b/src/main/java/jvcl/operation/exec/ScaleExec.java @@ -58,11 +58,6 @@ public class ScaleExec extends SingleOrMultiSourceExecBase { log.debug("operate: running script: "+script); final String scriptOutput = exec(script, op.isNoExec()); log.debug("operate: command output: "+scriptOutput); - if (output == subOutput) { - assetManager.addOperationAsset(output); - } else { - assetManager.addOperationAssetSlice(output, subOutput); - } } } diff --git a/src/main/java/jvcl/operation/exec/SingleOrMultiSourceExecBase.java b/src/main/java/jvcl/operation/exec/SingleOrMultiSourceExecBase.java index d5c470b..6232974 100644 --- a/src/main/java/jvcl/operation/exec/SingleOrMultiSourceExecBase.java +++ b/src/main/java/jvcl/operation/exec/SingleOrMultiSourceExecBase.java @@ -37,6 +37,7 @@ public abstract class SingleOrMultiSourceExecBase extends } subOutput.setPath(abs(outfile)); process(ctx, op, asset, output, subOutput, toolbox, assetManager); + assetManager.addOperationAssetSlice(output, subOutput); } } else { final File defaultOutfile = assetManager.assetPath(op, source, formatType); @@ -44,6 +45,7 @@ public abstract class SingleOrMultiSourceExecBase extends if (path == null) return; output.setPath(abs(path)); process(ctx, op, source, output, output, toolbox, assetManager); + assetManager.addOperationAsset(output); } } @@ -55,5 +57,4 @@ public abstract class SingleOrMultiSourceExecBase extends Toolbox toolbox, AssetManager assetManager); - } diff --git a/src/main/java/jvcl/operation/exec/TrimExec.java b/src/main/java/jvcl/operation/exec/TrimExec.java index 0aae9ef..e8743c4 100644 --- a/src/main/java/jvcl/operation/exec/TrimExec.java +++ b/src/main/java/jvcl/operation/exec/TrimExec.java @@ -55,11 +55,6 @@ public class TrimExec extends SingleOrMultiSourceExecBase { log.debug("operate: running script: "+script); final String scriptOutput = exec(script, op.isNoExec()); log.debug("operate: command output: "+scriptOutput); - if (output == subOutput) { - assetManager.addOperationAsset(output); - } else { - assetManager.addOperationAssetSlice(output, subOutput); - } } } diff --git a/src/test/java/javicle/test/BasicTest.java b/src/test/java/javicle/test/BasicTest.java index 33edc2e..d1df878 100644 --- a/src/test/java/javicle/test/BasicTest.java +++ b/src/test/java/javicle/test/BasicTest.java @@ -26,8 +26,9 @@ public class BasicTest { runSpec("tests/test_letterbox.jvcl"); } - @Test public void testOverlay() { runSpec("tests/test_overlay.jvcl"); } - @Test public void testKenBurns() { runSpec("tests/test_ken_burns.jvcl"); } + @Test public void testOverlay () { runSpec("tests/test_overlay.jvcl"); } + @Test public void testKenBurns () { runSpec("tests/test_ken_burns.jvcl"); } + @Test public void testRemoveTrack () { runSpec("tests/test_remove_track.jvcl"); } private void runSpec(String specPath) { try { diff --git a/src/test/resources/tests/test_concat.jvcl b/src/test/resources/tests/test_concat.jvcl index 8b10e87..5c21997 100644 --- a/src/test/resources/tests/test_concat.jvcl +++ b/src/test/resources/tests/test_concat.jvcl @@ -15,7 +15,7 @@ { "operation": "concat", // name of the operation "sources": ["vid1_splits[1..2]"], // concatentate these sources -- the 2nd and 3rd files only - "creates": "combined_vid2", // name of the output asset, will be written to scratch directory + "creates": "combined_vid2" // name of the output asset, will be written to scratch directory } ] } diff --git a/src/test/resources/tests/test_remove_track.jvcl b/src/test/resources/tests/test_remove_track.jvcl new file mode 100644 index 0000000..e37d923 --- /dev/null +++ b/src/test/resources/tests/test_remove_track.jvcl @@ -0,0 +1,42 @@ +{ + "assets": [ + // this is a US government video not covered by copyright + { + "name": "vid2", + "path": "https://archive.org/download/gov.archives.arc.49442/gov.archives.arc.49442_512kb.mp4", + "dest": "src/test/resources/sources/" + } + ], + "operations": [ + // trim video first, so test runs faster + { + "operation": "trim", + "creates": "v2", + "source": "vid2", + "start": "0", + "end": "20" + }, + { + "operation": "remove-track", // name of the operation + "creates": "vid2_video_only", // name of the output asset + "source": "v2", // main video asset + "track": "audio" // remove all audio tracks + }, + { + "operation": "remove-track", // name of the operation + "creates": "vid2_audio_only", // name of the output asset + "source": "v2", // main video asset + "track": "video" // remove all video tracks + }, + { + "operation": "remove-track", // name of the operation + "creates": "vid2_video_only2", // name of the output asset + "source": "v2", // main video asset + "track": { + // only remove the first audio track + "type": "audio", // track type to remove + "number": "0" // track number to remove + } + } + ] +}