Refuse a take shorter than the chain, and delete a digest nobody called
Browse filesTwo 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
- audio_lock.py +0 -31
- h3_ref_chain.py +33 -0
- tools/check_audio_lock.py +26 -4
|
@@ -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
|
|
@@ -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.
|
|
@@ -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 |
-
|
| 159 |
-
|
| 160 |
-
|
| 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:
|