summaryrefslogtreecommitdiffstats
path: root/tools/lg/build_video.sh
blob: 4f80be8416505ef530ef53540ec4d2c316a1784c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
#!/bin/bash
# SPDX-License-Identifier: BSD-3-Clause
# build_video.sh - build the final open-iov 1.0.0 demo video: a drawtext
# title card, the split-screen main segment (two GZDOOM VMs, one per pane,
# captured through the KVMFR ivshmem path), and an optional end card,
# concatenated into one 1280x720@30 libx264 yuv420p mp4.
#
# Usage:
#   build_video.sh <title-text> <main.mp4> [end-text]
#   build_video.sh <title-text> <dirA> <dirB> [end-text]
#     title-text  text for the title card (required, non-empty)
#     main.mp4    pre-built split-screen video (e.g. from split_screen.sh)
#     dirA dirB   two frame-%04d.ppm dirs; split_screen.sh (next to this
#                 script) is invoked first at FPS to build the main segment
#     end-text    optional text for the end card (omitted = no end card)
#
# Output: $OUT if set, else ./open-iov-1.0.0-demo.mp4 in the current dir.
# Env knobs: FPS (default 30), CARD_SECS (default 5), FONTSIZE (default 48).
#
# Approach: title/end cards are generated with ffmpeg drawtext (white on
# dark 0x101010, 1280x720); the main segment is normalized to 1280x720@30
# (aspect-preserving fit + letterbox pad, audio dropped - the demo video is
# video-only); the three segments are concatenated with the concat demuxer
# followed by ONE re-encode. Stream-copy concat is brittle across
# separately-generated mp4s (timestamp/param mismatches); the re-encode is
# cheap at these lengths and guarantees a clean single-stream output.
# The title font is auto-detected (DejaVuSans/NotoSans); the script fails
# with a clear message if no TTF font exists.
#
# Requires: ffmpeg with libx264 + drawtext (libfreetype); split_screen.sh
# next to this script for the two-dirs form.
set -euo pipefail

usage() {
    sed -n '3,31p' "$0" | sed 's/^# \{0,1\}//'
}

die() { echo "build_video.sh: ERROR: $*" >&2; exit 1; }

# grep -q would SIGPIPE the ffmpeg producer under pipefail; capture first.
ffmpeg_has() {  # $1 = encoder/filter token
    local out
    out=$(ffmpeg -hide_banner "$2" 2>/dev/null) || true
    case "$out" in *" $1 "*) return 0 ;; *) return 1 ;; esac
}

command -v ffmpeg >/dev/null 2>&1 || die "ffmpeg not found in PATH"
ffmpeg_has libx264 -encoders || die "ffmpeg has no libx264 encoder"
ffmpeg_has drawtext -filters || die "ffmpeg has no drawtext filter (needs libfreetype)"

# --- locate a usable TTF font -------------------------------------------
FONT=""
for f in \
    /usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf \
    /usr/share/fonts/truetype/dejavu/DejaVuSans.ttf \
    /usr/share/fonts/truetype/noto/NotoSans-Regular.ttf \
    /usr/share/fonts/dejavu/DejaVuSans.ttf; do
    if [ -f "$f" ]; then FONT=$f; break; fi
done
if [ -z "$FONT" ] && command -v fc-match >/dev/null 2>&1; then
    f=$(fc-match -f '%{file}' sans 2>/dev/null || true)
    [ -n "$f" ] && [ -f "$f" ] && FONT=$f
fi
[ -n "$FONT" ] || die "no usable TTF font found (looked for DejaVuSans, NotoSans; \
install fonts-dejavu-core or set up fontconfig)"
echo "build_video.sh: font=$FONT" >&2

# --- args ----------------------------------------------------------------
[ "$#" -ge 1 ] || { usage; exit 2; }
TITLE=$1; shift
[ -n "$TITLE" ] || die "title text must be non-empty"
MAIN="" DIR_A="" DIR_B="" END=""
if [ "$#" -ge 1 ] && [ -d "$1" ]; then
    DIR_A=$1
    [ "$#" -ge 2 ] && [ -d "$2" ] || die "two-dirs form needs two directories: <dirA> <dirB> [end-text]"
    DIR_B=$2
    END=${3:-}
elif [ "$#" -ge 1 ]; then
    [ -f "$1" ] || die "main input not found: $1"
    MAIN=$1
    END=${2:-}
else
    die "missing main input (mp4 file or two frame dirs)"
fi

FPS=${FPS:-30}
CARD_SECS=${CARD_SECS:-5}
FONTSIZE=${FONTSIZE:-48}
OUT=${OUT:-$PWD/open-iov-1.0.0-demo.mp4}
HERE=$(cd "$(dirname "$0")" && pwd)

TMP=$(mktemp -d "${TMPDIR:-/tmp}/build_video.XXXXXX")
trap 'rm -rf "$TMP"' EXIT

# --- cards (drawtext; text via textfile so ':' ',' '%' etc. are literal) --
make_card() {
    local text=$1 out=$2 tf="$TMP/card-$$.txt"
    printf '%s' "$text" > "$tf"
    ffmpeg -hide_banner -loglevel warning -y \
        -f lavfi -i "color=c=0x101010:s=1280x720:r=$FPS:d=$CARD_SECS" \
        -vf "drawtext=fontfile=$FONT:textfile=$tf:fontcolor=white:fontsize=$FONTSIZE:expansion=none:x=(w-text_w)/2:y=(h-text_h)/2" \
        -c:v libx264 -preset medium -crf 20 -pix_fmt yuv420p \
        "$out"
}

echo "build_video.sh: title card (${CARD_SECS}s)..." >&2
make_card "$TITLE" "$TMP/title.mp4"

# --- main segment --------------------------------------------------------
if [ -n "$DIR_A" ]; then
    [ -x "$HERE/split_screen.sh" ] || die "split_screen.sh not found/executable next to build_video.sh"
    echo "build_video.sh: building split-screen main from $DIR_A + $DIR_B..." >&2
    "$HERE/split_screen.sh" "$DIR_A" "$DIR_B" "$TMP/split.mp4" "$FPS"
    MAIN=$TMP/split.mp4
fi
echo "build_video.sh: normalizing main segment to 1280x720@$FPS..." >&2
ffmpeg -hide_banner -loglevel warning -y -i "$MAIN" \
    -vf "scale=1280:720:force_original_aspect_ratio=decrease,pad=1280:720:(ow-iw)/2:(oh-ih)/2,setsar=1,fps=$FPS" \
    -an -c:v libx264 -preset medium -crf 20 -pix_fmt yuv420p \
    "$TMP/main.mp4"

# --- concat [title, main, end] ------------------------------------------
printf "file '%s'\n" "$TMP/title.mp4" "$TMP/main.mp4" > "$TMP/list.txt"
if [ -n "$END" ]; then
    echo "build_video.sh: end card (${CARD_SECS}s)..." >&2
    make_card "$END" "$TMP/end.mp4"
    printf "file '%s'\n" "$TMP/end.mp4" >> "$TMP/list.txt"
fi

echo "build_video.sh: concatenating..." >&2
ffmpeg -hide_banner -loglevel warning -y -f concat -safe 0 -i "$TMP/list.txt" \
    -c:v libx264 -preset medium -crf 20 -pix_fmt yuv420p \
    -r "$FPS" -movflags +faststart \
    "$OUT"

[ -s "$OUT" ] || die "ffmpeg produced no output: $OUT"
if command -v ffprobe >/dev/null 2>&1; then
    ffprobe -v error -select_streams v:0 \
        -show_entries stream=width,height,r_frame_rate,duration \
        -show_entries format=duration,size \
        -of default=nw=1 "$OUT" | sed 's/^/build_video.sh: /' >&2
fi
echo "VIDEO_DONE $OUT" >&2