@@ -0,0 +1,66 @@ | |||
#!/bin/bash | |||
# | |||
# Concatenate media files | |||
# | |||
# Usage: | |||
# | |||
# jconcat out-file in-file-1 [in-file-2 ...] | |||
# | |||
# out-file : output file | |||
# in-file-N : input files | |||
# | |||
# BEWARE SHELL WILDCARDS! | |||
# Shell wildcards will match files in non-deterministic order, meaning that | |||
# your combined file will be assembled out of order. | |||
# | |||
# If you are combining files that were split with `jvcl` (or `jsplit`), then | |||
# sorting them by time (oldest first) is sufficient. | |||
# | |||
# For example, do this: | |||
# | |||
# jconcat combined-file.mp4 $(ls -1tr path/to/parts*.mp4) | |||
# | |||
# NOT THIS: | |||
# | |||
# jconcat combined-file.mp4 path/to/parts*.mp4 # args not ordered! | |||
# | |||
SCRIPT="${0}" | |||
SCRIPT_DIR="$(cd "$(dirname "${SCRIPT}")" && pwd)" | |||
. "${SCRIPT_DIR}"/jvcl_common | |||
OUT_FILE="${1?no out-file provided}" | |||
shift | |||
IN_FILE="${1?no in-file(s) provided}" | |||
echo " | |||
{ | |||
\"assets\": [ | |||
$( | |||
F_INDEX=0 | |||
for path in "$@" ; do | |||
if [[ ${F_INDEX} -gt 0 ]] ; then | |||
echo "," | |||
fi | |||
F_INDEX=$((F_INDEX + 1)) | |||
echo -n " { \"name\": \"input_${F_INDEX}\", \"path\": \"${path}\" }" | |||
shift | |||
done) | |||
], | |||
\"operations\": [ | |||
{ | |||
\"operation\": \"concat\", | |||
\"creates\": { | |||
\"name\": \"combined\", | |||
\"dest\": \"${OUT_FILE}\" | |||
}, | |||
\"sources\": [$(END=$# | |||
for ((i=1;i<=END;i++)); do | |||
if [[ ${i} -gt 1 ]] ; then echo -n "," ; fi | |||
echo -n " | |||
\"input_${i}\"" | |||
done) | |||
] | |||
} | |||
] | |||
} | |||
" | "${SCRIPT_DIR}"/jvcl -z ${JVCL_OPTIONS} |
@@ -0,0 +1,46 @@ | |||
#!/bin/bash | |||
#!/bin/bash | |||
# | |||
# Letterbox a video file | |||
# | |||
# Usage: | |||
# | |||
# jletterbox in-file out-file width height [color] | |||
# | |||
# in-file : file to trim | |||
# out-file : write scaled file here | |||
# width : output width | |||
# height : output height | |||
# color : padding color, can be a hex value (0xff0000 is red), or a color | |||
# name from https://ffmpeg.org/ffmpeg-utils.html#color-syntax | |||
# | |||
SCRIPT="${0}" | |||
SCRIPT_DIR="$(cd "$(dirname "${SCRIPT}")" && pwd)" | |||
. "${SCRIPT_DIR}"/jvcl_common | |||
IN_FILE="${1?no in-file provided}" | |||
OUT_FILE="${2?no out-file provided}" | |||
WIDTH="${3}" | |||
HEIGHT="${4}" | |||
COLOR="${5:-black}" | |||
echo " | |||
{ | |||
\"assets\": [ | |||
{ \"name\": \"input\", \"path\": \"${IN_FILE}\" } | |||
], | |||
\"operations\": [ | |||
{ | |||
\"operation\": \"letterbox\", | |||
\"creates\": { | |||
\"name\": \"boxed\", | |||
\"dest\": \"${OUT_FILE}\" | |||
}, | |||
\"source\": \"input\", | |||
\"width\": \"${WIDTH}\", | |||
\"height\": \"${HEIGHT}\", | |||
\"color\": \"${COLOR}\" | |||
} | |||
] | |||
} | |||
" | "${SCRIPT_DIR}"/jvcl ${JVCL_OPTIONS} |
@@ -0,0 +1,67 @@ | |||
#!/bin/bash | |||
# | |||
# Scale a media file | |||
# | |||
# Usage: | |||
# | |||
# jscale in-file out-file factor | |||
# or | |||
# jscale in-file out-file width height | |||
# | |||
# in-file : file to trim | |||
# out-file : write scaled file here | |||
# factor : scale factor | |||
# width : output width | |||
# height : output height | |||
# | |||
SCRIPT="${0}" | |||
SCRIPT_DIR="$(cd "$(dirname "${SCRIPT}")" && pwd)" | |||
. "${SCRIPT_DIR}"/jvcl_common | |||
IN_FILE="${1?no in-file provided}" | |||
OUT_FILE="${2?no out-file provided}" | |||
WIDTH="${3}" | |||
HEIGHT="${4}" | |||
FACTOR="" | |||
if [[ -z "${HEIGHT}" ]] ; then | |||
FACTOR=${WIDTH} | |||
echo " | |||
{ | |||
\"assets\": [ | |||
{ \"name\": \"input\", \"path\": \"${IN_FILE}\" } | |||
], | |||
\"operations\": [ | |||
{ | |||
\"operation\": \"scale\", | |||
\"creates\": { | |||
\"name\": \"scaled\", | |||
\"dest\": \"${OUT_FILE}\" | |||
}, | |||
\"source\": \"input\", | |||
\"factor\": \"${FACTOR}\" | |||
} | |||
] | |||
} | |||
" | "${SCRIPT_DIR}"/jvcl ${JVCL_OPTIONS} | |||
else | |||
echo " | |||
{ | |||
\"assets\": [ | |||
{ \"name\": \"input\", \"path\": \"${IN_FILE}\" } | |||
], | |||
\"operations\": [ | |||
{ | |||
\"operation\": \"scale\", | |||
\"creates\": { | |||
\"name\": \"scaled\", | |||
\"dest\": \"${OUT_FILE}\" | |||
}, | |||
\"source\": \"input\", | |||
\"width\": \"${WIDTH}\", | |||
\"height\": \"${HEIGHT}\" | |||
} | |||
] | |||
} | |||
" | "${SCRIPT_DIR}"/jvcl ${JVCL_OPTIONS} | |||
fi |
@@ -0,0 +1,49 @@ | |||
#!/bin/bash | |||
# | |||
# Split a media file into multiple files of equal time length | |||
# | |||
# Usage: | |||
# | |||
# jsplit in-file out-dir interval [start] [end] | |||
# | |||
# in-file : file to trim | |||
# out-dir : write split files to this directory (will be created if it does not exist) | |||
# interval : time duration of output files, in seconds | |||
# start : when to start splitting the in-file. default is 0 (start) | |||
# end : when to stop splitting the in-file. default is to continue until end of file is reached | |||
# | |||
SCRIPT="${0}" | |||
SCRIPT_DIR="$(cd "$(dirname "${SCRIPT}")" && pwd)" | |||
. "${SCRIPT_DIR}"/jvcl_common | |||
IN_FILE="${1?no in-file provided}" | |||
OUT_DIR="${2?no out-dir provided}" | |||
if [[ -e "${OUT_DIR}" && ! -d "${OUT_DIR}" ]] ; then | |||
die "Not a directory: ${OUT_DIR}" | |||
fi | |||
mkdir -p "${OUT_DIR}" || die "Error creating ${OUT_DIR}" | |||
INTERVAL="${3?no interval provided}" | |||
T_START="${4}" | |||
T_END="${5}" | |||
echo " | |||
{ | |||
\"assets\": [ | |||
{ \"name\": \"input\", \"path\": \"${IN_FILE}\" } | |||
], | |||
\"operations\": [ | |||
{ | |||
\"operation\": \"split\", | |||
\"creates\": { | |||
\"name\": \"splits\", | |||
\"dest\": \"${OUT_DIR}/\" | |||
}, | |||
\"source\": \"input\", | |||
\"interval\": \"${INTERVAL}\"$(if [[ -n "${T_START}" ]] ; then echo ", | |||
\"start\": \"${T_START}\"" ; fi)$(if [[ -n "${T_END}" ]] ; then echo ", | |||
\"end\": \"${T_END}\"," ; fi) | |||
} | |||
] | |||
} | |||
" | "${SCRIPT_DIR}"/jvcl ${JVCL_OPTIONS} |
@@ -19,6 +19,7 @@ IN_FILE="${1?no in-file provided}" | |||
OUT_FILE="${2?no out-file provided}" | |||
T_START="${3}" | |||
T_END="${4}" | |||
echo " | |||
{ | |||
\"assets\": [ | |||
@@ -31,10 +32,10 @@ echo " | |||
\"name\": \"trimmed\", | |||
\"dest\": \"${OUT_FILE}\" | |||
}, | |||
\"trim\": \"input\", | |||
\"source\": \"input\", | |||
\"start\": \"${T_START}\", | |||
\"end\": \"${T_END}\" | |||
} | |||
] | |||
} | |||
" | "${SCRIPT_DIR}"/jvcl | |||
" | "${SCRIPT_DIR}"/jvcl ${JVCL_OPTIONS} |
@@ -19,6 +19,15 @@ function handle_help_request() { | |||
break | |||
fi | |||
done <"${1}" | |||
echo "# Environment Variables | |||
# | |||
# JVCL_SCRATCH_DIR : Use this as the scratch directory | |||
# Default is to create a new temp directory | |||
# | |||
# JVCL_NO_EXEC : If set to anything, print the commands that would | |||
# have run but do not execute anything | |||
# | |||
" | |||
exit 1 | |||
fi | |||
} | |||
@@ -33,4 +42,15 @@ if [[ -z "${JVCL_JAR}" ]] ; then | |||
fi | |||
fi | |||
SCRATCH_DIR="" | |||
if [[ -n "${JVCL_SCRATCH_DIR}" ]] ; then | |||
SCRATCH_DIR="--temp-dir ${JVCL_SCRATCH_DIR}" | |||
fi | |||
NO_EXEC="" | |||
if [[ -n "${JVCL_NO_EXEC}" ]] ; then | |||
NO_EXEC="--no-exec" | |||
fi | |||
JVCL_OPTIONS="${SCRATCH_DIR} ${NO_EXEC}" | |||
handle_help_request "${0}" "${1}" |
@@ -12,8 +12,7 @@ import org.kohsuke.args4j.Option; | |||
import java.io.File; | |||
import static jvcl.service.Toolbox.JSON_MAPPER; | |||
import static org.cobbzilla.util.daemon.ZillaRuntime.die; | |||
import static org.cobbzilla.util.daemon.ZillaRuntime.readStdin; | |||
import static org.cobbzilla.util.daemon.ZillaRuntime.*; | |||
import static org.cobbzilla.util.io.FileUtil.abs; | |||
import static org.cobbzilla.util.io.FileUtil.toStringOrDie; | |||
import static org.cobbzilla.util.json.JsonUtil.json; | |||
@@ -37,7 +36,7 @@ public class JvclOptions extends BaseMainOptions { | |||
try { | |||
return json(json, JSpec.class, JSON_MAPPER); | |||
} catch (Exception e) { | |||
return die("getSpec: invalid spec: "+specFile); | |||
return die("getSpec: invalid spec: "+specFile+": "+shortError(e)); | |||
} | |||
} | |||
@@ -120,7 +120,7 @@ public class AssetManager { | |||
final int fromIndex = parseIndexExpression(from, list, JIndexType.from); | |||
final int toIndex = parseIndexExpression(to, list, JIndexType.to); | |||
if (toIndex < fromIndex) return die("parseIndexExpression("+indexExpr+"): 'to' index ("+toIndex+") < 'from' index ("+fromIndex+")"); | |||
final int len = toIndex - fromIndex; | |||
final int len = 1 + (toIndex - fromIndex); | |||
final JAsset[] subList = new JAsset[len]; | |||
System.arraycopy(list, fromIndex, subList, 0, len); | |||
return new JAsset(asset).setList(subList); | |||
@@ -142,7 +142,7 @@ public class AssetManager { | |||
if (empty(indexExpr)) { | |||
switch (type) { | |||
case from: return 0; | |||
case to: return list.length; | |||
case to: return list.length-1; | |||
case single: return die("parseIndexExpression(): no expression provided!"); | |||
default: return die("parseIndexExpression(): invalid type: "+type); | |||
} | |||
@@ -6,11 +6,16 @@ | |||
"operations": [ | |||
{ | |||
"operation": "concat", // name of the operation | |||
"sources": ["vid1_splits[1..]"], // concatentate these sources (everything but the first video in vid1_splits) | |||
"sources": ["vid1_splits"], // concatentate these sources -- all split files | |||
"creates": { | |||
"name": "combined_vid", // name of the output asset | |||
"dest": "src/test/resources/outputs/combined.mp4" // asset will be written to this file | |||
}, | |||
"name": "combined_vid1", // name of the output asset | |||
"dest": "src/test/resources/outputs/combined1.mp4" // asset will be written to this file | |||
} | |||
}, | |||
{ | |||
"operation": "concat", // name of the operation | |||
"sources": ["vid1_splits[1..2]"], // concatentate these sources -- the 2nd and 3rd files only | |||
"creates": "combined_vid2", // name of the output asset, will be written to scratch directory | |||
} | |||
] | |||
} |