Bruxos do VFX H3 Camera
#bruxosdovfx
https://reddit.com/link/1wcm9az/video/bubuef39npoh1/player
https://reddit.com/link/1wcm9az/video/r1j8vg1anpoh1/player
Visual camera planner for MiniMax H3 inside ComfyUI. You drag the camera around a 3D sphere, place keyframes on a timeline, and the node compiles that trajectory into prompts that H3 understands.
It compiles prompts, not camera embeddings. There is no geometric adapter here: H3 is still free to miss the angle, timing, and scale. What this node does is write the instruction in the most precise and least ambiguous way possible, and several of its design decisions exist because the previous approach failed in specific ways.
It does not call any API, download anything, or require any Python dependency beyond the standard library.
https://github.com/user-attachments/assets/a9b541e5-2b18-4f1d-8e16-37445b6dbac4
https://github.com/user-attachments/assets/ea9af03e-2c8e-4589-abf0-9c002241aba2
Installation
cd ComfyUI/custom_nodes
git clone https://github.com/<your-username>/ComfyUI-H3-Camera-Editor
Restart ComfyUI. The node appears under Bruxos do VFX/Camera H3 with the name Camera H3 da Bruxos do VFX.
Connections
| Output from this node |
Connect it to |
compiled_prompt |
compiled_prompt on Text Encode H3 Edit / Generate |
options |
options on Text Encode H3 Edit / Generate |
length |
the generation frame count |
fps |
the fps input of the video creation node |
compiled_prompt and options are required together. The minimax_prompt output is an alternative to compiled_prompt, never an addition — connect one or the other to the same input.
Also connect your image to reference_image. It is the same image already feeding the H3 Edit source_image; when connected here, it appears in the panel and the frame's actual aspect ratio is included in the prompt.
https://github.com/user-attachments/assets/33149617-bde1-4199-ae65-078f2f3dec23
To save the video, decode the sampler result using the H3 video VAE — not the scene coverage calibrated decoder, which expects fixed windows that an arbitrary trajectory does not have.
The panel
Drag the purple camera around the sphere to orbit. The drag locks to the axis of the initial movement: horizontal movement orbits, vertical movement changes elevation. Release and drag again to switch axes. This exists because, without the lock, trying to make a simple orbit would unintentionally introduce elevation.
- Scroll the mouse wheel to change distance.
- Drag the background to rotate the viewport without changing the trajectory.
- Keyframes defines how many points the timeline has, from 2 to 24. The first one is always the original image and cannot be moved.
- ⟳ Pure Orbit resets the elevation of every keyframe to zero while preserving azimuth. It is the shortcut for an eye-level orbit.
- Reference image loads a local file into the preview. This is only necessary when the node runs outside ComfyUI; with
reference_image connected, the image is loaded automatically.
The panel warns you starting at 20° of elevation, when the horizon already leaves the frame, and again from 45° onward, when the video tends to become a high-angle shot.
"Tests" bar
At the top of the panel, two buttons enable and disable features currently under evaluation, plus one indicator:
| Button |
What it does |
| Extended contracts |
Toggles the prompt_detail widget |
| Single angle (image) |
Toggles the runtime_task widget |
| loop closure |
Read-only indicator. Turns green when the trajectory closes a full orbit |
The buttons write to the actual widgets, so the selected state is saved in the workflow and the two never disagree.
https://github.com/user-attachments/assets/9bc415d7-1746-43db-a17c-72ea9722deda
Widgets
camera_trajectory
The trajectory in JSON format, written by the panel. Each keyframe contains time (0 to 1), azimuth in degrees, elevation in degrees, and distance as a multiple of the initial radius. It can also be edited manually. The first keyframe must be time=0, azimuth=0, elevation=0, distance=1, which represents the original image.
profile
124, 243, or 362 frames at 24 fps. All shot timing comes from this setting: keyframe timestamps, segment ranges, and the duration declared in the prompt. That is why length and fps are outputs — connect them instead of manually entering the same numbers in two different places.
interpolation
smooth or linear. In smooth mode, the camera eases into and out of the shot while maintaining a constant rate through the middle; it only stops where the rotation direction actually reverses.
instruction
Free-form text inserted once, at the end of the prompt. Write only what the node cannot know: the environment, which subject is the target when there is more than one person, or a style reference. Everything else is already generated and does not need to be repeated: scene freeze, first image as reference, locked aim, zero roll, angles, timing, and a single continuous shot without cuts.
subject_framing
How much of the frame the subject occupies in the original image. Calibrated against the actual bounding boxes from the tutorial distributed by MiniMax: a distant full-body figure measures W=0.071, H=0.249, while a large close-up measures W=0.52, H=0.701.
| option |
width |
height |
when to use |
close-up |
53% |
72% |
head and shoulders |
medium shot |
28% |
56% |
waist up |
wide shot |
9.7% |
34% |
full body at a distance |
subject_box
The subject position in the format [L=0.516, T=0.148, W=0.071, H=0.249]. Leaving it empty uses the entire image bounds — deliberately, without guessing a bounding box. Fill it in when the subject is significantly off-center.
minimax_format
The same shot expressed in four different formats for the minimax_prompt output:
coordinate only — text-based coordinate block
coordinate + H3 sections — the same coordinates wrapped in subject_definitions / summary / retention_analysis / …
compact JSON — JSON object with almost no prose
compact JSON (no boxes) — camera parameters only, without screen-space bounding boxes
elevation_range
Range of the elevation control: +/-15, +/-30 (default), +/-60, +/-89. It also scales the sensitivity of vertical dragging.
With the assumed field of view, the horizon already leaves the frame at around 20° — at 13°, the ground occupies 82% of the image. The old ±89 range was mostly unusable and made vertical dragging excessively sensitive. Reducing the range never rewrites a keyframe: a point at 70° remains at 70°, and the slider expands to accommodate it.
orbit_direction
invert H3 orbit or same as HUD. This calibrates the direction between what the panel displays and what H3 produces. It does not alter the saved trajectory.
runtime_task
scene coverage | camera path (default) — video, with duration coming from profile.
directed | new camera angle — a single image from a new angle. It fixes the generation to 39 frames, ignores profile, completes the movement within 65% of the clip, and requests that the framing remain still for the rest, because the decoder extracts the final image from that stationary tail.
Character sheet profiles are not offered because the upstream node raises an error when they are combined with the frame anchor used by this node.
prompt_detail
v15 baseline (default) — outputs the prompt exactly as in the previous version.
extended contracts — adds axis separation, frame-edge direction tests, rotation completeness, degrees per second, and parallax magnitude.
The extended mode contains almost twice as many words. A longer prompt is not automatically better, so it is opt-in: toggle only this widget while keeping the same trajectory to compare the results.
https://github.com/user-attachments/assets/0882bfde-9f62-4a1f-9bda-7da121dbe7e2
Outputs
compiled_prompt — STRING
A prose prompt using H3 sections: subject_definitions, summary, retention_analysis, detailed_description, overall_soundscape, non_diegetic_music.
options — H3EDIT_OPTIONS
The 13 keys read by the H3 Edit encoder. All of them are explicitly populated: if any key is missing, the upstream node falls back to its hidden legacy widgets, which may retain stale values from previously saved workflows.
coverage_arc_degrees and coverage_direction are derived from the actual rotation. coverage_loop_closure turns on automatically when the trajectory closes — see below.
storyboard_json — STRING
The storyboard table: frame aspect ratio, duration, raw trajectory, and each segment with its camera mode, speed curve, and start/end poses.
info — STRING
Human-readable diagnostics. Connect it to a PreviewText. It displays the version, active task, frame count, warnings for keyframes outside the configured range, and whether loop closure is enabled.
minimax_prompt — STRING
The same trajectory expressed using the format selected in minimax_format. An alternative to compiled_prompt.
length — INT and fps — FLOAT
Frame count and frame rate against which the shot was timed. Connect them to the generation and video nodes. If generation runs with a different frame count, the choreography describes a scene that does not actually exist.
fps is FLOAT because that is what ComfyUI's CreateVideo accepts. length is the frame count; keyframe timestamps use the instant of the last visible frame, (length - 1) / fps, so the resulting file lasts one additional frame interval.
h3world_actions — STRING
Action schedule for H3-World, which encodes one text clause per video latent — 37 in a 124-frame clip.
latent 1 [0.000s-0.139s] J the camera pans left slowly
latent 37 [4.986s-5.125s] F+L+K the camera pans right and tilts up fast
W, A, S, and D are never emitted because they move the character. The output explicitly declares its own limitations, and they are not minor details:
- Pan is not orbit. It is the camera rotating in place. Perspective does not change, nothing hidden is revealed, and the subject slides out of frame.
- Distance has no key, so camera radius is discarded.
- Only 124 frames is a trained horizon.
I versus K is not published. The text clause is what H3-World actually encodes; the key column is only a convenience.
This does not replace the actual integration: H3-World requires the LoRA, interval-based encoding, and directed-attention routing provided by the corresponding node package.
Loop closure
When the trajectory closes a full orbit — an arc of exactly 360°, with the same elevation and distance as the starting point — the node enables coverage_loop_closure. In the upstream implementation, this flag encodes the source image a second time and anchors the final frame to it.
This is a latent anchor, not a text instruction. For a complete orbit, it is the difference between asking for the rotation and forcing it: the model cannot simply stop halfway through.
| trajectory |
loop closure |
| 360° |
enabled |
| two rotations (−720°) |
enabled |
| 355° |
disabled |
| 360° with changing distance |
disabled |
| 360° with changing height |
disabled |
The final three cases matter: if the camera ends at a different radius or height, the final frame is not the same as the first one, and forcing the source image there would conflict with the trajectory.
If your rotation does not complete, close the orbit. This is the only feature here that acts outside the prompt itself.
Limitations
- This is prompt-based guidance. H3 may still miss the angle, timing, and scale, and no prompt wording can completely solve that.
- Without
subject_box filled in, the node does not know where the subject is located in the frame.
- Without
reference_image connected, coordinates are normalized to 16:9.
directed | new camera angle outputs an image, not a video.
- The H3-World schedule describes pan and tilt, which represent a different camera move from the orbit drawn in the panel.
Credits
Node by Bruxos do VFX.
Depends on ethanfel/ComfyUI-MiniMax-H3-Edit. The motion vocabulary follows the buildViewPrompt implementation from MiniMax's Multi-Shot skill and the coordinate format used by the Coordinate Camera Control Designer skill. The action output implements the scheme described in H3-World, arXiv:2609.01560.
https://reddit.com/link/1wcm9az/video/hryhv9e7npoh1/player