Get this effect
Switch to Code in your Claude Desktop App
Install the skill (once)
Show the prompt
Install the Recharm video effects skill: download https://recharm-growth-staging.s3.us-east-2.amazonaws.com/website/downloads/recharm-video-effects.zip and unzip it into ~/.claude/skillsAsk for the effect
Add light rays to input.mp4 Where the light comes from (0-1 across and down the frame): (0.12, 0.06) Color of the light: (255, 205, 140)
Several videos, several effects, one ask
Put your clips in the folder, mention them with @, and ask for everything at once. Claude runs one tested script after another on each clip, then joins them.
Add light rays to @clip1.mp4, put a sale badge on @clip2.mp4, give @clip3.mp4 a VHS look, then join all three with crossfades.
Settings you can change
Ask Claude to change any of these, or edit them at the top of the script.
| Setting | Default | What it does |
|---|---|---|
SOURCE | (0.12, 0.06) | Where the light comes from (0-1 across and down the frame). |
COLOR | (255, 205, 140) | Color of the light. Warm sunlight by default. |
BEAMS | (4, 7, 11) | Beams per full turn in three overlapping fans. Higher numbers give more, thinner beams. |
SWAY | 0.15 | How fast the beams drift, in radians per second. 0 holds them still. |
REACH | 1.8 | How far the beams carry before fading out, as a share of the frame height. |
HAZE | 0.12 | Soft light between the beams, as a share of a beam's brightness. |
THRESHOLD | 0.7 | Parts of the picture darker than this (0-1) block the light and cast shadow shafts. |
STRENGTH | 1.0 | How bright the light is (0-1). |
GLOW | 0.14 | Radius of the bright spot at the source, as a share of the frame width. |
START | 0.5 | Second the light starts fading up. |
RAMP | 1.0 | Seconds it takes to reach full strength. 0 turns it on at once. |
Without the skill
Don’t want to install the skill? Paste this prompt into Claude Code and Claude writes the script itself.
Show the prompt
Add light rays (god rays) to input.mp4 with Python, OpenCV and numpy, piping frames through ffmpeg. The light comes from a point 12% across and 6% down the frame. Beams: for each pixel take its angle a around that point and add up 0.5 + 0.5*cos(n*a + phase) for n = 4, 7 and 11, with the phases drifting at +0.15, -0.30 and +0.45 radians per second; divide by the frame's maximum, cube it, add 0.12 of soft haze, and fade it with distance d as (1 - d / (1.8 x frame height))^1.2. Shade the beams with the picture: on a half-size copy, take brightness (0-1) mapped from 0.70-0.85 to 0-1, radial-blur it toward the light point (7 passes, each averaging the image with a copy scaled toward the point, scales from sqrt(0.1) up to 0.1^(1/128)), divide by its mean, square it, clip to 0-1.5 and scale back up; multiply the beams by that so dark parts like the bottle leave a shadow shaft. Add a bright spot at the point, (1 - d / (14% of the frame width))^2. Clip the light to 0-1, tint it warm (255, 205, 140) and screen-blend it over the frame. The light is off for the first 0.5 seconds, then fades up linearly to full strength over 1 second and stays on to the end. Keep the size, the 30fps frame rate, the 6-second length and the audio, encode H.264 yuv420p, and save it as output.mp4.Run it without Claude
This is the exact script we tested. Save it as light-rays.py and run:
python3 light-rays.py input.mp4 output.mp4Show the script
#!/usr/bin/env python3
"""Light rays: warm beams of light fan out from a glowing spot in the top-left corner through haze, shaped by the shot.
Usage: python3 light-rays.py input.mp4 output.mp4
Needs ffmpeg, OpenCV and numpy (pip install opencv-python-headless numpy).
"""
import json
import math
import subprocess
import sys
import cv2
import numpy as np
SOURCE = (0.12, 0.06) # where the light comes from (0-1 across and down the frame)
COLOR = (255, 205, 140) # warm sunlight
BEAMS = (4, 7, 11) # beams per full turn in three overlapping fans, which together leave uneven gaps
SWAY = 0.15 # how fast the beams drift, in radians per second
REACH = 1.8 # how far the beams carry before fading out, as a share of the frame height
HAZE = 0.12 # soft light between the beams, as a share of a beam's brightness
THRESHOLD = 0.7 # parts darker than this (0-1) block the light and cast shadow shafts
STRENGTH = 1.0 # how bright the beams are (0-1)
GLOW = 0.14 # radius of the bright spot at the source, as a share of the frame width
START = 0.5 # second the light starts fading up
RAMP = 1.0 # seconds it takes to reach full strength
PASSES = 7 # radial blur passes (2^7 = 128 samples along each beam)
def beams(t):
"""Light leaving the source: three fans of soft-edged beams that slowly turn, fading with distance."""
fan = sum(0.5 + 0.5 * np.cos(n * angle + (i + 1) * SWAY * t * (-1) ** i) for i, n in enumerate(BEAMS))
fan = (fan / fan.max()) ** 3 # cubed for crisp edges, the brightest beam at 1
return (HAZE + fan) * np.clip(1 - dist / (REACH * h), 0, 1) ** 1.2
def shading(frame):
"""How much light gets through to each pixel: the share of bright, open picture on the way from the source,
found with a radial blur toward it, so the dark bottle leaves a shadow shaft behind it."""
small = cv2.resize(frame, (w // 2, h // 2), interpolation=cv2.INTER_AREA)
luma = cv2.cvtColor(small, cv2.COLOR_RGB2GRAY).astype(np.float32) / 255
open_ = np.clip((luma - THRESHOLD) / 0.15, 0, 1) # fully open from 0.15 above the threshold
cx, cy = SOURCE[0] * w / 2, SOURCE[1] * h / 2
scale = 0.1 # samples run from each pixel 90% of the way back to the source
for _ in range(PASSES):
# each pass averages with a copy pulled toward the source, doubling the samples along each beam
scale = math.sqrt(scale)
m = np.float32([[scale, 0, cx * (1 - scale)], [0, scale, cy * (1 - scale)]])
open_ = (open_ + cv2.warpAffine(open_, m, (w // 2, h // 2), flags=cv2.INTER_LINEAR | cv2.WARP_INVERSE_MAP)) / 2
open_ = np.clip((open_ / open_.mean()) ** 2, 0, 1.5) # streaks and shadows relative to the frame's average
return cv2.resize(open_, (w, h), interpolation=cv2.INTER_LINEAR)
def strength(t):
return STRENGTH * min(max((t - START) / RAMP, 0), 1) if RAMP else STRENGTH * (t >= START)
def probe(path):
info = json.loads(subprocess.check_output(["ffprobe", "-v", "error", "-select_streams", "v:0", "-show_entries",
"stream=width,height,r_frame_rate", "-of", "json", path]))
s = info["streams"][0]
num, den = s["r_frame_rate"].split("/")
return s["width"], s["height"], float(num) / float(den)
src, out = sys.argv[1:3]
w, h, fps = probe(src)
yy, xx = np.mgrid[0:h, 0:w].astype(np.float32)
dist = np.hypot(xx - SOURCE[0] * w, yy - SOURCE[1] * h)
angle = np.arctan2(yy - SOURCE[1] * h, xx - SOURCE[0] * w)
spot = np.clip(1 - dist / (GLOW * w), 0, 1) ** 2
color = np.float32(COLOR) / 255
reader = subprocess.Popen(["ffmpeg", "-v", "error", "-i", src, "-f", "rawvideo", "-pix_fmt", "rgb24", "-"],
stdout=subprocess.PIPE)
writer = subprocess.Popen(["ffmpeg", "-y", "-v", "error", "-f", "rawvideo", "-pix_fmt", "rgb24", "-s", f"{w}x{h}",
"-r", str(fps), "-i", "-", "-i", src, "-map", "0:v", "-map", "1:a?", "-r", "30",
"-c:v", "libx264", "-crf", "18", "-pix_fmt", "yuv420p", "-c:a", "copy", "-shortest", out],
stdin=subprocess.PIPE)
i = 0
while True:
raw = reader.stdout.read(w * h * 3)
if len(raw) < w * h * 3:
break
frame = np.frombuffer(raw, np.uint8).reshape(h, w, 3)
t = i / fps
k = strength(t)
if k > 0:
light = np.clip(beams(t) * shading(frame) + spot, 0, 1)
# screen blend: the light brightens the picture without ever pushing past white
beam = light[..., None] * color * k
frame = (255 * (1 - (1 - frame / 255) * (1 - beam))).astype(np.uint8)
writer.stdin.write(frame.tobytes())
i += 1
writer.stdin.close()
if reader.wait() or writer.wait():
sys.exit("ffmpeg failed")





