@@ -106,6 +106,10 @@ test suite. | |||||
### [add-silence](src/test/resources/tests/test_add_silence.jvc) | ### [add-silence](src/test/resources/tests/test_add_silence.jvc) | ||||
Add a silent audio track to a video asset. | 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) | ### [concat](src/test/resources/tests/test_concat.jvc) | ||||
Concatenate audio/video assets together into one asset. | 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 | "source": "v2", // main video asset | ||||
"channelLayout": "stereo", // optional channel layout, usually 'mono' or 'stereo'. Default is 'stereo' | "channelLayout": "stereo", // optional channel layout, usually 'mono' or 'stereo'. Default is 'stereo' | ||||
"samplingRate": 48000 // optional sampling rate, in Hz. default is 48000 | "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.JAsset; | ||||
import jvc.model.JFileExtension; | import jvc.model.JFileExtension; | ||||
import jvc.service.AssetManager; | |||||
import jvc.service.Toolbox; | |||||
import lombok.NoArgsConstructor; | import lombok.NoArgsConstructor; | ||||
import java.util.List; | import java.util.List; | ||||
@@ -11,8 +13,12 @@ public class JMultiOperationContext extends JOperationContextBase { | |||||
public List<JAsset> sources; | 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; | this.sources = sources; | ||||
} | } | ||||
} | } |
@@ -3,6 +3,7 @@ package jvc.model.operation; | |||||
import jvc.model.JAsset; | import jvc.model.JAsset; | ||||
import jvc.model.JFileExtension; | import jvc.model.JFileExtension; | ||||
import jvc.service.AssetManager; | import jvc.service.AssetManager; | ||||
import jvc.service.Toolbox; | |||||
import lombok.Getter; | import lombok.Getter; | ||||
import lombok.Setter; | import lombok.Setter; | ||||
@@ -17,7 +18,7 @@ public abstract class JMultiSourceOperation extends JOperation { | |||||
@Getter @Setter private String[] sources; | @Getter @Setter private String[] sources; | ||||
public JMultiOperationContext getMultiInputContext(AssetManager assetManager) { | |||||
public JMultiOperationContext getMultiInputContext(AssetManager assetManager, Toolbox toolbox) { | |||||
// validate sources | // validate sources | ||||
final List<JAsset> sources = flattenAssetList(assetManager.resolve(getSources())); | final List<JAsset> sources = flattenAssetList(assetManager.resolve(getSources())); | ||||
if (empty(sources)) die("operate: no sources"); | 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 | // set the path, check if output asset already exists | ||||
final JFileExtension formatType = output.getFormat().getFileExtension(); | 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.JAsset; | ||||
import jvc.model.JFileExtension; | import jvc.model.JFileExtension; | ||||
import jvc.service.AssetManager; | |||||
import jvc.service.Toolbox; | |||||
import lombok.AllArgsConstructor; | import lombok.AllArgsConstructor; | ||||
import lombok.NoArgsConstructor; | import lombok.NoArgsConstructor; | ||||
@@ -10,5 +12,7 @@ public class JOperationContextBase { | |||||
public JAsset output; | public JAsset output; | ||||
public JFileExtension formatType; | public JFileExtension formatType; | ||||
public AssetManager assetManager; | |||||
public Toolbox toolbox; | |||||
} | } |
@@ -2,6 +2,8 @@ package jvc.model.operation; | |||||
import jvc.model.JAsset; | import jvc.model.JAsset; | ||||
import jvc.model.JFileExtension; | import jvc.model.JFileExtension; | ||||
import jvc.service.AssetManager; | |||||
import jvc.service.Toolbox; | |||||
import lombok.AllArgsConstructor; | import lombok.AllArgsConstructor; | ||||
import lombok.NoArgsConstructor; | import lombok.NoArgsConstructor; | ||||
@@ -10,8 +12,12 @@ public class JSingleOperationContext extends JOperationContextBase { | |||||
public JAsset source; | 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; | this.source = source; | ||||
} | } | ||||
@@ -5,6 +5,7 @@ import jvc.model.JFileExtension; | |||||
import jvc.model.JFormat; | import jvc.model.JFormat; | ||||
import jvc.model.info.JTrackType; | import jvc.model.info.JTrackType; | ||||
import jvc.service.AssetManager; | import jvc.service.AssetManager; | ||||
import jvc.service.Toolbox; | |||||
import lombok.Getter; | import lombok.Getter; | ||||
import lombok.Setter; | import lombok.Setter; | ||||
@@ -18,7 +19,7 @@ public class JSingleSourceOperation extends JOperation { | |||||
protected JTrackType outputMediaType() { return video; } | 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 source = assetManager.resolve(getSource()); | ||||
final JAsset output = json2asset(getCreates()); | final JAsset output = json2asset(getCreates()); | ||||
output.mergeFormat(source.getFormat()); | output.mergeFormat(source.getFormat()); | ||||
@@ -35,7 +36,7 @@ public class JSingleSourceOperation extends JOperation { | |||||
} | } | ||||
final JFileExtension formatType = getFileExtension(source, output); | 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) { | 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; | package jvc.operation.exec; | ||||
import jvc.model.JAsset; | import jvc.model.JAsset; | ||||
import jvc.model.JFileExtension; | |||||
import jvc.model.operation.JSingleOperationContext; | import jvc.model.operation.JSingleOperationContext; | ||||
import jvc.operation.AddSilenceOperation; | import jvc.operation.AddSilenceOperation; | ||||
import jvc.service.AssetManager; | |||||
import jvc.service.Toolbox; | |||||
import lombok.extern.slf4j.Slf4j; | import lombok.extern.slf4j.Slf4j; | ||||
import java.util.HashMap; | |||||
import java.util.Map; | import java.util.Map; | ||||
@Slf4j | @Slf4j | ||||
@@ -21,20 +17,12 @@ public class AddSilenceExec extends SingleOrMultiSourceExecBase<AddSilenceOperat | |||||
@Override protected String getProcessTemplate() { return ADD_SILENCE_TEMPLATE; } | @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 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); | 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) { | @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 List<JAsset> sources = opCtx.sources; | ||||
final JAsset output = opCtx.output; | final JAsset output = opCtx.output; | ||||
final JFileExtension formatType = opCtx.formatType; | 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) { | public String exec(String script, boolean noExec) { | ||||
if (noExec) { | if (noExec) { | ||||
System.out.println(script); | System.out.println(script); | ||||
@@ -12,7 +12,6 @@ import org.cobbzilla.util.javascript.StandardJsEngine; | |||||
import java.io.File; | import java.io.File; | ||||
import java.math.BigDecimal; | import java.math.BigDecimal; | ||||
import java.util.HashMap; | |||||
import java.util.Map; | import java.util.Map; | ||||
import static java.math.BigDecimal.ONE; | 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) { | @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 source = opCtx.source; | ||||
final JAsset output = opCtx.output; | final JAsset output = opCtx.output; | ||||
final JFileExtension formatType = opCtx.formatType; | final JFileExtension formatType = opCtx.formatType; | ||||
@@ -53,9 +52,7 @@ public class KenBurnsExec extends ExecBase<KenBurnsOperation> { | |||||
output.setPath(abs(path)); | output.setPath(abs(path)); | ||||
final StandardJsEngine js = toolbox.getJs(); | 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("output", output); | ||||
ctx.put("width", op.getWidth(ctx, js)); | ctx.put("width", op.getWidth(ctx, js)); | ||||
ctx.put("height", op.getHeight(ctx, js)); | ctx.put("height", op.getHeight(ctx, js)); | ||||
@@ -1,15 +1,10 @@ | |||||
package jvc.operation.exec; | package jvc.operation.exec; | ||||
import jvc.model.JAsset; | |||||
import jvc.model.JFileExtension; | |||||
import jvc.model.operation.JSingleOperationContext; | import jvc.model.operation.JSingleOperationContext; | ||||
import jvc.operation.LetterboxOperation; | import jvc.operation.LetterboxOperation; | ||||
import jvc.service.AssetManager; | |||||
import jvc.service.Toolbox; | |||||
import lombok.extern.slf4j.Slf4j; | import lombok.extern.slf4j.Slf4j; | ||||
import org.cobbzilla.util.javascript.StandardJsEngine; | import org.cobbzilla.util.javascript.StandardJsEngine; | ||||
import java.util.HashMap; | |||||
import java.util.Map; | import java.util.Map; | ||||
import static org.cobbzilla.util.daemon.ZillaRuntime.die; | 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 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()) { | if (!op.hasWidth() || !op.hasHeight()) { | ||||
die("operate: both width and height must be set"); | 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("width", op.getWidth(ctx, js).intValue()); | ||||
ctx.put("height", op.getHeight(ctx, js).intValue()); | ctx.put("height", op.getHeight(ctx, js).intValue()); | ||||
@@ -55,8 +42,5 @@ public class LetterboxExec extends SingleOrMultiSourceExecBase<LetterboxOperatio | |||||
} else { | } else { | ||||
ctx.put("color", DEFAULT_LETTERBOX_COLOR); | 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 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); | final BigDecimal insertAt = op.getAt(ctx, js); | ||||
ctx.put("start", insertAt); | ctx.put("start", insertAt); | ||||
if (insertAt.compareTo(ZERO) > 0) { | 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); | ctx.put("audio", padded); | ||||
} | } | ||||
operate(op, toolbox, assetManager, source, output, formatType, ctx); | |||||
} | } | ||||
protected JAsset padWithSilence(MergeAudioOperation op, | protected JAsset padWithSilence(MergeAudioOperation op, | ||||
@@ -11,7 +11,6 @@ import org.cobbzilla.util.javascript.StandardJsEngine; | |||||
import java.io.File; | import java.io.File; | ||||
import java.math.BigDecimal; | import java.math.BigDecimal; | ||||
import java.util.HashMap; | |||||
import java.util.Map; | import java.util.Map; | ||||
import static org.cobbzilla.util.io.FileUtil.abs; | 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) { | @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 source = opCtx.source; | ||||
final JAsset output = opCtx.output; | final JAsset output = opCtx.output; | ||||
final JFileExtension formatType = opCtx.formatType; | final JFileExtension formatType = opCtx.formatType; | ||||
@@ -41,9 +40,7 @@ public class OverlayExec extends ExecBase<OverlayOperation> { | |||||
output.setPath(abs(path)); | output.setPath(abs(path)); | ||||
final StandardJsEngine js = toolbox.getJs(); | 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("overlay", overlaySource); | ||||
ctx.put("mainStart", op.getStartTime(ctx, js)); | ctx.put("mainStart", op.getStartTime(ctx, js)); | ||||
@@ -1,16 +1,11 @@ | |||||
package jvc.operation.exec; | package jvc.operation.exec; | ||||
import jvc.model.JAsset; | |||||
import jvc.model.JFileExtension; | |||||
import jvc.model.JTrackId; | import jvc.model.JTrackId; | ||||
import jvc.model.info.JTrackType; | import jvc.model.info.JTrackType; | ||||
import jvc.model.operation.JSingleOperationContext; | import jvc.model.operation.JSingleOperationContext; | ||||
import jvc.operation.RemoveTrackOperation; | import jvc.operation.RemoveTrackOperation; | ||||
import jvc.service.AssetManager; | |||||
import jvc.service.Toolbox; | |||||
import lombok.extern.slf4j.Slf4j; | import lombok.extern.slf4j.Slf4j; | ||||
import java.util.HashMap; | |||||
import java.util.Map; | import java.util.Map; | ||||
@Slf4j | @Slf4j | ||||
@@ -24,23 +19,13 @@ public class RemoveTrackExec extends SingleOrMultiSourceExecBase<RemoveTrackOper | |||||
@Override protected String getProcessTemplate() { return REMOVE_TRACK_TEMPLATE; } | @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 JTrackId trackId = op.getTrackId(); | ||||
final JTrackType trackType = trackId.getType(); | final JTrackType trackType = trackId.getType(); | ||||
ctx.put("trackType", trackType.ffmpegType()); | ctx.put("trackType", trackType.ffmpegType()); | ||||
if (trackId.hasNumber()) ctx.put("trackNumber", trackId.getNumber()); | if (trackId.hasNumber()) ctx.put("trackNumber", trackId.getNumber()); | ||||
operate(op, toolbox, assetManager, source, output, formatType, ctx); | |||||
} | } | ||||
} | } |
@@ -1,16 +1,12 @@ | |||||
package jvc.operation.exec; | package jvc.operation.exec; | ||||
import jvc.model.JAsset; | import jvc.model.JAsset; | ||||
import jvc.model.JFileExtension; | |||||
import jvc.model.operation.JSingleOperationContext; | import jvc.model.operation.JSingleOperationContext; | ||||
import jvc.operation.ScaleOperation; | import jvc.operation.ScaleOperation; | ||||
import jvc.service.AssetManager; | |||||
import jvc.service.Toolbox; | |||||
import lombok.extern.slf4j.Slf4j; | import lombok.extern.slf4j.Slf4j; | ||||
import org.cobbzilla.util.javascript.StandardJsEngine; | import org.cobbzilla.util.javascript.StandardJsEngine; | ||||
import java.math.BigDecimal; | import java.math.BigDecimal; | ||||
import java.util.HashMap; | |||||
import java.util.Map; | import java.util.Map; | ||||
@Slf4j | @Slf4j | ||||
@@ -23,17 +19,11 @@ public class ScaleExec extends SingleOrMultiSourceExecBase<ScaleOperation> { | |||||
@Override protected String getProcessTemplate() { return SCALE_TEMPLATE; } | @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 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()) { | if (op.hasFactor()) { | ||||
final BigDecimal factor = op.getFactor(ctx, js); | final BigDecimal factor = op.getFactor(ctx, js); | ||||
ctx.put("width", factor.multiply(source.getWidth()).intValue()); | ctx.put("width", factor.multiply(source.getWidth()).intValue()); | ||||
@@ -41,8 +31,6 @@ public class ScaleExec extends SingleOrMultiSourceExecBase<ScaleOperation> { | |||||
} else { | } else { | ||||
op.setProportionalWidthAndHeight(ctx, js, source); | 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.JAsset; | ||||
import jvc.model.JFileExtension; | 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.AssetManager; | ||||
import jvc.service.Toolbox; | import jvc.service.Toolbox; | ||||
import lombok.extern.slf4j.Slf4j; | import lombok.extern.slf4j.Slf4j; | ||||
@@ -14,7 +15,19 @@ import static org.cobbzilla.util.daemon.ZillaRuntime.die; | |||||
import static org.cobbzilla.util.io.FileUtil.*; | import static org.cobbzilla.util.io.FileUtil.*; | ||||
@Slf4j | @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) { | protected void operate(OP op, Toolbox toolbox, AssetManager assetManager, JAsset source, JAsset output, JFileExtension formatType, Map<String, Object> ctx) { | ||||
if (source.hasList()) { | if (source.hasList()) { | ||||
@@ -11,7 +11,6 @@ import org.cobbzilla.util.javascript.JsEngine; | |||||
import java.io.File; | import java.io.File; | ||||
import java.math.BigDecimal; | import java.math.BigDecimal; | ||||
import java.util.HashMap; | |||||
import java.util.Map; | import java.util.Map; | ||||
import static org.cobbzilla.util.daemon.ZillaRuntime.die; | 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) { | @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 source = opCtx.source; | ||||
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 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); | assetManager.addOperationArrayAsset(output); | ||||
final BigDecimal incr = op.getIntervalIncr(ctx, js); | final BigDecimal incr = op.getIntervalIncr(ctx, js); | ||||
@@ -1,8 +1,6 @@ | |||||
package jvc.operation.exec; | package jvc.operation.exec; | ||||
import jvc.model.JAsset; | import jvc.model.JAsset; | ||||
import jvc.model.JFileExtension; | |||||
import jvc.model.operation.JSingleOperationContext; | |||||
import jvc.operation.TrimOperation; | import jvc.operation.TrimOperation; | ||||
import jvc.service.AssetManager; | import jvc.service.AssetManager; | ||||
import jvc.service.Toolbox; | import jvc.service.Toolbox; | ||||
@@ -10,7 +8,6 @@ import lombok.extern.slf4j.Slf4j; | |||||
import org.cobbzilla.util.javascript.StandardJsEngine; | import org.cobbzilla.util.javascript.StandardJsEngine; | ||||
import java.math.BigDecimal; | import java.math.BigDecimal; | ||||
import java.util.HashMap; | |||||
import java.util.Map; | import java.util.Map; | ||||
@Slf4j | @Slf4j | ||||
@@ -24,19 +21,6 @@ public class TrimExec extends SingleOrMultiSourceExecBase<TrimOperation> { | |||||
@Override protected String getProcessTemplate() { return TRIM_TEMPLATE; } | @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, | @Override protected void process(Map<String, Object> ctx, | ||||
TrimOperation op, | TrimOperation op, | ||||
JAsset source, | JAsset source, | ||||
@@ -31,6 +31,7 @@ public class BasicTest { | |||||
@Test public void testRemoveTrack () { runSpec("tests/test_remove_track.jvc"); } | @Test public void testRemoveTrack () { runSpec("tests/test_remove_track.jvc"); } | ||||
@Test public void testMergeAudio () { runSpec("tests/test_merge_audio.jvc"); } | @Test public void testMergeAudio () { runSpec("tests/test_merge_audio.jvc"); } | ||||
@Test public void testAddSilence () { runSpec("tests/test_add_silence.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) { | private void runSpec(String specPath) { | ||||
try { | 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 | |||||
} | |||||
] | |||||
} |