From e01ceef71b37226ec5afa1adf7963406ed65254f Mon Sep 17 00:00:00 2001 From: OpenClaw Agent Date: Mon, 20 Jul 2026 09:17:04 +0000 Subject: [PATCH] Derive external timeline duration from sections --- js/ltx_director.js | 9 +++------ ltx_director.py | 6 ++++-- timeline_nodes.py | 18 ++++++------------ 3 files changed, 13 insertions(+), 20 deletions(-) diff --git a/js/ltx_director.js b/js/ltx_director.js index 7f264dd..f0cb07f 100644 --- a/js/ltx_director.js +++ b/js/ltx_director.js @@ -11589,14 +11589,10 @@ app.registerExtension({ return null; } - const rawDuration = - self._resolveNodeLinkedValue(timelineNode, "duration", { decimals: 3 }) ?? - parseFloat(self._readNodeWidgetValue(timelineNode, "duration") || 0); - const duration = Math.max(0.1, Number.isFinite(rawDuration) ? rawDuration : 5.0); - const sectionSetNode = self._findLinkedOriginNode(timelineNode, "section_set"); const setClass = sectionSetNode?.comfyClass || sectionSetNode?.type || ""; const sections = []; + let duration = 0; if (sectionSetNode && setClass === "TimelineSectionSetCS") { for (let i = 1; i <= 5; i++) { const sectionNode = self._findLinkedOriginNode(sectionSetNode, `section_${i}`); @@ -11610,6 +11606,7 @@ app.registerExtension({ self._resolveNodeLinkedValue(sectionNode, "duration", { decimals: 3 }) ?? parseFloat(self._readNodeWidgetValue(sectionNode, "duration") || 0); const sectionDuration = Math.max(0.1, Number.isFinite(rawSectionDuration) ? rawSectionDuration : 1.0); + duration += sectionDuration; const imageSource = self._extractLinkedImageSource(sectionNode, "image"); sections.push({ text, @@ -11620,7 +11617,7 @@ app.registerExtension({ } } - return { duration, sections }; + return { duration: Math.max(0.1, duration), sections }; }; this._syncTimelineFromLink = function () { const payload = self._buildExternalTimelinePayload(); diff --git a/ltx_director.py b/ltx_director.py index 6fee093..32b7cfb 100644 --- a/ltx_director.py +++ b/ltx_director.py @@ -177,11 +177,10 @@ def _build_runtime_timeline_payload(external_timeline, frame_rate: float) -> tup return tdata, "", "", 0.0, 0 sections = external_timeline.get("sections") or [] - duration_seconds = max(0.1, float(external_timeline.get("duration") or 0.0)) - duration_frames = max(1, int(round(duration_seconds * frame_rate))) current_start = 0 local_prompts = [] segment_lengths = [] + total_duration_seconds = 0.0 for index, section in enumerate(sections): if not isinstance(section, dict): @@ -191,6 +190,7 @@ def _build_runtime_timeline_payload(external_timeline, frame_rate: float) -> tup continue section_seconds = max(0.1, float(section.get("duration") or 0.0)) section_frames = max(1, int(round(section_seconds * frame_rate))) + total_duration_seconds += section_seconds seg = { "id": f"external_{index}", "start": current_start, @@ -205,6 +205,8 @@ def _build_runtime_timeline_payload(external_timeline, frame_rate: float) -> tup segment_lengths.append(str(section_frames)) current_start += section_frames + duration_seconds = max(0.1, float(total_duration_seconds or external_timeline.get("duration") or 0.0)) + duration_frames = max(1, int(round(duration_seconds * frame_rate))) return tdata, " | ".join(local_prompts), ",".join(segment_lengths), duration_seconds, duration_frames diff --git a/timeline_nodes.py b/timeline_nodes.py index 22ed227..e6559ca 100644 --- a/timeline_nodes.py +++ b/timeline_nodes.py @@ -106,21 +106,13 @@ class Timeline(io.ComfyNode): node_id="TimelineCS", display_name="Timeline DUMAS", category="WhatDreamsCost DUMAS", - description="Builds an external timeline payload for LTX Director from a section set and total duration.", + description="Builds an external timeline payload for LTX Director from a section set, deriving the total duration from the sections.", inputs=[ TimelineSectionSetData.Input( "section_set", optional=True, tooltip="Optional section set. When omitted, the timeline payload contains no sections.", ), - io.Float.Input( - "duration", - default=5.0, - min=0.1, - max=1000.0, - step=0.01, - tooltip="Total timeline duration in seconds.", - ), ], outputs=[ TimelineData.Output(display_name="timeline"), @@ -128,9 +120,11 @@ class Timeline(io.ComfyNode): ) @classmethod - def execute(cls, section_set=None, duration=5.0) -> io.NodeOutput: + def execute(cls, section_set=None, duration=None) -> io.NodeOutput: + sections = list((section_set or {}).get("sections") or []) + total_duration = sum(max(0.1, float(item.get("duration") or 0.0)) for item in sections) payload = { - "duration": max(0.1, float(duration or 0.0)), - "sections": list((section_set or {}).get("sections") or []), + "duration": max(0.1, float(total_duration or 0.0)), + "sections": sections, } return io.NodeOutput(payload)