Particles
Sparks, embers, smoke, and magic dust — a 2D particle system with a live preview
- video
- mask_video
- mask_image
- video
Particles is the node that makes your footage feel alive - sparks flying off a welder, embers drifting up from a fire, dust motes in a shaft of light, magic sparkle over a product. It's a full 2D particle simulator that composites onto your clip, and it has a live Canvas2D preview, which is the single most important feature it has: you dial the look before committing a render, instead of iterating blind on a GPU pass.
The schema is the biggest of anything in this pack - forty-odd parameters - but it's one simulation with sensible defaults, so don't panic. Here's how to read it.
The structure of a particle system
Emission - where particles are born. emitter (point default, or area), with e_x0/e_y0/e_x1/e_y1 defining the spawn region in normalized coordinates. rate is how many per second (default 120), lifetime how long each lives (2s). Drive emission from a mask and the emitter switches to mask_edge, spawning along the mask's outline - great for outlining a subject or the "dissolving into particles" effect.
Motion - the forces that push them around: speed, direction, spread (the cone), gravity, wind, turbulence + turb_scale (the organic wobble), drag, an attract_* group (a point they're pulled toward, with a radius), and swirl. There's also a collide option (with floor_y and bounce) if you want them to land on something.
Bursts - sub_mode/sub_count/sub_speed/sub_lifetime/sub_size_ratio/sub_color spawn secondary particles from each particle's death. This is how you get the "spark flies, then shatters" double-act.
Look - size and size_end_ratio (shrink over life), opacity_start/opacity_end, optional JSON size_curve/opacity_curve for per-life shaping, color0/color1 (gradient between them), sprite (e.g. glow), renderer (sprite, stretched streaks, or trail with trail_len), stretch, blend (additive default - that's the glow look), and warmup (seconds of sim to pre-run so the field is full on frame one). And seed, naturally.
The inputs and outputs
Optional inputs: video (the clip to composite onto - leave empty to render over a blank field), mask_video and mask_image (drive mask-edge emission). One output: video (COMFYTV_VIDEO).
A sane first pass: point emitter at the bottom of frame, rate ~120, lifetime ~2, speed ~120, direction -90 (up), spread ~30, a little turbulence, additive blend with the glow sprite. That's a firework or a campfire, depending on color. Tweak gravity and swirl to taste.
Install
Pack-wide one-liner:
cd ComfyUI/custom_nodes
git clone https://github.com/jtydhr88/ComfyTV
Restart ComfyUI - Desktop/macOS users: clone into the running instance by absolute path, verify __init__.py at the top level, and restart the backend, not just the browser. ComfyUI Manager finds it as "ComfyTV". No extra Python dependencies.
The trap here isn't install, it's over-parameterizing. Resist the urge to set all forty knobs - start with emission + one force + the look, preview live, and only add complexity when the shot demands it. And remember the output is a snapshot: re-run after changes, and Bridge out if you need native ComfyUI video downstream.
Inputs (50)
| Name | Type | Default | Description |
|---|---|---|---|
| force_run_token | INT | 00–2147483647 | Internal — bumped on Run to invalidate ComfyUI's input cache. |
| project_id | STRING | Internal — populated by the projectStore on the frontend. | |
| parent_output_id | INT | 00–2147483647 | Internal — lineage parent set by spawn handlers on the frontend. |
| emitter | COMBO | point | 5 options: point, line, rect, circle, mask_edge |
| e_x0 | FLOAT | 0.500–1 | — |
| e_y0 | FLOAT | 0.850–1 | — |
| e_x1 | FLOAT | 0.500–1 | — |
| e_y1 | FLOAT | 0.850–1 | — |
| rate | FLOAT | 1200–2000 | — |
| lifetime | FLOAT | 2.000.1–10 | — |
| speed | FLOAT | 1200–1200 | — |
| direction | FLOAT | -90-180–180 | — |
| spread | FLOAT | 300–180 | — |
| gravity | FLOAT | 60-600–600 | — |
| wind | FLOAT | 0-600–600 | — |
| turbulence | FLOAT | 600–600 | — |
| turb_scale | FLOAT | 1208–600 | — |
| drag | FLOAT | 0.100–0.99 | — |
| attract_strength | FLOAT | 0-600–600 | — |
| attract_x | FLOAT | 0.500–1 | — |
| attract_y | FLOAT | 0.500–1 | — |
| attract_radius | FLOAT | 0.500.05–1.5 | — |
| swirl | FLOAT | 0-600–600 | — |
| collide | COMBO | none | 3 options: none, bounce, die |
| floor_y | FLOAT | 0.900–1 | — |
| bounce | FLOAT | 0.500–1 | — |
| sub_mode | COMBO | none | 3 options: none, on_death, on_collide |
| sub_count | INT | 80–30 | — |
| sub_speed | FLOAT | 1200–600 | — |
| sub_lifetime | FLOAT | 0.600.1–5 | — |
| sub_size_ratio | FLOAT | 0.500.1–2 | — |
| sub_color | STRING | #FFF2B0 | — |
| size | FLOAT | 12.01–64 | — |
| size_end_ratio | FLOAT | 0.400–3 | — |
| opacity_start | FLOAT | 1.000–1 | — |
| opacity_end | FLOAT | 0.000–1 | — |
| size_curve | STRING | JSON [{t,v}] size multiplier over life | |
| opacity_curve | STRING | JSON [{t,v}] opacity over life | |
| color0 | STRING | #FFD27A | — |
| color1 | STRING | #FF5A2A | — |
| sprite | COMBO | glow | 3 options: glow, spark, star |
| renderer | COMBO | sprite | 3 options: sprite, stretched, trail |
| stretch | FLOAT | 1.000–3 | — |
| trail_len | INT | 42–5 | — |
| blend | COMBO | additive | 2 options: additive, over |
| warmup | FLOAT | 1.000–10 | — |
| seed | INT | 70–99999 | — |
| videoopt | COMFYTV_VIDEO | — | |
| mask_videoopt | COMFYTV_VIDEO | — | |
| mask_imageopt | COMFYTV_IMAGE | — |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| video | COMFYTV_VIDEO | — |