@@ -136,8 +136,11 @@ Concatenate audio/video assets together into one asset | |||
### trim | |||
Trim audio/video; crop a section of an asset, becomes a new asset | |||
### scale | |||
Scale a video asset from one size to another | |||
### overlay | |||
Overlay one audio or video file onto another | |||
Overlay one asset onto another | |||
### ken-burns | |||
For transforming still images into video via a fade-pan (aka Ken Burns) effect | |||
@@ -171,38 +174,41 @@ Here is a complex example using multiple assets and operations. | |||
"name": "vid3", | |||
"path": "https://archive.org/download/gov.archives.arc.49442/gov.archives.arc.49442_512kb.mp4", | |||
"dest": "src/test/resources/sources/" | |||
}, | |||
// Image URL | |||
{ | |||
"name": "img1", | |||
"path": "https://live.staticflickr.com/65535/48159911972_01efa0e5ea_b.jpg", | |||
"dest": "src/test/resources/sources/" | |||
} | |||
], | |||
"operations": [ | |||
{ | |||
"operation": "split", // name of the operation, | |||
"operation": "split", // name of the operation | |||
"creates": "vid1_split_%", // assets it creates, the '%' will be replaced with a counter | |||
"split": "vid1", // split this source asset | |||
"source": "vid1", // split this source asset | |||
"interval": "10" // split every ten seconds | |||
}, | |||
{ | |||
"operation": "concat", // name of the operation, | |||
"operation": "concat", // name of the operation | |||
"creates": "recombined_vid1", // assets it creates, the '%' will be replaced with a counter | |||
"concat": ["vid1_split"] // recombine all split assets | |||
"source": ["vid1_split"] // recombine all split assets | |||
}, | |||
{ | |||
"operation": "concat", // name of the operation, | |||
"operation": "concat", // name of the operation | |||
"creates": "combined_vid", // asset it creates, can be referenced later | |||
"concat": ["vid1", "vid2"] // operation-specific: this says, concatenate these named assets | |||
"source": ["vid1", "vid2"] // operation-specific: this says, concatenate these named assets | |||
}, | |||
{ | |||
"operation": "concat", // name of the operation, | |||
"operation": "concat", // name of the operation | |||
"creates": "combined_vid", // the asset it creates, can be referenced later | |||
"concat": ["vid1", "vid2"] // operation-specific: this says, concatenate these named assets | |||
"source": ["vid1", "vid2"] // operation-specific: this says, concatenate these named assets | |||
}, | |||
{ | |||
"operation": "overlay", // name of the operation, | |||
"creates": { | |||
"name": "overlay1", // asset it creates | |||
"width": "1920", // output width in pixels. default is source width | |||
"height": "1024" // output height in pixes. default is source height | |||
}, | |||
"main": "combined_vid1", // main video asset | |||
"operation": "overlay", // name of the operation | |||
"creates": "overlay1", // asset it creates | |||
"source": "combined_vid1", // main video asset | |||
"start": "30", // when (on the main video timeline) to begin showing the overlay. default is 0 (beginning) | |||
"end": "60", // when (on the main video timeline) to stop showing the overlay. default is to play the entire overlay | |||
"overlay": { | |||
@@ -214,6 +220,19 @@ Here is a complex example using multiple assets and operations. | |||
"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 | |||
} | |||
}, | |||
{ | |||
"operation": "ken-burns", // name of the operation | |||
"creates": "ken1", // asset it creates | |||
"source": "img1", // source image | |||
"zoom": "1.3", // zoom level, from 1 to 10 | |||
"duration": "5", // how long the resulting video will be | |||
"start": "0", // when to start zooming, default is 0 | |||
"end": "duration", // when to end zooming, default is duration | |||
"x": "source.width * 0.6", // pan to this x-position | |||
"y": "source.height * 0.4", // pan to this y-position | |||
"width": "1024", // width of output video | |||
"height": "768" // height of output video | |||
} | |||
] | |||
} | |||
@@ -48,4 +48,10 @@ public class JvclOptions extends BaseMainOptions { | |||
@Getter @Setter private File scratchDir = null; | |||
public File scratchDir() { return scratchDir == null ? new TempDir() : scratchDir; } | |||
public static final String USAGE_NO_EXEC = "Don't run anything, instead print out commands that would have been run"; | |||
public static final String OPT_NO_EXEC = "-n"; | |||
public static final String LONGOPT_NO_EXEC = "--no-exec"; | |||
@Option(name=OPT_NO_EXEC, aliases=LONGOPT_NO_EXEC, usage=USAGE_NO_EXEC) | |||
@Getter @Setter private boolean noExec = false; | |||
} |
@@ -1,6 +1,6 @@ | |||
package jvcl.model; | |||
import lombok.AllArgsConstructor; | |||
import jvcl.model.operation.JOperation; | |||
import lombok.Getter; | |||
import lombok.NoArgsConstructor; | |||
import lombok.Setter; | |||
@@ -0,0 +1,18 @@ | |||
package jvcl.model.operation; | |||
import jvcl.model.JAsset; | |||
import jvcl.model.JFileExtension; | |||
import lombok.NoArgsConstructor; | |||
import java.util.List; | |||
@NoArgsConstructor | |||
public class JMultiOperationContext extends JOperationContextBase { | |||
public List<JAsset> sources; | |||
public JMultiOperationContext(List<JAsset> sources, JAsset output, JFileExtension formatType) { | |||
super(output, formatType); | |||
this.sources = sources; | |||
} | |||
} |
@@ -0,0 +1,36 @@ | |||
package jvcl.model.operation; | |||
import jvcl.model.JAsset; | |||
import jvcl.model.JFileExtension; | |||
import jvcl.service.AssetManager; | |||
import lombok.Getter; | |||
import lombok.Setter; | |||
import java.util.List; | |||
import static jvcl.model.JAsset.flattenAssetList; | |||
import static jvcl.model.JAsset.json2asset; | |||
import static org.cobbzilla.util.daemon.ZillaRuntime.die; | |||
import static org.cobbzilla.util.daemon.ZillaRuntime.empty; | |||
public abstract class JMultiSourceOperation extends JOperation { | |||
@Getter @Setter private String[] sources; | |||
public JMultiOperationContext getMultiInputContext(AssetManager assetManager) { | |||
// validate sources | |||
final List<JAsset> sources = flattenAssetList(assetManager.resolve(getSources())); | |||
if (empty(sources)) die("operate: no sources"); | |||
// create output object | |||
final JAsset output = json2asset(getCreates()); | |||
// if any format settings are missing, use settings from first source | |||
output.mergeFormat(sources.get(0).getFormat()); | |||
// set the path, check if output asset already exists | |||
final JFileExtension formatType = output.getFormat().getFileExtension(); | |||
return new JMultiOperationContext(sources, output, formatType); | |||
} | |||
} |
@@ -1,7 +1,8 @@ | |||
package jvcl.model; | |||
package jvcl.model.operation; | |||
import com.fasterxml.jackson.annotation.JsonTypeInfo; | |||
import com.fasterxml.jackson.databind.JsonNode; | |||
import jvcl.model.JAsset; | |||
import jvcl.operation.exec.ExecBase; | |||
import lombok.Getter; | |||
import lombok.NoArgsConstructor; |
@@ -0,0 +1,14 @@ | |||
package jvcl.model.operation; | |||
import jvcl.model.JAsset; | |||
import jvcl.model.JFileExtension; | |||
import lombok.AllArgsConstructor; | |||
import lombok.NoArgsConstructor; | |||
@NoArgsConstructor @AllArgsConstructor | |||
public class JOperationContextBase { | |||
public JAsset output; | |||
public JFileExtension formatType; | |||
} |
@@ -0,0 +1,18 @@ | |||
package jvcl.model.operation; | |||
import jvcl.model.JAsset; | |||
import jvcl.model.JFileExtension; | |||
import lombok.AllArgsConstructor; | |||
import lombok.NoArgsConstructor; | |||
@NoArgsConstructor @AllArgsConstructor | |||
public class JSingleOperationContext extends JOperationContextBase { | |||
public JAsset source; | |||
public JSingleOperationContext(JAsset source, JAsset output, JFileExtension formatType) { | |||
super(output, formatType); | |||
this.source = source; | |||
} | |||
} |
@@ -0,0 +1,24 @@ | |||
package jvcl.model.operation; | |||
import jvcl.model.JAsset; | |||
import jvcl.model.JFileExtension; | |||
import jvcl.service.AssetManager; | |||
import lombok.Getter; | |||
import lombok.Setter; | |||
import static jvcl.model.JAsset.json2asset; | |||
public class JSingleSourceOperation extends JOperation { | |||
@Getter @Setter private String source; | |||
public JSingleOperationContext getSingleInputContext(AssetManager assetManager) { | |||
final JAsset source = assetManager.resolve(getSource()); | |||
final JAsset output = json2asset(getCreates()); | |||
output.mergeFormat(source.getFormat()); | |||
final JFileExtension formatType = output.getFormat().getFileExtension(); | |||
return new JSingleOperationContext(source, output, formatType); | |||
} | |||
} |
@@ -1,29 +1,7 @@ | |||
package jvcl.operation; | |||
import jvcl.model.JAsset; | |||
import jvcl.model.JFileExtension; | |||
import jvcl.model.JOperation; | |||
import jvcl.service.AssetManager; | |||
import jvcl.service.Toolbox; | |||
import lombok.Getter; | |||
import lombok.Setter; | |||
import jvcl.model.operation.JMultiSourceOperation; | |||
import lombok.extern.slf4j.Slf4j; | |||
import java.io.File; | |||
import java.util.HashMap; | |||
import java.util.List; | |||
import java.util.Map; | |||
import static jvcl.model.JAsset.flattenAssetList; | |||
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.system.CommandShell.execScript; | |||
@Slf4j | |||
public class ConcatOperation extends JOperation { | |||
@Getter @Setter private String[] concat; | |||
} | |||
public class ConcatOperation extends JMultiSourceOperation {} |
@@ -0,0 +1,18 @@ | |||
package jvcl.operation; | |||
import jvcl.model.operation.JSingleSourceOperation; | |||
import lombok.Getter; | |||
import lombok.Setter; | |||
public class KenBurnsOperation extends JSingleSourceOperation { | |||
@Getter @Setter private String zoom; | |||
@Getter @Setter private String duration; | |||
@Getter @Setter private String width; | |||
@Getter @Setter private String height; | |||
@Getter @Setter private String x; | |||
@Getter @Setter private String y; | |||
@Getter @Setter private String start; | |||
@Getter @Setter private String end; | |||
} |
@@ -1,6 +1,6 @@ | |||
package jvcl.operation; | |||
import jvcl.model.JOperation; | |||
import jvcl.model.operation.JSingleSourceOperation; | |||
import lombok.Getter; | |||
import lombok.Setter; | |||
import lombok.extern.slf4j.Slf4j; | |||
@@ -16,9 +16,8 @@ import static org.cobbzilla.util.daemon.ZillaRuntime.big; | |||
import static org.cobbzilla.util.daemon.ZillaRuntime.empty; | |||
@Slf4j | |||
public class OverlayOperation extends JOperation { | |||
public class OverlayOperation extends JSingleSourceOperation { | |||
@Getter @Setter private String source; | |||
@Getter @Setter private OverlayConfig overlay; | |||
@Getter @Setter private String start; | |||
@@ -1,7 +1,7 @@ | |||
package jvcl.operation; | |||
import jvcl.model.JAsset; | |||
import jvcl.model.JOperation; | |||
import jvcl.model.operation.JSingleSourceOperation; | |||
import lombok.Getter; | |||
import lombok.Setter; | |||
import lombok.extern.slf4j.Slf4j; | |||
@@ -12,9 +12,7 @@ import static jvcl.service.Toolbox.getDuration; | |||
import static org.cobbzilla.util.daemon.ZillaRuntime.empty; | |||
@Slf4j | |||
public class SplitOperation extends JOperation { | |||
@Getter @Setter private String split; | |||
public class SplitOperation extends JSingleSourceOperation { | |||
@Getter @Setter private String interval; | |||
public BigDecimal getIntervalIncr() { return getDuration(interval); } | |||
@@ -1,30 +1,17 @@ | |||
package jvcl.operation; | |||
import jvcl.model.JAsset; | |||
import jvcl.model.JFileExtension; | |||
import jvcl.model.JOperation; | |||
import jvcl.service.AssetManager; | |||
import jvcl.service.Toolbox; | |||
import jvcl.model.operation.JSingleSourceOperation; | |||
import lombok.Getter; | |||
import lombok.Setter; | |||
import lombok.extern.slf4j.Slf4j; | |||
import java.io.File; | |||
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.die; | |||
import static org.cobbzilla.util.daemon.ZillaRuntime.empty; | |||
import static org.cobbzilla.util.io.FileUtil.*; | |||
import static org.cobbzilla.util.system.CommandShell.execScript; | |||
@Slf4j | |||
public class TrimOperation extends JOperation { | |||
@Getter @Setter private String trim; | |||
public class TrimOperation extends JSingleSourceOperation { | |||
@Getter @Setter private String start; | |||
public BigDecimal getStartTime() { return empty(start) ? BigDecimal.ZERO : getDuration(start); } | |||
@@ -34,6 +21,6 @@ public class TrimOperation extends JOperation { | |||
public BigDecimal getEndTime() { return getDuration(end); } | |||
public String shortString() { return "trim_"+getStart()+(hasEnd() ? "_"+getEnd() : ""); } | |||
public String toString() { return trim+"_"+getStart()+(hasEnd() ? "_"+getEnd() : ""); } | |||
public String toString() { return getSource()+"_"+getStart()+(hasEnd() ? "_"+getEnd() : ""); } | |||
} |
@@ -2,6 +2,7 @@ package jvcl.operation.exec; | |||
import jvcl.model.JAsset; | |||
import jvcl.model.JFileExtension; | |||
import jvcl.model.operation.JMultiOperationContext; | |||
import jvcl.operation.ConcatOperation; | |||
import jvcl.service.AssetManager; | |||
import jvcl.service.Toolbox; | |||
@@ -12,22 +13,12 @@ import java.util.HashMap; | |||
import java.util.List; | |||
import java.util.Map; | |||
import static jvcl.model.JAsset.flattenAssetList; | |||
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.system.CommandShell.execScript; | |||
@Slf4j | |||
public class ConcatExec extends ExecBase<ConcatOperation> { | |||
public static final String CONCAT_RECODE_TEMPLATE_OLD | |||
// concat inputs | |||
= "{{ffmpeg}} -f concat{{#each sources}} -i {{{this.path}}}{{/each}} " | |||
// safely with copy codec | |||
+ "-safe 0 -c copy {{{output.path}}}"; | |||
public static final String CONCAT_RECODE_TEMPLATE_1 | |||
// list inputs | |||
= "{{ffmpeg}} {{#each sources}} -i {{{this.path}}}{{/each}} " | |||
@@ -39,22 +30,15 @@ public class ConcatExec extends ExecBase<ConcatOperation> { | |||
+ "concat=n={{sources.length}}:v=1:a=1 [v] [a]\" " | |||
// output combined result | |||
+ "-map \"[v]\" -map \"[a]\" {{{output.path}}}"; | |||
+ "-map \"[v]\" -map \"[a]\" -y {{{output.path}}}"; | |||
@Override public void operate(ConcatOperation op, Toolbox toolbox, AssetManager assetManager) { | |||
// validate sources | |||
final List<JAsset> sources = flattenAssetList(assetManager.resolve(op.getConcat())); | |||
if (empty(sources)) die("operate: no sources"); | |||
// create output object | |||
final JAsset output = json2asset(op.getCreates()); | |||
// if any format settings are missing, use settings from first source | |||
output.mergeFormat(sources.get(0).getFormat()); | |||
final JMultiOperationContext opCtx = op.getMultiInputContext(assetManager); | |||
final List<JAsset> sources = opCtx.sources; | |||
final JAsset output = opCtx.output; | |||
final JFileExtension formatType = opCtx.formatType; | |||
// set the path, check if output asset already exists | |||
final JFileExtension formatType = output.getFormat().getFileExtension(); | |||
final File defaultOutfile = assetManager.assetPath(op, sources, formatType); | |||
final File path = resolveOutputPath(output, defaultOutfile); | |||
if (path == null) return; | |||
@@ -1,7 +1,7 @@ | |||
package jvcl.operation.exec; | |||
import jvcl.model.JAsset; | |||
import jvcl.model.JOperation; | |||
import jvcl.model.operation.JOperation; | |||
import jvcl.service.AssetManager; | |||
import jvcl.service.Toolbox; | |||
import lombok.extern.slf4j.Slf4j; | |||
@@ -0,0 +1,36 @@ | |||
package jvcl.operation.exec; | |||
import jvcl.model.JAsset; | |||
import jvcl.model.JFileExtension; | |||
import jvcl.model.operation.JSingleOperationContext; | |||
import jvcl.operation.KenBurnsOperation; | |||
import jvcl.service.AssetManager; | |||
import jvcl.service.Toolbox; | |||
import lombok.extern.slf4j.Slf4j; | |||
@Slf4j | |||
public class KenBurnsExec extends ExecBase<KenBurnsOperation> { | |||
public static final String KEN_BURNS_TEMPLATE | |||
= "{{{ffmpeg}}} -i {{{source.path}}} -filter_complex \"" | |||
+ "scale={{expr width '*' 16}}x{{expr height '*' 16}}, " | |||
+ "zoompan=" | |||
+ "z='min(zoom*{{zoomIncrementFactor}},{{zoom}})':" | |||
+ "d={{duration}}:" | |||
+ "x='if(gte(zoom,{{zoom}}),x,x+{{deltaX}}/a)':" | |||
+ "y='if(gte(zoom,{{zoom}}),y,y+{{deltaY}})':" | |||
+ "s={{width}}x{{height}}" | |||
+ "\" -y {{{output.path}}}"; | |||
@Override public void operate(KenBurnsOperation 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; | |||
} | |||
} |
@@ -2,6 +2,7 @@ package jvcl.operation.exec; | |||
import jvcl.model.JAsset; | |||
import jvcl.model.JFileExtension; | |||
import jvcl.model.operation.JSingleOperationContext; | |||
import jvcl.operation.OverlayOperation; | |||
import jvcl.service.AssetManager; | |||
import jvcl.service.Toolbox; | |||
@@ -14,7 +15,6 @@ import java.util.HashMap; | |||
import java.util.Map; | |||
import static java.math.RoundingMode.HALF_EVEN; | |||
import static jvcl.model.JAsset.json2asset; | |||
import static org.cobbzilla.util.daemon.ZillaRuntime.big; | |||
import static org.cobbzilla.util.io.FileUtil.abs; | |||
import static org.cobbzilla.util.system.CommandShell.execScript; | |||
@@ -26,17 +26,17 @@ public class OverlayExec extends ExecBase<OverlayOperation> { | |||
= "ffmpeg -i {{{source.path}}} -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}}} " | |||
+ "\" {{{output.path}}}"; | |||
+ "\" -y {{{output.path}}}"; | |||
@Override public void operate(OverlayOperation op, Toolbox toolbox, AssetManager assetManager) { | |||
final JAsset source = assetManager.resolve(op.getSource()); | |||
final OverlayOperation.OverlayConfig overlay = op.getOverlay(); | |||
final JAsset overlaySource = assetManager.resolve(overlay.getSource()); | |||
final JAsset output = json2asset(op.getCreates()); | |||
output.mergeFormat(source.getFormat()); | |||
final JSingleOperationContext opCtx = op.getSingleInputContext(assetManager); | |||
final JAsset source = opCtx.source; | |||
final JAsset output = opCtx.output; | |||
final JFileExtension formatType = opCtx.formatType; | |||
final JFileExtension formatType = output.getFormat().getFileExtension(); | |||
final OverlayOperation.OverlayConfig overlay = op.getOverlay(); | |||
final JAsset overlaySource = assetManager.resolve(overlay.getSource()); | |||
final File defaultOutfile = assetManager.assetPath(op, source, formatType); | |||
final File path = resolveOutputPath(output, defaultOutfile); | |||
@@ -2,6 +2,7 @@ package jvcl.operation.exec; | |||
import jvcl.model.JAsset; | |||
import jvcl.model.JFileExtension; | |||
import jvcl.model.operation.JSingleOperationContext; | |||
import jvcl.operation.SplitOperation; | |||
import jvcl.service.AssetManager; | |||
import jvcl.service.Toolbox; | |||
@@ -12,7 +13,6 @@ import java.math.BigDecimal; | |||
import java.util.HashMap; | |||
import java.util.Map; | |||
import static jvcl.model.JAsset.json2asset; | |||
import static org.cobbzilla.util.daemon.ZillaRuntime.die; | |||
import static org.cobbzilla.util.io.FileUtil.abs; | |||
import static org.cobbzilla.util.io.FileUtil.mkdirOrDie; | |||
@@ -22,21 +22,14 @@ import static org.cobbzilla.util.system.CommandShell.execScript; | |||
public class SplitExec extends ExecBase<SplitOperation> { | |||
public static final String SPLIT_TEMPLATE | |||
= "{{ffmpeg}} -i {{{source.path}}} -ss {{startSeconds}} -t {{interval}} {{{output.path}}}"; | |||
= "{{ffmpeg}} -i {{{source.path}}} -ss {{startSeconds}} -t {{interval}} -y {{{output.path}}}"; | |||
@Override public void operate(SplitOperation op, Toolbox toolbox, AssetManager assetManager) { | |||
final JAsset source = assetManager.resolve(op.getSplit()); | |||
// create output object | |||
final JAsset output = json2asset(op.getCreates()); | |||
// if any format settings are missing, use settings from source | |||
output.mergeFormat(source.getFormat()); | |||
assetManager.addOperationArrayAsset(output); | |||
// get format type | |||
final JFileExtension formatType = output.getFormat().getFileExtension(); | |||
final JSingleOperationContext opCtx = op.getSingleInputContext(assetManager); | |||
final JAsset source = opCtx.source; | |||
final JAsset output = opCtx.output; | |||
final JFileExtension formatType = opCtx.formatType; | |||
final Map<String, Object> ctx = new HashMap<>(); | |||
ctx.put("ffmpeg", toolbox.getFfmpeg()); | |||
@@ -2,6 +2,7 @@ package jvcl.operation.exec; | |||
import jvcl.model.JAsset; | |||
import jvcl.model.JFileExtension; | |||
import jvcl.model.operation.JSingleOperationContext; | |||
import jvcl.operation.TrimOperation; | |||
import jvcl.service.AssetManager; | |||
import jvcl.service.Toolbox; | |||
@@ -12,7 +13,6 @@ import java.math.BigDecimal; | |||
import java.util.HashMap; | |||
import java.util.Map; | |||
import static jvcl.model.JAsset.json2asset; | |||
import static org.cobbzilla.util.daemon.ZillaRuntime.die; | |||
import static org.cobbzilla.util.io.FileUtil.*; | |||
import static org.cobbzilla.util.system.CommandShell.execScript; | |||
@@ -21,16 +21,17 @@ import static org.cobbzilla.util.system.CommandShell.execScript; | |||
public class TrimExec extends ExecBase<TrimOperation> { | |||
public static final String TRIM_TEMPLATE | |||
= "{{ffmpeg}} -i {{{source.path}}} -ss {{startSeconds}} {{#exists interval}}-t {{interval}} {{/exists}}{{{output.path}}}"; | |||
= "{{ffmpeg}} -i {{{source.path}}} " + | |||
"-ss {{startSeconds}} " + | |||
"{{#exists interval}}-t {{interval}} {{/exists}}" + | |||
"-y {{{output.path}}}"; | |||
@Override public void operate(TrimOperation op, Toolbox toolbox, AssetManager assetManager) { | |||
final JAsset source = assetManager.resolve(op.getTrim()); | |||
final JAsset output = json2asset(op.getCreates()); | |||
output.mergeFormat(source.getFormat()); | |||
final JFileExtension formatType = output.getFormat().getFileExtension(); | |||
final JSingleOperationContext opCtx = op.getSingleInputContext(assetManager); | |||
final JAsset source = opCtx.source; | |||
final JAsset output = opCtx.output; | |||
final JFileExtension formatType = opCtx.formatType; | |||
if (source.hasList()) { | |||
if (output.hasDest()) { | |||
@@ -2,7 +2,7 @@ package jvcl.service; | |||
import jvcl.model.JAsset; | |||
import jvcl.model.JFileExtension; | |||
import jvcl.model.JOperation; | |||
import jvcl.model.operation.JOperation; | |||
import org.cobbzilla.util.handlebars.HandlebarsUtil; | |||
import java.io.File; | |||
@@ -1,6 +1,6 @@ | |||
package jvcl.service; | |||
import jvcl.model.JOperation; | |||
import jvcl.model.operation.JOperation; | |||
public class OperationEngine { | |||
@@ -4,7 +4,7 @@ import com.fasterxml.jackson.databind.DeserializationContext; | |||
import com.fasterxml.jackson.databind.JavaType; | |||
import com.fasterxml.jackson.databind.deser.DeserializationProblemHandler; | |||
import com.fasterxml.jackson.databind.jsontype.TypeIdResolver; | |||
import jvcl.model.JOperation; | |||
import jvcl.model.operation.JOperation; | |||
import jvcl.operation.exec.ExecBase; | |||
import java.io.IOException; | |||
@@ -9,7 +9,7 @@ | |||
"name": "combined_vid", | |||
"dest": "src/test/resources/outputs/combined.mp4" | |||
}, | |||
"concat": ["vid1_splits[1..]"] | |||
"sources": ["vid1_splits[1..]"] | |||
} | |||
] | |||
} |
@@ -0,0 +1,24 @@ | |||
{ | |||
"assets": [ | |||
{ | |||
"name": "img1", | |||
"path": "https://live.staticflickr.com/65535/48159911972_01efa0e5ea_b.jpg", | |||
"dest": "src/test/resources/sources/" | |||
} | |||
], | |||
"operations": [ | |||
{ | |||
"operation": "ken-burns", // name of the operation | |||
"creates": "ken1", // asset it creates | |||
"source": "img1", // source image | |||
"zoom": "1.3", // zoom level, from 1 to 10 | |||
"duration": "5", // how long the resulting video will be | |||
"start": "0", // when to start zooming, default is 0 | |||
"end": "duration", // when to end zooming, default is duration | |||
"x": "source.width * 0.6", // pan to this x-position | |||
"y": "source.height * 0.4", // pan to this y-position | |||
"width": "1024", // width of output video | |||
"height": "768" // height of output video | |||
} | |||
] | |||
} |
@@ -18,8 +18,8 @@ | |||
"name": "vid1_splits", | |||
"dest": "src/test/resources/outputs/" | |||
}, | |||
"split": "vid1", // split this source asset | |||
"interval": "10", // split every ten seconds | |||
"source": "vid1", // split this source asset | |||
"interval": "10", // split every ten seconds | |||
"start": "65", // start one minute and five seconds into the video | |||
"end": "100" // end 100 seconds into the video | |||
} | |||
@@ -9,7 +9,7 @@ | |||
"name": "vid1_trims", | |||
"dest": "src/test/resources/outputs/trims/" | |||
}, | |||
"trim": "vid1_splits", // trim these source assets | |||
"source": "vid1_splits", // trim these source assets | |||
"start": "1", // cropped region starts here, default is zero | |||
"end": "6" // cropped region ends here, default is end of video | |||
} | |||