diff --git a/bin/jaddsilence b/bin/jaddsilence
old mode 100644
new mode 100755
index 0723510..7cdd4b0
--- a/bin/jaddsilence
+++ b/bin/jaddsilence
@@ -4,10 +4,12 @@
#
# Usage:
#
-# jaddsilence in-file out-file
+# jaddsilence in-file out-file [channel-mode] [sampling-rate]
#
-# in-file : input video file
-# out-file : write output file here
+# in-file : input video file
+# out-file : write output file here
+# channel-mode : channel layout, usually 'mono' or 'stereo'. Default is stereo
+# sampling-rate : sampling rate, in Hz. Default is 48000
#
SCRIPT="${0}"
SCRIPT_DIR="$(cd "$(dirname "${SCRIPT}")" && pwd)"
@@ -15,6 +17,8 @@ SCRIPT_DIR="$(cd "$(dirname "${SCRIPT}")" && pwd)"
IN_FILE="${1?no video-file provided}"
OUT_FILE="${2?no out-file provided}"
+CHANNEL_LAYOUT="${3}"
+SAMPLING_RATE="${4}"
echo "
{
@@ -28,7 +32,9 @@ echo "
\"name\": \"with_silence\",
\"dest\": \"${OUT_FILE}\"
},
- \"source\": \"input\"
+ \"source\": \"input\"$(if [[ -n "${CHANNEL_LAYOUT}" ]] ; then echo ",
+ \"channelLayout\": \"${CHANNEL_LAYOUT}\"" ; fi)$(if [[ -n "${SAMPLING_RATE}" ]] ; then echo ",
+ \"samplingRate: \"${SAMPLING_RATE}\"" ; fi)
}
]
}
diff --git a/bin/jmergeaudio b/bin/jmergeaudio
old mode 100644
new mode 100755
diff --git a/docs/complex_example.md b/docs/complex_example.md
index ed38fec..2a2d916 100644
--- a/docs/complex_example.md
+++ b/docs/complex_example.md
@@ -1,11 +1,15 @@
# Complex Example
Here is a complex example using multiple assets and operations.
-Note that comments, which are not usually legal in JSON, are allowed in JVCL files.
+Note that comments, which are not usually legal in JSON, are allowed in
+JVCL files.
-If you have other JSON-aware tools that need to read JVLC files, you may not want to
-use this comment syntax. The `asset` and `operation` JSON objects also support a `comment`
-field, which can be used as well.
+If you have other JSON-aware tools that need to read JVLC files, you may not
+want to use this comment syntax. The `asset` and `operation` JSON objects also
+support a `comment` field, which can be used as well.
+
+Doug C: I promise these will always be just comments; jvcl will never
+use [comments as parsing directives or otherwise break interoperability](https://web.archive.org/web/20120507155137/https://plus.google.com/118095276221607585885/posts/RK8qyGVaGSr) (note: disable javascript to view this link)
```js
{
@@ -188,6 +192,15 @@ field, which can be used as well.
"source": "vid2", // main video asset
"insert": "bull-roar", // audio asset to insert
"at": "5" // when (on the video timeline) to start playing the audio. default is 0 (beginning)
+ },
+
+ // add-silence example
+ {
+ "operation": "add-silence", // name of the operation
+ "creates": "v2_silent", // output asset name
+ "source": "v2", // main video asset
+ "channelLayout": "stereo", // optional channel layout, usually 'mono' or 'stereo'. Default is 'stereo'
+ "samplingRate": 48000 // optional samping rate, in Hz. default is 48000
}
]
}
diff --git a/src/main/java/jvcl/model/JAsset.java b/src/main/java/jvcl/model/JAsset.java
index 932b9bb..aa7f6a7 100644
--- a/src/main/java/jvcl/model/JAsset.java
+++ b/src/main/java/jvcl/model/JAsset.java
@@ -177,6 +177,10 @@ public class JAsset implements JsObjectView {
@JsonIgnore public String getChannelLayout() { return channelLayout(); }
public boolean hasChannelLayout() { return channelLayout() != null; }
+ public JFileExtension audioExtension() { return hasInfo() ? getInfo().audioExtension() : null; }
+ @JsonIgnore public JFileExtension getAudioExtension() { return audioExtension(); }
+ public boolean hasAudioExtension() { return audioExtension() != null; }
+
public JAsset init(AssetManager assetManager, Toolbox toolbox) {
final JAsset asset = initPath(assetManager);
if (!asset.hasListAssets()) {
diff --git a/src/main/java/jvcl/model/JFileExtension.java b/src/main/java/jvcl/model/JFileExtension.java
index 2178b2d..89f6821 100644
--- a/src/main/java/jvcl/model/JFileExtension.java
+++ b/src/main/java/jvcl/model/JFileExtension.java
@@ -7,10 +7,12 @@ import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import static jvcl.model.info.JTrackType.*;
+import static org.cobbzilla.util.daemon.ZillaRuntime.die;
@AllArgsConstructor @Slf4j
public enum JFileExtension {
+ avc (".mp4", video),
mp4 (".mp4", video),
mkv (".mkv", video),
mp3 (".mp3", audio),
@@ -48,11 +50,9 @@ public enum JFileExtension {
if (track.hasFormat()) {
try {
return fromString(track.getFormat().replace(" ", "_"));
- } catch (Exception e) {
- log.warn("fromTrack: unrecognized format: "+track.getFormat());
- }
+ } catch (Exception ignored) { }
}
- return null;
+ return die("fromTrack: unrecognized file extension/format: "+track.getFileExtension()+"/"+track.getFormat());
}
}
diff --git a/src/main/java/jvcl/model/info/JMediaInfo.java b/src/main/java/jvcl/model/info/JMediaInfo.java
index c5a829b..cd904ed 100644
--- a/src/main/java/jvcl/model/info/JMediaInfo.java
+++ b/src/main/java/jvcl/model/info/JMediaInfo.java
@@ -120,6 +120,11 @@ public class JMediaInfo {
return null;
}
+ public JFileExtension audioExtension() {
+ final JTrack audio = firstTrack(JTrackType.audio);
+ return audio == null ? null : JFileExtension.fromTrack(audio);
+ }
+
public BigDecimal width() {
if (emptyMedia()) return ZERO;
// find the first video track
diff --git a/src/main/java/jvcl/model/info/JTrack.java b/src/main/java/jvcl/model/info/JTrack.java
index 11b78f5..5d98596 100644
--- a/src/main/java/jvcl/model/info/JTrack.java
+++ b/src/main/java/jvcl/model/info/JTrack.java
@@ -44,7 +44,9 @@ public class JTrack {
@JsonProperty("ChannelLayout") @Getter @Setter private String channelLayout;
public String channelLayout () {
- if (!empty(channelLayout)) return channelLayout;
+ if (!empty(channelLayout)) {
+ return channelLayout.equals("L R") ? "stereo": channelLayout;
+ }
if (!empty(channels)) {
if (isOnlyDigits(channels)) {
switch (parseInt(channels)) {
diff --git a/src/main/java/jvcl/operation/AddSilenceOperation.java b/src/main/java/jvcl/operation/AddSilenceOperation.java
index 80c1983..2b972f3 100644
--- a/src/main/java/jvcl/operation/AddSilenceOperation.java
+++ b/src/main/java/jvcl/operation/AddSilenceOperation.java
@@ -1,5 +1,15 @@
package jvcl.operation;
import jvcl.model.operation.JSingleSourceOperation;
+import lombok.Getter;
+import lombok.Setter;
-public class AddSilenceOperation extends JSingleSourceOperation {}
+public class AddSilenceOperation extends JSingleSourceOperation {
+
+ private static final String DEFAULT_CHANNEL_LAYOUT = "stereo";
+ private static final Integer DEFAULT_SAMPLING_RATE = 48000;
+
+ @Getter @Setter private String channelLayout = DEFAULT_CHANNEL_LAYOUT;
+ @Getter @Setter private Integer samplingRate = DEFAULT_SAMPLING_RATE;
+
+}
diff --git a/src/main/java/jvcl/operation/exec/AddSilenceExec.java b/src/main/java/jvcl/operation/exec/AddSilenceExec.java
index 6be65bd..4d6bcdf 100644
--- a/src/main/java/jvcl/operation/exec/AddSilenceExec.java
+++ b/src/main/java/jvcl/operation/exec/AddSilenceExec.java
@@ -14,7 +14,12 @@ import java.util.Map;
@Slf4j
public class AddSilenceExec extends SingleOrMultiSourceExecBase {
- public static final String ADD_SILENCE_TEMPLATE = "";
+ public static final String ADD_SILENCE_TEMPLATE
+ = "{{{ffmpeg}}} -i {{{source.path}}} -i {{{silence.path}}} "
+ + "-map 0:v -map 1:a -c:v copy -shortest "
+ + "-y {{{output.path}}}";
+
+ @Override protected String getProcessTemplate() { return ADD_SILENCE_TEMPLATE; }
@Override public void operate(AddSilenceOperation op, Toolbox toolbox, AssetManager assetManager) {
final JSingleOperationContext opCtx = op.getSingleInputContext(assetManager);
@@ -26,11 +31,10 @@ public class AddSilenceExec extends SingleOrMultiSourceExecBase ctx, AddSilenceOperation addSilenceOperation, JAsset source, JAsset output, JAsset asset, Toolbox toolbox, AssetManager assetManager) {
- // todo
+ operate(op, toolbox, assetManager, source, output, formatType, ctx);
}
}
diff --git a/src/main/java/jvcl/operation/exec/ExecBase.java b/src/main/java/jvcl/operation/exec/ExecBase.java
index 9c75001..bc2a3b6 100644
--- a/src/main/java/jvcl/operation/exec/ExecBase.java
+++ b/src/main/java/jvcl/operation/exec/ExecBase.java
@@ -1,6 +1,7 @@
package jvcl.operation.exec;
import jvcl.model.JAsset;
+import jvcl.model.JFileExtension;
import jvcl.model.operation.JOperation;
import jvcl.service.AssetManager;
import jvcl.service.Toolbox;
@@ -8,8 +9,11 @@ import lombok.extern.slf4j.Slf4j;
import org.cobbzilla.util.handlebars.HandlebarsUtil;
import java.io.File;
+import java.math.BigDecimal;
+import java.util.HashMap;
import java.util.Map;
+import static org.cobbzilla.util.daemon.ZillaRuntime.die;
import static org.cobbzilla.util.io.FileUtil.abs;
import static org.cobbzilla.util.io.FileUtil.basename;
import static org.cobbzilla.util.system.CommandShell.execScript;
@@ -46,4 +50,40 @@ public abstract class ExecBase {
return execScript(script);
}
}
+
+ public static final String CREATE_SILENCE_TEMPLATE
+ = "{{{ffmpeg}}} -f lavfi "
+ + "-i anullsrc=channel_layout={{channelLayout}}:sample_rate={{samplingRate}} "
+ + "-t {{duration}} "
+ + "-y {{{silence.path}}}";
+
+ protected JAsset createSilence(OP op,
+ Toolbox toolbox,
+ AssetManager assetManager,
+ BigDecimal duration,
+ JAsset asset) {
+ final Map ctx = new HashMap<>();
+ ctx.put("ffmpeg", toolbox.getFfmpeg());
+ ctx.put("duration", duration);
+
+ if (!asset.hasSamplingRate()) return die("createSilence: no sampling rate could be determined: "+asset);
+ ctx.put("samplingRate", asset.samplingRate());
+
+ if (!asset.hasChannelLayout()) return die("createSilence: no channel layout could be determined: "+asset);
+ ctx.put("channelLayout", asset.channelLayout());
+
+ final JFileExtension ext = asset.audioExtension();
+ final File silenceFile = assetManager.assetPath(op, asset, ext, new Object[]{duration});
+ final JAsset silence = new JAsset().setPath(abs(silenceFile));
+ ctx.put("silence", silence);
+
+ final String script = renderScript(toolbox, ctx, CREATE_SILENCE_TEMPLATE);
+
+ log.debug("createSilence: running script: "+script);
+ final String scriptOutput = exec(script, op.isNoExec());
+ log.debug("createSilence: command output: "+scriptOutput);
+
+ return silence;
+ }
+
}
diff --git a/src/main/java/jvcl/operation/exec/LetterboxExec.java b/src/main/java/jvcl/operation/exec/LetterboxExec.java
index bd19c3b..49919d5 100644
--- a/src/main/java/jvcl/operation/exec/LetterboxExec.java
+++ b/src/main/java/jvcl/operation/exec/LetterboxExec.java
@@ -18,6 +18,8 @@ import static org.cobbzilla.util.string.StringUtil.safeShellArg;
@Slf4j
public class LetterboxExec extends SingleOrMultiSourceExecBase {
+ public static final String DEFAULT_LETTERBOX_COLOR = "black";
+
public static final String LETTERBOX_TEMPLATE
= "{{ffmpeg}} -i {{{source.path}}} -filter_complex \""
+ "pad="
@@ -28,7 +30,7 @@ public class LetterboxExec extends SingleOrMultiSourceExecBase ctx,
- LetterboxOperation op,
- JAsset source,
- JAsset output,
- JAsset subOutput,
- Toolbox toolbox,
- AssetManager assetManager) {
- ctx.put("source", source);
- ctx.put("output", subOutput);
- final String script = renderScript(toolbox, ctx, LETTERBOX_TEMPLATE);
-
- log.debug("operate: running script: "+script);
- final String scriptOutput = exec(script, op.isNoExec());
- log.debug("operate: command output: "+scriptOutput);
- }
-
}
diff --git a/src/main/java/jvcl/operation/exec/MergeAudioExec.java b/src/main/java/jvcl/operation/exec/MergeAudioExec.java
index 984d5e6..8058a91 100644
--- a/src/main/java/jvcl/operation/exec/MergeAudioExec.java
+++ b/src/main/java/jvcl/operation/exec/MergeAudioExec.java
@@ -23,12 +23,6 @@ import static org.cobbzilla.util.io.FileUtil.*;
@Slf4j
public class MergeAudioExec extends SingleOrMultiSourceExecBase {
- public static final String CREATE_SILENCE_TEMPLATE
- = "{{{ffmpeg}}} -f lavfi "
- + "-i anullsrc=channel_layout={{channelLayout}}:sample_rate={{samplingRate}} "
- + "-t {{duration}} "
- + "-y {{{silence.path}}}";
-
public static final String PAD_WITH_SILENCE_TEMPLATE
= "cd {{{tempDir}}} && {{{ffmpeg}}} -f concat -i {{{playlist.path}}} -codec copy -y {{{padded}}}";
@@ -39,6 +33,8 @@ public class MergeAudioExec extends SingleOrMultiSourceExecBase ctx = new HashMap<>();
- ctx.put("ffmpeg", toolbox.getFfmpeg());
- ctx.put("duration", duration);
-
- if (!audio.hasSamplingRate()) return die("createSilence: no sampling rate could be determined: "+audio);
- ctx.put("samplingRate", audio.samplingRate());
-
- if (!audio.hasChannelLayout()) return die("createSilence: no channel layout could be determined: "+audio);
- ctx.put("channelLayout", audio.channelLayout());
-
- final JFileExtension ext = audio.getFormat().getFileExtension();
- final File silenceFile = assetManager.assetPath(op, audio, ext, new Object[]{duration});
- final JAsset silence = new JAsset().setPath(abs(silenceFile));
- ctx.put("silence", silence);
-
- final String script = renderScript(toolbox, ctx, CREATE_SILENCE_TEMPLATE);
-
- log.debug("operate: running script: "+script);
- final String scriptOutput = exec(script, op.isNoExec());
- log.debug("operate: command output: "+scriptOutput);
-
- return silence;
- }
-
protected JAsset padWithSilence(MergeAudioOperation op,
Toolbox toolbox,
AssetManager assetManager,
@@ -139,20 +106,4 @@ public class MergeAudioExec extends SingleOrMultiSourceExecBase ctx,
- MergeAudioOperation op,
- JAsset source,
- JAsset output,
- JAsset subOutput,
- Toolbox toolbox,
- AssetManager assetManager) {
- ctx.put("source", source);
- ctx.put("output", subOutput);
- final String script = renderScript(toolbox, ctx, MERGE_AUDIO_TEMPLATE);
-
- log.debug("operate: running script: "+script);
- final String scriptOutput = exec(script, op.isNoExec());
- log.debug("operate: command output: "+scriptOutput);
- }
-
}
diff --git a/src/main/java/jvcl/operation/exec/RemoveTrackExec.java b/src/main/java/jvcl/operation/exec/RemoveTrackExec.java
index 453f4d5..3574782 100644
--- a/src/main/java/jvcl/operation/exec/RemoveTrackExec.java
+++ b/src/main/java/jvcl/operation/exec/RemoveTrackExec.java
@@ -22,6 +22,8 @@ public class RemoveTrackExec extends SingleOrMultiSourceExecBase ctx,
- RemoveTrackOperation op,
- JAsset source,
- JAsset output,
- JAsset subOutput,
- Toolbox toolbox,
- AssetManager assetManager) {
- ctx.put("source", source);
- ctx.put("output", subOutput);
- final String script = renderScript(toolbox, ctx, REMOVE_TRACK_TEMPLATE);
-
- log.debug("operate: running script: "+script);
- final String scriptOutput = exec(script, op.isNoExec());
- log.debug("operate: command output: "+scriptOutput);
- }
-
}
diff --git a/src/main/java/jvcl/operation/exec/ScaleExec.java b/src/main/java/jvcl/operation/exec/ScaleExec.java
index 5a12264..c9a622a 100644
--- a/src/main/java/jvcl/operation/exec/ScaleExec.java
+++ b/src/main/java/jvcl/operation/exec/ScaleExec.java
@@ -21,6 +21,8 @@ public class ScaleExec extends SingleOrMultiSourceExecBase {
+ "scale={{width}}x{{height}}" +
"\" -y {{{output.path}}}";
+ @Override protected String getProcessTemplate() { return SCALE_TEMPLATE; }
+
@Override public void operate(ScaleOperation op, Toolbox toolbox, AssetManager assetManager) {
final JSingleOperationContext opCtx = op.getSingleInputContext(assetManager);
@@ -43,21 +45,4 @@ public class ScaleExec extends SingleOrMultiSourceExecBase {
operate(op, toolbox, assetManager, source, output, formatType, ctx);
}
- @Override protected void process(Map ctx,
- ScaleOperation op,
- JAsset source,
- JAsset output,
- JAsset subOutput,
- Toolbox toolbox,
- AssetManager assetManager) {
-
- ctx.put("source", source);
- ctx.put("output", subOutput);
- final String script = renderScript(toolbox, ctx, SCALE_TEMPLATE);
-
- log.debug("operate: running script: "+script);
- final String scriptOutput = exec(script, op.isNoExec());
- log.debug("operate: command output: "+scriptOutput);
- }
-
}
diff --git a/src/main/java/jvcl/operation/exec/SingleOrMultiSourceExecBase.java b/src/main/java/jvcl/operation/exec/SingleOrMultiSourceExecBase.java
index 6232974..df10076 100644
--- a/src/main/java/jvcl/operation/exec/SingleOrMultiSourceExecBase.java
+++ b/src/main/java/jvcl/operation/exec/SingleOrMultiSourceExecBase.java
@@ -49,12 +49,22 @@ public abstract class SingleOrMultiSourceExecBase extends
}
}
- protected abstract void process(Map ctx,
- OP op,
- JAsset source,
- JAsset output,
- JAsset asset,
- Toolbox toolbox,
- AssetManager assetManager);
+ protected abstract String getProcessTemplate();
+
+ protected void process(Map ctx,
+ OP op,
+ JAsset source,
+ JAsset output,
+ JAsset subOutput,
+ Toolbox toolbox,
+ AssetManager assetManager) {
+ ctx.put("source", source);
+ ctx.put("output", subOutput);
+ final String script = renderScript(toolbox, ctx, getProcessTemplate());
+
+ log.debug("operate: running script: "+script);
+ final String scriptOutput = exec(script, op.isNoExec());
+ log.debug("operate: command output: "+scriptOutput);
+ }
}
diff --git a/src/main/java/jvcl/operation/exec/TrimExec.java b/src/main/java/jvcl/operation/exec/TrimExec.java
index cffd5d5..bf2c14d 100644
--- a/src/main/java/jvcl/operation/exec/TrimExec.java
+++ b/src/main/java/jvcl/operation/exec/TrimExec.java
@@ -22,6 +22,8 @@ public class TrimExec extends SingleOrMultiSourceExecBase {
"{{#exists interval}}-t {{interval}} {{/exists}}" +
"-y {{{output.path}}}";
+ @Override protected String getProcessTemplate() { return TRIM_TEMPLATE; }
+
@Override public void operate(TrimOperation op, Toolbox toolbox, AssetManager assetManager) {
final JSingleOperationContext opCtx = op.getSingleInputContext(assetManager);
@@ -42,19 +44,12 @@ public class TrimExec extends SingleOrMultiSourceExecBase {
JAsset subOutput,
Toolbox toolbox,
AssetManager assetManager) {
-
- ctx.put("source", source);
- ctx.put("output", subOutput);
-
final StandardJsEngine js = toolbox.getJs();
final BigDecimal startTime = op.getStartTime(ctx, js);
ctx.put("startSeconds", startTime);
if (op.hasEndTime()) ctx.put("interval", op.getEndTime(ctx, js).subtract(startTime));
- final String script = renderScript(toolbox, ctx, TRIM_TEMPLATE);
- log.debug("operate: running script: "+script);
- final String scriptOutput = exec(script, op.isNoExec());
- log.debug("operate: command output: "+scriptOutput);
+ super.process(ctx, op, source, output, subOutput, toolbox, assetManager);
}
}
diff --git a/src/test/resources/tests/test_add_silence.jvcl b/src/test/resources/tests/test_add_silence.jvcl
index ef1a7bc..f7203f0 100644
--- a/src/test/resources/tests/test_add_silence.jvcl
+++ b/src/test/resources/tests/test_add_silence.jvcl
@@ -19,7 +19,9 @@
{
"operation": "add-silence", // name of the operation
"creates": "v2_silent", // output asset name
- "source": "v2" // main video asset
+ "source": "v2", // main video asset
+ "channelLayout": "stereo", // optional channel layout, usually 'mono' or 'stereo'. Default is 'stereo'
+ "samplingRate": 48000 // optional samping rate, in Hz. default is 48000
}
]
}