From b2c37e32b7205c4650eefe8af16a1c4702a4b578 Mon Sep 17 00:00:00 2001 From: Jonathan Cobb Date: Sat, 19 Dec 2020 07:53:40 -0500 Subject: [PATCH] add adjust-speed operation, refactor --- README.md | 4 ++ bin/jspeed | 43 ++++++++++++++++ docs/complex_example.md | 10 ++++ .../operation/JMultiOperationContext.java | 10 +++- .../operation/JMultiSourceOperation.java | 5 +- .../operation/JOperationContextBase.java | 4 ++ .../operation/JSingleOperationContext.java | 10 +++- .../operation/JSingleSourceOperation.java | 5 +- .../jvc/operation/AdjustSpeedOperation.java | 29 +++++++++++ .../jvc/operation/exec/AddSilenceExec.java | 20 ++------ .../jvc/operation/exec/AdjustSpeedExec.java | 50 +++++++++++++++++++ .../java/jvc/operation/exec/ConcatExec.java | 2 +- .../java/jvc/operation/exec/ExecBase.java | 7 +++ .../java/jvc/operation/exec/KenBurnsExec.java | 7 +-- .../jvc/operation/exec/LetterboxExec.java | 24 ++------- .../jvc/operation/exec/MergeAudioExec.java | 24 +++------ .../java/jvc/operation/exec/OverlayExec.java | 7 +-- .../jvc/operation/exec/RemoveTrackExec.java | 21 ++------ .../java/jvc/operation/exec/ScaleExec.java | 20 ++------ .../exec/SingleOrMultiSourceExecBase.java | 17 ++++++- .../java/jvc/operation/exec/SplitExec.java | 7 +-- .../java/jvc/operation/exec/TrimExec.java | 16 ------ src/test/java/jvc/test/BasicTest.java | 1 + .../resources/tests/test_adjust_speed.jvc | 28 +++++++++++ 24 files changed, 241 insertions(+), 130 deletions(-) create mode 100755 bin/jspeed create mode 100644 src/main/java/jvc/operation/AdjustSpeedOperation.java create mode 100644 src/main/java/jvc/operation/exec/AdjustSpeedExec.java create mode 100644 src/test/resources/tests/test_adjust_speed.jvc diff --git a/README.md b/README.md index c703995..7e7e925 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/bin/jspeed b/bin/jspeed new file mode 100755 index 0000000..86145b9 --- /dev/null +++ b/bin/jspeed @@ -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} diff --git a/docs/complex_example.md b/docs/complex_example.md index 2d61a47..4d37ee4 100644 --- a/docs/complex_example.md +++ b/docs/complex_example.md @@ -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 } ] } diff --git a/src/main/java/jvc/model/operation/JMultiOperationContext.java b/src/main/java/jvc/model/operation/JMultiOperationContext.java index 9f19081..798699f 100644 --- a/src/main/java/jvc/model/operation/JMultiOperationContext.java +++ b/src/main/java/jvc/model/operation/JMultiOperationContext.java @@ -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 sources; - public JMultiOperationContext(List sources, JAsset output, JFileExtension formatType) { - super(output, formatType); + public JMultiOperationContext(List sources, + JAsset output, + JFileExtension formatType, + AssetManager assetManager, + Toolbox toolbox) { + super(output, formatType, assetManager, toolbox); this.sources = sources; } } diff --git a/src/main/java/jvc/model/operation/JMultiSourceOperation.java b/src/main/java/jvc/model/operation/JMultiSourceOperation.java index 25ae381..3e43cee 100644 --- a/src/main/java/jvc/model/operation/JMultiSourceOperation.java +++ b/src/main/java/jvc/model/operation/JMultiSourceOperation.java @@ -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 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); } } diff --git a/src/main/java/jvc/model/operation/JOperationContextBase.java b/src/main/java/jvc/model/operation/JOperationContextBase.java index a612e3d..01a243b 100644 --- a/src/main/java/jvc/model/operation/JOperationContextBase.java +++ b/src/main/java/jvc/model/operation/JOperationContextBase.java @@ -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; } diff --git a/src/main/java/jvc/model/operation/JSingleOperationContext.java b/src/main/java/jvc/model/operation/JSingleOperationContext.java index df7ca77..f2ccf4c 100644 --- a/src/main/java/jvc/model/operation/JSingleOperationContext.java +++ b/src/main/java/jvc/model/operation/JSingleOperationContext.java @@ -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; } diff --git a/src/main/java/jvc/model/operation/JSingleSourceOperation.java b/src/main/java/jvc/model/operation/JSingleSourceOperation.java index 9b9daa0..227f6d7 100644 --- a/src/main/java/jvc/model/operation/JSingleSourceOperation.java +++ b/src/main/java/jvc/model/operation/JSingleSourceOperation.java @@ -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) { diff --git a/src/main/java/jvc/operation/AdjustSpeedOperation.java b/src/main/java/jvc/operation/AdjustSpeedOperation.java new file mode 100644 index 0000000..5047b32 --- /dev/null +++ b/src/main/java/jvc/operation/AdjustSpeedOperation.java @@ -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 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()); } + } + +} diff --git a/src/main/java/jvc/operation/exec/AddSilenceExec.java b/src/main/java/jvc/operation/exec/AddSilenceExec.java index 9409d4f..dbeb2e9 100644 --- a/src/main/java/jvc/operation/exec/AddSilenceExec.java +++ b/src/main/java/jvc/operation/exec/AddSilenceExec.java @@ -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 ctx) { final JAsset source = opCtx.source; - final JAsset output = opCtx.output; - final JFileExtension formatType = opCtx.formatType; - - final Map ctx = new HashMap<>(); - ctx.put("ffmpeg", toolbox.getFfmpeg()); - ctx.put("source", source); - - final 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); } } diff --git a/src/main/java/jvc/operation/exec/AdjustSpeedExec.java b/src/main/java/jvc/operation/exec/AdjustSpeedExec.java new file mode 100644 index 0000000..a3e5d66 --- /dev/null +++ b/src/main/java/jvc/operation/exec/AdjustSpeedExec.java @@ -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 { + + 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 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); + } + +} diff --git a/src/main/java/jvc/operation/exec/ConcatExec.java b/src/main/java/jvc/operation/exec/ConcatExec.java index bdd9b2a..82e5e7a 100644 --- a/src/main/java/jvc/operation/exec/ConcatExec.java +++ b/src/main/java/jvc/operation/exec/ConcatExec.java @@ -33,7 +33,7 @@ public class ConcatExec extends ExecBase { @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 sources = opCtx.sources; final JAsset output = opCtx.output; final JFileExtension formatType = opCtx.formatType; diff --git a/src/main/java/jvc/operation/exec/ExecBase.java b/src/main/java/jvc/operation/exec/ExecBase.java index 870e303..325d5f0 100644 --- a/src/main/java/jvc/operation/exec/ExecBase.java +++ b/src/main/java/jvc/operation/exec/ExecBase.java @@ -42,6 +42,13 @@ public abstract class ExecBase { } } + protected Map initialContext(Toolbox toolbox, JAsset source) { + final Map 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); diff --git a/src/main/java/jvc/operation/exec/KenBurnsExec.java b/src/main/java/jvc/operation/exec/KenBurnsExec.java index a117ef2..015da36 100644 --- a/src/main/java/jvc/operation/exec/KenBurnsExec.java +++ b/src/main/java/jvc/operation/exec/KenBurnsExec.java @@ -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 { @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 { output.setPath(abs(path)); final StandardJsEngine js = toolbox.getJs(); - final Map ctx = new HashMap<>(); - ctx.put("ffmpeg", toolbox.getFfmpeg()); - ctx.put("source", source); + final Map ctx = initialContext(toolbox, source); ctx.put("output", output); ctx.put("width", op.getWidth(ctx, js)); ctx.put("height", op.getHeight(ctx, js)); diff --git a/src/main/java/jvc/operation/exec/LetterboxExec.java b/src/main/java/jvc/operation/exec/LetterboxExec.java index 555eabb..f2444aa 100644 --- a/src/main/java/jvc/operation/exec/LetterboxExec.java +++ b/src/main/java/jvc/operation/exec/LetterboxExec.java @@ -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 ctx = new HashMap<>(); - ctx.put("ffmpeg", toolbox.getFfmpeg()); - ctx.put("source", source); - + @Override protected void addCommandContext(LetterboxOperation op, + JSingleOperationContext opCtx, + Map 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 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 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, diff --git a/src/main/java/jvc/operation/exec/OverlayExec.java b/src/main/java/jvc/operation/exec/OverlayExec.java index 8ad57aa..0945530 100644 --- a/src/main/java/jvc/operation/exec/OverlayExec.java +++ b/src/main/java/jvc/operation/exec/OverlayExec.java @@ -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 { @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 { output.setPath(abs(path)); final StandardJsEngine js = toolbox.getJs(); - final Map ctx = new HashMap<>(); - ctx.put("ffmpeg", toolbox.getFfmpeg()); - ctx.put("source", source); + final Map ctx = initialContext(toolbox, source); ctx.put("overlay", overlaySource); ctx.put("mainStart", op.getStartTime(ctx, js)); diff --git a/src/main/java/jvc/operation/exec/RemoveTrackExec.java b/src/main/java/jvc/operation/exec/RemoveTrackExec.java index 4819953..34d27b4 100644 --- a/src/main/java/jvc/operation/exec/RemoveTrackExec.java +++ b/src/main/java/jvc/operation/exec/RemoveTrackExec.java @@ -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 ctx = new HashMap<>(); - ctx.put("ffmpeg", toolbox.getFfmpeg()); - ctx.put("source", source); - + @Override protected void addCommandContext(RemoveTrackOperation op, + JSingleOperationContext opCtx, + Map 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); } } diff --git a/src/main/java/jvc/operation/exec/ScaleExec.java b/src/main/java/jvc/operation/exec/ScaleExec.java index 5f0ea02..16ae03d 100644 --- a/src/main/java/jvc/operation/exec/ScaleExec.java +++ b/src/main/java/jvc/operation/exec/ScaleExec.java @@ -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 { @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 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 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 { } else { op.setProportionalWidthAndHeight(ctx, js, source); } - - operate(op, toolbox, assetManager, source, output, formatType, ctx); } } diff --git a/src/main/java/jvc/operation/exec/SingleOrMultiSourceExecBase.java b/src/main/java/jvc/operation/exec/SingleOrMultiSourceExecBase.java index 8188770..5cb616f 100644 --- a/src/main/java/jvc/operation/exec/SingleOrMultiSourceExecBase.java +++ b/src/main/java/jvc/operation/exec/SingleOrMultiSourceExecBase.java @@ -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 extends ExecBase { +public abstract class SingleOrMultiSourceExecBase extends ExecBase { + + @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 ctx = initialContext(toolbox, source); + addCommandContext(op, opCtx, ctx); + operate(op, toolbox, assetManager, source, output, formatType, ctx); + } + + protected void addCommandContext(OP op, JSingleOperationContext opCtx, Map ctx) {} protected void operate(OP op, Toolbox toolbox, AssetManager assetManager, JAsset source, JAsset output, JFileExtension formatType, Map ctx) { if (source.hasList()) { diff --git a/src/main/java/jvc/operation/exec/SplitExec.java b/src/main/java/jvc/operation/exec/SplitExec.java index d2194df..9796ac8 100644 --- a/src/main/java/jvc/operation/exec/SplitExec.java +++ b/src/main/java/jvc/operation/exec/SplitExec.java @@ -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 { @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 ctx = new HashMap<>(); - ctx.put("ffmpeg", toolbox.getFfmpeg()); - ctx.put("source", source); + final Map ctx = initialContext(toolbox, source); assetManager.addOperationArrayAsset(output); final BigDecimal incr = op.getIntervalIncr(ctx, js); diff --git a/src/main/java/jvc/operation/exec/TrimExec.java b/src/main/java/jvc/operation/exec/TrimExec.java index ae965dd..7ae3c37 100644 --- a/src/main/java/jvc/operation/exec/TrimExec.java +++ b/src/main/java/jvc/operation/exec/TrimExec.java @@ -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 { @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 ctx = new HashMap<>(); - ctx.put("ffmpeg", toolbox.getFfmpeg()); - - operate(op, toolbox, assetManager, source, output, formatType, ctx); - } - @Override protected void process(Map ctx, TrimOperation op, JAsset source, diff --git a/src/test/java/jvc/test/BasicTest.java b/src/test/java/jvc/test/BasicTest.java index 6265fa6..d48eace 100644 --- a/src/test/java/jvc/test/BasicTest.java +++ b/src/test/java/jvc/test/BasicTest.java @@ -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 { diff --git a/src/test/resources/tests/test_adjust_speed.jvc b/src/test/resources/tests/test_adjust_speed.jvc new file mode 100644 index 0000000..127f5f9 --- /dev/null +++ b/src/test/resources/tests/test_adjust_speed.jvc @@ -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 + } + ] +}