From c771fa3451b4f0363ff09b4cf732e08b34ed7ffc Mon Sep 17 00:00:00 2001 From: Ethanfel Date: Sat, 4 Apr 2026 14:34:03 +0200 Subject: [PATCH] feat: split frame strength into high/low noise strength per frame Each frame path now has two strength fields instead of one: 'start frame high strength', 'start frame low strength' (and same for middle/end). Migration splits old single 'X frame strength' into both new keys using the same value. Co-Authored-By: Claude Sonnet 4.6 --- api_routes.py | 15 +++++++++++---- tab_batch_ng.py | 22 ++++++++++++---------- utils.py | 16 +++++++++++++--- 3 files changed, 36 insertions(+), 17 deletions(-) diff --git a/api_routes.py b/api_routes.py index 0db2010..8ce351f 100644 --- a/api_routes.py +++ b/api_routes.py @@ -96,9 +96,12 @@ def _get_data(name: str, file_name: str, seq: int = Query(default=1)) -> dict[st result = dict(match) # Inject strength defaults if not yet saved to JSON for key, default in ( - ("start frame strength", 1.0), - ("middle frame strength", 1.0), - ("end frame strength", 1.0), + ("start frame high strength", 1.0), + ("start frame low strength", 1.0), + ("middle frame high strength", 1.0), + ("middle frame low strength", 1.0), + ("end frame high strength", 1.0), + ("end frame low strength", 1.0), ): result.setdefault(key, default) # Computed stem names from frame paths @@ -133,7 +136,11 @@ def _get_keys(name: str, file_name: str, seq: int = Query(default=1)) -> dict[st else: types.append("STRING") # Injected defaults — always present even if not yet saved to JSON - for key in ("start frame strength", "middle frame strength", "end frame strength"): + for key in ( + "start frame high strength", "start frame low strength", + "middle frame high strength", "middle frame low strength", + "end frame high strength", "end frame low strength", + ): if key not in match: keys.append(key) types.append("FLOAT") diff --git a/tab_batch_ng.py b/tab_batch_ng.py index a936fea..112deae 100644 --- a/tab_batch_ng.py +++ b/tab_batch_ng.py @@ -317,9 +317,9 @@ def render_batch_processor(state: AppState): 'seed', 'camera', KEY_SEQUENCE_NUMBER, 'frame_to_skip', 'logic index', 'transition', 'vace_length', 'input_a_frames', 'input_b_frames', 'reference switch', 'vace schedule', - 'start frame path', 'start frame strength', - 'middle frame path', 'middle frame strength', - 'end frame path', 'end frame strength', + 'start frame path', 'start frame high strength', 'start frame low strength', + 'middle frame path', 'middle frame high strength', 'middle frame low strength', + 'end frame path', 'end frame high strength', 'end frame low strength', 'video file path', } standard_keys.update(lora_keys) @@ -560,10 +560,10 @@ def _render_sequence_card(i, seq, batch_list, data, file_path, state, # --- Frame paths (start / middle / end) --- logic_val = int(seq.get('logic index', 0)) - for bit, img_label, img_key, str_key in [ - (0, 'Start Frame', 'start frame path', 'start frame strength'), - (1, 'Middle Frame', 'middle frame path', 'middle frame strength'), - (2, 'End Frame', 'end frame path', 'end frame strength'), + for bit, img_label, img_key, hi_key, lo_key in [ + (0, 'Start Frame', 'start frame path', 'start frame high strength', 'start frame low strength'), + (1, 'Middle Frame', 'middle frame path', 'middle frame high strength', 'middle frame low strength'), + (2, 'End Frame', 'end frame path', 'end frame high strength', 'end frame low strength'), ]: ui.label(img_label).classes('text-caption text-weight-bold q-mt-sm') with ui.row().classes('w-full items-center no-wrap q-mt-xs'): @@ -581,11 +581,13 @@ def _render_sequence_card(i, seq, batch_list, data, file_path, state, f'style="width:36px;height:36px;object-fit:cover;' f'border-radius:4px;cursor:pointer;flex-shrink:0">' ).on('click', img_dlg.open) - str_inp = dict_number('Strength', seq, str_key, default=1.0, - step=0.05, format='%.2f').style( - 'width:80px').props('outlined dense') sw = ui.switch(value=bool((logic_val >> bit) & 1)) frame_switches.append(sw) + with ui.row().classes('w-full no-wrap q-mt-xs q-gutter-xs'): + dict_number('High', seq, hi_key, default=1.0, + step=0.05, format='%.2f').classes('col').props('outlined dense') + dict_number('Low', seq, lo_key, default=1.0, + step=0.05, format='%.2f').classes('col').props('outlined dense') with splitter.after: # Mode diff --git a/utils.py b/utils.py index 831cf6f..07dbcec 100644 --- a/utils.py +++ b/utils.py @@ -44,11 +44,14 @@ DEFAULTS = { "reference switch": 1, "video file path": "", "start frame path": "", - "start frame strength": 1.0, + "start frame high strength": 1.0, + "start frame low strength": 1.0, "middle frame path": "", - "middle frame strength": 1.0, + "middle frame high strength": 1.0, + "middle frame low strength": 1.0, "end frame path": "", - "end frame strength": 1.0, + "end frame high strength": 1.0, + "end frame low strength": 1.0, # --- LoRAs (name as STRING, strength as FLOAT) --- "lora 1 high": "", @@ -173,6 +176,13 @@ def _migrate_key_renames(data: dict) -> None: item['end frame path'] = item.pop('flf image path') if 'reference image path' in item and 'start frame path' not in item: item['start frame path'] = item.pop('reference image path') + # Split old single strength into high+low + for prefix in ('start frame', 'middle frame', 'end frame'): + old_key = f'{prefix} strength' + if old_key in item: + val = item.pop(old_key) + item.setdefault(f'{prefix} high strength', val) + item.setdefault(f'{prefix} low strength', val) def _migrate_lora_keys(data: dict) -> None: