Browse Source

WIP. adding overlay operation

master
Jonathan Cobb 3 years ago
parent
commit
1175cbf116
9 changed files with 160 additions and 25 deletions
  1. +11
    -11
      README.md
  2. +1
    -0
      src/main/java/jvcl/model/JAsset.java
  3. +2
    -4
      src/main/java/jvcl/op/ConcatOperation.java
  4. +88
    -0
      src/main/java/jvcl/op/OverlayOperation.java
  5. +2
    -4
      src/main/java/jvcl/op/SplitOperation.java
  6. +2
    -4
      src/main/java/jvcl/op/TrimOperation.java
  7. +13
    -0
      src/main/java/jvcl/service/JOperator.java
  8. +39
    -0
      src/test/resources/tests/test_overlay.json
  9. +2
    -2
      src/test/resources/tests/test_split.json

+ 11
- 11
README.md View File

@@ -133,20 +133,20 @@ Here is a complex example using multiple assets and operations:
"creates": "overlay1", // asset it creates
"perform": {
"source": "combined_vid1", // main video asset
"overlay": "vid1", // overlay this video on the main video
"overlay": "vid2", // overlay this video on the main video

"start": "vid1.end_ts", // when (on the main video timeline) to start the overlay. default is 0 (beginning)
"duration": "vid1.duration", // how long to play the overlay. default is to play the entire overlay asset
"offset": "30", // when (on the main video timeline) to begin showing the overlay. default is 0 (beginning)
"overlayStart": "0", // when (on the overlay video timeline) to begin playback on the overlay. default is 0 (beginning)
"overlayEnd": "0", // when (on the overlay video timeline) to end playback on the overlay. default is to play the whole overlay

"width": 400, // how wide the overlay will be, in pixels. default is "overlay.width"
"height": 300, // how tall the overlay will be, in pixels. default is "overlay.height"
"width": "overlay.width / 2", // how wide the overlay will be, in pixels. default is the full overlay width, or maintain aspect ratio if height was set
"height": "", // how tall the overlay will be, in pixels. default is the full overlay height, or maintain aspect ratio if width was set

"x": "source.width / 2", // horizontal overlay position. default is 0
"y": "source.height / 2", // vertical overlay position. default is 0
"out": "1080p", // this is a shortcut to the two lines below, and is the preferred way of specifying the output resolution
"out_width": 1920, // output width in pixels. default is source width
"out_height": 1024 // output height in pixes. default is source height
"x": "source.width/2", // horizontal overlay position on main video. default is 0
"y": "source.height/2", // vertical overlay position on main video. default is 0

"outputWidth": "1920", // output width in pixels. default is source width
"outputHeight": "1024" // output height in pixes. default is source height
}
}
]


+ 1
- 0
src/main/java/jvcl/model/JAsset.java View File

@@ -143,6 +143,7 @@ public class JAsset {
}

public BigDecimal duration() { return getInfo().duration(); }
@JsonIgnore public BigDecimal getDuration () { return duration(); }

public JAsset init(AssetManager assetManager, Toolbox toolbox) {
final JAsset asset = initPath(assetManager);


+ 2
- 4
src/main/java/jvcl/op/ConcatOperation.java View File

@@ -10,7 +10,6 @@ import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.cobbzilla.util.handlebars.HandlebarsUtil;

import java.io.File;
import java.util.HashMap;
@@ -22,7 +21,6 @@ import static jvcl.model.JAsset.json2asset;
import static org.cobbzilla.util.daemon.ZillaRuntime.die;
import static org.cobbzilla.util.daemon.ZillaRuntime.empty;
import static org.cobbzilla.util.io.FileUtil.abs;
import static org.cobbzilla.util.json.JsonUtil.json;
import static org.cobbzilla.util.system.CommandShell.execScript;

@Slf4j
@@ -49,7 +47,7 @@ public class ConcatOperation implements JOperator {

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

final ConcatConfig config = json(json(op.getPerform()), ConcatConfig.class);
final ConcatConfig config = loadConfig(op, ConcatConfig.class);

// validate sources
final List<JAsset> sources = flattenAssetList(assetManager.resolve(config.getConcat()));
@@ -82,7 +80,7 @@ public class ConcatOperation implements JOperator {
ctx.put("ffmpeg", toolbox.getFfmpeg());
ctx.put("sources", sources);
ctx.put("output", output);
final String script = HandlebarsUtil.apply(toolbox.getHandlebars(), CONCAT_RECODE_TEMPLATE_1, ctx);
final String script = renderScript(toolbox, ctx, CONCAT_RECODE_TEMPLATE_1);

log.debug("operate: running script: "+script);
final String scriptOutput = execScript(script);


+ 88
- 0
src/main/java/jvcl/op/OverlayOperation.java View File

@@ -0,0 +1,88 @@
package jvcl.op;

import jvcl.model.JAsset;
import jvcl.model.JFileExtension;
import jvcl.model.JOperation;
import jvcl.service.AssetManager;
import jvcl.service.JOperator;
import jvcl.service.Toolbox;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;

import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Map;

import static jvcl.model.JAsset.json2asset;
import static jvcl.service.Toolbox.getDuration;
import static org.cobbzilla.util.daemon.ZillaRuntime.empty;
import static org.cobbzilla.util.system.CommandShell.execScript;

@Slf4j
public class OverlayOperation implements JOperator {

public static final String OVERLAY_TEMPLATE
= "{{ffmpeg}} ";

@Override public void operate(JOperation op, Toolbox toolbox, AssetManager assetManager) {
final OverlayConfig config = loadConfig(op, OverlayConfig.class);
final JAsset source = assetManager.resolve(config.getSource());
final JAsset overlay = assetManager.resolve(config.getOverlay());

final JAsset output = json2asset(op.getCreates());
output.mergeFormat(source.getFormat());

final JFileExtension formatType = output.getFormat().getFileExtension();

final Map<String, Object> ctx = new HashMap<>();
ctx.put("source", source);
ctx.put("overlay", overlay);
ctx.put("output", output);
ctx.put("offset", config.getOffsetSeconds());
ctx.put("overlayStart", config.getOverlayStartSeconds());
if (config.hasOverlayEnd()) ctx.put("overlayEnd", config.getOverlayEndSeconds());
if (config.hasWidth()) ctx.put("width", config.getWidth());
if (config.hasHeight()) ctx.put("height", config.getHeight());

final String script = renderScript(toolbox, ctx, OVERLAY_TEMPLATE);

log.debug("operate: running script: "+script);
final String scriptOutput = execScript(script);
log.debug("operate: command output: "+scriptOutput);
assetManager.addOperationAsset(output);
}

private static class OverlayConfig {
@Getter @Setter private String source;
@Getter @Setter private String overlay;

@Getter @Setter private String offset;
public BigDecimal getOffsetSeconds () { return empty(offset) ? BigDecimal.ZERO : getDuration(offset); }

@Getter @Setter private String overlayStart;
public BigDecimal getOverlayStartSeconds () { return empty(overlayStart) ? BigDecimal.ZERO : getDuration(overlayStart); }

@Getter @Setter private String overlayEnd;
public boolean hasOverlayEnd () { return !empty(overlayEnd); }
public BigDecimal getOverlayEndSeconds () { return getDuration(overlayEnd); }

@Getter @Setter private String width;
public boolean hasWidth () { return !empty(width); }

@Getter @Setter private String height;
public boolean hasHeight () { return !empty(height); }

@Getter @Setter private String x;
public boolean hasX () { return !empty(x); }

@Getter @Setter private String y;
public boolean hasY () { return !empty(y); }

@Getter @Setter private String outputWidth;
public boolean hasOutputWidth () { return !empty(outputWidth); }

@Getter @Setter private String outputHeight;
public boolean hasOutputHeight () { return !empty(outputHeight); }
}
}

+ 2
- 4
src/main/java/jvcl/op/SplitOperation.java View File

@@ -10,7 +10,6 @@ import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.cobbzilla.util.handlebars.HandlebarsUtil;

import java.io.File;
import java.math.BigDecimal;
@@ -23,7 +22,6 @@ import static org.cobbzilla.util.daemon.ZillaRuntime.die;
import static org.cobbzilla.util.daemon.ZillaRuntime.empty;
import static org.cobbzilla.util.io.FileUtil.abs;
import static org.cobbzilla.util.io.FileUtil.mkdirOrDie;
import static org.cobbzilla.util.json.JsonUtil.json;
import static org.cobbzilla.util.system.CommandShell.execScript;

@Slf4j
@@ -33,7 +31,7 @@ public class SplitOperation implements JOperator {
= "{{ffmpeg}} -i {{{source.path}}} -ss {{startSeconds}} -t {{interval}} {{{output.path}}}";

@Override public void operate(JOperation op, Toolbox toolbox, AssetManager assetManager) {
final SplitConfig config = json(json(op.getPerform()), SplitConfig.class);
final SplitConfig config = loadConfig(op, SplitConfig.class);

final JAsset source = assetManager.resolve(config.getSplit());

@@ -85,7 +83,7 @@ public class SplitOperation implements JOperator {
ctx.put("output", slice);
ctx.put("startSeconds", i);
ctx.put("interval", incr);
final String script = HandlebarsUtil.apply(toolbox.getHandlebars(), SPLIT_TEMPLATE, ctx);
final String script = renderScript(toolbox, ctx, SPLIT_TEMPLATE);

log.debug("operate: running script: "+script);
final String scriptOutput = execScript(script);


+ 2
- 4
src/main/java/jvcl/op/TrimOperation.java View File

@@ -11,7 +11,6 @@ import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.cobbzilla.util.handlebars.HandlebarsUtil;

import java.io.File;
import java.math.BigDecimal;
@@ -23,7 +22,6 @@ import static jvcl.service.Toolbox.getDuration;
import static org.cobbzilla.util.daemon.ZillaRuntime.die;
import static org.cobbzilla.util.daemon.ZillaRuntime.empty;
import static org.cobbzilla.util.io.FileUtil.*;
import static org.cobbzilla.util.json.JsonUtil.json;
import static org.cobbzilla.util.system.CommandShell.execScript;

@Slf4j
@@ -34,7 +32,7 @@ public class TrimOperation implements JOperator {

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

final TrimConfig config = json(json(op.getPerform()), TrimConfig.class);
final TrimConfig config = loadConfig(op, TrimConfig.class);
final JAsset source = assetManager.resolve(config.getTrim());

final JAsset output = json2asset(op.getCreates());
@@ -85,7 +83,7 @@ public class TrimOperation implements JOperator {
final BigDecimal startTime = config.getStartTime();
ctx.put("startSeconds", startTime);
if (config.hasEnd()) ctx.put("interval", config.getEndTime().subtract(startTime));
final String script = HandlebarsUtil.apply(toolbox.getHandlebars(), TRIM_TEMPLATE, ctx);
final String script = renderScript(toolbox, ctx, TRIM_TEMPLATE);

log.debug("operate: running script: "+script);
final String scriptOutput = execScript(script);


+ 13
- 0
src/main/java/jvcl/service/JOperator.java View File

@@ -1,9 +1,22 @@
package jvcl.service;

import jvcl.model.JOperation;
import org.cobbzilla.util.handlebars.HandlebarsUtil;

import java.util.Map;

import static org.cobbzilla.util.json.JsonUtil.json;

public interface JOperator {

void operate(JOperation op, Toolbox toolbox, AssetManager assetManager);

default <T> T loadConfig(JOperation op, Class<T> configClass) {
return json(json(op.getPerform()), configClass);
}

default String renderScript(Toolbox toolbox, Map<String, Object> ctx, String template) {
return HandlebarsUtil.apply(toolbox.getHandlebars(), template, ctx);
}

}

+ 39
- 0
src/test/resources/tests/test_overlay.json View File

@@ -0,0 +1,39 @@
{
"assets": [
{
"name": "vid1",
"path": "https://archive.org/download/gov.archives.arc.1257628/gov.archives.arc.1257628_512kb.mp4",
"dest": "src/test/resources/sources/"
},
{
"name": "vid2",
"path": "https://archive.org/download/gov.archives.arc.49442/gov.archives.arc.49442_512kb.mp4",
"dest": "src/test/resources/sources/"
}
],
"operations": [
{
"operation": "overlay", // name of the operation
"creates": {
"name": "overlay1",
"dest": "src/test/resources/outputs/overlay/"
},
"perform": {
"source": "vid1", // main video asset
"overlay": "vid2", // overlay this video on the main video

"offset": "30", // when (on the main video timeline) to begin showing the overlay. default is 0 (beginning)
"overlayStart": "0", // when (on the overlay video timeline) to begin playback on the overlay. default is 0 (beginning)
"overlayEnd": "0", // when (on the overlay video timeline) to end playback on the overlay. default is to play the whole overlay

"width": "overlay.width / 2", // how wide the overlay will be, in pixels. default is the full overlay width, or maintain aspect ratio if height was set
"height": "", // how tall the overlay will be, in pixels. default is the full overlay height, or maintain aspect ratio if width was set

"x": "source.width / 2", // horizontal overlay position on main video. default is 0
"y": "source.height / 2", // vertical overlay position on main video. default is 0

"outputWidth": "1920", // output width in pixels. default is source width
"outputHeight": "1024" // output height in pixes. default is source height
}
]
}

+ 2
- 2
src/test/resources/tests/test_split.json View File

@@ -3,12 +3,12 @@
{
"name": "vid1",
"path": "https://archive.org/download/gov.archives.arc.1257628/gov.archives.arc.1257628_512kb.mp4",
"dest": "src/test/resources/sources/gov.archives.arc.1257628_512kb.mp4"
"dest": "src/test/resources/sources/"
},
{
"name": "vid2",
"path": "https://archive.org/download/gov.archives.arc.49442/gov.archives.arc.49442_512kb.mp4",
"dest": "src/test/resources/sources/gov.archives.arc.49442_512kb.mp4"
"dest": "src/test/resources/sources/"
}
],
"operations": [


Loading…
Cancel
Save