Files
ElyOffice/site/video/update-0.1.5/record/sync.py
T
ZacharyZhang-NYandClaude Opus 5.5 8f31e53cfc 0.1.5 update video and Chinese changelog
Real footage of the installed 0.1.5 apps, composited in Remotion with
shape morphs between sections; synthesized score and UI sounds. Logs
P3-17 (ElyTable keyboard zoom leaves the grid half-scrolled).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-15 09:24:06 -04:00

48 lines
2.0 KiB
Python

"""Finds each take's sync jump; writes footage time = log time + offset.
Usage: sync.py <takes dir> <name> ...
Converts each take to constant 60 fps (`<name>.60.mp4`) and writes
`<name>.sync.json` with the offset and the source start time.
"""
import json
import subprocess
import sys
from pathlib import Path
import av
import numpy as np
def first_change(video: Path, x: int, y: int, after: float, until: float):
"""The first frame time when the patch at (x, y) changes."""
with av.open(str(video)) as box:
stream = box.streams.video[0]
start = float(stream.start_time * stream.time_base) if stream.start_time else 0.0
base = None
for frame in box.decode(stream):
t = float(frame.pts * stream.time_base) - start
if t < after:
continue
if t > until:
break
patch = frame.to_ndarray(format="gray")[y - 24:y + 24, x - 24:x + 24].astype(np.int16)
if base is None:
base = patch
continue
if np.abs(patch - base).mean() > 6:
return t, start
raise RuntimeError(f"{video.name}: no sync jump between {after} and {until} s")
root = Path(sys.argv[1])
for name in sys.argv[2:]:
events = json.loads((root / f"{name}.json").read_text(encoding="utf-8"))
sync = next(e for e in events if e["kind"] == "sync")
seen, start = first_change(root / f"{name}.mp4", sync["x"], sync["y"], sync["t"] - 0.5, sync["t"] + 1.5)
offset = round(seen - sync["t"], 4)
(root / f"{name}.sync.json").write_text(json.dumps({"offset": offset, "start": start}), encoding="utf-8")
subprocess.run(["ffmpeg", "-hide_banner", "-loglevel", "error", "-y", "-i", str(root / f"{name}.mp4"),
"-vf", "fps=60", "-c:v", "h264_nvenc", "-preset", "p7", "-rc", "vbr", "-cq", "14",
"-g", "30", "-pix_fmt", "yuv420p", str(root / f"{name}.60.mp4")], check=True)
print(f"{name}: jump seen at {seen:.3f} s, logged at {sync['t']:.3f} s, offset {offset:+.3f} s")