Browse Source

add remove-track operation, refactor a bit

master
Jonathan Cobb 4 years ago
parent
commit
9bfc55abe2
18 changed files with 353 additions and 39 deletions
  1. +67
    -4
      README.md
  2. +9
    -0
      src/main/java/jvcl/model/JAsset.java
  3. +24
    -2
      src/main/java/jvcl/model/JFileExtension.java
  4. +34
    -0
      src/main/java/jvcl/model/JTrackId.java
  5. +33
    -4
      src/main/java/jvcl/model/info/JMediaInfo.java
  6. +2
    -0
      src/main/java/jvcl/model/info/JTrack.java
  7. +11
    -6
      src/main/java/jvcl/model/info/JTrackType.java
  8. +4
    -0
      src/main/java/jvcl/model/operation/JOperation.java
  9. +3
    -3
      src/main/java/jvcl/model/operation/JSingleSourceOperation.java
  10. +58
    -0
      src/main/java/jvcl/operation/RemoveTrackOperation.java
  11. +0
    -6
      src/main/java/jvcl/operation/exec/LetterboxExec.java
  12. +60
    -0
      src/main/java/jvcl/operation/exec/RemoveTrackExec.java
  13. +0
    -5
      src/main/java/jvcl/operation/exec/ScaleExec.java
  14. +2
    -1
      src/main/java/jvcl/operation/exec/SingleOrMultiSourceExecBase.java
  15. +0
    -5
      src/main/java/jvcl/operation/exec/TrimExec.java
  16. +3
    -2
      src/test/java/javicle/test/BasicTest.java
  17. +1
    -1
      src/test/resources/tests/test_concat.jvcl
  18. +42
    -0
      src/test/resources/tests/test_remove_track.jvcl

+ 67
- 4
README.md View File

@@ -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`. The above would set the `start` value to ten seconds before the end of `someAsset`.


### Supported Operations ### 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. 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. 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. 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 # Complex Example
Here is a complex example using multiple assets and operations. 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 ```json
{ {
"assets": [ "assets": [
// file -- will be referenced directory // file -- will be referenced directory
{ {
"comment": "first video, already local",
"name": "vid1", "name": "vid1",
"path": "/tmp/path/to/video1.mp4" "path": "/tmp/path/to/video1.mp4"
}, },


// URL -- will be downloaded to scratch directory and referenced from there // URL -- will be downloaded to scratch directory and referenced from there
{ {
"comment": "second video, will be downloaded",
"name": "vid2", "name": "vid2",
"path": "https://archive.org/download/gov.archives.arc.1257628/gov.archives.arc.1257628_512kb.mp4" "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 // URL -- will be downloaded to `dest` directory and referenced from there
{ {
"comment": "third video, will be downloaded",
"name": "vid3", "name": "vid3",
"path": "https://archive.org/download/gov.archives.arc.49442/gov.archives.arc.49442_512kb.mp4", "path": "https://archive.org/download/gov.archives.arc.49442/gov.archives.arc.49442_512kb.mp4",
"dest": "src/test/resources/sources/" "dest": "src/test/resources/sources/"
@@ -176,13 +188,16 @@ Here is a complex example using multiple assets and operations.


// Image URL // Image URL
{ {
"comment": "JPEG image, will be downloaded",
"name": "img1", "name": "img1",
"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/"
} }
], ],
"operations": [ "operations": [
// scale examples
{ {
"comment": "scale using explicity height x width",
"operation": "scale", // name of the operation "operation": "scale", // name of the operation
"creates": "vid2_scaled", // asset it creates "creates": "vid2_scaled", // asset it creates
"source": "vid2", // source asset "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 "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 "operation": "scale", // name of the operation
"creates": "vid2_big", // asset it creates "creates": "vid2_big", // asset it creates
"source": "vid2", // source asset "source": "vid2", // source asset
"factor": "2.2" // scale factor. if factor is set, width and height are ignored. "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 "operation": "split", // name of the operation
"creates": "vid1_split_%", // assets it creates, the '%' will be replaced with a counter "creates": "vid1_split_%", // assets it creates, the '%' will be replaced with a counter
"source": "vid1", // split this source asset "source": "vid1", // split this source asset
"interval": "10" // split every ten seconds "interval": "10" // split every ten seconds
}, },

// concat examples
{ {
"comment": "re-combine previously split assets back together",
"operation": "concat", // name of the operation "operation": "concat", // name of the operation
"creates": "recombined_vid1", // assets it creates, the '%' will be replaced with a counter "creates": "recombined_vid1", // assets it creates, the '%' will be replaced with a counter
"source": ["vid1_split"] // recombine all split assets "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 "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 "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 "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 "operation": "trim", // name of the operation
"creates": { // create multiple files, will be prefixed with `name`, store them in `dest` "creates": { // create multiple files, will be prefixed with `name`, store them in `dest`
"name": "vid1_trims", "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 "start": "1", // cropped region starts here, default is zero
"end": "6" // cropped region ends here, default is end of video "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 "operation": "overlay", // name of the operation
"creates": "overlay1", // asset it creates "creates": "overlay1", // asset it creates
"source": "combined_vid1", // main video asset "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 "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 "operation": "ken-burns", // name of the operation
"creates": "ken1", // asset it creates "creates": "ken1", // asset it creates
"source": "img1", // source image "source": "img1", // source image
@@ -256,13 +289,43 @@ Here is a complex example using multiple assets and operations.
"width": "1024", // width of output video "width": "1024", // width of output video
"height": "768" // height 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 "operation": "letterbox", // name of the operation
"creates": "boxed1", // asset it creates "creates": "boxed1", // asset it creates
"source": "ken1", // source asset "source": "ken1", // source asset
"width": "source.width * 1.5", // make it wider "width": "source.width * 1.5", // make it wider
"height": "source.height * 0.9", // and shorter "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 "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
}
} }
] ]
} }


+ 9
- 0
src/main/java/jvcl/model/JAsset.java View File

@@ -3,6 +3,8 @@ package jvcl.model;
import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import jvcl.model.info.JMediaInfo; import jvcl.model.info.JMediaInfo;
import jvcl.model.info.JTrack;
import jvcl.model.info.JTrackType;
import jvcl.service.AssetManager; import jvcl.service.AssetManager;
import jvcl.service.Toolbox; import jvcl.service.Toolbox;
import lombok.*; import lombok.*;
@@ -113,6 +115,9 @@ public class JAsset implements JsObjectView {
} }
public boolean hasInfo() { return info != null; } public boolean hasInfo() { return info != null; }


@Getter @Setter private String comment;
public boolean hasComment () { return !empty(comment); }

@Override public boolean equals(Object o) { @Override public boolean equals(Object o) {
if (this == o) return true; if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false; 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; } public BigDecimal height() { return hasInfo() ? getInfo().height() : null; }
@JsonIgnore public BigDecimal getHeight () { return height(); } @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() { public BigDecimal aspectRatio() {
final BigDecimal width = width(); final BigDecimal width = width();
final BigDecimal height = height(); final BigDecimal height = height();


+ 24
- 2
src/main/java/jvcl/model/JFileExtension.java View File

@@ -1,12 +1,14 @@
package jvcl.model; package jvcl.model;


import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonCreator;
import jvcl.model.info.JTrack;
import jvcl.model.info.JTrackType; import jvcl.model.info.JTrackType;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;


import static jvcl.model.info.JTrackType.*; import static jvcl.model.info.JTrackType.*;


@AllArgsConstructor
@AllArgsConstructor @Slf4j
public enum JFileExtension { public enum JFileExtension {


mp4 (".mp4", video), mp4 (".mp4", video),
@@ -16,7 +18,9 @@ public enum JFileExtension {
flac (".flac", audio), flac (".flac", audio),
png (".png", image), png (".png", image),
jpg (".jpg", 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()); } @JsonCreator public static JFileExtension fromString(String v) { return valueOf(v.toLowerCase()); }


@@ -31,4 +35,22 @@ public enum JFileExtension {
private final JTrackType mediaType; private final JTrackType mediaType;
public JTrackType mediaType() { return 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;
}

} }

+ 34
- 0
src/main/java/jvcl/model/JTrackId.java View File

@@ -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);
}
}

}

+ 33
- 4
src/main/java/jvcl/model/info/JMediaInfo.java View File

@@ -1,22 +1,34 @@
package jvcl.model.info; package jvcl.model.info;


import jvcl.model.JFormat;
import jvcl.model.JFileExtension; import jvcl.model.JFileExtension;
import jvcl.model.JFormat;
import lombok.Getter; import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter; import lombok.Setter;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;


import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.concurrent.atomic.AtomicReference;


import static java.math.BigDecimal.ZERO; import static java.math.BigDecimal.ZERO;
import static org.cobbzilla.util.daemon.ZillaRuntime.*; import static org.cobbzilla.util.daemon.ZillaRuntime.*;


@Slf4j
@NoArgsConstructor @Slf4j
public class JMediaInfo { public class JMediaInfo {


public JMediaInfo (JMediaInfo other, JFormat format) {
this.media = other.getMedia();
this.formatRef.set(format);
}

@Getter @Setter private JMedia media; @Getter @Setter private JMedia media;
@Getter(lazy=true) private final JFormat format = initFormat();
public boolean hasFormat () { return getFormat() != null; }

private final AtomicReference<JFormat> formatRef = new AtomicReference<>();

public JFormat getFormat () {
if (formatRef.get() == null) formatRef.set(initFormat());
return formatRef.get();
}


private JFormat initFormat () { private JFormat initFormat () {
if (media == null || empty(media.getTrack())) return null; if (media == null || empty(media.getTrack())) return null;
@@ -106,4 +118,21 @@ public class JMediaInfo {
return null; 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;
}

} }

+ 2
- 0
src/main/java/jvcl/model/info/JTrack.java View File

@@ -33,6 +33,8 @@ public class JTrack {
public boolean hasFileExtension () { return !empty(fileExtension); } public boolean hasFileExtension () { return !empty(fileExtension); }


@JsonProperty("Format") @Getter @Setter private String format; @JsonProperty("Format") @Getter @Setter private String format;
public boolean hasFormat () { return !empty(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;
@JsonProperty("Format_Level") @Getter @Setter private String formatLevel; @JsonProperty("Format_Level") @Getter @Setter private String formatLevel;


+ 11
- 6
src/main/java/jvcl/model/info/JTrackType.java View File

@@ -7,16 +7,21 @@ import lombok.AllArgsConstructor;
@AllArgsConstructor @AllArgsConstructor
public enum JTrackType { 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()); } @JsonCreator public static JTrackType fromString(String val) { return valueOf(val.toLowerCase()); }


private final JFileExtension ext; private final JFileExtension ext;

public JFileExtension ext() { return ext; } public JFileExtension ext() { return ext; }


private final String ffmpegType;
public String ffmpegType() { return ffmpegType; }
public boolean hasFfmpegType() { return ffmpegType != null; }

} }

+ 4
- 0
src/main/java/jvcl/model/operation/JOperation.java View File

@@ -14,6 +14,7 @@ import java.util.HashMap;
import java.util.Map; import java.util.Map;


import static jvcl.service.json.JOperationFactory.getOperationExecClass; 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.daemon.ZillaRuntime.hashOf;
import static org.cobbzilla.util.json.JsonUtil.json; import static org.cobbzilla.util.json.JsonUtil.json;
import static org.cobbzilla.util.reflect.ReflectionUtil.instantiate; import static org.cobbzilla.util.reflect.ReflectionUtil.instantiate;
@@ -33,6 +34,9 @@ public abstract class JOperation {
@Getter @Setter private JsonNode creates; @Getter @Setter private JsonNode creates;
@Getter @Setter private boolean noExec = false; @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) { return hash(sources, null); }


public String hash(JAsset[] sources, Object[] args) { public String hash(JAsset[] sources, Object[] args) {


+ 3
- 3
src/main/java/jvcl/model/operation/JSingleSourceOperation.java View File

@@ -23,7 +23,7 @@ public class JSingleSourceOperation extends JOperation {
final JAsset output = json2asset(getCreates()); final JAsset output = json2asset(getCreates());
output.mergeFormat(source.getFormat()); output.mergeFormat(source.getFormat());


// ensure output is in the correct fprmat
// ensure output is in the correct format
final JFormat format = output.getFormat(); final JFormat format = output.getFormat();
final JTrackType type = outputMediaType(); final JTrackType type = outputMediaType();
if (!format.hasFileExtension() || format.getFileExtension().mediaType() != type) { if (!format.hasFileExtension() || format.getFileExtension().mediaType() != type) {
@@ -33,12 +33,12 @@ public class JSingleSourceOperation extends JOperation {
} }
format.setFileExtension(ext); format.setFileExtension(ext);
} }
final JFileExtension formatType = getFileExtension(output);
final JFileExtension formatType = getFileExtension(source, output);


return new JSingleOperationContext(source, output, formatType); return new JSingleOperationContext(source, output, formatType);
} }


protected JFileExtension getFileExtension(JAsset output) {
protected JFileExtension getFileExtension(JAsset source, JAsset output) {
return output.getFormat().getFileExtension(); return output.getFormat().getFileExtension();
} }




+ 58
- 0
src/main/java/jvcl/operation/RemoveTrackOperation.java View File

@@ -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;
}

}

+ 0
- 6
src/main/java/jvcl/operation/exec/LetterboxExec.java View File

@@ -64,7 +64,6 @@ public class LetterboxExec extends SingleOrMultiSourceExecBase<LetterboxOperatio
JAsset subOutput, JAsset subOutput,
Toolbox toolbox, Toolbox toolbox,
AssetManager assetManager) { AssetManager assetManager) {

ctx.put("source", source); ctx.put("source", source);
ctx.put("output", subOutput); ctx.put("output", subOutput);
final String script = renderScript(toolbox, ctx, LETTERBOX_TEMPLATE); final String script = renderScript(toolbox, ctx, LETTERBOX_TEMPLATE);
@@ -72,11 +71,6 @@ public class LetterboxExec extends SingleOrMultiSourceExecBase<LetterboxOperatio
log.debug("operate: running script: "+script); log.debug("operate: running script: "+script);
final String scriptOutput = exec(script, op.isNoExec()); final String scriptOutput = exec(script, op.isNoExec());
log.debug("operate: command output: "+scriptOutput); log.debug("operate: command output: "+scriptOutput);
if (output == subOutput) {
assetManager.addOperationAsset(output);
} else {
assetManager.addOperationAssetSlice(output, subOutput);
}
} }


} }

+ 60
- 0
src/main/java/jvcl/operation/exec/RemoveTrackExec.java View File

@@ -0,0 +1,60 @@
package jvcl.operation.exec;

import jvcl.model.JAsset;
import jvcl.model.JFileExtension;
import jvcl.model.JTrackId;
import jvcl.model.info.JTrackType;
import jvcl.model.operation.JSingleOperationContext;
import jvcl.operation.RemoveTrackOperation;
import jvcl.service.AssetManager;
import jvcl.service.Toolbox;
import lombok.extern.slf4j.Slf4j;

import java.util.HashMap;
import java.util.Map;

@Slf4j
public class RemoveTrackExec extends SingleOrMultiSourceExecBase<RemoveTrackOperation> {

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<String, Object> 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<String, Object> 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);
}

}

+ 0
- 5
src/main/java/jvcl/operation/exec/ScaleExec.java View File

@@ -58,11 +58,6 @@ public class ScaleExec extends SingleOrMultiSourceExecBase<ScaleOperation> {
log.debug("operate: running script: "+script); log.debug("operate: running script: "+script);
final String scriptOutput = exec(script, op.isNoExec()); final String scriptOutput = exec(script, op.isNoExec());
log.debug("operate: command output: "+scriptOutput); log.debug("operate: command output: "+scriptOutput);
if (output == subOutput) {
assetManager.addOperationAsset(output);
} else {
assetManager.addOperationAssetSlice(output, subOutput);
}
} }


} }

+ 2
- 1
src/main/java/jvcl/operation/exec/SingleOrMultiSourceExecBase.java View File

@@ -37,6 +37,7 @@ public abstract class SingleOrMultiSourceExecBase<OP extends JOperation> extends
} }
subOutput.setPath(abs(outfile)); subOutput.setPath(abs(outfile));
process(ctx, op, asset, output, subOutput, toolbox, assetManager); process(ctx, op, asset, output, subOutput, toolbox, assetManager);
assetManager.addOperationAssetSlice(output, subOutput);
} }
} else { } else {
final File defaultOutfile = assetManager.assetPath(op, source, formatType); final File defaultOutfile = assetManager.assetPath(op, source, formatType);
@@ -44,6 +45,7 @@ public abstract class SingleOrMultiSourceExecBase<OP extends JOperation> extends
if (path == null) return; if (path == null) return;
output.setPath(abs(path)); output.setPath(abs(path));
process(ctx, op, source, output, output, toolbox, assetManager); process(ctx, op, source, output, output, toolbox, assetManager);
assetManager.addOperationAsset(output);
} }
} }


@@ -55,5 +57,4 @@ public abstract class SingleOrMultiSourceExecBase<OP extends JOperation> extends
Toolbox toolbox, Toolbox toolbox,
AssetManager assetManager); AssetManager assetManager);



} }

+ 0
- 5
src/main/java/jvcl/operation/exec/TrimExec.java View File

@@ -55,11 +55,6 @@ public class TrimExec extends SingleOrMultiSourceExecBase<TrimOperation> {
log.debug("operate: running script: "+script); log.debug("operate: running script: "+script);
final String scriptOutput = exec(script, op.isNoExec()); final String scriptOutput = exec(script, op.isNoExec());
log.debug("operate: command output: "+scriptOutput); log.debug("operate: command output: "+scriptOutput);
if (output == subOutput) {
assetManager.addOperationAsset(output);
} else {
assetManager.addOperationAssetSlice(output, subOutput);
}
} }


} }

+ 3
- 2
src/test/java/javicle/test/BasicTest.java View File

@@ -26,8 +26,9 @@ public class BasicTest {
runSpec("tests/test_letterbox.jvcl"); 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) { private void runSpec(String specPath) {
try { try {


+ 1
- 1
src/test/resources/tests/test_concat.jvcl View File

@@ -15,7 +15,7 @@
{ {
"operation": "concat", // name of the operation "operation": "concat", // name of the operation
"sources": ["vid1_splits[1..2]"], // concatentate these sources -- the 2nd and 3rd files only "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
} }
] ]
} }

+ 42
- 0
src/test/resources/tests/test_remove_track.jvcl View File

@@ -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
}
}
]
}

Loading…
Cancel
Save