110 lines
3.5 KiB
Bash
Executable File
110 lines
3.5 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Converts PNG cards to fading videos.
|
|
# Assumes NONE of the filenames have spaces in them.
|
|
|
|
if [ -z "$3" ]; then
|
|
echo "Usage: $0 <source.mov> <talk ID> <input start timestamp [[HH:]MM:]SS> <input end timestamp [[HH:]MM:]SS>" >&2
|
|
exit 1
|
|
fi
|
|
|
|
set -e
|
|
|
|
PNG_PATH='cards/png'
|
|
VID_OUT_PATH='upload-ready-vids'
|
|
VID_GEN_LOG_PATH="$VID_OUT_PATH/0000_gen_vids_log.txt"
|
|
mkdir -p "${VID_OUT_PATH}"
|
|
|
|
TITLE_SHOW_DURATION_SECS=2
|
|
FADE_DURATION_SECS=1
|
|
|
|
AUDIO_FADE_DURATION_SECS=1
|
|
TITLE_SHOW_DURATION_MSECS=$((TITLE_SHOW_DURATION_SECS * 1000))
|
|
|
|
VID_IN_FNAME="$1"
|
|
TALK_ID="$2"
|
|
INPUT_START_TIMESTAMP="$3"
|
|
INPUT_END_TIMESTAMP="$4"
|
|
|
|
PNG_FNAME=$(ls ${PNG_PATH}/${TALK_ID}-*.png)
|
|
if [ ! -e "${PNG_FNAME}" ]; then
|
|
echo "No such talk card: ${PNG_FNAME}" >&2
|
|
exit 2
|
|
fi
|
|
|
|
BASENAME=$(basename ${PNG_FNAME/.png})
|
|
VID_OUT_COMBINED=${VID_OUT_PATH}/${BASENAME}-combined.mkv
|
|
|
|
if [ ! -e "${VID_IN_FNAME}" ]; then
|
|
echo "Source video ${VID_IN_FNAME} does not exist, aborting." >&2
|
|
exit 3
|
|
fi
|
|
|
|
if [ -e "${VID_OUT_COMBINED}" ]; then
|
|
echo "Destination video ${VID_OUT_COMBINED} already exists."
|
|
echo "Press [ENTER] to overwrite, [CTRL]+[C] to abort."
|
|
read dummy
|
|
fi
|
|
|
|
FILTER_COMPLEX="
|
|
[0:v]format=pix_fmts=yuva420p,fade=t=out:st=${TITLE_SHOW_DURATION_SECS}:d=${FADE_DURATION_SECS}:alpha=1,setpts=PTS-STARTPTS[vid_title];
|
|
[1:v]format=pix_fmts=yuva420p,fade=t=in:st=0:d=${FADE_DURATION_SECS}:alpha=1,setpts=PTS-STARTPTS+${TITLE_SHOW_DURATION_SECS}/TB[vid_main];
|
|
[vid_title][vid_main] overlay [vid];
|
|
[2:a] afade=t=out:st=${TITLE_SHOW_DURATION_SECS}:d=${AUDIO_FADE_DURATION_SECS} [aud_title];
|
|
[1:a] afade=t=in:st=0:d=${FADE_DURATION_SECS} [aud_main_faded];
|
|
[aud_main_faded] adelay=${TITLE_SHOW_DURATION_MSECS}|${TITLE_SHOW_DURATION_MSECS} [aud_main];
|
|
[aud_title][aud_main] amix=duration=longest [audio]
|
|
"
|
|
|
|
DURATION=$(ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "${VID_IN_FNAME}")
|
|
TOTAL_DURATION=$(python3 -c "
|
|
import datetime
|
|
def parse_timestamp(timestamp_str):
|
|
return (datetime.datetime.strptime(timestamp_str, '%S') if timestamp_str.count(':') == 0 else
|
|
datetime.datetime.strptime(timestamp_str, '%M:%S') if timestamp_str.count(':') == 1 else
|
|
datetime.datetime.strptime(timestamp_str, '%H:%M:%S') if timestamp_str.count(':') == 2 else ...)
|
|
|
|
input_starttime = parse_timestamp('$INPUT_START_TIMESTAMP')
|
|
input_endtime = parse_timestamp('$INPUT_END_TIMESTAMP')
|
|
duration = (input_endtime - input_starttime).total_seconds()
|
|
print($TITLE_SHOW_DURATION_SECS + (duration if 0.0 < duration < $DURATION else $DURATION))
|
|
")
|
|
|
|
python3 -c "
|
|
import datetime
|
|
input_dur = datetime.timedelta(seconds=$DURATION)
|
|
output_dur = datetime.timedelta(seconds=$TOTAL_DURATION)
|
|
print('Input Duration: ', input_dur, '\nOutput Duration:', output_dur)
|
|
"
|
|
|
|
ffmpeg \
|
|
-stats \
|
|
-v warning \
|
|
-hwaccel auto \
|
|
-loop 1 \
|
|
-i ${PNG_FNAME} \
|
|
-ss ${INPUT_START_TIMESTAMP} \
|
|
-i "${VID_IN_FNAME}" \
|
|
-i silence-24.wav \
|
|
-filter_complex "${FILTER_COMPLEX}" \
|
|
-map '[vid]' \
|
|
-map '[audio]' \
|
|
-preset:v fast \
|
|
-c:v h264 \
|
|
-c:a mp3 \
|
|
-b:a 192k \
|
|
-crf 23 \
|
|
-t ${TOTAL_DURATION} \
|
|
-y "${VID_OUT_COMBINED}"
|
|
|
|
NOW_DATETIME=$(python3 -c "
|
|
import datetime
|
|
print(datetime.datetime.now())
|
|
")
|
|
if [ ! -e "${VID_GEN_LOG_PATH}" ]; then
|
|
echo "\"Date Time\",\"Source File\",\"Talk ID\",\"Start Time\",\"End Time\"" >> "$VID_GEN_LOG_PATH"
|
|
fi
|
|
echo "\"$NOW_DATETIME\",\"$1\",\"$2\",\"$3\",\"$4\"" >> "$VID_GEN_LOG_PATH"
|
|
|
|
echo
|
|
echo 'DØNER'
|