#!/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"