From 3cb10d337543e84cacbdbcbcd38e3e5e5f926224 Mon Sep 17 00:00:00 2001 From: digital-garbage <133010426+digital-garbage@users.noreply.github.com> Date: Sun, 20 Sep 2026 02:26:13 +0300 Subject: [PATCH] feat: [phrase:1.5] is a weight too -- brackets are the universal prompt markup Co-Authored-By: Claude Opus 5 --- h3_token_weights.py | 12 ++++++++---- movie_editor/frontend/composer.js | 4 ++-- tests/test_h3_token_weights.py | 6 ++++++ 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/h3_token_weights.py b/h3_token_weights.py index 1e9728fa..19f90405 100644 --- a/h3_token_weights.py +++ b/h3_token_weights.py @@ -76,7 +76,10 @@ FPS = 24 FRAME_PER_TOKEN = (1, 4, 4, 4, 4) -_MARKUP = re.compile(_TIMED.pattern + "|" + _WEIGHTED.pattern) +# [phrase:1.5] - a weight with no window, in brackets so a shortcut replacement's own +# parentheses cannot break it (the reason timed phrases use brackets at all). +_BRACKET_WEIGHTED = re.compile(r"(? t0 and phrase.strip(): timed.append((start, start + len(phrase), float(m.group(2)) if m.group(2) else 1.0, t0, t1)) - else: # (phrase:w) - phrase = m.group(5) - weighted.append((start, start + len(phrase), float(m.group(6)))) + else: # (phrase:w) or [phrase:w] + phrase = m.group(5) if m.group(5) is not None else m.group(7) + weight = m.group(6) if m.group(6) is not None else m.group(8) + weighted.append((start, start + len(phrase), float(weight))) out.append(phrase) pos = m.end() out.append(text[pos:]) diff --git a/movie_editor/frontend/composer.js b/movie_editor/frontend/composer.js index cdc37f75..5139d94a 100644 --- a/movie_editor/frontend/composer.js +++ b/movie_editor/frontend/composer.js @@ -452,8 +452,8 @@ found.push({ phrase, t0, t1, w: m[2] ? parseFloat(m[2]) : 1, bad: !(t1 > t0) }); } // (phrase:1.5) — a weight with no window. Mirrors h3_token_weights._WEIGHTED. - const weighted = [...txt.matchAll(/(? ({ phrase: m[1].trim(), w: parseFloat(m[2]) })).filter((f) => f.phrase); + const weighted = [...txt.matchAll(/(? ({ phrase: (m[1] ?? m[3]).trim(), w: parseFloat(m[2] ?? m[4]) })).filter((f) => f.phrase); const PC = window.PipelineCaps; const h3 = !!(PC && PC.isH3 && PC.isH3(S.get())); let msg = ""; diff --git a/tests/test_h3_token_weights.py b/tests/test_h3_token_weights.py index af55d5d1..90f6319b 100644 --- a/tests/test_h3_token_weights.py +++ b/tests/test_h3_token_weights.py @@ -942,3 +942,9 @@ def test_a_typed_weight_alone_is_enough(refiner, monkeypatch): "minimax_token_tags": [1] * 8} out = refiner._v2_apply_h3_timed_phrases([[torch.zeros(1, 8, 4), meta]], _H3Clip()) assert out[0][1]["funpack_h3_token_weights"]["spans"] == [(0, 3, 1.5)] + + +def test_a_bracketed_weight_without_a_window_is_a_plain_weight(): + clean, weighted, timed = tw.parse_markup("a [cat (sitting):1.5] [runs@1-2]") + assert clean == "a cat (sitting) runs" + assert weighted == [(2, 15, 1.5)] and timed == [(16, 20, 1.0, 1.0, 2.0)]