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 a text locked into the scene to input.mp4 Text: GLOW
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 a text locked into the scene 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 |
|---|---|---|
TEXT | GLOW | The title. |
START | 0.5 | Second the title appears; the track is measured from here. |
X, Y | 0.24, 0.12 | Where the title is centered when it appears, as shares of the frame width and height. |
SIZE | 0.13 | Letter height as a share of the frame width. |
COLOR | (236, 72, 153) | Title color. |
WALL | (0, 0, 1, 0.45) | The part of the frame whose features drive the track: the surface the title is stuck to. |
SUBJECT | (0.3, 0.2, 0.7, 1) | The part left out of the track because it moves differently from the wall (here, the bottle). |
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
Put a title into the scene of input.mp4 (540x960, the camera orbiting a bottle) so it looks painted on the backdrop wall. Track the wall with OpenCV (opencv-python-headless): from 0.5 seconds on, on each frame find up to 400 corners with goodFeaturesToTrack (quality 0.01, min distance 7) in the top 45% of the frame, leaving out the bottle's area from 30% to 70% across and 20% down to the bottom, follow them to the next frame with calcOpticalFlowPyrLK, fit a homography with findHomography (RANSAC, 2-pixel threshold) and chain those so you have, for every frame, the homography from the wall at 0.5 seconds to the wall in that frame. Draw "GLOW" with Pillow in Arial Black, 13% of the frame width tall, hot pink (236, 72, 153) at 92% opacity, centered 24% across and 12% down, on a transparent full-frame layer, and warp that layer onto each frame with that frame's homography (cv2.warpPerspective), so the title slides right with the wall and goes out of frame on the right near the end. It fades in from 0.5 to 0.9 seconds; nothing is drawn before 0.5 seconds. Don't use ffmpeg's drawtext filter; use ffmpeg to put the layer on the video, keep the audio, and save it as output.mp4.Run it without Claude
This is the exact script we tested. Save it as text-locked-in-scene.py and run:
python3 text-locked-in-scene.py input.mp4 output.mp4Show the script
#!/usr/bin/env python3
"""Text locked into the scene: a pink "GLOW" title sits on the backdrop wall and slides with it as the camera orbits.
Usage: python3 text-locked-in-scene.py input.mp4 output.mp4
Needs ffmpeg, OpenCV, numpy and Pillow (pip install opencv-python-headless numpy pillow).
"""
import json
import subprocess
import sys
from pathlib import Path
import cv2
import numpy as np
from PIL import Image, ImageDraw, ImageFont
TEXT = "GLOW"
START = 0.5 # second the title appears
FADE = 0.4 # seconds it takes to fade in
X, Y = 0.24, 0.12 # title center when it appears (0-1 across and down the frame)
SIZE = 0.13 # letter height as a share of the frame width
COLOR = (236, 72, 153) # title color (hot pink)
OPACITY = 0.92 # a touch see-through, so the wall shows through like paint
WALL = (0, 0, 1, 0.45) # where the backdrop is in the frame (x0, y0, x1, y1, 0-1): its features drive the track
SUBJECT = (0.3, 0.2, 0.7, 1) # the bottle, left out of the track because it doesn't move with the wall
FONTS = ["/System/Library/Fonts/Supplemental/Arial Black.ttf", # macOS
"/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", # Linux
"C:/Windows/Fonts/ariblk.ttf"] # Windows
FONT_FILE = next((f for f in FONTS if Path(f).exists()), None) or sys.exit("No bold font found: add one to FONTS")
def wall_mask():
mask = np.zeros((h, w), np.uint8)
x0, y0, x1, y1 = WALL
mask[round(y0 * h):round(y1 * h), round(x0 * w):round(x1 * w)] = 255
x0, y0, x1, y1 = SUBJECT
mask[round(y0 * h):round(y1 * h), round(x0 * w):round(x1 * w)] = 0
return mask
def track(src):
"""For every frame, the homography taking the wall as it was at START to where it is in that frame."""
cap = cv2.VideoCapture(src)
mask, start = wall_mask(), round(START * fps)
homographies, total, prev = [], np.eye(3), None
while True:
ok, img = cap.read()
if not ok:
break
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
if prev is not None and len(homographies) > start:
pts = cv2.goodFeaturesToTrack(prev, 400, 0.01, 7, mask=mask)
moved, found, _ = cv2.calcOpticalFlowPyrLK(prev, gray, pts, None)
ok = found[:, 0] == 1
step, _ = cv2.findHomography(pts[ok], moved[ok], cv2.RANSAC, 2.0)
total = step @ total
homographies.append(total)
prev = gray
return homographies
def title():
"""The title as a full-frame RGBA layer, placed where it sits at START."""
font = ImageFont.truetype(FONT_FILE, round(w * SIZE))
img = Image.new("RGBA", (w, h), COLOR + (0,)) # transparent pink, so warped edges don't pick up a dark fringe
draw = ImageDraw.Draw(img)
draw.text((w * X, h * Y), TEXT, font=font, anchor="mm", fill=COLOR + (round(255 * OPACITY),))
return np.array(img)
def overlay(i):
p = min(max((i / fps - START) / FADE, 0), 1)
if p == 0:
return bytes(w * h * 4)
layer = cv2.warpPerspective(base, homographies[min(i, len(homographies) - 1)], (w, h), flags=cv2.INTER_LINEAR,
borderMode=cv2.BORDER_CONSTANT, borderValue=COLOR + (0,))
layer[:, :, 3] = (layer[:, :, 3] * p).astype(np.uint8)
return layer.tobytes()
def probe(path):
info = json.loads(subprocess.check_output(["ffprobe", "-v", "error", "-select_streams", "v:0", "-show_entries",
"stream=width,height,r_frame_rate:format=duration", "-of", "json", path]))
s = info["streams"][0]
num, den = s["r_frame_rate"].split("/")
return s["width"], s["height"], s["r_frame_rate"], float(num) / float(den), float(info["format"]["duration"])
def render(src, out):
"""Draw overlay(frame number) on every frame of src and save to out, keeping the audio."""
ff = subprocess.Popen(["ffmpeg", "-y", "-v", "error", "-i", src,
"-f", "rawvideo", "-pix_fmt", "rgba", "-s", f"{w}x{h}", "-r", rate, "-i", "-",
"-filter_complex", "[0:v][1:v]overlay=shortest=1,format=yuv420p[v]",
"-map", "[v]", "-map", "0:a?", "-c:v", "libx264", "-crf", "18", "-c:a", "copy", out],
stdin=subprocess.PIPE)
for i in range(round(duration * fps)):
ff.stdin.write(overlay(i))
ff.stdin.close()
if ff.wait():
sys.exit("ffmpeg failed")
src, out = sys.argv[1:3]
w, h, rate, fps, duration = probe(src)
homographies = track(src)
base = title()
render(src, out)





