PoopMan333 commited on
Commit
66647ba
·
verified ·
1 Parent(s): c7305db

Upload 46 files

Browse files
custom_nodes/ComfyUI-Nugget/__init__.py CHANGED
@@ -49,7 +49,8 @@ _NODES = []
49
  for _module, _label in (("nodes_transcribe", "transcriber"),
50
  ("nodes_motion", "motion transfer"),
51
  ("nodes_prompt", "prompt"),
52
- ("nodes_image", "image")):
 
53
  try:
54
  _mod = importlib.import_module(f".{_module}", __name__)
55
  _NODES.extend(_mod.NODES)
 
49
  for _module, _label in (("nodes_transcribe", "transcriber"),
50
  ("nodes_motion", "motion transfer"),
51
  ("nodes_prompt", "prompt"),
52
+ ("nodes_image", "image"),
53
+ ("nodes_h3_tailtrim", "h3 tail trim")):
54
  try:
55
  _mod = importlib.import_module(f".{_module}", __name__)
56
  _NODES.extend(_mod.NODES)
custom_nodes/ComfyUI-Nugget/__pycache__/__init__.cpython-310.pyc ADDED
Binary file (3.01 kB). View file
 
custom_nodes/ComfyUI-Nugget/__pycache__/__init__.cpython-313.pyc CHANGED
Binary files a/custom_nodes/ComfyUI-Nugget/__pycache__/__init__.cpython-313.pyc and b/custom_nodes/ComfyUI-Nugget/__pycache__/__init__.cpython-313.pyc differ
 
custom_nodes/ComfyUI-Nugget/__pycache__/nodes_h3_tailtrim.cpython-310.pyc ADDED
Binary file (5.94 kB). View file
 
custom_nodes/ComfyUI-Nugget/__pycache__/nodes_h3_tailtrim.cpython-313.pyc ADDED
Binary file (7.85 kB). View file
 
custom_nodes/ComfyUI-Nugget/nodes_h3_tailtrim.py ADDED
@@ -0,0 +1,164 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Drop the tail off an H3 Motion Context latent before it is pinned.
2
+
3
+ H3's last ~1s of a clip is often the weakest part of the render. Because
4
+ Motion Context pins the LAST frames of the previous clip, that weak tail is
5
+ exactly what the next clip continues from, so the fault propagates down the
6
+ whole chain.
7
+
8
+ This node sits between `H3 Motion Context Load Latent` and
9
+ `H3 Motion Context`, and shortens the loaded clip so the pinned window ends
10
+ earlier -- before the bad part.
11
+
12
+ Load Latent -> H3 Context Tail Trim -> Motion Context.context_latent
13
+
14
+ WHY 17 FRAMES AND NOT 24
15
+ ------------------------
16
+ The H3 video VAE packs pixel frames into latent steps in a 1,4,4,4,4 cycle:
17
+ 5 latent steps cover exactly 17 pixel frames, and a legal clip is always
18
+ 17g+5 frames (5g+2 steps). Motion Context refuses to pin a run that does not
19
+ start on a cycle boundary -- it would silently shift the join. So the tail can
20
+ only come off in whole 17-frame units:
21
+
22
+ 17 frames = 0.708 s
23
+ 34 frames = 1.417 s
24
+ 51 frames = 2.125 s
25
+
26
+ Asking for anything else is rejected here rather than misaligned later.
27
+
28
+ Written to match ComfyUI-H3-Motion-Context (NikoDemon80). This was originally
29
+ a standalone single-file pack (ComfyUI-H3-Context-TailTrim); it now lives in
30
+ the Nugget pack, rewritten in the new comfy_api node style so it can coexist
31
+ with Nugget's other nodes. The node id is unchanged, so existing workflows
32
+ that reference `MiniMaxH3ContextTailTrim` still resolve.
33
+ """
34
+
35
+ from __future__ import annotations
36
+
37
+ import logging
38
+
39
+ from comfy_api.latest import io
40
+
41
+ log = logging.getLogger("Nugget")
42
+
43
+ FRAME_PER_TOKEN = (1, 4, 4, 4, 4) # pixel frames covered by each latent step
44
+ FRAME_RESCALE = 5.0 / 3.0 # audio latent steps per pixel frame
45
+ FRAMES_PER_CYCLE = 17 # 5 latent steps
46
+ STEPS_PER_CYCLE = 5
47
+
48
+
49
+ def _pixel_frames(latent_t):
50
+ """Pixel frames covered by latent_t latent steps."""
51
+ return sum(FRAME_PER_TOKEN[k % 5] for k in range(int(latent_t)))
52
+
53
+
54
+ def _streams(latent):
55
+ """Pull [video, audio] out of the paired AV latent the Load node makes."""
56
+ if not isinstance(latent, dict) or "samples" not in latent:
57
+ raise ValueError(
58
+ "h3_tail_trim: expected an H3 AV latent from 'H3 Motion Context "
59
+ "Load Latent'. Got %r." % (type(latent),))
60
+ s = latent["samples"]
61
+ if not isinstance(s, (list, tuple)) or len(s) < 2:
62
+ raise ValueError(
63
+ "h3_tail_trim: this is not a paired video/audio latent. Wire the "
64
+ "output of 'H3 Motion Context Load Latent' here, not a normal "
65
+ "latent.")
66
+ return s[0], s[1]
67
+
68
+
69
+ def _trim(latent, drop_frames=17):
70
+ """The core trim. Kept as a plain function so it is unit-testable without
71
+ ComfyUI, exactly as the original standalone node's method was."""
72
+ if latent is None: # clip 1 -- nothing loaded
73
+ return None
74
+ n = int(drop_frames)
75
+ if n <= 0:
76
+ return latent
77
+ if n % FRAMES_PER_CYCLE:
78
+ raise ValueError(
79
+ "h3_tail_trim: drop_frames must be a multiple of %d "
80
+ "(0.708 s at 24 fps); got %d. The H3 video VAE packs %d "
81
+ "pixel frames into %d latent steps, and Motion Context "
82
+ "refuses a pinned run that does not start on that boundary."
83
+ % (FRAMES_PER_CYCLE, n, FRAMES_PER_CYCLE, STEPS_PER_CYCLE))
84
+
85
+ video, audio = _streams(latent)
86
+ if video.ndim == 4:
87
+ video = video.unsqueeze(0)
88
+ if video.ndim != 5:
89
+ raise ValueError("h3_tail_trim: expected video latent [B,C,T,H,W], "
90
+ "got %s" % (tuple(video.shape),))
91
+ if audio.ndim == 3:
92
+ audio = audio.unsqueeze(0)
93
+
94
+ steps = int(video.shape[2])
95
+ drop_steps = n // FRAMES_PER_CYCLE * STEPS_PER_CYCLE
96
+ new_steps = steps - drop_steps
97
+ # 7 steps is the 22-frame window; below that there is nothing to pin.
98
+ if new_steps < 7:
99
+ raise ValueError(
100
+ "h3_tail_trim: dropping %d frames leaves %d latent steps, "
101
+ "which is less than the 7 steps a 22-frame context window "
102
+ "needs. Use a longer clip or a smaller drop."
103
+ % (n, max(0, new_steps)))
104
+
105
+ old_frames = _pixel_frames(steps)
106
+ new_frames = _pixel_frames(new_steps)
107
+ # H3's audio latent runs at 40 Hz against 24 fps picture. The pack
108
+ # accepts an overhang of 0 or +/-1/3 of a step, which is exactly what
109
+ # rounding gives for every legal clip length.
110
+ new_audio_t = int(round(FRAME_RESCALE * new_frames))
111
+ new_audio_t = min(new_audio_t, int(audio.shape[-1]))
112
+
113
+ out_video = video[:, :, :new_steps].contiguous().clone()
114
+ out_audio = audio[..., :new_audio_t].contiguous().clone()
115
+
116
+ log.info("[Nugget] h3_tail_trim: dropped %d frames (%d latent steps): "
117
+ "%d -> %d frames, audio %d -> %d steps",
118
+ n, drop_steps, old_frames, new_frames,
119
+ int(audio.shape[-1]), new_audio_t)
120
+
121
+ return {"samples": [out_video, out_audio]}
122
+
123
+
124
+ class NuggetH3ContextTailTrim(io.ComfyNode):
125
+ @classmethod
126
+ def define_schema(cls):
127
+ return io.Schema(
128
+ # node id kept identical to the old standalone pack so existing
129
+ # workflows referencing MiniMaxH3ContextTailTrim still resolve.
130
+ node_id="MiniMaxH3ContextTailTrim",
131
+ display_name="H3 Context Tail Trim",
132
+ category="conditioning/minimax",
133
+ search_aliases=["h3", "tail", "trim", "motion context",
134
+ "tailtrim"],
135
+ description=(
136
+ "Shorten a loaded H3 context latent so Motion Context pins an "
137
+ "earlier window, skipping the weak tail of the previous clip. "
138
+ "Trims in 17-frame steps; picture and sound together."),
139
+ inputs=[
140
+ io.Latent.Input(
141
+ "latent",
142
+ tooltip="From 'H3 Motion Context Load Latent'. Passes "
143
+ "straight through when nothing is loaded (clip 1), "
144
+ "so it is safe to leave wired for every block."),
145
+ io.Int.Input(
146
+ "drop_frames", default=17, min=0, max=340,
147
+ step=FRAMES_PER_CYCLE, display_name="drop frames",
148
+ tooltip="Pixel frames to remove from the END of the "
149
+ "previous clip before its last frames are pinned. "
150
+ "Must be a multiple of 17 (0.708 s) -- that is the "
151
+ "H3 latent's cycle length. 17 drops ~0.7 s, 34 "
152
+ "drops ~1.4 s."),
153
+ ],
154
+ outputs=[
155
+ io.Latent.Output(display_name="latent"),
156
+ ],
157
+ )
158
+
159
+ @classmethod
160
+ def execute(cls, latent=None, drop_frames=17) -> io.NodeOutput:
161
+ return io.NodeOutput(_trim(latent, drop_frames))
162
+
163
+
164
+ NODES = [NuggetH3ContextTailTrim]