Fast_deploy / ComfyUI-MiniMaxH3-Context-Loop /tests /_audio_rounding_unit_test.py
dzmdd's picture
Upload folder using huggingface_hub (part 2)
bc50ea0 verified
Raw History Blame Contribute Delete
6.42 kB
#!/usr/bin/env python3
"""Standalone regressions for cumulative H3 audio sample budgeting."""
import importlib.util
import pathlib
import sys
import types
import torch
ROOT = pathlib.Path(__file__).resolve().parents[1]
PACKAGE = "h3_audio_rounding_unit"
folder_paths = types.ModuleType("folder_paths")
folder_paths.get_output_directory = lambda: str(ROOT)
folder_paths.get_temp_directory = lambda: str(ROOT)
folder_paths.get_input_directory = lambda: str(ROOT)
folder_paths.get_annotated_filepath = lambda value: str(value)
sys.modules["folder_paths"] = folder_paths
package = types.ModuleType(PACKAGE)
package.__path__ = [str(ROOT)]
sys.modules[PACKAGE] = package
shared_nodes = types.ModuleType(PACKAGE + ".nodes")
shared_nodes.MiniMaxH3MotionContext = object
shared_nodes._claim_inline_patch_ownership = lambda _conditioning=None: "test patch owner"
shared_nodes._prepare_native_guide_conditioning = lambda *args: None
shared_nodes._resize = lambda *args: None
shared_nodes._streams_from_latent = lambda *args: None
sys.modules[shared_nodes.__name__] = shared_nodes
spec = importlib.util.spec_from_file_location(
PACKAGE + ".chain_nodes", ROOT / "chain_nodes.py")
chain = importlib.util.module_from_spec(spec)
sys.modules[spec.name] = chain
spec.loader.exec_module(chain)
def generated_audio_case(frames, saved_samples):
loads = iter([
{"delivered_audio": torch.ones((1, 2, count))}
for count in saved_samples
])
chain._st_load = lambda _path: next(loads)
return chain._generated_audio({"segments": [
{
"index": index,
"checkpoint": "clip_%04d.safetensors" % index,
"sample_rate": 8000,
"delivered_frames": frame_count,
}
for index, frame_count in enumerate(frames, start=1)
]})
def main():
original_st_load = chain._st_load
original_prelude_audio = chain._prelude_audio
try:
trimmed = generated_audio_case([5, 5], [1667, 1667])
assert trimmed["waveform"].shape[-1] == round(10 / 24 * 8000)
padded = generated_audio_case([4, 4], [1333, 1333])
assert padded["waveform"].shape[-1] == round(8 / 24 * 8000)
assert torch.count_nonzero(padded["waveform"][..., -1:]) == 0
# A later masked-AV scene owns its complete decoded overlap. This is
# the only way Soft AV's audio feather survives Loop Trim into the
# final generated soundtrack.
loads = iter([
{"delivered_audio": torch.ones((1, 1, 100))},
{
"delivered_audio": torch.full((1, 1, 61), 3.0),
"audio_with_overlap": torch.cat((
torch.full((1, 1, 39), 2.0),
torch.full((1, 1, 61), 3.0),
), dim=-1),
},
])
chain._st_load = lambda _path: next(loads)
owned = chain._generated_audio({
"compatibility": {"continuation_mode": "guide"},
"segments": [
{"index": 1, "checkpoint": "one", "sample_rate": 24,
"raw_frames": 100, "delivered_frames": 100,
"continuation_mode": "guide"},
{"index": 2, "checkpoint": "two", "sample_rate": 24,
"raw_frames": 100, "delivered_frames": 61,
"continuation_mode": "audio_feathered_av"},
],
})
assert owned["waveform"].shape[-1] == 161
assert torch.equal(
owned["waveform"][0, 0, :61], torch.ones(61))
assert torch.equal(
owned["waveform"][0, 0, 61:100], torch.full((39,), 2.0))
assert torch.equal(
owned["waveform"][0, 0, 100:], torch.full((61,), 3.0))
# Scene 1 can itself continue an external video prelude. Carry its
# private raw AV audio through _generated_audio so the generated scene
# owns the incoming overlap at that boundary too.
loads = iter([{
"delivered_audio": torch.full((1, 1, 61), 3.0),
"audio_with_overlap": torch.cat((
torch.full((1, 1, 39), 2.0),
torch.full((1, 1, 61), 3.0),
), dim=-1),
}])
chain._st_load = lambda _path: next(loads)
first_owned = chain._generated_audio({
"compatibility": {
"continuation_mode": "audio_feathered_av",
},
"segments": [
{"index": 1, "checkpoint": "first", "sample_rate": 24,
"raw_frames": 100, "delivered_frames": 61,
"continuation_mode": "audio_feathered_av"},
],
})
assert first_owned[chain.AUDIO_WITH_OVERLAP_FRAMES_KEY] == 100
assert first_owned[chain.AUDIO_TRIM_FRAMES_KEY] == 39
chain._prelude_audio = lambda _record: {
"waveform": torch.ones((1, 1, 100)), "sample_rate": 24}
external_owned = chain._audio_with_prelude(
first_owned, 61, {"frame_count": 100})
assert external_owned["waveform"].shape[-1] == 161
assert torch.equal(
external_owned["waveform"][0, 0, :61], torch.ones(61))
assert torch.equal(
external_owned["waveform"][0, 0, 61:100],
torch.full((39,), 2.0))
assert torch.equal(
external_owned["waveform"][0, 0, 100:],
torch.full((61,), 3.0))
chain._prelude_audio = lambda _record: {
"waveform": torch.ones((1, 2, 1667)), "sample_rate": 8000}
joined = chain._audio_with_prelude(
{"waveform": torch.ones((1, 2, 1667)), "sample_rate": 8000},
5, {"frame_count": 5})
assert joined["waveform"].shape[-1] == round(10 / 24 * 8000)
one_short = chain._fit_pyav_audio_samples(
torch.ones((2, 226666)), 226667)
assert one_short.shape[-1] == 226667
assert torch.count_nonzero(one_short[..., -1:]) == 0
try:
chain._fit_pyav_audio_samples(torch.ones((2, 226665)), 226667)
except ValueError as exc:
assert "226665 samples; 226667 are required" in str(exc)
else:
raise AssertionError("PyAV accepted an audio deficit above one sample")
finally:
chain._st_load = original_st_load
chain._prelude_audio = original_prelude_audio
print("H3 audio rounding: cumulative scene and prelude boundaries are exact")
if __name__ == "__main__":
main()