diff --git a/tools/trailer/make_trailer.sh b/tools/trailer/make_trailer.sh index 1c3f96f..5513c22 100755 --- a/tools/trailer/make_trailer.sh +++ b/tools/trailer/make_trailer.sh @@ -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 \ diff --git a/tools/trailer/make_trailer_voice.py b/tools/trailer/make_trailer_voice.py new file mode 100644 index 0000000..657f00b --- /dev/null +++ b/tools/trailer/make_trailer_voice.py @@ -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/.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() diff --git a/tools/trailer/trailer.gd b/tools/trailer/trailer.gd index c790057..c4e28fb 100644 --- a/tools/trailer/trailer.gd +++ b/tools/trailer/trailer.gd @@ -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 diff --git a/tools/trailer/voice/cove.ogg b/tools/trailer/voice/cove.ogg new file mode 100644 index 0000000..c326ec1 Binary files /dev/null and b/tools/trailer/voice/cove.ogg differ diff --git a/tools/trailer/voice/dawn.ogg b/tools/trailer/voice/dawn.ogg new file mode 100644 index 0000000..f704934 Binary files /dev/null and b/tools/trailer/voice/dawn.ogg differ diff --git a/tools/trailer/voice/lines.json b/tools/trailer/voice/lines.json new file mode 100644 index 0000000..97fbfc2 --- /dev/null +++ b/tools/trailer/voice/lines.json @@ -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!" +} \ No newline at end of file diff --git a/tools/trailer/voice/wolves.ogg b/tools/trailer/voice/wolves.ogg new file mode 100644 index 0000000..bb6ec64 Binary files /dev/null and b/tools/trailer/voice/wolves.ogg differ