The trailer narrated by Old Tam: a line over every shot (welcome, survive, build, the wolves, winter, the cliffs, the Cove, play with friends), the game's sound dipping under him

This commit is contained in:
Paul Bailey
2026-11-09 00:45:40 +00:00
parent e5e7c0e343
commit 47966e464d
14 changed files with 129 additions and 34 deletions

View File

@@ -28,18 +28,40 @@ OUT_AT=$(echo "$D - 2.5" | bc)
# A slow A-minor-ish drone, left and right a little apart, echoed and soft.
PAD="0.06*(sin(2*PI*110*t)+0.7*sin(2*PI*164.8*t)+0.5*sin(2*PI*220*t)+0.35*sin(2*PI*261.6*t)*(0.5+0.5*sin(2*PI*0.08*t)))*(0.75+0.25*sin(2*PI*0.21*t))"
PAD_R="0.06*(sin(2*PI*110.4*t)+0.7*sin(2*PI*165.2*t)+0.5*sin(2*PI*219.6*t)+0.35*sin(2*PI*262*t)*(0.5+0.5*cos(2*PI*0.08*t)))*(0.75+0.25*cos(2*PI*0.21*t))"
# Old Tam (0.6.2: tools/trailer/voice, made by make_trailer_voice.py), each
# line a moment into its shot: the dawn flyover (shot 0), the wolves (3) and
# the Cove (6). The shots' start times are in the render log.
at() { awk -v n="$1" -v d="$2" '$1 == "[shot]" && $2 == n { printf "%d", ($3 + d) * 1000 }' build/trailer/render.log; }
# Old Tam narrates (0.6.2: tools/trailer/voice, made by make_trailer_voice.py):
# each line `at` seconds into its shot, by the shots' start times in the
# render log. The game's sound and the music dip under him.
V=tools/trailer/voice
T_DAWN=$(at 0 2.2); T_WOLVES=$(at 3 1.0); T_COVE=$(at 6 1.2)
[ -n "$T_DAWN" ] && [ -n "$T_WOLVES" ] && [ -n "$T_COVE" ] || { echo "no shot times in build/trailer/render.log"; exit 1; }
ffmpeg -v error -y -i "$RAW" -f lavfi -i "aevalsrc='$PAD|$PAD_R':s=48000:d=$D" \
-i "$V/dawn.ogg" -i "$V/wolves.ogg" -i "$V/cove.ogg" \
NARR=$(python3 - "$V/lines.json" build/trailer/render.log <<'EOF'
import json, sys
lines = json.load(open(sys.argv[1]))
starts = {}
for l in open(sys.argv[2]):
p = l.split()
if len(p) == 3 and p[0] == "[shot]":
starts[int(p[1])] = float(p[2])
inputs, chains, labels = [], [], []
for n, x in enumerate(lines):
if x["shot"] not in starts:
sys.exit("no start time for shot %d in the render log" % x["shot"])
ms = int((starts[x["shot"]] + x["at"]) * 1000)
inputs.append("%s/%s" % (sys.argv[1].rsplit("/", 1)[0], x["file"]))
chains.append("[%d:a]aresample=48000,adelay=%d|%d,volume=1.7[n%d]" % (n + 2, ms, ms, n))
labels.append("[n%d]" % n)
print("\t".join(inputs))
print(";".join(chains) + ";" + "".join(labels) + "amix=inputs=%d:duration=longest:normalize=0[v]" % len(labels))
EOF
) || exit 1
NARR_IN=$(printf '%s\n' "$NARR" | head -1)
NARR_FX=$(printf '%s\n' "$NARR" | tail -1)
set --
OLDIFS=$IFS; IFS=' '
for f in $NARR_IN; do set -- "$@" -i "$f"; done
IFS=$OLDIFS
ffmpeg -v error -y -i "$RAW" -f lavfi -i "aevalsrc='$PAD|$PAD_R':s=48000:d=$D" "$@" \
-filter_complex "[1:a]lowpass=f=1800,aecho=0.8:0.7:180|340:0.35|0.25,afade=t=in:d=2.5,afade=t=out:st=$OUT_AT:d=2.5[m];[0:a]volume=0.8[g];\
[2:a]aresample=48000,adelay=$T_DAWN|$T_DAWN,volume=1.6[v0];[3:a]aresample=48000,adelay=$T_WOLVES|$T_WOLVES,volume=1.6[v1];[4:a]aresample=48000,adelay=$T_COVE|$T_COVE,volume=1.6[v2];\
[v0][v1][v2]amix=inputs=3:duration=longest:normalize=0[v];[g][m][v]amix=inputs=3:duration=first:normalize=0[a]" \
$NARR_FX;[v]asplit=2[v1][vk];[g][m]amix=inputs=2:duration=first:normalize=0[bed];\
[bed][vk]sidechaincompress=threshold=0.02:ratio=6:attack=40:release=500[ducked];[ducked][v1]amix=inputs=2:duration=first:normalize=0[a]" \
-map 0:v -map "[a]" -vf "scale=in_range=pc:out_range=tv,format=yuv420p" -color_range tv \
-colorspace bt709 -color_primaries bt709 -color_trc bt709 \
-c:v libx264 -profile:v high -level 4.0 -preset slow -crf 23 -maxrate 3500k -bufsize 7000k -tag:v avc1 \