Video Camera Shake
Proof that a tripod looks fake
- video
- shake_curve
- video
Generations from video models are usually locked off, because the sampler has no hands. That reads as artificial - the same reason a CGI shot with no camera move reads as CGI. Video Camera Shake adds a handheld shake: sway, roll, and a little zoom breathing, generated from smooth wobbles rather than per-frame random jitter, so it looks like a person holding a camera rather than a broken encoder.
The seed is the whole trick. Same seed, same shake, every run, which means you can iterate on a look and come back to it, and it also means two shots can share an identical hand and feel like they were captured by the same operator.
Inputs
amplitude is sway as a share of the frame's short side, and the node's tooltip gives you a real scale: 0.004 is a steady hand, 0.01 is handheld (the default), 0.03 is running with the camera. rotation is roll in degrees - 0 level, 0.3 handheld, 2 violent. zoom is breathing, 0.002 subtle, 0.02 pumping. frequency is the centre of the shake in hertz: 0.5 slow drift, 1.5 handheld, 6 engine or impact rattle.
Those four are coupled in your head, not in the node, so keep them in proportion. Big amplitude with tiny rotation looks like a sliding frame rather than a hand; big rotation with a high frequency looks like a seizure.
shutter_angle is the blur the shake leaves, as a film shutter in degrees: 0 none, 180 the film standard, 360 the whole frame interval. This is what stops the shake looking like a still image being wiggled - a real handheld camera blurs slightly during the wobble. Turn it off only if you want the sharp, obviously-post look.
borders decides what fills the edge the shaken frame pulls away from: zoom enlarges until none shows, up to max_zoom, or edge, mirror, black fill it. max_zoom (default 1.2, range 1–3) is the ceiling on that enlargement. In practice: zoom with max_zoom 1.2 is the answer for most clips, and raising max_zoom is what lets you push amplitude hard. Try it on a shot with a subject right at the frame edge and you will see why the default is only 1.2.
shake_curve is the optional that makes this a storytelling tool rather than a filter. It takes an array - the values out of Curve to Numbers - stretched to the clip's length, and it multiplies amplitude, rotation and zoom together. [0, 0, 3, 0.5, 0] is calm, then an impact, then settling. For an explosion, a punch or a car crash, that is the difference between "the camera is shaky" and "something hit the camera".
Output is one video: same size, length, frame rate and audio.
Install
ComfyUI Manager → search WAS Node Suite v3 → install → restart. Or:
cd ComfyUI/custom_nodes
git clone https://github.com/WASasquatch/was-node-suite-comfyui.git
ComfyUI 0.14.0+ and Python 3.10+. The pack installs no packages of its own - requirements.txt is a comment and nothing runs pip - so there is no dependency surprise here. No models either; the shake is generated, not learned. First start (and the first after an update) takes an extra second or two while the pack writes config.yaml and compiles.
Where people trip up
Zoom borders at the wrong moment. zoom has to enlarge the frame to hide the gaps, and that crop is permanent for the clip. On a tight shot, a subject's head can end up clipped because the shake pushed the frame and the crop followed. Check max_zoom before you dial amplitude up.
Repeating the same shake. Same seed, same shake. If you want two shots in a sequence to feel like separate takes, use different seeds - the reverse of the usual advice, and the reason this node is seeded rather than random.
Overcooking it. 0.03 amplitude with 2 degrees of roll and frequency 6 is not handheld, it is a found-footage parody. The defaults really are close to right for a normal shot; the honest move is to start there, watch the clip once, and nudge one dial at a time.
Audio. It passes through untouched, including the length - shake is a transform of existing frames, so the soundtrack stays in sync by construction. If it does not, something upstream re-timed the clip and the audio you gave it was already out.
Inputs (10)
| Name | Type | Default | Description |
|---|---|---|---|
| video | VIDEO | The clip to shake. | |
| amplitude | FLOAT | 0.0100–0.2 | Sway as a share of the frame's short side: 0.004 = a steady hand; 0.01 = handheld; 0.03 = running with the camera. |
| rotation | FLOAT | 0.300–20 | Roll in degrees: 0 = level; 0.3 = handheld; 2 = violent. |
| zoom | FLOAT | 0.0020–0.2 | Zoom breathing as a share of the frame: 0 = none; 0.002 = subtle; 0.02 = pumping. |
| frequency | FLOAT | 1.500.05–30 | Centre of the shake in hertz: 0.5 = slow drift; 1.5 = handheld; 6 = engine or impact rattle. |
| seed | INT | 00–18446744073709550000 | Which shake; the same seed always gives the same one. Any whole number, as `7`. |
| shutter_angle | FLOAT | 1800–360 | Blur the shake leaves, as a film shutter in degrees: 0 = none; 180 = the film standard; 360 = the whole frame interval. |
| borders | COMBO | zoom | What fills the edge the shaken frame pulls away from: 'zoom' enlarges until none shows, up to max_zoom; 'edge', 'mirror' or 'black' fill it. |
| max_zoom | FLOAT | 1.201–3 | Most enlargement 'zoom' may use: 1.0 = none; 1.2 = default; 1.5 = heavy shake. |
| shake_curveopt | ARRAY | Strength across the clip, such as the values of Curve to Numbers: [0, 0, 3, 0.5, 0] is calm, then an impact that settles. Stretched to the clip's length; multiplies amplitude, rotation and zoom. |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| video | VIDEO | The shaken clip: same size, length, frame rate and audio. |