From 514fb8480da85a60f8b34ff1e498479a3da51581 Mon Sep 17 00:00:00 2001 From: gramps Date: Thu, 12 Nov 2026 14:03:45 -0800 Subject: [PATCH] fix(tools): avsync says "unmeasurable" instead of trusting a 0.08 correlation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On ty-20261112-1401-952-0000-2.mp4 the per-window estimates ranged from +400 to -370 ms and only one window cleared 0.35, yet the tool printed "-370 ms, adjust by +370" from an overall correlation of 0.08. A number that wrong is worse than no number: it invites another build. Now: windows above 0.35 must also agree within 150 ms, and anything weaker reports "unmeasurable" with the remedy — three claps with the hands in the webcam frame, two seconds apart, which gives a sharp visual and audio transient instead of a mouth-motion proxy. Also reverted an onset-based envelope tried on the same clip: it dropped the best window's correlation from 0.47 to 0.08, so the plain level envelope stays. What the run did establish: lengths match (15.555 s video against 15.601 s audio, 0.3%), and the log confirms "mic delay 350 ms" was applied — the drift fix and the delay wiring both hold. --- scripts/avsync.py | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/scripts/avsync.py b/scripts/avsync.py index 28f63c4..a452969 100644 --- a/scripts/avsync.py +++ b/scripts/avsync.py @@ -129,10 +129,16 @@ def offset_ms(audio: np.ndarray, motion: np.ndarray, fps: float, max_ms: float = lag, corr = best_for(m[start:start + seg], a[start:start + seg]) windows.append((start / grid, lag, corr)) strong = sorted(lag for _, lag, corr in windows if corr >= 0.35) - if strong: + if len(strong) >= 2: mid = len(strong) // 2 robust = strong[mid] if len(strong) % 2 else (strong[mid - 1] + strong[mid]) / 2 + spread = strong[-1] - strong[0] + if spread > 150: + # Windows that disagree this much cannot be averaged into an answer. Say so. + return float("nan"), max(corr for _, _, corr in windows), windows return robust, max(corr for _, _, corr in windows), windows + if strong: + return float("nan"), max(corr for _, _, corr in windows), windows return overall_lag, overall_corr, windows @@ -168,13 +174,18 @@ def main() -> int: print(f"delta {diff:+8.3f} s ({pct:+.2f}% of the video length)") verdict = "lengths match" if abs(diff) < 0.05 else "LENGTH MISMATCH — a clock problem" print(f" {verdict}") - quality = "strong" if corr >= 0.35 else "weak — treat the offset as a hint only" for start, wlag, wcorr in windows: mark = "*" if wcorr >= 0.35 else " " print(f" {start:5.1f}s {wlag:+7.0f} ms corr {wcorr:.2f} {mark}") - print(f"offset {lag:+8.0f} ms (correlation {corr:.2f}, {quality})") - direction = "sound LATER than the lips" if lag > 0 else "sound EARLIER than the lips" + + if lag != lag or corr < 0.35: + print("offset unmeasurable — too little agreement between mouth motion and speech") + print(" record 3 claps with your hands in the webcam frame, 2 s apart") + return 0 + + print(f"offset {lag:+8.0f} ms (correlation {corr:.2f})") if abs(lag) >= 20: + direction = "sound LATER than the lips" if lag > 0 else "sound EARLIER than the lips" print(f" {direction} — adjust the mic delay by {-lag:+.0f} ms") else: print(" within a frame or two — leave it alone")