124 lines
3.7 KiB
Bash
124 lines
3.7 KiB
Bash
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
# title_one_line.sh — one centered line with fade in/out in a time window
|
|
# Requires: ffmpeg with libfreetype
|
|
|
|
show_help() {
|
|
cat <<'EOF'
|
|
Usage:
|
|
title_one_line.sh -i INPUT -o OUTPUT --text "YOUR TEXT" [options]
|
|
|
|
Required:
|
|
-i, --input Input video
|
|
-o, --output Output video
|
|
--text Text to display (one line)
|
|
|
|
Timing:
|
|
--start Start time seconds (default: 30)
|
|
--end End time seconds (default: 34)
|
|
--fade-in Fade-in seconds (default: 0.6)
|
|
--fade-out Fade-out seconds (default: 0.6)
|
|
|
|
Style:
|
|
--fs Font size (default: 78)
|
|
--dy Vertical nudge in px (default: 0) # negative = up
|
|
--font Font family (FontConfig), e.g. "DejaVu Sans"
|
|
--fontfile Path to TTF/OTF (overrides --font)
|
|
--shadow 1 to add shadow, 0 to disable (default: 1)
|
|
--color Text color (default: white)
|
|
|
|
Encoding:
|
|
--crf x264 CRF (default: 18)
|
|
--preset x264 preset (default: medium)
|
|
|
|
Examples:
|
|
title_one_line.sh -i in.mp4 -o out.mp4 --text "ROSE ASH VILLAGE HALL presents"
|
|
title_one_line.sh -i in.mp4 -o out.mp4 --text "Summer Fête" --start 12 --end 18 --fs 90 --dy -30
|
|
EOF
|
|
}
|
|
|
|
# --- defaults ---
|
|
INPUT=""
|
|
OUTPUT=""
|
|
TEXT=""
|
|
START="30"
|
|
END="34"
|
|
FADEIN="0.6"
|
|
FADEOUT="0.6"
|
|
FS="78"
|
|
DY="0"
|
|
FONT=""
|
|
FONTFILE=""
|
|
SHADOW="1"
|
|
COLOR="white"
|
|
CRF="18"
|
|
PRESET="medium"
|
|
|
|
# --- parse args ---
|
|
[[ $# -eq 0 ]] && { show_help; exit 1; }
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
-i|--input) INPUT="${2:-}"; shift 2;;
|
|
-o|--output) OUTPUT="${2:-}"; shift 2;;
|
|
--text) TEXT="${2:-}"; shift 2;;
|
|
--start) START="${2:-}"; shift 2;;
|
|
--end) END="${2:-}"; shift 2;;
|
|
--fade-in) FADEIN="${2:-}"; shift 2;;
|
|
--fade-out) FADEOUT="${2:-}"; shift 2;;
|
|
--fs) FS="${2:-}"; shift 2;;
|
|
--dy) DY="${2:-}"; shift 2;;
|
|
--font) FONT="${2:-}"; shift 2;;
|
|
--fontfile) FONTFILE="${2:-}"; shift 2;;
|
|
--shadow) SHADOW="${2:-}"; shift 2;;
|
|
--color) COLOR="${2:-}"; shift 2;;
|
|
--crf) CRF="${2:-}"; shift 2;;
|
|
--preset) PRESET="${2:-}"; shift 2;;
|
|
-h|--help) show_help; exit 0;;
|
|
*) echo "Unknown arg: $1"; echo; show_help; exit 1;;
|
|
esac
|
|
done
|
|
|
|
[[ -z "$INPUT" || -z "$OUTPUT" || -z "$TEXT" ]] && { echo "Error: -i, -o and --text are required."; exit 1; }
|
|
command -v ffmpeg >/dev/null 2>&1 || { echo "Error: ffmpeg not found."; exit 1; }
|
|
|
|
# Escape text for drawtext (backslash, colon, percent, and single quote)
|
|
esc() {
|
|
local s="$1"
|
|
s="${s//\\/\\\\}" # backslashes
|
|
s="${s//:/\\:}" # colons
|
|
s="${s//%/\\%}" # percent
|
|
s="${s//\'/\\\'}" # single quotes
|
|
echo "$s"
|
|
}
|
|
ETEXT="$(esc "$TEXT")"
|
|
|
|
# Font options: prefer fontfile, else family, else default
|
|
FONT_OPTS=""
|
|
if [[ -n "$FONTFILE" ]]; then
|
|
FONT_OPTS="fontfile='${FONTFILE}'"
|
|
elif [[ -n "$FONT" ]]; then
|
|
FONT_OPTS="font='${FONT}'"
|
|
fi
|
|
|
|
# Optional shadow for contrast
|
|
SHADOW_OPTS=""
|
|
if [[ "$SHADOW" == "1" ]]; then
|
|
SHADOW_OPTS=":shadowcolor=black@0.6:shadowx=2:shadowy=2"
|
|
fi
|
|
|
|
# Alpha expression: fade in, hold, fade out (commas escaped)
|
|
ALPHA="if(lt(t\\,${START})\\,0\\,if(lt(t\\,${START}+${FADEIN})\\,(t-${START})/${FADEIN}\\,if(lt(t\\,${END}-${FADEOUT})\\,1\\,if(lt(t\\,${END})\\,(${END}-t)/${FADEOUT}\\,0))))"
|
|
|
|
DT="drawtext=${FONT_OPTS}:text='${ETEXT}':fontcolor=${COLOR}:fontsize=${FS}${SHADOW_OPTS}:x=(w-text_w)/2:y=(h-text_h)/2+${DY}:alpha='${ALPHA}'"
|
|
|
|
VF="${DT}"
|
|
|
|
echo "Running:"
|
|
echo " ffmpeg -hide_banner -y -i '$INPUT' -vf \"$VF\" -c:v libx264 -crf $CRF -preset $PRESET -c:a copy '$OUTPUT'"
|
|
|
|
ffmpeg -hide_banner -y -i "$INPUT" -vf "$VF" -c:v libx264 -crf "$CRF" -preset "$PRESET" -c:a copy "$OUTPUT"
|
|
|
|
echo "Done: $OUTPUT"
|