feat: timed phrases use square brackets so shortcut parentheses can sit inside
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
aa4efe3248
commit
25e5cc8de0
+1
-1
@@ -4456,7 +4456,7 @@ class FunPackVideoRefinerV2(FunPackVideoRefiner):
|
||||
self._v2_tally_encode(purpose, 0.0, cached=True)
|
||||
return cached
|
||||
_encode_started = time.perf_counter()
|
||||
# Timed phrases `(walks left@2.0-3.5)` are a sampler instruction, not prompt text.
|
||||
# Timed phrases `[walks left@2.0-3.5]` are a sampler instruction, not prompt text.
|
||||
# Stripped for EVERY encoder (Qwen or Gemma would read them as punctuation); only H3
|
||||
# can act on them, and the sampler says so when it cannot.
|
||||
try:
|
||||
|
||||
+5
-5
@@ -66,11 +66,11 @@ def parse(text: str):
|
||||
return "".join(out), spans
|
||||
|
||||
|
||||
# (phrase@2.0-3.5) / (phrase:1.5@2.0-3.5): a TIMED phrase. Seconds from the scene start.
|
||||
# The phrase may contain `:` and `@` (shortcut replacements do): only a `:number` directly
|
||||
# before the `@t0-t1)` tail is a weight. Parentheses inside the phrase are still not allowed.
|
||||
# [phrase@2.0-3.5] / [phrase:1.5@2.0-3.5]: a TIMED phrase. Seconds from the scene start.
|
||||
# Square brackets so a shortcut replacement's parentheses and colons can sit inside; only a
|
||||
# `:number` directly before the `@t0-t1]` tail is a weight.
|
||||
_TIMED = re.compile(
|
||||
r"(?<!\\)\(([^()]*?)(?::\s*(-?\d+(?:\.\d+)?))?\s*@\s*(\d+(?:\.\d+)?)\s*-\s*(\d+(?:\.\d+)?)\s*\)")
|
||||
r"(?<!\\)\[([^\[\]]*?)(?::\s*(-?\d+(?:\.\d+)?))?\s*@\s*(\d+(?:\.\d+)?)\s*-\s*(\d+(?:\.\d+)?)\s*\]")
|
||||
# H3's latent time grid: latent frame k spans FRAME_PER_TOKEN[k % 5] pixel frames at 24 fps.
|
||||
FPS = 24
|
||||
FRAME_PER_TOKEN = (1, 4, 4, 4, 4)
|
||||
@@ -79,7 +79,7 @@ FRAME_PER_TOKEN = (1, 4, 4, 4, 4)
|
||||
def parse_timed(text: str):
|
||||
"""Strip timed-phrase syntax. -> (clean_text, [(start_char, end_char, weight, t0, t1)]).
|
||||
|
||||
The model must never see `@2.0-3.5`: Qwen would read it as punctuation. Spans index
|
||||
The model must never see `[…@2.0-3.5]`: Qwen would read it as punctuation. Spans index
|
||||
into `clean_text` (same contract as `parse`). A window with t1 <= t0 is dropped.
|
||||
"""
|
||||
if not text or "@" not in text:
|
||||
|
||||
@@ -256,7 +256,7 @@
|
||||
let composeTextarea = null;
|
||||
let varsOpen = false; // Variables panel expanded?
|
||||
let varHintEl = null; // undeclared / cycle hint line under the prompt
|
||||
let timedHintEl = null; // timed phrases `(walks left@2.0-3.5)` found in the prompt
|
||||
let timedHintEl = null; // timed phrases `[walks left@2.0-3.5]` found in the prompt
|
||||
|
||||
// ── templates bar (above the global prompt) ─────────────────────────────────────
|
||||
const TPL_NONE = "__none__";
|
||||
@@ -438,9 +438,9 @@
|
||||
return cycles;
|
||||
}
|
||||
|
||||
// Timed phrases: `(phrase@t0-t1)` / `(phrase:w@t0-t1)`, seconds from the scene start. Mirrors
|
||||
// Timed phrases: `[phrase@t0-t1]` / `[phrase:w@t0-t1]`, seconds from the scene start. Mirrors
|
||||
// h3_token_weights._TIMED — the phrase is only allowed to shape the video inside its window.
|
||||
const TIMED_RE = /(?<!\\)\(([^()]*?)(?::\s*(-?\d+(?:\.\d+)?))?\s*@\s*(\d+(?:\.\d+)?)\s*-\s*(\d+(?:\.\d+)?)\s*\)/g;
|
||||
const TIMED_RE = /(?<!\\)\[([^\[\]]*?)(?::\s*(-?\d+(?:\.\d+)?))?\s*@\s*(\d+(?:\.\d+)?)\s*-\s*(\d+(?:\.\d+)?)\s*\]/g;
|
||||
function updateTimedHint() {
|
||||
if (!timedHintEl) return;
|
||||
const txt = composeTextarea ? composeTextarea.value : "";
|
||||
|
||||
@@ -801,13 +801,13 @@ def test_variability_out_of_range_is_clamped_not_inverted(refiner, monkeypatch):
|
||||
# --- timed phrases -----------------------------------------------------------------
|
||||
|
||||
def test_timed_syntax_is_stripped_and_windows_kept():
|
||||
clean, spans = tw.parse_timed("a man (walks left@1.0-2.5) then (waves:1.5 @ 3-4) (x:1.2)")
|
||||
clean, spans = tw.parse_timed("a man [walks left@1.0-2.5] then [waves:1.5 @ 3-4] (x:1.2)")
|
||||
assert clean == "a man walks left then waves (x:1.2)"
|
||||
assert spans == [(6, 16, 1.0, 1.0, 2.5), (22, 27, 1.5, 3.0, 4.0)]
|
||||
|
||||
|
||||
def test_an_empty_window_is_dropped():
|
||||
assert tw.parse_timed("(walks@2-2)")[1] == []
|
||||
assert tw.parse_timed("[walks@2-2]")[1] == []
|
||||
|
||||
|
||||
def test_a_window_maps_to_whole_latent_frames():
|
||||
@@ -869,7 +869,7 @@ def test_timed_and_untimed_spans_stack_and_a_window_weight_is_a_boost():
|
||||
assert outside[0, 0, 0, 4] == tw.MASKED_BIAS
|
||||
|
||||
|
||||
def test_a_phrase_may_contain_colons_and_at_signs():
|
||||
clean, spans = tw.parse_timed("(camera: slow push-in, mail@x.com @1-2) (fast:0.5@2-3)")
|
||||
assert clean == "camera: slow push-in, mail@x.com fast"
|
||||
assert spans == [(0, 32, 1.0, 1.0, 2.0), (33, 37, 0.5, 2.0, 3.0)]
|
||||
def test_a_phrase_may_contain_parentheses_colons_and_at_signs():
|
||||
clean, spans = tw.parse_timed("[camera: (slow) push-in, mail@x.com @1-2] [fast:0.5@2-3]")
|
||||
assert clean == "camera: (slow) push-in, mail@x.com fast"
|
||||
assert spans == [(0, 34, 1.0, 1.0, 2.0), (35, 39, 0.5, 2.0, 3.0)]
|
||||
|
||||
Reference in New Issue
Block a user