Sandpies Claude Opus 5 commited on
Commit
4a004a8
·
1 Parent(s): 00396ca

Refuse a take shorter than the chain, and delete a digest nobody called

Browse files

Two findings from reading v1.1..v2, both in audio_lock's neighbourhood.

_prepare_master_audio loaded the take, printed its duration, and nothing ever
compared it to the chain. A take shorter than the render runs the last hops
past its end, fit_samples zero-pads them, and those hops come back MUTE --
found after paying for the render. Every other duration in this pack is
validated on the queue.

That is the third defect the review of the contributed patch listed --
"master_audio_secs is computed, printed, and never used again" -- and
rebuilding the feature from its prose reproduced it faithfully. It was also
hit during this project's own GPU testing and worked around by hand, which is
the clearest argument a check could have.

It raises rather than warns. Trailing silence is expressible by padding the
take; a mute final hop nobody asked for is not worth the minutes it costs to
discover. A take LONGER than the chain just notes how much goes unused.

recording_digest is deleted. It was defined in audio_lock, asserted in
check_audio_lock, and called by nothing -- h3_ref_chain digests the loaded
waveform through _store.audio_digest. Its docstring argued for size+mtime
"because the take is minutes long and this runs on every queue", a performance
case for a function nobody ran, while the path that does run hashes the
samples and is the better one anyway.

Its assertions went with it. A checker asserting the behaviour of dead code is
the fixture-versus-production problem from sections 63 and 68 in a third
costume: not a fixture that fails to match production, but an assertion about
something that is not production at all.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RVz8qNGn2NhrP3KG2Lh5yT

Files changed (3) hide show
  1. audio_lock.py +0 -31
  2. h3_ref_chain.py +33 -0
  3. tools/check_audio_lock.py +26 -4
audio_lock.py CHANGED
@@ -38,7 +38,6 @@ tone, regenerable):
38
  """
39
  from __future__ import annotations
40
 
41
- import hashlib
42
  import math
43
  import os
44
 
@@ -160,36 +159,6 @@ def grid_samples(audio_latent_length, sr=VAE_SR, audio_hz=AUDIO_HZ):
160
  return int(math.ceil(int(audio_latent_length) / float(audio_hz) * int(sr)))
161
 
162
 
163
- def recording_digest(path):
164
- """Identity of the take for the hop-cache key.
165
-
166
- Empty / missing -> None, so `master_audio_file=""` does not move a
167
- key. Size + mtime + basename, not a full-file hash: the take is
168
- minutes long and this runs on every queue.
169
- """
170
- path = str(path or "").strip()
171
- if not path:
172
- return None
173
- try:
174
- st = os.stat(path)
175
- except OSError:
176
- # The file will fail to load later with a readable error. A missing
177
- # file must still change the key, or a render against a now-gone
178
- # take could be served from cache.
179
- h = hashlib.sha256()
180
- h.update(b"missing:")
181
- h.update(path.encode("utf-8", "replace"))
182
- return h.hexdigest()[:16]
183
- h = hashlib.sha256()
184
- h.update(os.path.basename(path).encode("utf-8", "replace"))
185
- h.update(b":")
186
- h.update(str(int(st.st_size)).encode())
187
- h.update(b":")
188
- h.update(str(int(st.st_mtime_ns if hasattr(st, "st_mtime_ns")
189
- else st.st_mtime)).encode())
190
- return h.hexdigest()[:16]
191
-
192
-
193
  def force_stereo(wav):
194
  """wav is [C, T] or [B, C, T]. Mono is duplicated. Returns [C, T] C=2."""
195
  import torch
 
38
  """
39
  from __future__ import annotations
40
 
 
41
  import math
42
  import os
43
 
 
159
  return int(math.ceil(int(audio_latent_length) / float(audio_hz) * int(sr)))
160
 
161
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
162
  def force_stereo(wav):
163
  """wav is [C, T] or [B, C, T]. Mono is duplicated. Returns [C, T] C=2."""
164
  import torch
h3_ref_chain.py CHANGED
@@ -2909,6 +2909,39 @@ class HandTieClips:
2909
  f"old formula would have been "
2910
  f"{sum(lengths) - overlap_n * (n - 1)}f)",
2911
  flush=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2912
  # A dry run must not allocate the master. At 8 x 15 s and 1280x736 that
2913
  # is 2742 full float frames -- ~31 GB -- for a feature whose entire
2914
  # point is that it costs seconds.
 
2909
  f"old formula would have been "
2910
  f"{sum(lengths) - overlap_n * (n - 1)}f)",
2911
  flush=True)
2912
+ # Is the take long enough for the chain it is locked to?
2913
+ #
2914
+ # Every other duration in this pack is validated on the queue, and this
2915
+ # one was not. `_prepare_master_audio` loads the file, prints how long
2916
+ # it is, and nothing ever compares that to the chain. A take shorter
2917
+ # than the render runs the last hops past its end, `fit_samples`
2918
+ # zero-pads them, and those hops come back MUTE -- discovered after
2919
+ # paying for the render.
2920
+ #
2921
+ # It is the defect the review of the contributed patch listed third
2922
+ # ("`master_audio_secs` is computed, printed, and never used again"),
2923
+ # and rebuilding that feature from its prose reproduced it faithfully.
2924
+ # It was also hit during this project's own GPU testing and worked
2925
+ # around by hand, which is the clearest possible argument for a check.
2926
+ #
2927
+ # Raises rather than warns. Trailing silence is expressible -- pad the
2928
+ # take file -- but a mute final hop that nobody asked for is not worth
2929
+ # the minutes it costs to find out about.
2930
+ if locked is not None:
2931
+ take_s = float(locked["wav"].shape[-1]) / float(locked["sr"])
2932
+ need_s = float(total_frames) / FPS
2933
+ if take_s + 1.0 / FPS < need_s:
2934
+ raise ValueError(
2935
+ f"{TAG}: master_audio_file is {take_s:.2f}s but this chain "
2936
+ f"is {need_s:.2f}s ({total_frames}f at {FPS:g} fps). The "
2937
+ f"last {need_s - take_s:.2f}s would be locked to silence "
2938
+ f"the take does not contain. Shorten the chain, or pad the "
2939
+ f"recording to at least {need_s:.2f}s.")
2940
+ if take_s > need_s + 1.0:
2941
+ print(f"[{TAG}] master_audio_file is {take_s:.2f}s for a "
2942
+ f"{need_s:.2f}s chain; the last {take_s - need_s:.2f}s "
2943
+ f"is not used", flush=True)
2944
+
2945
  # A dry run must not allocate the master. At 8 x 15 s and 1280x736 that
2946
  # is 2742 full float frames -- ~31 GB -- for a feature whose entire
2947
  # point is that it costs seconds.
tools/check_audio_lock.py CHANGED
@@ -155,10 +155,15 @@ def main():
155
  ck("nine hops 0..8 cover the 64.67 s tester chain",
156
  abs(L.hop_audio_window_s(8, 192, 22, 24.0)[1] - 64.666666) < 1e-4)
157
 
158
- print("digest: empty is None, so it cannot move a cache key")
159
- ck("empty string digests to None", L.recording_digest("") is None)
160
- ck("whitespace digests to None", L.recording_digest(" ") is None)
161
-
 
 
 
 
 
162
  print("mask polarity: 1 on video, 0 on audio")
163
  v = torch.ones((1, 1, 4, 2, 2))
164
  a = torch.zeros((1, 1, 4, 2))
@@ -221,6 +226,23 @@ def main():
221
  "_batch_wav(left)" in src and "_batch_wav(right)" in src)
222
 
223
  print()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
224
  splice_checks(ck, sys.modules["htcpack.h3_ref_chain"], torch)
225
 
226
  if FAIL:
 
155
  ck("nine hops 0..8 cover the 64.67 s tester chain",
156
  abs(L.hop_audio_window_s(8, 192, 22, 24.0)[1] - 64.666666) < 1e-4)
157
 
158
+ # No assertions for `recording_digest` here any more: the function is gone.
159
+ # It was defined, tested, and called by nothing -- h3_ref_chain digests the
160
+ # loaded waveform through _store.audio_digest instead. Its docstring argued
161
+ # for size+mtime "because the take is minutes long and this runs on every
162
+ # queue", a performance case for a function nobody ran, while the path that
163
+ # does run hashes the samples. A checker asserting the behaviour of dead
164
+ # code is the same fixture-versus-production problem as sections 63 and 68
165
+ # in a third costume: not a fixture that fails to match production, but an
166
+ # assertion about something that is not production at all.
167
  print("mask polarity: 1 on video, 0 on audio")
168
  v = torch.ones((1, 1, 4, 2, 2))
169
  a = torch.zeros((1, 1, 4, 2))
 
226
  "_batch_wav(left)" in src and "_batch_wav(right)" in src)
227
 
228
  print()
229
+ # The take-length pre-flight. A take shorter than the chain runs the last
230
+ # hops past its end, fit_samples zero-pads them, and those hops come back
231
+ # mute -- after the render is paid for. Asserted on the source, because
232
+ # driving run() to the point where `locked` and `total_frames` both exist
233
+ # needs a model.
234
+ print(chr(10) + "take length is checked on the queue")
235
+ with open(os.path.join(HERE, "h3_ref_chain.py"), encoding="utf-8") as _fh:
236
+ src = _fh.read()
237
+ ck("run() compares the take against the chain",
238
+ "master_audio_file is" in src and "is not used" in src,
239
+ "both the refusal and the unused-tail note")
240
+ ck("the refusal names both durations",
241
+ "but this chain" in src and "{total_frames}f" in src)
242
+ ck("it raises rather than warning",
243
+ "raise ValueError(" in src.split("master_audio_file is")[0][-400:],
244
+ "a mute final hop nobody asked for is not a warning")
245
+
246
  splice_checks(ck, sys.modules["htcpack.h3_ref_chain"], torch)
247
 
248
  if FAIL: