Keep multiline beat prompts intact
This commit is contained in:
+42
-57
@@ -312,44 +312,35 @@ def is_directive_line(line):
|
||||
return bool(re.match(r"\s*(" + "|".join(DIRECTIVE_KEYS) + r")\s*:", line or "", re.I))
|
||||
|
||||
|
||||
def expand_beats(paras, mode="auto"):
|
||||
"""Turn the prompt's beat PARAGRAPHS into the final beat list. Returns
|
||||
(beats, note).
|
||||
|
||||
Beats are separated by a BLANK line (or a '##' line). That is unambiguous, but
|
||||
it is also the single easiest thing to get wrong in a textarea: six beats typed
|
||||
on six consecutive lines are one paragraph, so they render as ONE shot with six
|
||||
actions crammed into it -- which reads as characters moving at triple speed, not
|
||||
as a splitting problem.
|
||||
|
||||
mode:
|
||||
'auto' -- blank lines first; any paragraph still holding more than one
|
||||
content line is then split one beat per line, and says so.
|
||||
'each line' -- every content line is its own beat. Same result as 'auto'; kept
|
||||
so the intent can be stated explicitly.
|
||||
|
||||
There is deliberately no strict blank-lines-only mode any more. It was the ONE
|
||||
setting that could silently lose beats: six beats typed as two blocks of three
|
||||
rendered as two shots, with no note to say why, because the split note is only
|
||||
written when a paragraph is actually split. Nothing else on the node can change
|
||||
the beat count, so removing that option removes the whole failure class. A
|
||||
workflow that still stores 'blank line' falls through to 'auto' below.
|
||||
|
||||
Directive lines ('wardrobe:', 'seconds:', 'exit:' ...) are never beats of their
|
||||
own: they attach to the next content line, or to the previous beat if they
|
||||
trail the paragraph."""
|
||||
# Any unrecognized mode means AUTO, never "do nothing". An earlier version fell
|
||||
# through an if/elif with no else and silently DROPPED every multi-line paragraph
|
||||
# -- six beats arrived as two shots with four beats simply gone. A stale value on
|
||||
# this widget (including 'blank line' from a workflow saved before it was removed)
|
||||
# is enough to trigger it, so the safe branch has to be the default.
|
||||
if mode not in ("auto", "each line"):
|
||||
mode = "auto"
|
||||
out, split_from = [], 0
|
||||
for p in paras:
|
||||
lines = [ln for ln in (p or "").splitlines() if ln.strip()]
|
||||
content = [ln for ln in lines if not is_directive_line(ln)]
|
||||
if len(content) <= 1:
|
||||
def expand_beats(paras, mode="auto"):
|
||||
"""Turn the prompt's beat PARAGRAPHS into the final beat list. Returns
|
||||
(beats, note).
|
||||
|
||||
Beats are separated by a BLANK line (or a '##' line). That is unambiguous, but
|
||||
it is also easy to misread once a dedicated beat editor exists: inside the
|
||||
beat editor, one textbox is one beat, so plain newlines inside that textbox
|
||||
should stay inside the same beat rather than quietly creating new shots.
|
||||
|
||||
mode:
|
||||
'auto' -- preserve each paragraph as one beat.
|
||||
'each line' -- every content line is its own beat, with directive lines
|
||||
attaching to the beat they configure.
|
||||
|
||||
Directive lines ('wardrobe:', 'seconds:', 'exit:' ...) are never beats of their
|
||||
own: they attach to the next content line, or to the previous beat if they
|
||||
trail the paragraph."""
|
||||
# Any unrecognized mode means AUTO, never "do nothing". Older workflows may
|
||||
# still store removed values such as 'blank line'; those should preserve whole
|
||||
# paragraphs instead of unexpectedly splitting every newline into a beat.
|
||||
if mode not in ("auto", "each line"):
|
||||
mode = "auto"
|
||||
if mode == "auto":
|
||||
return [p.strip() for p in (paras or []) if str(p or "").strip()], ""
|
||||
out, split_from = [], 0
|
||||
for p in paras:
|
||||
lines = [ln for ln in (p or "").splitlines() if ln.strip()]
|
||||
content = [ln for ln in lines if not is_directive_line(ln)]
|
||||
if len(content) <= 1:
|
||||
out.append(p)
|
||||
else:
|
||||
split_from += 1
|
||||
@@ -368,13 +359,12 @@ def expand_beats(paras, mode="auto"):
|
||||
if out:
|
||||
out[-1] = out[-1] + NL + NL.join(pending)
|
||||
else:
|
||||
out.append(NL.join(pending))
|
||||
note = ""
|
||||
if split_from and mode == "auto":
|
||||
note = (f"{split_from} paragraph(s) held several lines and were split one beat per LINE "
|
||||
f"-> {len(out)} beats. Separate beats with a BLANK line (or a '##' line) to control "
|
||||
f"this yourself")
|
||||
return out, note
|
||||
out.append(NL.join(pending))
|
||||
note = ""
|
||||
if split_from:
|
||||
note = (f"beat_split '{mode}' split {split_from} multi-line paragraph(s) into "
|
||||
f"{len(out)} beats")
|
||||
return out, note
|
||||
|
||||
|
||||
def _garment_side(side):
|
||||
@@ -6153,18 +6143,13 @@ class H3LongVideos:
|
||||
"identity fidelity, but reference rows are re-attended EVERY step of "
|
||||
"EVERY ref-conditioned shot, so on a long chain it is several times "
|
||||
"slower. Neither ever upscales a small reference."}),
|
||||
"beat_split": (["auto", "each line"], {"default": "auto",
|
||||
"tooltip": "How the prompt box becomes beats. Beats are meant to be separated by a "
|
||||
"BLANK line (or a '##' line) -- but six beats typed on six consecutive "
|
||||
"lines are ONE paragraph, so they would render as one shot with six actions "
|
||||
"crammed into it, which looks like everyone is moving at triple speed. "
|
||||
"auto (default): blank lines first, then any paragraph still holding "
|
||||
"several lines is split one beat per LINE, and the info output says so. "
|
||||
"'each line': every line is its own beat -- same result, stated explicitly. "
|
||||
"Neither can lose a beat. (The old strict 'blank line' option was REMOVED: "
|
||||
"it was the only setting that could silently collapse beats, and a stored "
|
||||
"value of it now reads as 'auto'.) Directive lines (wardrobe:, seconds:, exit:) "
|
||||
"are never beats -- they attach to the beat that follows them."}),
|
||||
"beat_split": (["auto", "each line"], {"default": "auto",
|
||||
"tooltip": "How the prompt box becomes beats. Beats are separated by a BLANK line "
|
||||
"(or a '##' line). auto (default): each paragraph stays one beat, so "
|
||||
"plain newlines inside a beat remain part of that beat. 'each line': every "
|
||||
"content line becomes its own beat, with directive lines (wardrobe:, "
|
||||
"seconds:, exit:, etc.) attaching to the beat they configure. Older "
|
||||
"removed values such as 'blank line' now behave like 'auto'."}),
|
||||
"anchor_override": ("STRING", {"multiline": True, "forceInput": True, "default": "",
|
||||
"tooltip": "Set the persistent look explicitly instead of using the first paragraph. "
|
||||
"When this is filled in, EVERY paragraph of the prompt box is a beat/shot -- "
|
||||
|
||||
@@ -160,6 +160,45 @@ class DumasH3LongVideosHelperTests(unittest.TestCase):
|
||||
"keyframe carry",
|
||||
)
|
||||
|
||||
def test_expand_beats_auto_preserves_multiline_paragraph_as_one_beat(self):
|
||||
beats, note = self.module.expand_beats(
|
||||
["wardrobe: Maya = red jacket\nMaya enters the room.\nShe sits at the table."],
|
||||
"auto",
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
beats,
|
||||
["wardrobe: Maya = red jacket\nMaya enters the room.\nShe sits at the table."],
|
||||
)
|
||||
self.assertEqual(note, "")
|
||||
|
||||
def test_expand_beats_legacy_blank_line_value_falls_back_to_auto(self):
|
||||
beats, note = self.module.expand_beats(
|
||||
["Maya enters the room.\nShe sits at the table."],
|
||||
"blank line",
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
beats,
|
||||
["Maya enters the room.\nShe sits at the table."],
|
||||
)
|
||||
self.assertEqual(note, "")
|
||||
|
||||
def test_expand_beats_each_line_still_splits_multiline_paragraphs(self):
|
||||
beats, note = self.module.expand_beats(
|
||||
["wardrobe: Maya = red jacket\nMaya enters the room.\nseconds: 8\nShe sits at the table."],
|
||||
"each line",
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
beats,
|
||||
[
|
||||
"wardrobe: Maya = red jacket\nMaya enters the room.",
|
||||
"seconds: 8\nShe sits at the table.",
|
||||
],
|
||||
)
|
||||
self.assertIn("beat_split 'each line' split 1 multi-line paragraph(s) into 2 beats", note)
|
||||
|
||||
def test_timing_summary_reports_retry_and_bucket_totals(self):
|
||||
note = self.module._format_timing_note([
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user