Add YouTube Shorts tab (C# port of amigadb-short.sh)
New "Video | Shorts" tab strip on the main page. The existing Video tab and its render pipeline are unchanged; Shorts is a new, independent workflow sharing the same render-progress/log/output controls. - Services/ShortsPipeline.cs: C# port of amigadb-short.sh rather than shelling out to bash — end users on plain Windows don't have bash/WSL, and shipping this as a native part of the app keeps distribution self-contained. All 19 visual style filtergraphs (brand, pixel, mirror, crop, workbench, blur, crt, copper, stars, grid, tiles, scan, starfield, plasma, rasterbars, vhs, monitor, split, spectrum) are copied verbatim from the bash heredocs. Per explicit decision, kept parity with the script's actual behavior where the brand-style HOOK_BOX_Y/INFO_BOX_Y/font-size variables are computed but never actually used in the final filter (dead code in the original) rather than "fixing" it and changing rendered output. Reuses ToolExtractor/MediaProbe/ProcessRunner exactly as RenderPipeline does, so FFmpeg resolution and NVENC fallback behave identically. - Models/ShortSettings.cs: job parameters + ShortStyle enum. - AppSettings gains sticky Shorts defaults (style, hook, website, font, background image, CRF, preset) alongside the existing Video defaults. - MainPage: Video/Shorts tab buttons toggle two content panels; the background-image field auto-hides for non-brand styles; font field defaults to the first bold system font found (Segoe UI Bold, Segoe UI Semibold, Arial Bold, Calibri Bold); blank output path is auto-derived from the input filename and style, matching the script's default. - 13 new unit tests (escaping order, meta-line joining, validation, every style producing a non-empty filter, the drawtext trailer's timing gates). Full run is 72/72 passing. - Fixed a bug caught during manual testing: MainPage.Resources["PrimaryButtonStyle"] looked in the Page's own (empty) resource dictionary instead of Application.Current.Resources, throwing on every tab switch. Verified end-to-end: rendered a real Short from an existing video through the actual running app (not just unit tests) and inspected extracted frames — hook/title/meta/website text overlays appear and disappear at the correct timestamps with correct styling. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,674 @@
|
||||
#!/usr/bin/env bash
|
||||
set -Eeuo pipefail
|
||||
|
||||
# AmigaDB YouTube Shorts generator
|
||||
# Creates a 1080x1920, 50 FPS vertical MP4 from an existing Amiga video.
|
||||
# V3 expanded style pack for 4:3 Amiga footage, with demoscene-inspired FX and optional branded background image.
|
||||
|
||||
SCRIPT_NAME="$(basename "$0")"
|
||||
|
||||
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"
|
||||
CRF="16"
|
||||
PRESET="slow"
|
||||
|
||||
usage() {
|
||||
cat <<USAGE
|
||||
Usage:
|
||||
$SCRIPT_NAME --input VIDEO --title TITLE [options]
|
||||
|
||||
Required:
|
||||
--input FILE Source video
|
||||
--title TEXT Production title
|
||||
|
||||
Production metadata:
|
||||
--group TEXT Group / creator
|
||||
--year YEAR Release year
|
||||
--type TEXT Demo, Intro, Cracktro, Music Disk, ...
|
||||
--hook TEXT Opening hook (default: THIS RAN ON AN AMIGA)
|
||||
--website TEXT Closing website text (default: AMIGADB.NET)
|
||||
|
||||
Clip settings:
|
||||
--start TIME Start position, e.g. 00:01:23.500 (default: 00:00:00)
|
||||
--duration SECONDS Output duration in seconds (default: 30)
|
||||
--output FILE Output MP4 path
|
||||
|
||||
Visual style:
|
||||
--style NAME brand, pixel, mirror, crop, workbench, blur,
|
||||
crt, copper, stars, grid, tiles, scan,
|
||||
starfield, plasma, rasterbars, vhs,
|
||||
monitor, split, spectrum
|
||||
default: brand
|
||||
|
||||
--background-image Background image for brand style
|
||||
default: amigadb_short.png
|
||||
|
||||
Encoding:
|
||||
--font FILE Bold TrueType/OpenType font file
|
||||
--crf NUMBER H.264 quality; lower is better (default: 16)
|
||||
--preset NAME x264 preset (default: slow)
|
||||
|
||||
Other:
|
||||
-h, --help Show this help
|
||||
|
||||
Examples:
|
||||
$SCRIPT_NAME \
|
||||
--input "ami-back.mp4" \
|
||||
--start "00:00:34" \
|
||||
--duration 30 \
|
||||
--title "Ami-Back V1.04A" \
|
||||
--group "Pirates" \
|
||||
--year "1991" \
|
||||
--type "Cracktro" \
|
||||
--hook "A 1991 AMIGA CRACKTRO" \
|
||||
--style brand \
|
||||
--background-image "amigadb_short.png" \
|
||||
--output "ami-back-brand-short.mp4"
|
||||
|
||||
$SCRIPT_NAME \
|
||||
--input "demo.mp4" \
|
||||
--title "Demo Title" \
|
||||
--group "Demo Group" \
|
||||
--year "1992" \
|
||||
--type "Demo" \
|
||||
--style crt
|
||||
USAGE
|
||||
}
|
||||
|
||||
fail() {
|
||||
printf 'Error: %s\n' "$*" >&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
|
||||
;;
|
||||
--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
|
||||
;;
|
||||
-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"
|
||||
|
||||
case "$STYLE" in
|
||||
brand|pixel|mirror|crop|workbench|blur|crt|copper|stars|grid|tiles|scan|starfield|plasma|rasterbars|vhs|monitor|split|spectrum)
|
||||
;;
|
||||
*)
|
||||
fail "Unknown style '$STYLE'. Use brand, pixel, mirror, crop, workbench, blur, crt, copper, stars, grid, tiles, scan, starfield, plasma, rasterbars, vhs, monitor, split, or spectrum"
|
||||
;;
|
||||
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<v) v=d; printf "%.3f", v }')"
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
resolve_path() {
|
||||
local p="$1"
|
||||
if [[ -f "$p" ]]; then
|
||||
printf "%s\n" "$p"
|
||||
return 0
|
||||
fi
|
||||
if [[ -f "$SCRIPT_DIR/$p" ]]; then
|
||||
printf "%s\n" "$SCRIPT_DIR/$p"
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
# 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=3[fg_src][top_src][bottom_src];
|
||||
[top_src]scale=1080:620:force_original_aspect_ratio=increase,crop=1080:620,vflip,eq=brightness=-0.34:saturation=0.82,gblur=sigma=9,setsar=1[top];
|
||||
[bottom_src]scale=1080:620:force_original_aspect_ratio=increase,crop=1080:620,vflip,eq=brightness=-0.34:saturation=0.82,gblur=sigma=9,setsar=1[bottom];
|
||||
[fg_src]scale=1000:820:force_original_aspect_ratio=decrease,pad=iw+12:ih+12:6:6:color=white@0.70,setsar=1[fg];
|
||||
[top]pad=1080:1920:0:0:color=0x050616[canvas];
|
||||
[canvas][bottom]overlay=x=0:y=1300[tmp];
|
||||
[tmp][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
|
||||
)
|
||||
;;
|
||||
|
||||
esac
|
||||
|
||||
FILTER_COMPLEX=$(cat <<FILTER
|
||||
${BASE_FILTER};
|
||||
[base]
|
||||
drawbox=x=0:y=0:w=1080:h=245:color=black@0.50:t=fill:enable='between(t,0,${HOOK_END})',
|
||||
drawtext=fontfile='${FONT_ESC}':textfile='${HOOK_ESC}':fontcolor=white:fontsize=70:line_spacing=10:x=(w-text_w)/2:y=70:fix_bounds=true:shadowcolor=black@0.75:shadowx=4:shadowy=4:enable='between(t,0,${HOOK_END})',
|
||||
drawbox=x=0:y=1490:w=1080:h=430:color=black@0.62:t=fill:enable='gte(t,${INFO_START})',
|
||||
drawtext=fontfile='${FONT_ESC}':textfile='${TITLE_ESC}':fontcolor=white:fontsize=62:line_spacing=8:x=(w-text_w)/2:y=1560:fix_bounds=true:shadowcolor=black@0.80:shadowx=4:shadowy=4:enable='gte(t,${INFO_START})',
|
||||
drawtext=fontfile='${FONT_ESC}':textfile='${META_ESC}':fontcolor=white@0.92:fontsize=42:x=(w-text_w)/2:y=1655:fix_bounds=true:shadowcolor=black@0.80:shadowx=3:shadowy=3:enable='gte(t,${INFO_START})',
|
||||
drawtext=fontfile='${FONT_ESC}':textfile='${WEBSITE_ESC}':fontcolor=0x55c8ff:fontsize=52:x=(w-text_w)/2:y=1780:fix_bounds=true:shadowcolor=black@0.85:shadowx=4:shadowy=4:enable='gte(t,${CTA_START})',
|
||||
fps=50,format=yuv420p[vout]
|
||||
FILTER
|
||||
)
|
||||
|
||||
printf 'Creating AmigaDB YouTube Short...\n'
|
||||
printf ' Input: %s\n' "$INPUT"
|
||||
printf ' Segment: %s + %ss\n' "$START" "$DURATION"
|
||||
printf ' Style: %s\n' "$STYLE"
|
||||
printf ' Output: %s\n' "$OUTPUT"
|
||||
printf ' Font: %s\n' "$FONT_FILE"
|
||||
if [[ "$STYLE" == "brand" ]]; then
|
||||
printf ' Bg image: %s\n' "$BACKGROUND_IMAGE"
|
||||
fi
|
||||
|
||||
FFMPEG_ARGS=(
|
||||
-hide_banner -y
|
||||
-ss "$START"
|
||||
-t "$DURATION"
|
||||
-i "$INPUT"
|
||||
)
|
||||
|
||||
if [[ "$STYLE" == "brand" ]]; then
|
||||
FFMPEG_ARGS+=( -loop 1 -i "$BACKGROUND_IMAGE" )
|
||||
fi
|
||||
|
||||
FFMPEG_ARGS+=(
|
||||
-filter_complex "$FILTER_COMPLEX"
|
||||
-map "[vout]"
|
||||
-map 0:a?
|
||||
-c:v libx264
|
||||
-preset "$PRESET"
|
||||
-crf "$CRF"
|
||||
-profile:v high
|
||||
-level:v 4.2
|
||||
-pix_fmt yuv420p
|
||||
-c:a aac
|
||||
-b:a 320k
|
||||
-ar 48000
|
||||
-movflags +faststart
|
||||
-shortest
|
||||
"$OUTPUT"
|
||||
)
|
||||
|
||||
ffmpeg "${FFMPEG_ARGS[@]}"
|
||||
|
||||
printf '\nDone: %s\n' "$OUTPUT"
|
||||
Reference in New Issue
Block a user