Browse Source

improve support for input videos with no audio track

master
Jonathan Cobb 3 years ago
parent
commit
1103c97eea
7 changed files with 63 additions and 15 deletions
  1. +3
    -0
      docs/jvc_js.md
  2. +1
    -1
      src/main/java/jvc/model/JStreamType.java
  3. +5
    -1
      src/main/java/jvc/model/info/JTrack.java
  4. +2
    -0
      src/main/java/jvc/model/js/JAssetJs.java
  5. +1
    -0
      src/main/java/jvc/operation/exec/ExecBase.java
  6. +20
    -8
      src/main/java/jvc/operation/exec/MergeAudioExec.java
  7. +31
    -5
      src/main/java/jvc/operation/exec/OverlayExec.java

+ 3
- 0
docs/jvc_js.md View File

@@ -67,6 +67,9 @@ Ratio of width / height (video or image).
#### `samplingRate`
Sampling rate in Hz (audio).

#### `channelLayout`
Channel layout (audio).

#### `tracks`
An array of the A/V tracks in a video. Only includes audio and video tracks.



+ 1
- 1
src/main/java/jvc/model/JStreamType.java View File

@@ -18,7 +18,7 @@ public enum JStreamType {
mkv (".mkv", video),
mp3 (".mp3", audio),
mpeg_audio (".mp3", audio),
aac (".aac", audio),
aac (".m4a", audio),
flac (".flac", audio),
png (".png", image),
jpg (".jpg", image),


+ 5
- 1
src/main/java/jvc/model/info/JTrack.java View File

@@ -45,7 +45,11 @@ public class JTrack {

public String channelLayout () {
if (!empty(channelLayout)) {
return channelLayout.equals("L R") ? "stereo": channelLayout;
return channelLayout.equals("L R")
? "stereo"
: channelLayout.equals("C")
? "mono"
: channelLayout;
}
if (!empty(channels)) {
if (isOnlyDigits(channels)) {


+ 2
- 0
src/main/java/jvc/model/js/JAssetJs.java View File

@@ -20,6 +20,7 @@ public class JAssetJs {
@Getter public final Integer width;
@Getter public final Integer height;
@Getter public final Double aspectRatio;
@Getter public final String channelLayout;
@Getter public final Integer samplingRate;
@Getter public JTrackJs[] allTracks = EMPTY_TRACKS;
@Getter public JTrackJs[] tracks = EMPTY_TRACKS;
@@ -42,6 +43,7 @@ public class JAssetJs {
this.height = h == null ? null : h.intValue();

this.aspectRatio = asset.aspectRatio() == null ? Double.NaN : asset.aspectRatio().doubleValue();
this.channelLayout = asset.hasChannelLayout() ? asset.channelLayout() : null;
this.samplingRate = asset.hasSamplingRate() ? asset.samplingRate().intValue() : 0;

if (asset.hasInfo()) {


+ 1
- 0
src/main/java/jvc/operation/exec/ExecBase.java View File

@@ -78,6 +78,7 @@ public abstract class ExecBase<OP extends JOperation> {
System.out.println(script);
return "";
} else {
log.info("exec: "+script);
return execScript(script);
}
}


+ 20
- 8
src/main/java/jvc/operation/exec/MergeAudioExec.java View File

@@ -27,21 +27,30 @@ public class MergeAudioExec extends SingleOrMultiSourceExecBase<MergeAudioOperat
= "cd {{{tempDir}}} && {{{ffmpeg}}} -f concat -i {{{playlist.path}}} -codec copy -y {{{padded}}}";

public static final String MERGE_AUDIO_TEMPLATE
= "{{{ffmpeg}}} -i {{{source.path}}} -i {{audio.path}} "
= "{{{ffmpeg}}} -i {{{source.path}}} -itsoffset {{start}} -i {{audio.path}} "
// if source has no audio, define a null audio input source
+ "{{#unless source.hasAudio}}"
+ "-i anullsrc=channel_layout={{audio.channelLayout}}:sample_rate={{audio.samplingRate}} "
+ "{{/unless}}"

+ "-filter_complex \""
+ "{{#if source.hasAudio}}"
+ "-filter_complex \""
// source has audio -- mix with insertion
+ "[0:a][1:a] amix=inputs=2 [merged]"
+ "\" "
+ "-map 0:v -map \"[merged]\" -c:v copy "
+ "{{else}}"
+ "-map 0:v -map 1:a -c:v copy -c:a copy "
// source has no audio -- mix null source with insertion
+ "[1:a] aresample=ocl={{audio.channelLayout}}:osr={{audio.samplingRate}} [1a]; "
+ "[2:a][1a] amix=inputs=2 [merged]"
+ "{{/if}}"
+ "\" "
+ "-map 0:v -map \"[merged]\" -c:v copy "
+ "-y {{{output.path}}}";

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

@Override
protected void addCommandContext(MergeAudioOperation op, JSingleOperationContext opCtx, Map<String, Object> ctx) {
@Override protected void addCommandContext(MergeAudioOperation op,
JSingleOperationContext opCtx,
Map<String, Object> ctx) {
final JAsset audio = opCtx.assetManager.resolve(op.getInsert());
final JsEngine js = opCtx.toolbox.getJs();
final BigDecimal insertAt = op.getAt(ctx, js);
@@ -64,7 +73,7 @@ public class MergeAudioExec extends SingleOrMultiSourceExecBase<MergeAudioOperat
final Map<String, Object> ctx = new HashMap<>();
ctx.put("ffmpeg", toolbox.getFfmpeg());

final JStreamType streamType = audio.getFormat().getStreamType();
final JStreamType streamType = audio.audioExtension();
final JAsset padded = new JAsset().setPath(abs(assetManager.assetPath(op, audio, streamType)));
final String paddedName = basename(padded.getPath());
ctx.put("padded", paddedName);
@@ -98,6 +107,9 @@ public class MergeAudioExec extends SingleOrMultiSourceExecBase<MergeAudioOperat

log.debug("padWithSilence: command output: "+scriptOutput);

// initialize metadata
padded.init(assetManager, toolbox);

return padded;
}



+ 31
- 5
src/main/java/jvc/operation/exec/OverlayExec.java View File

@@ -13,19 +13,38 @@ import java.io.File;
import java.math.BigDecimal;
import java.util.Map;

import static java.math.BigDecimal.ZERO;
import static org.cobbzilla.util.io.FileUtil.abs;

@Slf4j
public class OverlayExec extends ExecBase<OverlayOperation> {

public static final String OVERLAY_TEMPLATE
= "ffmpeg -i {{{source.path}}} -i {{{overlay.path}}} "
= "ffmpeg -i {{{source.path}}} "
+ "{{#if hasMainStart}}-ss {{mainStart}} {{/if}}"
+ "{{#if hasMainEnd}}-t {{mainDuration}} {{/if}}"
+ "-i {{{overlay.path}}} "

+ "-filter_complex \""
+ "[1:v] setpts=PTS-STARTPTS+(1/TB){{#exists width}}, scale={{width}}x{{height}}{{/exists}} [1v]; "
+ "[0:v][1v] overlay={{{overlayFilterConfig}}} "
+ "\" -y {{{output.path}}}";
+ "[1:v] setpts=PTS-STARTPTS"
+ "{{#exists width}}, scale={{width}}x{{height}}{{/exists}}"
+ " [1v]; "
+ "[0:v][1v] overlay={{{overlayFilterConfig}}} [v]; "

+ "{{#if source.hasAudio}}"
// source has audio -- mix with overlay
+ "[1:a] setpts=PTS-STARTPTS{{#if hasOverlayStart}}-({{overlayStart}}/TB){{/if}} [1a]; "
+ "[0:a][1a] amix=inputs=2 [merged]"
+ "{{else}}"
// source has no audio -- mix null source with overlay
+ "anullsrc=channel_layout={{overlay.channelLayout}}:sample_rate={{overlay.samplingRate}}:duration={{source.duration}} [silence]; "
// + "[1:a] setpts=PTS-STARTPTS{{#if hasOverlayStart}}-({{overlayStart}}/TB){{/if}} [1a]; "
+ "[silence][1:a] amix=inputs=2 [merged]"
+ "{{/if}}"

+ "\" "
+ "-map \"[v]\" -map \"[merged]\" "
+ " -y {{{output.path}}}";

@Override public Map<String, Object> operate(OverlayOperation op, Toolbox toolbox, AssetManager assetManager) {

@@ -81,7 +100,14 @@ public class OverlayExec extends ExecBase<OverlayOperation> {
final StringBuilder b = new StringBuilder();
final BigDecimal startTime = overlay.getStartTime(ctx, js);
final BigDecimal endTime = overlay.hasEndTime() ? overlay.getEndTime(ctx, js) : overlaySource.duration();
b.append("enable=between(t\\,").append(startTime).append("\\,").append(endTime).append(")");
ctx.put("overlayStart", startTime.doubleValue());
ctx.put("hasOverlayStart", !startTime.equals(ZERO));
ctx.put("overlayEnd", endTime.doubleValue());
b.append("enable=between(t\\,")
.append(startTime.doubleValue())
.append("\\,")
.append(endTime.doubleValue())
.append(")");

if (overlay.hasX() && overlay.hasY()) {
b.append(":x=").append(overlay.getX(ctx, js).intValue())


Loading…
Cancel
Save