#!/usr/bin/env bash set -Eeuo pipefail # AmigaDB YouTube Shorts generator # Creates a 1080x1920, 50 FPS vertical MP4 from an existing Amiga video. # V4 expanded style pack for 4:3 Amiga footage, with demoscene-inspired FX and optional image/video backgrounds. SCRIPT_NAME="$(basename "$0")" SCRIPT_VERSION="4.2" INPUT="" OUTPUT="" START="00:00:00" DURATION="30" TITLE="" GROUP="" YEAR="" TYPE="" HOOK="THIS RAN ON AN AMIGA" WEBSITE="AMIGADB.NET" FONT_FILE="" STYLE="brand" BACKGROUND_IMAGE="amigadb_short.png" BACKGROUND_VIDEO="" VIDEO_BLUR="0" VIDEO_OPACITY="1.0" SOURCE_CROP_MODE="auto" CROP_LEFT="0" CROP_RIGHT="0" CROP_TOP="0" CROP_BOTTOM="0" SOURCE_MARGIN_X="40" SOURCE_FADE_IN="1.5" CRF="16" PRESET="slow" usage() { cat <&2 exit 1 } command_exists() { command -v "$1" >/dev/null 2>&1 } find_default_font() { local candidate="" if command_exists fc-match; then candidate="$(fc-match -f '%{file}\n' 'DejaVu Sans:style=Bold' 2>/dev/null | head -n 1 || true)" if [[ -n "$candidate" && -f "$candidate" ]]; then printf '%s\n' "$candidate" return 0 fi fi for candidate in \ /usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf \ /usr/share/fonts/truetype/liberation2/LiberationSans-Bold.ttf \ /usr/share/fonts/opentype/noto/NotoSans-Bold.ttf; do if [[ -f "$candidate" ]]; then printf '%s\n' "$candidate" return 0 fi done return 1 } while (($#)); do case "$1" in --input) (($# >= 2)) || fail "Missing value after --input" INPUT="$2" shift 2 ;; --output) (($# >= 2)) || fail "Missing value after --output" OUTPUT="$2" shift 2 ;; --start) (($# >= 2)) || fail "Missing value after --start" START="$2" shift 2 ;; --duration) (($# >= 2)) || fail "Missing value after --duration" DURATION="$2" shift 2 ;; --title) (($# >= 2)) || fail "Missing value after --title" TITLE="$2" shift 2 ;; --group) (($# >= 2)) || fail "Missing value after --group" GROUP="$2" shift 2 ;; --year) (($# >= 2)) || fail "Missing value after --year" YEAR="$2" shift 2 ;; --type) (($# >= 2)) || fail "Missing value after --type" TYPE="$2" shift 2 ;; --hook) (($# >= 2)) || fail "Missing value after --hook" HOOK="$2" shift 2 ;; --website) (($# >= 2)) || fail "Missing value after --website" WEBSITE="$2" shift 2 ;; --font) (($# >= 2)) || fail "Missing value after --font" FONT_FILE="$2" shift 2 ;; --background-image|--background) (($# >= 2)) || fail "Missing value after --background-image" BACKGROUND_IMAGE="$2" shift 2 ;; --background-video) (($# >= 2)) || fail "Missing value after --background-video" BACKGROUND_VIDEO="$2" shift 2 ;; --video-blur|--background-video-blur) (($# >= 2)) || fail "Missing value after --video-blur" VIDEO_BLUR="$2" shift 2 ;; --video-opacity|--background-video-opacity) (($# >= 2)) || fail "Missing value after --video-opacity" VIDEO_OPACITY="$2" shift 2 ;; --source-crop) (($# >= 2)) || fail "Missing value after --source-crop" SOURCE_CROP_MODE="${2,,}" shift 2 ;; --crop-left|--source-crop-left) (($# >= 2)) || fail "Missing value after --crop-left" CROP_LEFT="$2" shift 2 ;; --crop-right|--source-crop-right) (($# >= 2)) || fail "Missing value after --crop-right" CROP_RIGHT="$2" shift 2 ;; --crop-top|--source-crop-top) (($# >= 2)) || fail "Missing value after --crop-top" CROP_TOP="$2" shift 2 ;; --crop-bottom|--source-crop-bottom) (($# >= 2)) || fail "Missing value after --crop-bottom" CROP_BOTTOM="$2" shift 2 ;; --source-margin-x|--video-margin-x|--source-margin) (($# >= 2)) || fail "Missing value after --source-margin-x" SOURCE_MARGIN_X="$2" shift 2 ;; --source-fade-in|--source-fade|--fade-in) (($# >= 2)) || fail "Missing value after --source-fade-in" SOURCE_FADE_IN="$2" shift 2 ;; --style) (($# >= 2)) || fail "Missing value after --style" STYLE="${2,,}" shift 2 ;; --crf) (($# >= 2)) || fail "Missing value after --crf" CRF="$2" shift 2 ;; --preset) (($# >= 2)) || fail "Missing value after --preset" PRESET="$2" shift 2 ;; --version) printf "%s v%s\n" "$SCRIPT_NAME" "$SCRIPT_VERSION" exit 0 ;; -h|--help) usage exit 0 ;; *) fail "Unknown argument: $1" ;; esac done command_exists ffmpeg || fail "ffmpeg is not installed or not in PATH" [[ -n "$INPUT" ]] || fail "--input is required" [[ -f "$INPUT" ]] || fail "Input file not found: $INPUT" [[ -n "$TITLE" ]] || fail "--title is required" [[ "$DURATION" =~ ^[0-9]+([.][0-9]+)?$ ]] || fail "--duration must be a positive number" awk -v d="$DURATION" 'BEGIN { exit !(d > 0) }' || fail "--duration must be greater than zero" [[ "$CRF" =~ ^[0-9]+$ ]] || fail "--crf must be an integer" [[ "$VIDEO_BLUR" =~ ^[0-9]+([.][0-9]+)?$ ]] || fail "--video-blur must be zero or a positive number" awk -v v="$VIDEO_BLUR" 'BEGIN { exit !(v >= 0) }' || fail "--video-blur must be zero or greater" [[ "$VIDEO_OPACITY" =~ ^([0-9]+([.][0-9]+)?|[.][0-9]+)$ ]] || fail "--video-opacity must be a number between 0.0 and 1.0" awk -v v="$VIDEO_OPACITY" 'BEGIN { exit !(v >= 0 && v <= 1) }' || fail "--video-opacity must be between 0.0 and 1.0" case "$SOURCE_CROP_MODE" in auto|center) ;; *) fail "--source-crop must be 'auto' or 'center'" ;; esac for crop_value in "$CROP_LEFT" "$CROP_RIGHT" "$CROP_TOP" "$CROP_BOTTOM"; do [[ "$crop_value" =~ ^[0-9]+$ ]] || fail "Manual crop values must be non-negative integer pixel values" done [[ "$SOURCE_MARGIN_X" =~ ^[0-9]+$ ]] || fail "--source-margin-x must be a non-negative integer pixel value" (( SOURCE_MARGIN_X < 540 )) || fail "--source-margin-x must be less than 540" SOURCE_TARGET_WIDTH=$((1080 - (SOURCE_MARGIN_X * 2))) [[ "$SOURCE_FADE_IN" =~ ^([0-9]+([.][0-9]+)?|[.][0-9]+)$ ]] || fail "--source-fade-in must be zero or a positive number" awk -v v="$SOURCE_FADE_IN" 'BEGIN { exit !(v >= 0) }' || fail "--source-fade-in must be zero or greater" SOURCE_FADE_IN="$(awk -v v="$SOURCE_FADE_IN" -v d="$DURATION" 'BEGIN { if (v > d) v = d; printf "%.3f", v; }')" case "$STYLE" in brand|pixel|mirror|crop|workbench|blur|crt|copper|stars|grid|tiles|scan|starfield|plasma|rasterbars|vhs|monitor|split|spectrum|video) ;; *) fail "Unknown style '$STYLE'. Use brand, pixel, mirror, crop, workbench, blur, crt, copper, stars, grid, tiles, scan, starfield, plasma, rasterbars, vhs, monitor, split, spectrum, or video" ;; esac if [[ -z "$FONT_FILE" ]]; then FONT_FILE="$(find_default_font || true)" fi [[ -n "$FONT_FILE" && -f "$FONT_FILE" ]] || fail "No usable font found. Install fonts-dejavu-core or use --font FILE" if [[ -z "$OUTPUT" ]]; then base="$(basename "$INPUT")" base="${base%.*}" OUTPUT="${base}-${STYLE}-short.mp4" fi mkdir -p "$(dirname "$OUTPUT")" TMP_DIR="$(mktemp -d -t amigadb-short.XXXXXX)" cleanup() { rm -rf "$TMP_DIR" } trap cleanup EXIT printf '%s\n' "$HOOK" > "$TMP_DIR/hook.txt" printf '%s\n' "$TITLE" > "$TMP_DIR/title.txt" META_PARTS=() [[ -n "$GROUP" ]] && META_PARTS+=("$GROUP") [[ -n "$YEAR" ]] && META_PARTS+=("$YEAR") [[ -n "$TYPE" ]] && META_PARTS+=("$TYPE") META="" if ((${#META_PARTS[@]})); then for part in "${META_PARTS[@]}"; do if [[ -n "$META" ]]; then META+=" ยท " fi META+="$part" done fi printf '%s\n' "$META" > "$TMP_DIR/meta.txt" printf '%s\n' "$WEBSITE" > "$TMP_DIR/website.txt" INFO_START="$(awk -v d="$DURATION" 'BEGIN { v=d-7; if (v<0) v=0; printf "%.3f", v }')" CTA_START="$(awk -v d="$DURATION" 'BEGIN { v=d-3.5; if (v<0) v=0; printf "%.3f", v }')" HOOK_END="$(awk -v d="$DURATION" 'BEGIN { v=2.7; if (d&1 || true } | grep -oE 'crop=[0-9]+:[0-9]+:[0-9]+:[0-9]+' \ | sort | uniq -c | sort -nr | head -n 1 | awk '{print $2}' || true)" [[ "$candidate" =~ ^crop=([0-9]+):([0-9]+):([0-9]+):([0-9]+)$ ]] || return 1 local w="${BASH_REMATCH[1]}" local x="${BASH_REMATCH[3]}" # cropdetect can occasionally leave a few black pixels at each side. Trim a # tiny extra safety margin (0.5% of detected width per side), still keeping # even dimensions/offsets for H.264 compatibility. crop_spec="$(awk -v w="$w" -v x="$x" 'BEGIN { inset = int((w * 0.005) / 2) * 2; nw = w - (2 * inset); nx = x + inset; nw = int(nw / 2) * 2; nx = int(nx / 2) * 2; if (nw < 16) exit 1; printf "%d:%d", nw, nx; }')" || return 1 [[ -n "$crop_spec" ]] || return 1 printf '%s\n' "$crop_spec" } if [[ "$STYLE" == "brand" ]]; then BACKGROUND_IMAGE="$(resolve_path "$BACKGROUND_IMAGE" || true)" [[ -n "$BACKGROUND_IMAGE" && -f "$BACKGROUND_IMAGE" ]] || fail "Brand style requires background image. Put amigadb_short.png next to the script or pass --background-image FILE" fi if [[ "$STYLE" == "video" ]]; then [[ -n "$BACKGROUND_VIDEO" ]] || fail "Video style requires --background-video FILE" BACKGROUND_VIDEO="$(resolve_path "$BACKGROUND_VIDEO" || true)" [[ -n "$BACKGROUND_VIDEO" && -f "$BACKGROUND_VIDEO" ]] || fail "Background video not found. Pass a valid file with --background-video FILE" fi SOURCE_CROP_FILTER="crop='min(iw,ih*4/3)':'min(ih,iw*3/4)':'(iw-ow)/2':'(ih-oh)/2'" SOURCE_SCALE_FILTER="scale=${SOURCE_TARGET_WIDTH}:-2:flags=lanczos" SOURCE_CROP_SPEC="centered 4:3" MANUAL_CROP=0 if (( CROP_LEFT > 0 || CROP_RIGHT > 0 || CROP_TOP > 0 || CROP_BOTTOM > 0 )); then MANUAL_CROP=1 fi if [[ "$STYLE" == "video" && "$MANUAL_CROP" -eq 1 ]]; then # Manual values are pixels removed from each source edge. Manual crop takes # precedence over automatic/center crop modes. Scale the remaining picture # to the configured Short source width while preserving its aspect ratio. SOURCE_CROP_FILTER="crop=iw-${CROP_LEFT}-${CROP_RIGHT}:ih-${CROP_TOP}-${CROP_BOTTOM}:${CROP_LEFT}:${CROP_TOP}" SOURCE_SCALE_FILTER="scale=${SOURCE_TARGET_WIDTH}:-2:flags=lanczos" SOURCE_CROP_SPEC="manual L=${CROP_LEFT} R=${CROP_RIGHT} T=${CROP_TOP} B=${CROP_BOTTOM}" elif [[ "$STYLE" == "video" && "$SOURCE_CROP_MODE" == "auto" ]]; then DETECTED_SOURCE_CROP="$(detect_source_horizontal_crop || true)" if [[ -n "$DETECTED_SOURCE_CROP" ]]; then DETECTED_W="${DETECTED_SOURCE_CROP%%:*}" DETECTED_X="${DETECTED_SOURCE_CROP##*:}" SOURCE_CROP_FILTER="crop=${DETECTED_W}:ih:${DETECTED_X}:0" SOURCE_SCALE_FILTER="scale=${SOURCE_TARGET_WIDTH}:-2:flags=lanczos" SOURCE_CROP_SPEC="w=${DETECTED_W}, x=${DETECTED_X} (auto left/right black crop)" else printf 'Warning: could not auto-detect left/right source black borders; using centered 4:3 crop.\n' >&2 fi fi # Escape paths for FFmpeg filter option parsing. ffmpeg_escape_path() { local value="$1" value="${value//\\/\\\\}" value="${value//:/\\:}" value="${value//\'/\\\'}" printf '%s' "$value" } FONT_ESC="$(ffmpeg_escape_path "$FONT_FILE")" HOOK_ESC="$(ffmpeg_escape_path "$TMP_DIR/hook.txt")" TITLE_ESC="$(ffmpeg_escape_path "$TMP_DIR/title.txt")" META_ESC="$(ffmpeg_escape_path "$TMP_DIR/meta.txt")" WEBSITE_ESC="$(ffmpeg_escape_path "$TMP_DIR/website.txt")" BACKGROUND_ESC="" if [[ "$STYLE" == "brand" ]]; then BACKGROUND_ESC="$(ffmpeg_escape_path "$BACKGROUND_IMAGE")" fi HOOK_BOX_Y=0 HOOK_BOX_H=245 HOOK_TEXT_Y=70 HOOK_FONT_SIZE=70 INFO_BOX_Y=1490 INFO_BOX_H=430 TITLE_TEXT_Y=1560 TITLE_FONT_SIZE=62 META_TEXT_Y=1655 META_FONT_SIZE=42 WEBSITE_TEXT_Y=1780 WEBSITE_FONT_SIZE=52 if [[ "$STYLE" == "brand" ]]; then HOOK_BOX_Y=250 HOOK_BOX_H=140 HOOK_TEXT_Y=285 HOOK_FONT_SIZE=60 INFO_BOX_Y=1310 INFO_BOX_H=270 TITLE_TEXT_Y=1360 TITLE_FONT_SIZE=56 META_TEXT_Y=1435 META_FONT_SIZE=38 WEBSITE_TEXT_Y=1510 WEBSITE_FONT_SIZE=48 fi HAS_AUDIO=0 if command_exists ffprobe && ffprobe -v error -select_streams a:0 -show_entries stream=index -of csv=p=0 "$INPUT" | grep -q .; then HAS_AUDIO=1 fi if [[ "$STYLE" == "spectrum" && "$HAS_AUDIO" -ne 1 ]]; then fail "The spectrum style requires an audio stream in the source video" fi case "$STYLE" in brand) BASE_FILTER=$(cat <<'FILTER' [1:v]scale=1080:1920:force_original_aspect_ratio=increase,crop=1080:1920,setsar=1[bg]; [0:v]scale=920:700:force_original_aspect_ratio=decrease,pad=iw+16:ih+16:8:8:color=0x07111f,setsar=1,eq=contrast=1.02:saturation=1.02[fg]; [bg]drawbox=x=65:y=505:w=950:h=730:color=black@0.22:t=fill[bg2]; [bg2][fg]overlay=x=(W-w)/2:y=520:format=auto[base] FILTER ) ;; pixel) BASE_FILTER=$(cat <<'FILTER' [0:v]split=2[bg_src][fg_src]; [bg_src]scale=270:480:force_original_aspect_ratio=increase,crop=270:480,scale=1080:1920:flags=neighbor,eq=brightness=-0.36:saturation=1.18,gblur=sigma=2,setsar=1[bg]; [fg_src]scale=1000:820:force_original_aspect_ratio=decrease,pad=iw+16:ih+16:8:8:color=0x55c8ff,setsar=1[fg]; [bg][fg]overlay=x=(W-w)/2:y=(H-h)/2:format=auto, drawbox=x=0:y='300+20*sin(t*1.70)':w=1080:h=8:color=0x55c8ff@0.72:t=fill, drawbox=x=0:y='1610+18*sin(t*1.35)':w=1080:h=8:color=0xff7020@0.72:t=fill[base] FILTER ) ;; mirror) BASE_FILTER=$(cat <<'FILTER' [0:v]split=4[canvas_src][fg_src][top_src][bottom_src]; [canvas_src]scale=1080:1920:force_original_aspect_ratio=increase,crop=1080:1920,setsar=1,drawbox=x=0:y=0:w=1080:h=1920:color=0x010203:t=fill[canvas]; [fg_src]scale=940:760:force_original_aspect_ratio=decrease,setsar=1[fg]; [top_src]scale=1400:470:flags=lanczos,vflip,eq=brightness=-0.48:contrast=0.92:saturation=0.68,gblur=sigma=7,setsar=1,format=rgba,geq=r='r(X,Y)':g='g(X,Y)':b='b(X,Y)':a='255*0.62*(Y/H)'[top]; [bottom_src]scale=1400:470:flags=lanczos,vflip,eq=brightness=-0.50:contrast=0.90:saturation=0.64,gblur=sigma=7,setsar=1,format=rgba,geq=r='r(X,Y)':g='g(X,Y)':b='b(X,Y)':a='255*0.58*(1-Y/H)'[bottom]; [canvas][top]overlay=x=(W-w)/2:y=250:format=auto[tmp1]; [tmp1][bottom]overlay=x=(W-w)/2:y=1435:format=auto[tmp2]; [tmp2][fg]overlay=x=(W-w)/2:y=(H-h)/2:format=auto[base] FILTER ) ;; crop) BASE_FILTER=$(cat <<'FILTER' [0:v]scale=1080:1920:force_original_aspect_ratio=increase,crop=1080:1920,setsar=1,eq=brightness=-0.04:saturation=1.05[base] FILTER ) ;; workbench) BASE_FILTER=$(cat <<'FILTER' [0:v]split=2[canvas_src][screen_src]; [canvas_src]scale=1080:1920:force_original_aspect_ratio=increase,crop=1080:1920,setsar=1, drawbox=x=0:y=0:w=1080:h=1920:color=0x090c20:t=fill, drawbox=x=55:y=410:w=970:h=930:color=0xc4c8d0:t=fill, drawbox=x=70:y=425:w=940:h=900:color=0x1b4f9c:t=fill, drawbox=x=70:y=425:w=940:h=74:color=0xf28c28:t=fill, drawbox=x=88:y=445:w=34:h=34:color=0x111111:t=fill[window]; [screen_src]scale=960:760:force_original_aspect_ratio=decrease,pad=iw+10:ih+10:5:5:color=0xffffff,setsar=1[screen]; [window][screen]overlay=x=(W-w)/2:y=525:format=auto[base] FILTER ) ;; blur) BASE_FILTER=$(cat <<'FILTER' [0:v]split=2[bg_src][fg_src]; [bg_src]scale=1080:1920:force_original_aspect_ratio=increase,crop=1080:1920,gblur=sigma=35,eq=brightness=-0.22:saturation=0.85,setsar=1[bg]; [fg_src]scale=1080:900:force_original_aspect_ratio=decrease,setsar=1[fg]; [bg][fg]overlay=x=(W-w)/2:y=(H-h)/2:format=auto[base] FILTER ) ;; crt) BASE_FILTER=$(cat <<'FILTER' [0:v]split=2[bg_src][fg_src]; [bg_src]scale=1080:1920:force_original_aspect_ratio=increase,crop=1080:1920,gblur=sigma=28,eq=brightness=-0.45:saturation=0.55,setsar=1[bg]; [fg_src]scale=980:800:force_original_aspect_ratio=decrease,lenscorrection=k1=-0.060:k2=0.020,vignette=angle=PI/5,eq=contrast=1.08:saturation=1.10,setsar=1, drawgrid=width=iw:height=4:thickness=1:color=black@0.18[fg]; [bg][fg]overlay=x=(W-w)/2:y=(H-h)/2:format=auto, drawbox=x=50:y='220+mod(t*340,1480)':w=980:h=4:color=white@0.08:t=fill[base] FILTER ) ;; copper) BASE_FILTER=$(cat <<'FILTER' [0:v]split=2[bg_src][fg_src]; [bg_src]scale=1080:1920:force_original_aspect_ratio=increase,crop=1080:1920,eq=brightness=-0.50:saturation=0.80,gblur=sigma=18,setsar=1, drawbox=x=0:y=0:w=1080:h=1920:color=0x061a5f@0.55:t=fill, drawbox=x=0:y='220+20*sin(t*1.00)':w=1080:h=140:color=0x123fcb@0.28:t=fill, drawbox=x=0:y='420+24*sin(t*1.20)':w=1080:h=100:color=0x2b8cff@0.22:t=fill, drawbox=x=0:y='660+16*sin(t*1.50)':w=1080:h=120:color=0x00b8ff@0.18:t=fill, drawbox=x=0:y='1110+22*sin(t*1.10)':w=1080:h=150:color=0xff7a1f@0.20:t=fill, drawbox=x=0:y='1360+18*sin(t*1.30)':w=1080:h=110:color=0xffc120@0.18:t=fill, drawbox=x=0:y='1660+15*sin(t*1.70)':w=1080:h=130:color=0x58d4ff@0.18:t=fill[bg]; [fg_src]scale=1000:820:force_original_aspect_ratio=decrease,pad=iw+14:ih+14:7:7:color=0xffffff,setsar=1[fg]; [bg][fg]overlay=x=(W-w)/2:y=(H-h)/2:format=auto[base] FILTER ) ;; stars) BASE_FILTER=$(cat <<'FILTER' [0:v]split=2[bg_src][fg_src]; [bg_src]scale=1080:1920:force_original_aspect_ratio=increase,crop=1080:1920,gblur=sigma=30,eq=brightness=-0.72:saturation=0.28,setsar=1, drawbox=x=0:y=0:w=1080:h=1920:color=0x05081b@0.75:t=fill, drawbox=x='mod(100+80*t,1080)':y='mod(80+210*t,1920)':w=3:h=3:color=white@0.95:t=fill, drawbox=x='mod(880-55*t,1080)':y='mod(220+170*t,1920)':w=2:h=2:color=white@0.85:t=fill, drawbox=x='mod(300+120*t,1080)':y='mod(520+140*t,1920)':w=3:h=3:color=0x9ad8ff@0.90:t=fill, drawbox=x='mod(980-75*t,1080)':y='mod(710+160*t,1920)':w=2:h=2:color=white@0.80:t=fill, drawbox=x='mod(420+65*t,1080)':y='mod(1040+200*t,1920)':w=4:h=4:color=0xffd46b@0.85:t=fill, drawbox=x='mod(750-90*t,1080)':y='mod(1280+110*t,1920)':w=2:h=2:color=white@0.88:t=fill, drawbox=x='mod(210+75*t,1080)':y='mod(1440+180*t,1920)':w=3:h=3:color=0x9ad8ff@0.90:t=fill, drawbox=x='mod(1030-60*t,1080)':y='mod(1680+140*t,1920)':w=2:h=2:color=white@0.82:t=fill, drawbox=x='mod(560+95*t,1080)':y='mod(260+260*t,1920)':w=2:h=2:color=white@0.92:t=fill, drawbox=x='mod(60+110*t,1080)':y='mod(860+120*t,1920)':w=3:h=3:color=0xffd46b@0.82:t=fill[bg]; [fg_src]scale=1000:820:force_original_aspect_ratio=decrease,pad=iw+12:ih+12:6:6:color=0x55c8ff,setsar=1[fg]; [bg][fg]overlay=x=(W-w)/2:y=(H-h)/2:format=auto[base] FILTER ) ;; grid) BASE_FILTER=$(cat <<'FILTER' [0:v]split=2[bg_src][fg_src]; [bg_src]scale=1080:1920:force_original_aspect_ratio=increase,crop=1080:1920,gblur=sigma=24,eq=brightness=-0.62:saturation=0.36,setsar=1, drawbox=x=0:y=0:w=1080:h=1920:color=0x090b17@0.72:t=fill, drawgrid=width=90:height=90:thickness=2:color=0x39d2ff@0.18, drawbox=x=0:y=0:w=1080:h=1120:color=0x050814@0.80:t=fill, drawbox=x=0:y=1120:w=1080:h=4:color=0xff54d9@0.70:t=fill[bg]; [fg_src]scale=1000:820:force_original_aspect_ratio=decrease,pad=iw+14:ih+14:7:7:color=0xff54d9,setsar=1[fg]; [bg][fg]overlay=x=(W-w)/2:y=(H-h)/2:format=auto[base] FILTER ) ;; tiles) BASE_FILTER=$(cat <<'FILTER' [0:v]split=5[fg_src][a][b][c][d]; [a]scale=540:960:force_original_aspect_ratio=increase,crop=540:960,eq=brightness=-0.45:saturation=0.68,gblur=sigma=4,setsar=1[ta]; [b]scale=540:960:force_original_aspect_ratio=increase,crop=540:960,hflip,eq=brightness=-0.45:saturation=0.68,gblur=sigma=4,setsar=1[tb]; [c]scale=540:960:force_original_aspect_ratio=increase,crop=540:960,vflip,eq=brightness=-0.45:saturation=0.68,gblur=sigma=4,setsar=1[tc]; [d]scale=540:960:force_original_aspect_ratio=increase,crop=540:960,hflip,vflip,eq=brightness=-0.45:saturation=0.68,gblur=sigma=4,setsar=1[td]; [ta][tb]hstack=inputs=2[top]; [tc][td]hstack=inputs=2[bottom]; [top][bottom]vstack=inputs=2[bg]; [fg_src]scale=980:800:force_original_aspect_ratio=decrease,pad=iw+16:ih+16:8:8:color=0xffffff,setsar=1[fg]; [bg][fg]overlay=x=(W-w)/2:y=(H-h)/2:format=auto[base] FILTER ) ;; scan) BASE_FILTER=$(cat <<'FILTER' [0:v]split=2[bg_src][fg_src]; [bg_src]scale=1080:1920:force_original_aspect_ratio=increase,crop=1080:1920,scale=360:640:flags=neighbor,scale=1080:1920:flags=neighbor,eq=brightness=-0.44:saturation=1.05,gblur=sigma=2,setsar=1[bg]; [fg_src]scale=1000:820:force_original_aspect_ratio=decrease,pad=iw+16:ih+16:8:8:color=0x55c8ff,setsar=1[fg]; [bg][fg]overlay=x=(W-w)/2:y=(H-h)/2:format=auto, drawbox=x=40:y='220+mod(t*380,1480)':w=1000:h=8:color=white@0.14:t=fill, drawbox=x=0:y='280+20*sin(t*1.40)':w=1080:h=6:color=0x55c8ff@0.45:t=fill, drawbox=x=0:y='1640+15*sin(t*1.20)':w=1080:h=6:color=0xff7020@0.45:t=fill[base] FILTER ) ;; starfield) BASE_FILTER=$(cat <<'FILTER' [0:v]split=2[bg_src][fg_src]; [bg_src]scale=1080:1920:force_original_aspect_ratio=increase,crop=1080:1920,gblur=sigma=32,eq=brightness=-0.78:saturation=0.22,setsar=1, drawbox=x=0:y=0:w=1080:h=1920:color=0x020511@0.82:t=fill, drawbox=x='mod(120+75*t,1080)':y='mod(80+520*t,1920)':w=3:h=18:color=white@0.92:t=fill, drawbox=x='mod(970-55*t,1080)':y='mod(250+430*t,1920)':w=2:h=14:color=0xa4dcff@0.85:t=fill, drawbox=x='mod(370+95*t,1080)':y='mod(430+600*t,1920)':w=4:h=24:color=white@0.90:t=fill, drawbox=x='mod(810-80*t,1080)':y='mod(640+470*t,1920)':w=2:h=17:color=0xffdc83@0.85:t=fill, drawbox=x='mod(230+65*t,1080)':y='mod(900+540*t,1920)':w=3:h=20:color=white@0.88:t=fill, drawbox=x='mod(1020-100*t,1080)':y='mod(1110+650*t,1920)':w=4:h=26:color=0x8fd5ff@0.90:t=fill, drawbox=x='mod(560+85*t,1080)':y='mod(1320+490*t,1920)':w=2:h=16:color=white@0.86:t=fill, drawbox=x='mod(70+110*t,1080)':y='mod(1510+590*t,1920)':w=3:h=22:color=0xffdc83@0.82:t=fill, drawbox=x='mod(720-60*t,1080)':y='mod(1720+450*t,1920)':w=2:h=15:color=white@0.88:t=fill, drawbox=x='mod(440+105*t,1080)':y='mod(180+700*t,1920)':w=3:h=28:color=0xa4dcff@0.88:t=fill, drawbox=x='mod(900-72*t,1080)':y='mod(780+560*t,1920)':w=2:h=18:color=white@0.84:t=fill, drawbox=x='mod(310+90*t,1080)':y='mod(1240+620*t,1920)':w=4:h=24:color=white@0.90:t=fill[bg]; [fg_src]scale=1000:820:force_original_aspect_ratio=decrease,pad=iw+14:ih+14:7:7:color=0x70cfff,setsar=1[fg]; [bg][fg]overlay=x=(W-w)/2:y=(H-h)/2:format=auto[base] FILTER ) ;; plasma) BASE_FILTER=$(cat <<'FILTER' [0:v]split=2[dim_src][fg_src]; nullsrc=size=1080x1920:rate=50,format=rgb24, geq=r='128+105*sin(X/68+T*1.9)+22*sin(Y/91-T*1.1)':g='128+96*sin(Y/75+T*1.5)+26*sin((X+Y)/120+T*1.3)':b='128+110*sin((X-Y)/82-T*1.7)', gblur=sigma=18,eq=brightness=-0.16:saturation=1.22[plasma]; [dim_src]scale=1080:1920:force_original_aspect_ratio=increase,crop=1080:1920,gblur=sigma=26,eq=brightness=-0.70:saturation=0.35,setsar=1[dim]; [plasma][dim]blend=all_mode=overlay:all_opacity=0.35[bg]; [fg_src]scale=1000:820:force_original_aspect_ratio=decrease,pad=iw+16:ih+16:8:8:color=0xffffff,setsar=1[fg]; [bg][fg]overlay=x=(W-w)/2:y=(H-h)/2:format=auto[base] FILTER ) ;; rasterbars) BASE_FILTER=$(cat <<'FILTER' [0:v]split=2[bg_src][fg_src]; [bg_src]scale=1080:1920:force_original_aspect_ratio=increase,crop=1080:1920,gblur=sigma=30,eq=brightness=-0.70:saturation=0.35,setsar=1, drawbox=x=0:y=0:w=1080:h=1920:color=0x05061b@0.80:t=fill, drawbox=x=0:y='250+90*sin(t*1.25)':w=1080:h=30:color=0xff3b7f@0.78:t=fill, drawbox=x=0:y='300+90*sin(t*1.25)':w=1080:h=24:color=0xff7b32@0.72:t=fill, drawbox=x=0:y='345+90*sin(t*1.25)':w=1080:h=18:color=0xffd12b@0.66:t=fill, drawbox=x=0:y='1510+85*sin(t*1.10+1.2)':w=1080:h=18:color=0x55e6ff@0.68:t=fill, drawbox=x=0:y='1550+85*sin(t*1.10+1.2)':w=1080:h=24:color=0x4b8cff@0.72:t=fill, drawbox=x=0:y='1600+85*sin(t*1.10+1.2)':w=1080:h=30:color=0x8b55ff@0.76:t=fill[bg]; [fg_src]scale=1000:820:force_original_aspect_ratio=decrease,pad=iw+14:ih+14:7:7:color=0xffffff,setsar=1[fg]; [bg][fg]overlay=x=(W-w)/2:y=(H-h)/2:format=auto[base] FILTER ) ;; vhs) BASE_FILTER=$(cat <<'FILTER' [0:v]split=2[bg_src][fg_src]; [bg_src]scale=1080:1920:force_original_aspect_ratio=increase,crop=1080:1920,gblur=sigma=26,eq=brightness=-0.44:saturation=0.55,noise=alls=10:allf=t+u,setsar=1[bg]; [fg_src]scale=1000:820:force_original_aspect_ratio=decrease,chromashift=cbh=2:crh=-2,noise=alls=5:allf=t+u,eq=contrast=1.08:saturation=0.92,vignette=angle=PI/4.5,setsar=1, drawgrid=width=iw:height=5:thickness=1:color=black@0.13[fg]; [bg][fg]overlay=x=(W-w)/2:y=(H-h)/2:format=auto, drawbox=x=0:y='300+mod(t*510,1320)':w=1080:h=10:color=white@0.08:t=fill, drawbox=x=0:y='420+mod(t*320,1080)':w=1080:h=3:color=0x7bd7ff@0.10:t=fill[base] FILTER ) ;; monitor) BASE_FILTER=$(cat <<'FILTER' [0:v]split=2[bg_src][screen_src]; [bg_src]scale=1080:1920:force_original_aspect_ratio=increase,crop=1080:1920,gblur=sigma=34,eq=brightness=-0.68:saturation=0.32,setsar=1, drawbox=x=0:y=0:w=1080:h=1920:color=0x11131a@0.78:t=fill, drawbox=x=42:y=365:w=996:h=1000:color=0xaaa69a:t=fill, drawbox=x=68:y=392:w=944:h=944:color=0x2a2926:t=fill, drawbox=x=105:y=438:w=870:h=790:color=0x050505:t=fill, drawbox=x=792:y=1260:w=44:h=18:color=0x6aff73:t=fill, drawbox=x=852:y=1253:w=30:h=30:color=0x232323:t=fill, drawbox=x=910:y=1253:w=30:h=30:color=0x232323:t=fill, drawbox=x=380:y=1365:w=320:h=75:color=0x8f8b80:t=fill, drawbox=x=290:y=1438:w=500:h=36:color=0x77736b:t=fill[monitor]; [screen_src]scale=840:750:force_original_aspect_ratio=decrease,lenscorrection=k1=-0.045:k2=0.012,vignette=angle=PI/5,eq=contrast=1.06:saturation=1.06,setsar=1[screen]; [monitor][screen]overlay=x=(W-w)/2:y=458:format=auto[base] FILTER ) ;; split) BASE_FILTER=$(cat <<'FILTER' [0:v]split=4[bg_src][main_src][top_src][bottom_src]; [bg_src]scale=1080:1920:force_original_aspect_ratio=increase,crop=1080:1920,gblur=sigma=34,eq=brightness=-0.55:saturation=0.55,setsar=1[bg]; [top_src]scale=1080:520:force_original_aspect_ratio=increase,crop=1080:360:y='(ih-360)/2',eq=brightness=-0.28:saturation=0.88,gblur=sigma=2,setsar=1[top]; [bottom_src]scale=1080:520:force_original_aspect_ratio=increase,crop=1080:360:y='(ih-360)/2',hflip,eq=brightness=-0.32:saturation=0.80,gblur=sigma=2,setsar=1[bottom]; [main_src]scale=1000:820:force_original_aspect_ratio=decrease,pad=iw+14:ih+14:7:7:color=0xffffff,setsar=1[main]; [bg][top]overlay=x=0:y=275[tmp1]; [tmp1][bottom]overlay=x=0:y=1285[tmp2]; [tmp2][main]overlay=x=(W-w)/2:y=(H-h)/2:format=auto[base] FILTER ) ;; spectrum) BASE_FILTER=$(cat <<'FILTER' [0:v]split=2[bg_src][fg_src]; [bg_src]scale=1080:1920:force_original_aspect_ratio=increase,crop=1080:1920,gblur=sigma=30,eq=brightness=-0.62:saturation=0.40,setsar=1, drawbox=x=0:y=0:w=1080:h=1920:color=0x040717@0.68:t=fill[bg]; [fg_src]scale=1000:780:force_original_aspect_ratio=decrease,pad=iw+12:ih+12:6:6:color=0x55c8ff,setsar=1[fg]; [0:a]aformat=sample_fmts=fltp:channel_layouts=stereo,showspectrum=s=1000x250:mode=combined:color=rainbow:scale=sqrt:fscale=log:slide=scroll:win_func=hann:fps=50:opacity=0.85,format=rgba[spec]; [bg][spec]overlay=x=(W-w)/2:y=1360:format=auto[tmp]; [tmp][fg]overlay=x=(W-w)/2:y=480:format=auto[base] FILTER ) ;; video) VIDEO_BLUR_FILTER="" if awk -v v="$VIDEO_BLUR" 'BEGIN { exit !(v > 0) }'; then VIDEO_BLUR_FILTER=",gblur=sigma=${VIDEO_BLUR}" fi BASE_FILTER=$(cat < 0) }'; then # Fade only the Amiga source stream. Background video/image stays fully # visible from frame 0; the source overlay (and source-derived layers) # ramp from transparent to opaque. SOURCE_FADE_PREFIX="[0:v]format=rgba,fade=t=in:st=0:d=${SOURCE_FADE_IN}:alpha=1[srcfade];" BASE_FILTER="${BASE_FILTER//\[0:v]/[srcfade]}" fi FILTER_COMPLEX=$(cat <