Old Tam in the trailer: three short lines, timed to the dawn flyover, the wolves and the Cove (same clip size)

This commit is contained in:
Paul Bailey
2026-11-08 23:58:43 +00:00
parent a629ddba49
commit 73599401bf
7 changed files with 74 additions and 1 deletions

View File

@@ -28,8 +28,18 @@ 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; }
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" \
-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];[g][m]amix=inputs=2:duration=first:normalize=0[a]" \
-i "$V/dawn.ogg" -i "$V/wolves.ogg" -i "$V/cove.ogg" \
-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]" \
-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 \

View File

@@ -0,0 +1,51 @@
"""Old Tam's lines for the trailer (tools/trailer/make_trailer.sh mixes them
in): short ones, in his voice (the same clone as tools/voice/make_voice.py),
each checked by Whisper and the best of three takes kept. Output:
tools/trailer/voice/<name>.ogg. Run with the voice environment:
tools/voice/.venv-qwen/bin/python tools/trailer/make_trailer_voice.py
"""
import json, os, subprocess, sys, tempfile
HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, os.path.join(HERE, "..", "voice"))
import make_voice as mv # (its voice, its checks)
OUT = os.path.join(HERE, "voice")
## name -> what he says (make_trailer.sh times each against the shots).
LINES = {
"dawn": "Ha! Awake at last! Washed up on my island, eh?",
"wolves": "Hush now. See it, there, at the edge of the light?",
"cove": "Well, you made it down!",
}
def main():
import numpy as np
import soundfile as sf
import mlx_whisper
from mlx_audio.tts.utils import load_model
os.makedirs(OUT, exist_ok=True)
model = load_model(mv.MODEL)
sr = getattr(model, "sample_rate", 24000)
tmp = tempfile.mkdtemp()
for name, text in LINES.items():
best = None
for k in range(3):
res = list(model.generate(text=text, ref_audio=mv.REF_AUDIO, ref_text=mv.REF_TEXT))
audio = np.concatenate([np.array(r.audio) for r in res])
wav = os.path.join(tmp, "%s_%d.wav" % (name, k))
sf.write(wav, audio, sr)
heard = mlx_whisper.transcribe(wav, path_or_hf_repo=mv.WHISPER, language="en")["text"].strip()
score = mv.match(text, heard)
if best is None or score > best[0]:
best = (score, wav, len(audio) / sr)
if score >= 0.98:
break
subprocess.run(["ffmpeg", "-v", "error", "-y", "-i", best[1], "-ac", "2", "-c:a", "vorbis", "-strict", "-2",
"-q:a", "5", os.path.join(OUT, name + ".ogg")], check=True)
print(" %s (%.1f s, words %.0f%%)" % (name, best[2], best[0] * 100), flush=True)
json.dump(LINES, open(os.path.join(OUT, "lines.json"), "w"), indent=2)
if __name__ == "__main__":
main()

View File

@@ -19,7 +19,11 @@ var hut_c := Vector3.ZERO
var fire_p := Vector3.ZERO
var yaw := 0.0
var _shots := 0
var _frame0 := 0
func _initialize() -> void:
_frame0 = Engine.get_process_frames()
_run.call_deferred()
func _h(x: float, z: float) -> float:
@@ -167,6 +171,9 @@ static func _env(t: float, a: float, b: float, fin: float = 0.6, fout: float = 0
## from and to black at its ends.
func _shot(secs: float, f: Callable, caption: String = "", fade: float = 0.5) -> void:
var n := int(secs * FPS)
# (When each shot starts, in the film: make_trailer.sh times Old Tam's lines by it.)
print("[shot] %d %.3f" % [_shots, (Engine.get_process_frames() - _frame0) / FPS])
_shots += 1
cap.text = caption
for i in range(n):
var t := i / FPS

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,5 @@
{
"dawn": "Ha! Awake at last! Washed up on my island, eh?",
"wolves": "Hush now. See it, there, at the edge of the light?",
"cove": "Well, you made it down!"
}

Binary file not shown.