@@ -106,6 +106,10 @@ test suite. | |||
### [add-silence](src/test/resources/tests/test_add_silence.jvc) | |||
Add a silent audio track to a video asset. | |||
### [adjust-speed](src/test/resources/tests/test_adjust_speed.jvc) | |||
Speed up or slow down a video asset. Sound can be silenced, played at | |||
regular speed, or sped up along with the video. | |||
### [concat](src/test/resources/tests/test_concat.jvc) | |||
Concatenate audio/video assets together into one asset. | |||
@@ -0,0 +1,43 @@ | |||
#!/bin/bash | |||
# | |||
# Adjust the speed of a video, optionally adjusting the audio as well. | |||
# | |||
# Usage: | |||
# | |||
# jspeed in-file out-file speed-factor [audio-speed] | |||
# | |||
# in-file : input video file | |||
# out-file : write output file here | |||
# speed-factor : factor=1 is unchanged, factor>1 is faster, factor<1 is slower | |||
# audio-speed : can be: silent (default), unchanged, or match | |||
# | |||
# Note: if audio-speed is match, then speed-factor must be between 0.5 and 100 | |||
# | |||
SCRIPT="${0}" | |||
SCRIPT_DIR="$(cd "$(dirname "${SCRIPT}")" && pwd)" | |||
. "${SCRIPT_DIR}"/jvc_common | |||
IN_FILE="${1?no video-file provided}" | |||
OUT_FILE="${2?no out-file provided}" | |||
SPEED_FACTOR="${3?no speed-factor provided}" | |||
AUDIO_SPEED="${4}" | |||
echo " | |||
{ | |||
\"assets\": [ | |||
{ \"name\": \"input\", \"path\": \"${IN_FILE}\" } | |||
], | |||
\"operations\": [ | |||
{ | |||
\"operation\": \"adjust-speed\", | |||
\"creates\": { | |||
\"name\": \"speed_adjusted\", | |||
\"dest\": \"${OUT_FILE}\" | |||
}, | |||
\"source\": \"input\", | |||
\"factor\": \"${SPEED_FACTOR}\"$(if [[ -n "${AUDIO_SPEED}" ]] ; then echo ", | |||
\"audio\": \"${AUDIO_SPEED}\"" ; fi) | |||
} | |||
] | |||
} | |||
" | "${SCRIPT_DIR}"/jvc ${JVC_OPTIONS} |
@@ -198,6 +198,16 @@ support a `comment` field, which can be used as well. | |||
"source": "v2", // main video asset | |||
"channelLayout": "stereo", // optional channel layout, usually 'mono' or 'stereo'. Default is 'stereo' | |||
"samplingRate": 48000 // optional sampling rate, in Hz. default is 48000 | |||
}, | |||
// adjust-speed example | |||
{ | |||
"operation": "adjust-speed", // name of the operation | |||
"creates": "quickened", // output asset name | |||
"source": "v2", // main video asset | |||
"factor": "4", // factor=1 is no change, factor>1 is faster, factor<1 is slower | |||
"audio": "silent" // audio: silent (default), unchanged, match | |||
// if audio is match, then factor must be between 0.5 and 100 | |||
} | |||
] | |||
} | |||
@@ -2,6 +2,8 @@ package jvc.model.operation; | |||
import jvc.model.JAsset; | |||
import jvc.model.JFileExtension; | |||
import jvc.service.AssetManager; | |||
import jvc.service.Toolbox; | |||
import lombok.NoArgsConstructor; | |||
import java.util.List; | |||
@@ -11,8 +13,12 @@ public class JMultiOperationContext extends JOperationContextBase { | |||
public List<JAsset> sources; | |||
public JMultiOperationContext(List<JAsset> sources, JAsset output, JFileExtension formatType) { | |||
super(output, formatType); | |||
public JMultiOperationContext(List<JAsset> sources, | |||
JAsset output, | |||
JFileExtension formatType, | |||
AssetManager assetManager, | |||
Toolbox toolbox) { | |||
super(output, formatType, assetManager, toolbox); | |||
this.sources = sources; | |||
} | |||
} |
@@ -3,6 +3,7 @@ package jvc.model.operation; | |||
import jvc.model.JAsset; | |||
import jvc.model.JFileExtension; | |||
import jvc.service.AssetManager; | |||
import jvc.service.Toolbox; | |||
import lombok.Getter; | |||
import lombok.Setter; | |||
@@ -17,7 +18,7 @@ public abstract class JMultiSourceOperation extends JOperation { | |||
@Getter @Setter private String[] sources; | |||
public JMultiOperationContext getMultiInputContext(AssetManager assetManager) { | |||
public JMultiOperationContext getMultiInputContext(AssetManager assetManager, Toolbox toolbox) { | |||
// validate sources | |||
final List<JAsset> sources = flattenAssetList(assetManager.resolve(getSources())); | |||
if (empty(sources)) die("operate: no sources"); | |||
@@ -31,6 +32,6 @@ public abstract class JMultiSourceOperation extends JOperation { | |||
// set the path, check if output asset already exists | |||
final JFileExtension formatType = output.getFormat().getFileExtension(); | |||
return new JMultiOperationContext(sources, output, formatType); | |||
return new JMultiOperationContext(sources, output, formatType, assetManager, toolbox); | |||
} | |||
} |
@@ -2,6 +2,8 @@ package jvc.model.operation; | |||
import jvc.model.JAsset; | |||
import jvc.model.JFileExtension; | |||
import jvc.service.AssetManager; | |||
import jvc.service.Toolbox; | |||
import lombok.AllArgsConstructor; | |||
import lombok.NoArgsConstructor; | |||
@@ -10,5 +12,7 @@ public class JOperationContextBase { | |||
public JAsset output; | |||
public JFileExtension formatType; | |||
public AssetManager assetManager; | |||
public Toolbox toolbox; | |||
} |
@@ -2,6 +2,8 @@ package jvc.model.operation; | |||
import jvc.model.JAsset; | |||
import jvc.model.JFileExtension; | |||
import jvc.service.AssetManager; | |||
import jvc.service.Toolbox; | |||
import lombok.AllArgsConstructor; | |||
import lombok.NoArgsConstructor; | |||
@@ -10,8 +12,12 @@ public class JSingleOperationContext extends JOperationContextBase { | |||
public JAsset source; | |||
public JSingleOperationContext(JAsset source, JAsset output, JFileExtension formatType) { | |||
super(output, formatType); | |||
public JSingleOperationContext(JAsset source, | |||
JAsset output, | |||
JFileExtension formatType, | |||
AssetManager assetManager, | |||
Toolbox toolbox) { | |||
super(output, formatType, assetManager, toolbox); | |||
this.source = source; | |||
} | |||
@@ -5,6 +5,7 @@ import jvc.model.JFileExtension; | |||
import jvc.model.JFormat; | |||
import jvc.model.info.JTrackType; | |||
import jvc.service.AssetManager; | |||
import jvc.service.Toolbox; | |||
import lombok.Getter; | |||
import lombok.Setter; | |||
@@ -18,7 +19,7 @@ public class JSingleSourceOperation extends JOperation { | |||
protected JTrackType outputMediaType() { return video; } | |||
public JSingleOperationContext getSingleInputContext(AssetManager assetManager) { | |||
public JSingleOperationContext getSingleInputContext(AssetManager assetManager, Toolbox toolbox) { | |||
final JAsset source = assetManager.resolve(getSource()); | |||
final JAsset output = json2asset(getCreates()); | |||
output.mergeFormat(source.getFormat()); | |||
@@ -35,7 +36,7 @@ public class JSingleSourceOperation extends JOperation { | |||
} | |||
final JFileExtension formatType = getFileExtension(source, output); | |||
return new JSingleOperationContext(source, output, formatType); | |||
return new JSingleOperationContext(source, output, formatType, assetManager, toolbox); | |||
} | |||
protected JFileExtension getFileExtension(JAsset source, JAsset output) { | |||
@@ -0,0 +1,29 @@ | |||
package jvc.operation; | |||
import com.fasterxml.jackson.annotation.JsonCreator; | |||
import jvc.model.operation.JSingleSourceOperation; | |||
import lombok.Getter; | |||
import lombok.Setter; | |||
import org.cobbzilla.util.javascript.JsEngine; | |||
import java.math.BigDecimal; | |||
import java.util.Map; | |||
import static java.math.BigDecimal.ONE; | |||
import static jvc.service.Toolbox.evalBig; | |||
public class AdjustSpeedOperation extends JSingleSourceOperation { | |||
@Getter @Setter private String factor; | |||
public BigDecimal getFactor(Map<String, Object> ctx, JsEngine js) { | |||
return evalBig(factor, ctx, js, ONE); | |||
} | |||
@Getter @Setter private AudioSpeed audio = AudioSpeed.silent; | |||
public enum AudioSpeed { | |||
silent, unchanged, match; | |||
@JsonCreator public static AudioSpeed fromString (String val) { return valueOf(val.toLowerCase()); } | |||
} | |||
} |
@@ -1,14 +1,10 @@ | |||
package jvc.operation.exec; | |||
import jvc.model.JAsset; | |||
import jvc.model.JFileExtension; | |||
import jvc.model.operation.JSingleOperationContext; | |||
import jvc.operation.AddSilenceOperation; | |||
import jvc.service.AssetManager; | |||
import jvc.service.Toolbox; | |||
import lombok.extern.slf4j.Slf4j; | |||
import java.util.HashMap; | |||
import java.util.Map; | |||
@Slf4j | |||
@@ -21,20 +17,12 @@ public class AddSilenceExec extends SingleOrMultiSourceExecBase<AddSilenceOperat | |||
@Override protected String getProcessTemplate() { return ADD_SILENCE_TEMPLATE; } | |||
@Override public void operate(AddSilenceOperation op, Toolbox toolbox, AssetManager assetManager) { | |||
final JSingleOperationContext opCtx = op.getSingleInputContext(assetManager); | |||
@Override protected void addCommandContext(AddSilenceOperation op, | |||
JSingleOperationContext opCtx, | |||
Map<String, Object> ctx) { | |||
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 JAsset silence = createSilence(op, toolbox, assetManager, source.duration(), source); | |||
final JAsset silence = createSilence(op, opCtx.toolbox, opCtx.assetManager, source.duration(), source); | |||
ctx.put("silence", silence); | |||
operate(op, toolbox, assetManager, source, output, formatType, ctx); | |||
} | |||
} |
@@ -0,0 +1,50 @@ | |||
package jvc.operation.exec; | |||
import jvc.model.operation.JSingleOperationContext; | |||
import jvc.operation.AdjustSpeedOperation; | |||
import lombok.extern.slf4j.Slf4j; | |||
import org.cobbzilla.util.javascript.StandardJsEngine; | |||
import java.math.BigDecimal; | |||
import java.util.Map; | |||
import static java.math.BigDecimal.ONE; | |||
import static java.math.RoundingMode.HALF_EVEN; | |||
import static jvc.operation.AdjustSpeedOperation.AudioSpeed.match; | |||
import static org.cobbzilla.util.daemon.ZillaRuntime.big; | |||
import static org.cobbzilla.util.daemon.ZillaRuntime.die; | |||
@Slf4j | |||
public class AdjustSpeedExec extends SingleOrMultiSourceExecBase<AdjustSpeedOperation> { | |||
public static final BigDecimal MINIMUM_ATEMPO = big(0.5); | |||
public static final BigDecimal MAXIMUM_ATEMPO = big(100); | |||
public static final String ADJUST_SPEED_TEMPLATE | |||
= "{{{ffmpeg}}} -i {{{source.path}}} " | |||
+ "-filter_complex \"" | |||
+ "[0:v]setpts={{inverseFactor}}*PTS[v]{{#if match}};[0:a]atempo={{factor}}[a]{{/if}}" | |||
+ "\" " | |||
+ "-map \"[v]\" " | |||
+ "{{#if silent}}-an{{else}}-map \"[a]\"{{/if}} " | |||
+ "-y {{{output.path}}}"; | |||
@Override protected String getProcessTemplate() { return ADJUST_SPEED_TEMPLATE; } | |||
@Override protected void addCommandContext(AdjustSpeedOperation op, | |||
JSingleOperationContext opCtx, | |||
Map<String, Object> ctx) { | |||
final StandardJsEngine js = opCtx.toolbox.getJs(); | |||
final BigDecimal factor = op.getFactor(ctx, js); | |||
ctx.put(op.getAudio().name(), true); | |||
if (op.getAudio() == match) { | |||
if (factor.compareTo(MINIMUM_ATEMPO) < 0) die("addCommandContext: atempo cannot be less than "+MINIMUM_ATEMPO); | |||
if (factor.compareTo(MAXIMUM_ATEMPO) > 0) die("addCommandContext: atempo cannot be greater than "+MAXIMUM_ATEMPO); | |||
} | |||
final BigDecimal inverseFactor = ONE.divide(factor, 8, HALF_EVEN); | |||
ctx.put("factor", factor); | |||
ctx.put("inverseFactor", inverseFactor); | |||
} | |||
} |
@@ -33,7 +33,7 @@ public class ConcatExec extends ExecBase<ConcatOperation> { | |||
@Override public void operate(ConcatOperation op, Toolbox toolbox, AssetManager assetManager) { | |||
final JMultiOperationContext opCtx = op.getMultiInputContext(assetManager); | |||
final JMultiOperationContext opCtx = op.getMultiInputContext(assetManager, toolbox); | |||
final List<JAsset> sources = opCtx.sources; | |||
final JAsset output = opCtx.output; | |||
final JFileExtension formatType = opCtx.formatType; | |||
@@ -42,6 +42,13 @@ public abstract class ExecBase<OP extends JOperation> { | |||
} | |||
} | |||
protected Map<String, Object> initialContext(Toolbox toolbox, JAsset source) { | |||
final Map<String, Object> ctx = new HashMap<>(); | |||
ctx.put("ffmpeg", toolbox.getFfmpeg()); | |||
ctx.put("source", source); | |||
return ctx; | |||
} | |||
public String exec(String script, boolean noExec) { | |||
if (noExec) { | |||
System.out.println(script); | |||
@@ -12,7 +12,6 @@ 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; | |||
@@ -42,7 +41,7 @@ public class KenBurnsExec extends ExecBase<KenBurnsOperation> { | |||
@Override public void operate(KenBurnsOperation op, Toolbox toolbox, AssetManager assetManager) { | |||
final JSingleOperationContext opCtx = op.getSingleInputContext(assetManager); | |||
final JSingleOperationContext opCtx = op.getSingleInputContext(assetManager, toolbox); | |||
final JAsset source = opCtx.source; | |||
final JAsset output = opCtx.output; | |||
final JFileExtension formatType = opCtx.formatType; | |||
@@ -53,9 +52,7 @@ public class KenBurnsExec extends ExecBase<KenBurnsOperation> { | |||
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); | |||
final Map<String, Object> ctx = initialContext(toolbox, source); | |||
ctx.put("output", output); | |||
ctx.put("width", op.getWidth(ctx, js)); | |||
ctx.put("height", op.getHeight(ctx, js)); | |||
@@ -1,15 +1,10 @@ | |||
package jvc.operation.exec; | |||
import jvc.model.JAsset; | |||
import jvc.model.JFileExtension; | |||
import jvc.model.operation.JSingleOperationContext; | |||
import jvc.operation.LetterboxOperation; | |||
import jvc.service.AssetManager; | |||
import jvc.service.Toolbox; | |||
import lombok.extern.slf4j.Slf4j; | |||
import org.cobbzilla.util.javascript.StandardJsEngine; | |||
import java.util.HashMap; | |||
import java.util.Map; | |||
import static org.cobbzilla.util.daemon.ZillaRuntime.die; | |||
@@ -32,21 +27,13 @@ public class LetterboxExec extends SingleOrMultiSourceExecBase<LetterboxOperatio | |||
@Override protected String getProcessTemplate() { return LETTERBOX_TEMPLATE; } | |||
@Override public void operate(LetterboxOperation 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 StandardJsEngine js = toolbox.getJs(); | |||
final Map<String, Object> ctx = new HashMap<>(); | |||
ctx.put("ffmpeg", toolbox.getFfmpeg()); | |||
ctx.put("source", source); | |||
@Override protected void addCommandContext(LetterboxOperation op, | |||
JSingleOperationContext opCtx, | |||
Map<String, Object> ctx) { | |||
if (!op.hasWidth() || !op.hasHeight()) { | |||
die("operate: both width and height must be set"); | |||
} | |||
final StandardJsEngine js = opCtx.toolbox.getJs(); | |||
ctx.put("width", op.getWidth(ctx, js).intValue()); | |||
ctx.put("height", op.getHeight(ctx, js).intValue()); | |||
@@ -55,8 +42,5 @@ public class LetterboxExec extends SingleOrMultiSourceExecBase<LetterboxOperatio | |||
} else { | |||
ctx.put("color", DEFAULT_LETTERBOX_COLOR); | |||
} | |||
operate(op, toolbox, assetManager, source, output, formatType, ctx); | |||
} | |||
} |
@@ -35,30 +35,18 @@ public class MergeAudioExec extends SingleOrMultiSourceExecBase<MergeAudioOperat | |||
@Override protected String getProcessTemplate() { return MERGE_AUDIO_TEMPLATE; } | |||
@Override public void operate(MergeAudioOperation 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 JAsset audio = assetManager.resolve(op.getInsert()); | |||
final StandardJsEngine js = toolbox.getJs(); | |||
final Map<String, Object> ctx = new HashMap<>(); | |||
ctx.put("ffmpeg", toolbox.getFfmpeg()); | |||
ctx.put("source", source); | |||
ctx.put("audio", audio); | |||
@Override | |||
protected void addCommandContext(MergeAudioOperation op, JSingleOperationContext opCtx, Map<String, Object> ctx) { | |||
final JAsset audio = opCtx.assetManager.resolve(op.getInsert()); | |||
final StandardJsEngine js = opCtx.toolbox.getJs(); | |||
final BigDecimal insertAt = op.getAt(ctx, js); | |||
ctx.put("start", insertAt); | |||
if (insertAt.compareTo(ZERO) > 0) { | |||
final JAsset silence = createSilence(op, toolbox, assetManager, insertAt, audio); | |||
final JAsset padded = padWithSilence(op, toolbox, assetManager, audio, silence); | |||
final JAsset silence = createSilence(op, opCtx.toolbox, opCtx.assetManager, insertAt, audio); | |||
final JAsset padded = padWithSilence(op, opCtx.toolbox, opCtx.assetManager, audio, silence); | |||
ctx.put("audio", padded); | |||
} | |||
operate(op, toolbox, assetManager, source, output, formatType, ctx); | |||
} | |||
protected JAsset padWithSilence(MergeAudioOperation op, | |||
@@ -11,7 +11,6 @@ import org.cobbzilla.util.javascript.StandardJsEngine; | |||
import java.io.File; | |||
import java.math.BigDecimal; | |||
import java.util.HashMap; | |||
import java.util.Map; | |||
import static org.cobbzilla.util.io.FileUtil.abs; | |||
@@ -27,7 +26,7 @@ public class OverlayExec extends ExecBase<OverlayOperation> { | |||
@Override public void operate(OverlayOperation op, Toolbox toolbox, AssetManager assetManager) { | |||
final JSingleOperationContext opCtx = op.getSingleInputContext(assetManager); | |||
final JSingleOperationContext opCtx = op.getSingleInputContext(assetManager, toolbox); | |||
final JAsset source = opCtx.source; | |||
final JAsset output = opCtx.output; | |||
final JFileExtension formatType = opCtx.formatType; | |||
@@ -41,9 +40,7 @@ public class OverlayExec extends ExecBase<OverlayOperation> { | |||
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); | |||
final Map<String, Object> ctx = initialContext(toolbox, source); | |||
ctx.put("overlay", overlaySource); | |||
ctx.put("mainStart", op.getStartTime(ctx, js)); | |||
@@ -1,16 +1,11 @@ | |||
package jvc.operation.exec; | |||
import jvc.model.JAsset; | |||
import jvc.model.JFileExtension; | |||
import jvc.model.JTrackId; | |||
import jvc.model.info.JTrackType; | |||
import jvc.model.operation.JSingleOperationContext; | |||
import jvc.operation.RemoveTrackOperation; | |||
import jvc.service.AssetManager; | |||
import jvc.service.Toolbox; | |||
import lombok.extern.slf4j.Slf4j; | |||
import java.util.HashMap; | |||
import java.util.Map; | |||
@Slf4j | |||
@@ -24,23 +19,13 @@ public class RemoveTrackExec extends SingleOrMultiSourceExecBase<RemoveTrackOper | |||
@Override protected String getProcessTemplate() { return REMOVE_TRACK_TEMPLATE; } | |||
@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); | |||
@Override protected void addCommandContext(RemoveTrackOperation op, | |||
JSingleOperationContext opCtx, | |||
Map<String, Object> ctx) { | |||
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); | |||
} | |||
} |
@@ -1,16 +1,12 @@ | |||
package jvc.operation.exec; | |||
import jvc.model.JAsset; | |||
import jvc.model.JFileExtension; | |||
import jvc.model.operation.JSingleOperationContext; | |||
import jvc.operation.ScaleOperation; | |||
import jvc.service.AssetManager; | |||
import jvc.service.Toolbox; | |||
import lombok.extern.slf4j.Slf4j; | |||
import org.cobbzilla.util.javascript.StandardJsEngine; | |||
import java.math.BigDecimal; | |||
import java.util.HashMap; | |||
import java.util.Map; | |||
@Slf4j | |||
@@ -23,17 +19,11 @@ public class ScaleExec extends SingleOrMultiSourceExecBase<ScaleOperation> { | |||
@Override protected String getProcessTemplate() { return SCALE_TEMPLATE; } | |||
@Override public void operate(ScaleOperation op, Toolbox toolbox, AssetManager assetManager) { | |||
final JSingleOperationContext opCtx = op.getSingleInputContext(assetManager); | |||
@Override protected void addCommandContext(ScaleOperation op, | |||
JSingleOperationContext opCtx, | |||
Map<String, Object> ctx) { | |||
final StandardJsEngine js = opCtx.toolbox.getJs(); | |||
final JAsset source = opCtx.source; | |||
final JAsset output = opCtx.output; | |||
final JFileExtension formatType = opCtx.formatType; | |||
final StandardJsEngine js = toolbox.getJs(); | |||
final Map<String, Object> ctx = new HashMap<>(); | |||
ctx.put("ffmpeg", toolbox.getFfmpeg()); | |||
if (op.hasFactor()) { | |||
final BigDecimal factor = op.getFactor(ctx, js); | |||
ctx.put("width", factor.multiply(source.getWidth()).intValue()); | |||
@@ -41,8 +31,6 @@ public class ScaleExec extends SingleOrMultiSourceExecBase<ScaleOperation> { | |||
} else { | |||
op.setProportionalWidthAndHeight(ctx, js, source); | |||
} | |||
operate(op, toolbox, assetManager, source, output, formatType, ctx); | |||
} | |||
} |
@@ -2,7 +2,8 @@ package jvc.operation.exec; | |||
import jvc.model.JAsset; | |||
import jvc.model.JFileExtension; | |||
import jvc.model.operation.JOperation; | |||
import jvc.model.operation.JSingleOperationContext; | |||
import jvc.model.operation.JSingleSourceOperation; | |||
import jvc.service.AssetManager; | |||
import jvc.service.Toolbox; | |||
import lombok.extern.slf4j.Slf4j; | |||
@@ -14,7 +15,19 @@ import static org.cobbzilla.util.daemon.ZillaRuntime.die; | |||
import static org.cobbzilla.util.io.FileUtil.*; | |||
@Slf4j | |||
public abstract class SingleOrMultiSourceExecBase<OP extends JOperation> extends ExecBase<OP> { | |||
public abstract class SingleOrMultiSourceExecBase<OP extends JSingleSourceOperation> extends ExecBase<OP> { | |||
@Override public void operate(OP op, Toolbox toolbox, AssetManager assetManager) { | |||
final JSingleOperationContext opCtx = op.getSingleInputContext(assetManager, toolbox); | |||
final JAsset source = opCtx.source; | |||
final JAsset output = opCtx.output; | |||
final JFileExtension formatType = opCtx.formatType; | |||
final Map<String, Object> ctx = initialContext(toolbox, source); | |||
addCommandContext(op, opCtx, ctx); | |||
operate(op, toolbox, assetManager, source, output, formatType, ctx); | |||
} | |||
protected void addCommandContext(OP op, JSingleOperationContext opCtx, Map<String, Object> ctx) {} | |||
protected void operate(OP op, Toolbox toolbox, AssetManager assetManager, JAsset source, JAsset output, JFileExtension formatType, Map<String, Object> ctx) { | |||
if (source.hasList()) { | |||
@@ -11,7 +11,6 @@ import org.cobbzilla.util.javascript.JsEngine; | |||
import java.io.File; | |||
import java.math.BigDecimal; | |||
import java.util.HashMap; | |||
import java.util.Map; | |||
import static org.cobbzilla.util.daemon.ZillaRuntime.die; | |||
@@ -26,15 +25,13 @@ public class SplitExec extends ExecBase<SplitOperation> { | |||
@Override public void operate(SplitOperation op, Toolbox toolbox, AssetManager assetManager) { | |||
final JSingleOperationContext opCtx = op.getSingleInputContext(assetManager); | |||
final JSingleOperationContext opCtx = op.getSingleInputContext(assetManager, toolbox); | |||
final JAsset source = opCtx.source; | |||
final JAsset output = opCtx.output; | |||
final JFileExtension formatType = opCtx.formatType; | |||
final JsEngine js = toolbox.getJs(); | |||
final Map<String, Object> ctx = new HashMap<>(); | |||
ctx.put("ffmpeg", toolbox.getFfmpeg()); | |||
ctx.put("source", source); | |||
final Map<String, Object> ctx = initialContext(toolbox, source); | |||
assetManager.addOperationArrayAsset(output); | |||
final BigDecimal incr = op.getIntervalIncr(ctx, js); | |||
@@ -1,8 +1,6 @@ | |||
package jvc.operation.exec; | |||
import jvc.model.JAsset; | |||
import jvc.model.JFileExtension; | |||
import jvc.model.operation.JSingleOperationContext; | |||
import jvc.operation.TrimOperation; | |||
import jvc.service.AssetManager; | |||
import jvc.service.Toolbox; | |||
@@ -10,7 +8,6 @@ import lombok.extern.slf4j.Slf4j; | |||
import org.cobbzilla.util.javascript.StandardJsEngine; | |||
import java.math.BigDecimal; | |||
import java.util.HashMap; | |||
import java.util.Map; | |||
@Slf4j | |||
@@ -24,19 +21,6 @@ public class TrimExec extends SingleOrMultiSourceExecBase<TrimOperation> { | |||
@Override protected String getProcessTemplate() { return TRIM_TEMPLATE; } | |||
@Override public void operate(TrimOperation 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()); | |||
operate(op, toolbox, assetManager, source, output, formatType, ctx); | |||
} | |||
@Override protected void process(Map<String, Object> ctx, | |||
TrimOperation op, | |||
JAsset source, | |||
@@ -31,6 +31,7 @@ public class BasicTest { | |||
@Test public void testRemoveTrack () { runSpec("tests/test_remove_track.jvc"); } | |||
@Test public void testMergeAudio () { runSpec("tests/test_merge_audio.jvc"); } | |||
@Test public void testAddSilence () { runSpec("tests/test_add_silence.jvc"); } | |||
@Test public void testAdjustSpeed () { runSpec("tests/test_adjust_speed.jvc"); } | |||
private void runSpec(String specPath) { | |||
try { | |||
@@ -0,0 +1,28 @@ | |||
{ | |||
"assets": [ | |||
// this US government videos is 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": "10", | |||
"end": "30" | |||
}, | |||
{ | |||
"operation": "adjust-speed", // name of the operation | |||
"creates": "quickened", // output asset name | |||
"source": "v2", // main video asset | |||
"factor": "4", // factor=1 is no change, factor>1 is faster, factor<1 is slower | |||
"audio": "silent" // audio: silent (default), unchanged, match | |||
// if audio is match, then factor must be between 0.5 and 100 | |||
} | |||
] | |||
} |