perf: tighten director tag and segment helpers
This commit is contained in:
@@ -53,7 +53,15 @@ function stripAudioSegmentTransient(s) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function dedupeById(items = []) {
|
function dedupeById(items = []) {
|
||||||
return items.filter((seg, index, self) => index === self.findIndex((s) => s.id === seg.id));
|
const seen = new Set();
|
||||||
|
const deduped = [];
|
||||||
|
for (const seg of items || []) {
|
||||||
|
const id = seg?.id;
|
||||||
|
if (!id || seen.has(id)) continue;
|
||||||
|
seen.add(id);
|
||||||
|
deduped.push(seg);
|
||||||
|
}
|
||||||
|
return deduped;
|
||||||
}
|
}
|
||||||
|
|
||||||
function findActiveSegmentAtFrame(segments = [], targetFrame, type) {
|
function findActiveSegmentAtFrame(segments = [], targetFrame, type) {
|
||||||
@@ -61,6 +69,10 @@ function findActiveSegmentAtFrame(segments = [], targetFrame, type) {
|
|||||||
(seg) => seg?.type === type && targetFrame >= seg.start && targetFrame < seg.start + seg.length
|
(seg) => seg?.type === type && targetFrame >= seg.start && targetFrame < seg.start + seg.length
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function segmentMatchesAudioSource(seg, audioFile, blobUrl) {
|
||||||
|
return !!seg && (seg.audioFile === audioFile || seg._blobUrl === blobUrl);
|
||||||
|
}
|
||||||
|
|
||||||
function hideWidget(w) {
|
function hideWidget(w) {
|
||||||
if (!w) return;
|
if (!w) return;
|
||||||
@@ -2004,6 +2016,14 @@ class TimelineEditor {
|
|||||||
return found;
|
return found;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_forEachMatchingAudioSegment(audioFile, blobUrl, visitor) {
|
||||||
|
for (const seg of this.timeline.audioSegments || []) {
|
||||||
|
if (segmentMatchesAudioSource(seg, audioFile, blobUrl)) {
|
||||||
|
visitor(seg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_getSegmentsByFileKey(fileKey) {
|
_getSegmentsByFileKey(fileKey) {
|
||||||
const matches = [];
|
const matches = [];
|
||||||
this._forEachMatchingSegment(fileKey, (seg) => {
|
this._forEachMatchingSegment(fileKey, (seg) => {
|
||||||
@@ -2231,11 +2251,10 @@ class TimelineEditor {
|
|||||||
|
|
||||||
const audioBuffer = await this._loadAudioBuffer(seg);
|
const audioBuffer = await this._loadAudioBuffer(seg);
|
||||||
|
|
||||||
const matchingSegs = this.timeline.audioSegments.filter(s => s.audioFile === seg.audioFile || s._blobUrl === seg._blobUrl);
|
this._forEachMatchingAudioSegment(seg.audioFile, seg._blobUrl, (s) => {
|
||||||
for (const s of matchingSegs) {
|
s._audioBuffer = audioBuffer;
|
||||||
s._audioBuffer = audioBuffer;
|
s._decoding = false;
|
||||||
s._decoding = false;
|
});
|
||||||
}
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.warn("Failed to preload audio segment:", err);
|
console.warn("Failed to preload audio segment:", err);
|
||||||
seg._decoding = false;
|
seg._decoding = false;
|
||||||
|
|||||||
@@ -32,7 +32,12 @@ from .prompt_relay import (
|
|||||||
|
|
||||||
from .patches import detect_model_type, apply_patches
|
from .patches import detect_model_type, apply_patches
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
CHARACTER_TAG_GROUPS = (
|
||||||
|
("@character1", "@char1"),
|
||||||
|
("@character2", "@char2"),
|
||||||
|
("@character3", "@char3"),
|
||||||
|
)
|
||||||
|
|
||||||
# Setup global event loop exception handler to silence ConnectionResetError (WinError 10054/10053) on Windows
|
# Setup global event loop exception handler to silence ConnectionResetError (WinError 10054/10053) on Windows
|
||||||
try:
|
try:
|
||||||
@@ -281,40 +286,26 @@ def _time_range_to_latent_indices(rel_start_frames: float, rel_length_frames: fl
|
|||||||
return start_idx, end_idx
|
return start_idx, end_idx
|
||||||
|
|
||||||
|
|
||||||
def _preprocess_prompts_with_characters(global_prompt, local_prompts, char1="", char2="", char3=""):
|
def _preprocess_prompts_with_characters(global_prompt, local_prompts, char1="", char2="", char3=""):
|
||||||
"""Invisibly swaps out @character1/@char1 tags with their high-fidelity VLM descriptions."""
|
"""Invisibly swaps out @character1/@char1 tags with their high-fidelity VLM descriptions."""
|
||||||
gp = global_prompt or ""
|
replacements = {}
|
||||||
char1 = char1 if char1 else ""
|
for tags, value in zip(CHARACTER_TAG_GROUPS, (char1 or "", char2 or "", char3 or "")):
|
||||||
char2 = char2 if char2 else ""
|
for tag in tags:
|
||||||
char3 = char3 if char3 else ""
|
replacements[tag] = value
|
||||||
|
|
||||||
# Process Global Prompt
|
def apply_replacements(text: str) -> str:
|
||||||
for tag in ["@character1", "@char1"]:
|
updated = text or ""
|
||||||
if tag in gp:
|
for tag, replacement in replacements.items():
|
||||||
gp = gp.replace(tag, char1)
|
if tag in updated:
|
||||||
for tag in ["@character2", "@char2"]:
|
updated = updated.replace(tag, replacement)
|
||||||
if tag in gp:
|
return updated
|
||||||
gp = gp.replace(tag, char2)
|
|
||||||
for tag in ["@character3", "@char3"]:
|
gp = apply_replacements(global_prompt or "")
|
||||||
if tag in gp:
|
if not local_prompts:
|
||||||
gp = gp.replace(tag, char3)
|
return gp, ""
|
||||||
|
|
||||||
# Process Local Timeline Prompts
|
processed_locals = [apply_replacements(part.strip()) for part in local_prompts.split("|")]
|
||||||
locals_list = [p.strip() for p in local_prompts.split("|")] if local_prompts else []
|
return gp, " | ".join(processed_locals)
|
||||||
processed_locals = []
|
|
||||||
for lp in locals_list:
|
|
||||||
for tag in ["@character1", "@char1"]:
|
|
||||||
if tag in lp:
|
|
||||||
lp = lp.replace(tag, char1)
|
|
||||||
for tag in ["@character2", "@char2"]:
|
|
||||||
if tag in lp:
|
|
||||||
lp = lp.replace(tag, char2)
|
|
||||||
for tag in ["@character3", "@char3"]:
|
|
||||||
if tag in lp:
|
|
||||||
lp = lp.replace(tag, char3)
|
|
||||||
processed_locals.append(lp)
|
|
||||||
|
|
||||||
return gp, " | ".join(processed_locals)
|
|
||||||
|
|
||||||
|
|
||||||
def _load_image_source(b64_or_url: str, filename: str = None, cache: dict | None = None,
|
def _load_image_source(b64_or_url: str, filename: str = None, cache: dict | None = None,
|
||||||
@@ -1391,8 +1382,7 @@ def _build_reference_mode_outputs(
|
|||||||
raise ValueError("Licon MSR (Prefix) ref option requires connecting the VAE to LTX Director!")
|
raise ValueError("Licon MSR (Prefix) ref option requires connecting the VAE to LTX Director!")
|
||||||
|
|
||||||
prompt_text = (global_prompt or "") + " " + (local_prompts or "")
|
prompt_text = (global_prompt or "") + " " + (local_prompts or "")
|
||||||
tag_pairs = [("@character1", "@char1"), ("@character2", "@char2"), ("@character3", "@char3")]
|
referenced_slots = [i for i, tags in enumerate(CHARACTER_TAG_GROUPS) if any(t in prompt_text for t in tags)]
|
||||||
referenced_slots = [i for i, tags in enumerate(tag_pairs) if any(t in prompt_text for t in tags)]
|
|
||||||
selected = []
|
selected = []
|
||||||
for slot in referenced_slots:
|
for slot in referenced_slots:
|
||||||
if slot < len(char_slot_images):
|
if slot < len(char_slot_images):
|
||||||
|
|||||||
Reference in New Issue
Block a user