瀏覽代碼

add-silence operation now working. more refactoring.

master
Jonathan Cobb 3 年之前
父節點
當前提交
88eba8d2ce
共有 17 個文件被更改,包括 135 次插入136 次删除
  1. +10
    -4
      bin/jaddsilence
  2. +0
    -0
     
  3. +17
    -4
      docs/complex_example.md
  4. +4
    -0
      src/main/java/jvcl/model/JAsset.java
  5. +4
    -4
      src/main/java/jvcl/model/JFileExtension.java
  6. +5
    -0
      src/main/java/jvcl/model/info/JMediaInfo.java
  7. +3
    -1
      src/main/java/jvcl/model/info/JTrack.java
  8. +11
    -1
      src/main/java/jvcl/operation/AddSilenceOperation.java
  9. +9
    -5
      src/main/java/jvcl/operation/exec/AddSilenceExec.java
  10. +40
    -0
      src/main/java/jvcl/operation/exec/ExecBase.java
  11. +3
    -17
      src/main/java/jvcl/operation/exec/LetterboxExec.java
  12. +2
    -51
      src/main/java/jvcl/operation/exec/MergeAudioExec.java
  13. +2
    -16
      src/main/java/jvcl/operation/exec/RemoveTrackExec.java
  14. +2
    -17
      src/main/java/jvcl/operation/exec/ScaleExec.java
  15. +17
    -7
      src/main/java/jvcl/operation/exec/SingleOrMultiSourceExecBase.java
  16. +3
    -8
      src/main/java/jvcl/operation/exec/TrimExec.java
  17. +3
    -1
      src/test/resources/tests/test_add_silence.jvcl

+ 10
- 4
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)
}
]
}


+ 0
- 0
查看文件


+ 17
- 4
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.

<sub><sup>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)</sup></sub>

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


+ 4
- 0
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()) {


+ 4
- 4
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());
}

}

+ 5
- 0
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


+ 3
- 1
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)) {


+ 11
- 1
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;

}

+ 9
- 5
src/main/java/jvcl/operation/exec/AddSilenceExec.java 查看文件

@@ -14,7 +14,12 @@ import java.util.Map;
@Slf4j
public class AddSilenceExec extends SingleOrMultiSourceExecBase<AddSilenceOperation> {

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<AddSilenceOperat
ctx.put("ffmpeg", toolbox.getFfmpeg());
ctx.put("source", source);

operate(op, toolbox, assetManager, source, output, formatType, ctx);
}
final JAsset silence = createSilence(op, toolbox, assetManager, source.duration(), source);
ctx.put("silence", silence);

@Override protected void process(Map<String, Object> ctx, AddSilenceOperation addSilenceOperation, JAsset source, JAsset output, JAsset asset, Toolbox toolbox, AssetManager assetManager) {
// todo
operate(op, toolbox, assetManager, source, output, formatType, ctx);
}

}

+ 40
- 0
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<OP extends JOperation> {
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<String, Object> 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;
}

}

+ 3
- 17
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<LetterboxOperation> {

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<LetterboxOperatio
+ "color={{{color}}}"
+ "\" -y {{{output.path}}}";

public static final String DEFAULT_LETTERBOX_COLOR = "black";
@Override protected String getProcessTemplate() { return LETTERBOX_TEMPLATE; }

@Override public void operate(LetterboxOperation op, Toolbox toolbox, AssetManager assetManager) {

@@ -57,20 +59,4 @@ public class LetterboxExec extends SingleOrMultiSourceExecBase<LetterboxOperatio
operate(op, toolbox, assetManager, source, output, formatType, ctx);
}

@Override protected void process(Map<String, Object> 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);
}

}

+ 2
- 51
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<MergeAudioOperation> {

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<MergeAudioOperat
+ "-map 0:v -map \"[merged]\" -c:v copy "
+ "-y {{{output.path}}}";

@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;
@@ -65,35 +61,6 @@ public class MergeAudioExec extends SingleOrMultiSourceExecBase<MergeAudioOperat
operate(op, toolbox, assetManager, source, output, formatType, ctx);
}

protected JAsset createSilence(MergeAudioOperation op,
Toolbox toolbox,
AssetManager assetManager,
BigDecimal duration,
JAsset audio) {
final Map<String, Object> 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<MergeAudioOperat
return padded;
}

@Override protected void process(Map<String, Object> 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);
}

}

+ 2
- 16
src/main/java/jvcl/operation/exec/RemoveTrackExec.java 查看文件

@@ -22,6 +22,8 @@ public class RemoveTrackExec extends SingleOrMultiSourceExecBase<RemoveTrackOper
+ "-c copy "
+ "-y {{{output.path}}}";

@Override protected String getProcessTemplate() { return REMOVE_TRACK_TEMPLATE; }

@Override public void operate(RemoveTrackOperation op, Toolbox toolbox, AssetManager assetManager) {

final JSingleOperationContext opCtx = op.getSingleInputContext(assetManager);
@@ -41,20 +43,4 @@ public class RemoveTrackExec extends SingleOrMultiSourceExecBase<RemoveTrackOper
operate(op, toolbox, assetManager, source, output, formatType, ctx);
}

@Override protected void process(Map<String, Object> ctx,
RemoveTrackOperation op,
JAsset source,
JAsset output,
JAsset subOutput,
Toolbox toolbox,
AssetManager assetManager) {
ctx.put("source", source);
ctx.put("output", subOutput);
final String script = renderScript(toolbox, ctx, REMOVE_TRACK_TEMPLATE);

log.debug("operate: running script: "+script);
final String scriptOutput = exec(script, op.isNoExec());
log.debug("operate: command output: "+scriptOutput);
}

}

+ 2
- 17
src/main/java/jvcl/operation/exec/ScaleExec.java 查看文件

@@ -21,6 +21,8 @@ public class ScaleExec extends SingleOrMultiSourceExecBase<ScaleOperation> {
+ "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<ScaleOperation> {
operate(op, toolbox, assetManager, source, output, formatType, ctx);
}

@Override protected void process(Map<String, Object> 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);
}

}

+ 17
- 7
src/main/java/jvcl/operation/exec/SingleOrMultiSourceExecBase.java 查看文件

@@ -49,12 +49,22 @@ public abstract class SingleOrMultiSourceExecBase<OP extends JOperation> extends
}
}

protected abstract void process(Map<String, Object> ctx,
OP op,
JAsset source,
JAsset output,
JAsset asset,
Toolbox toolbox,
AssetManager assetManager);
protected abstract String getProcessTemplate();

protected void process(Map<String, Object> 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);
}

}

+ 3
- 8
src/main/java/jvcl/operation/exec/TrimExec.java 查看文件

@@ -22,6 +22,8 @@ public class TrimExec extends SingleOrMultiSourceExecBase<TrimOperation> {
"{{#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<TrimOperation> {
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);
}

}

+ 3
- 1
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
}
]
}

Loading…
取消
儲存