Files
alisson-anjos-ComfyUI-BFSNodes/tests/test_bfs_h3_native_shot_loop.py

89 lines
4.9 KiB
Python

import importlib.util
import sys
from pathlib import Path
from types import SimpleNamespace
import pytest
import torch
ROOT=Path(__file__).resolve().parents[1]
spec=importlib.util.spec_from_file_location('native_shot_loop_test',ROOT/'bfs_h3_native_shot_loop.py')
m=importlib.util.module_from_spec(spec);spec.loader.exec_module(m)
def test_ranges_respect_camera_cuts_and_cover_every_frame():
spans=m.ranges(220,107,'110')
assert [(s['start'],s['end'],s['camera_cut']) for s in spans]==[(0,107,False),(107,110,False),(110,217,True),(217,220,False)]
assert sum(s['end']-s['start'] for s in spans)==220
with pytest.raises(ValueError):m.ranges(220,107,'220')
def test_tail_padding_and_continuity_do_not_renumber_references(tmp_path,monkeypatch):
monkeypatch.setitem(sys.modules,'folder_paths',SimpleNamespace(get_temp_directory=lambda:str(tmp_path)))
video=torch.zeros(8,8,8,3)
start=m.BFSH3NativeShotLoopStart()
_,state,*_=start.start(video,chunk_frames=5)
previous=torch.ones(5,8,8,3);p=tmp_path/'previous.pt';torch.save(previous,p)
state.update(index=1,segments=[{'path':str(p),'frames':5}])
tail,state,*_=start.start(video,chunk_frames=5,loop_state=state)
assert len(tail)==5
refs=(torch.zeros(1,4,8,3),)
output,prompt,status=m.BFSH3NativeShotContinuity().prepare(refs,'motion_transfer: test',state)
assert output[0] is refs[0] and torch.equal(output[1],previous[-1:])
assert '<Picture 2>' in prompt and 'ON' in status
state['ranges'][1]['camera_cut']=True
output,prompt,status=m.BFSH3NativeShotContinuity().prepare(refs,'original',state)
assert output is refs and prompt=='original' and 'camera cut' in status
state['ranges'][1]['camera_cut']=False
with pytest.raises(ValueError,match='free Picture slot'):
m.BFSH3NativeShotContinuity().prepare(refs*9,'original',state)
# Join trims the padded tail and preserves exactly the original 8 frames.
joined,fps,_=m.BFSH3NativeShotLoopEnd().finish(tail,state)
assert len(joined)==8 and fps==24.
def test_graph_expansion_clones_only_loop_body_and_carries_state():
# Use the actual ComfyUI graph builder when available; no GPU/model execution.
comfy=Path('/data/comfyui')
if not (comfy/'comfy_execution/graph_utils.py').exists():pytest.skip('ComfyUI graph utility not available')
sys.path.insert(0,str(comfy))
graph={'source':{'class_type':'Load','inputs':{}},
'model':{'class_type':'Model','inputs':{}},
'start':{'class_type':'BFSH3NativeShotLoopStart','inputs':{'images':['source',0]}},
'sampler':{'class_type':'Sample','inputs':{'images':['start',0],'model':['model',0]}},
'end':{'class_type':'BFSH3NativeShotLoopEnd','inputs':{'images':['sampler',0],'loop_state':['start',1]}}}
dyn=SimpleNamespace(get_node=lambda n:graph[n],get_display_node_id=lambda n:n)
out=m.BFSH3NativeShotLoopEnd.expand(dyn,'end',{'index':1})
expanded=list(out['expand'].values())
assert {n['class_type'] for n in expanded}=={'BFSH3NativeShotLoopStart','Sample','BFSH3NativeShotLoopEnd'}
start=next(n for n in expanded if n['class_type']=='BFSH3NativeShotLoopStart')
assert start['inputs']['images']==['source',0] and start['inputs']['loop_state']=={'index':1}
assert next(n for n in expanded if n['class_type']=='Sample')['inputs']['model']==['model',0]
def test_example_ui_links_are_bidirectional_and_match_api_loop():
import json
d=json.loads((ROOT/'examples/h3_tara_native_auto_prompt_loop.comfy.json').read_text());nodes={n['id']:n for n in d['nodes']}
for lid,src,slot,dst,input_slot,kind in d['links']:
assert lid in nodes[src]['outputs'][slot]['links']
assert nodes[dst]['inputs'][input_slot]['link']==lid
api=json.loads((ROOT/'examples/h3_tara_native_auto_prompt_loop.api.json').read_text())
assert api['22']['inputs']['images']==['42',0]
assert api['22']['inputs']['audio']==['10',2]
def test_seconds_selection_rounding_and_legacy_frames(tmp_path, monkeypatch):
monkeypatch.setitem(sys.modules, 'folder_paths', SimpleNamespace(get_temp_directory=lambda: str(tmp_path)))
video = torch.zeros(250, 2, 2, 3)
start = m.BFSH3NativeShotLoopStart()
_, legacy, *_ = start.start(video)
assert legacy['ranges'][0]['end'] == 107
_, seconds, *_ = start.start(video, chunk_frames=107, segment_unit='seconds', chunk_seconds=5.)
assert [(x['start'], x['end']) for x in seconds['ranges']] == [(0, 120), (120, 240), (240, 250)]
_, rounded, *_ = start.start(video, segment_unit='seconds', chunk_seconds=1.03, camera_cuts='30')
assert [(x['start'], x['end']) for x in rounded['ranges'][:3]] == [(0, 25), (25, 30), (30, 55)]
_, disabled, *_ = start.start(video, enabled=False, segment_unit='seconds', chunk_seconds=5.)
assert len(disabled['ranges']) == 1 and disabled['ranges'][0]['end'] == 250
with pytest.raises(ValueError, match='seconds'):
start.start(video, segment_unit='seconds', chunk_seconds=float('nan'))