How to add a toy camera look with Claude

The silvery backdrop turns saturated cyan-turquoise and the purple bottle stays crisp in the middle, while the picture smears out and falls to near black toward all four corners, like a shot through a cheap plastic lens.

Get this effect

  1. Switch to Code in your Claude Desktop App

  2. 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/skills
  3. Ask for the effect

    Add a toy camera look to input.mp4
    Red curve points (input/output): 0/0 0.3/0.18 0.7/0.72 1/0.95
    Green curve points: 0/0 0.3/0.24 0.7/0.8 1/1

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 toy camera look 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.

SettingDefaultWhat it does
RED0/0 0.3/0.18 0.7/0.72 1/0.95Red curve points (input/output). Held below the others, so the picture leans cyan.
GREEN0/0 0.3/0.24 0.7/0.8 1/1Green curve points. The S-shape adds contrast.
BLUE0/0.08 0.3/0.3 0.7/0.8 1/1Blue curve points. Raising 0 tints the shadows blue.
SATURATION1.8Extra color on top of the curves. 1 leaves it as is.
BLUR7Gaussian blur radius at the corners, in pixels. 0 keeps the edges sharp.
DARKEN0.85How much darker the very corners get. 0.85 is 85% darker; 0 turns the vignette off.
CLEAR0.45The sharp, undarkened middle, as a share of the distance from the center to a corner.

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
Give input.mp4 a toy camera look for the whole clip. 1) With ffmpeg, grade it: curves=r='0/0 0.3/0.18 0.7/0.72 1/0.95':g='0/0 0.3/0.24 0.7/0.8 1/1':b='0/0.08 0.3/0.3 0.7/0.8 1/1' (red held down so the picture leans cyan, more contrast, blue-lifted shadows), then eq=saturation=1.8. 2) Then, per frame with numpy and Pillow, make the edges soft and dark like a cheap plastic lens. For each pixel, let r be its distance from the frame center with x measured in half-widths and y in half-heights, divided by sqrt(2) so r is 0 at the center and 1 at the corners; s = clamp((r - 0.45) / 0.55, 0, 1) and edge = s*s*(3 - 2*s). Blend the sharp frame with a copy blurred by Pillow's GaussianBlur(7) using edge as the blurred copy's weight, then multiply by 1 - 0.85*edge, so the middle stays sharp and untouched and the corners are blurred and 85% darker. The cool blue-white backdrop (around RGB 193, 203, 216) should come out cyan-turquoise (around RGB 163, 213, 221), the frame corners should be around brightness 35, and the middle of the frame should stay as bright as before. Don't add any text (if you ever do, draw it with Pillow, not ffmpeg's drawtext). Keep the size (540x960), 30fps, 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 toy-camera.py and run:

python3 toy-camera.py input.mp4 output.mp4
Show the script
#!/usr/bin/env python3
"""Toy camera: punchy, oversaturated cyan-shifted color, with corners that go soft and then dark like a cheap plastic lens.

Usage: python3 toy-camera.py input.mp4 output.mp4
Needs ffmpeg, numpy and Pillow (pip install numpy pillow).
"""
import json
import subprocess
import sys

import numpy as np
from PIL import Image, ImageFilter

RED = "0/0 0.3/0.18 0.7/0.72 1/0.95"      # red S-curve, held down so the picture leans cyan
GREEN = "0/0 0.3/0.24 0.7/0.8 1/1"        # green S-curve: more contrast
BLUE = "0/0.08 0.3/0.3 0.7/0.8 1/1"       # blue S-curve with lifted blacks, so shadows go blue
SATURATION = 1.8                          # extra color on top of the curves (1 leaves it as is)
BLUR = 7                                  # Gaussian blur radius, in pixels, at the corners
DARKEN = 0.85                             # how much darker the very corners get (0.85 = 85% darker)
CLEAR = 0.45                              # sharp, undarkened middle, as a share of the distance to the corner


def probe(path):
    info = json.loads(subprocess.check_output(["ffprobe", "-v", "error", "-select_streams", "v:0", "-show_entries",
                                               "stream=width,height", "-of", "json", path]))
    return info["streams"][0]["width"], info["streams"][0]["height"]


def falloff():
    """0 in the middle of the frame, rising smoothly to 1 at the corners."""
    y, x = np.mgrid[0:h, 0:w].astype(np.float32)
    r = np.hypot((x - w / 2) / (w / 2), (y - h / 2) / (h / 2)) / np.sqrt(2)
    s = np.clip((r - CLEAR) / (1 - CLEAR), 0, 1)
    return (s * s * (3 - 2 * s))[:, :, None]


def render(src, out):
    grade = f"fps=30,curves=r='{RED}':g='{GREEN}':b='{BLUE}',eq=saturation={SATURATION}"
    reader = subprocess.Popen(["ffmpeg", "-v", "error", "-i", src, "-vf", grade,
                               "-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", "30", "-i", "-", "-i", src, "-map", "0:v", "-map", "1:a?",
                               "-c:v", "libx264", "-crf", "18", "-pix_fmt", "yuv420p", "-c:a", "copy", "-shortest", out],
                              stdin=subprocess.PIPE)
    edge = falloff()
    shade = 1 - DARKEN * edge
    while raw := reader.stdout.read(w * h * 3):
        img = Image.frombuffer("RGB", (w, h), raw)
        sharp = np.asarray(img, np.float32)
        soft = np.asarray(img.filter(ImageFilter.GaussianBlur(BLUR)), np.float32)
        frame = (sharp * (1 - edge) + soft * edge) * shade
        writer.stdin.write(np.clip(frame, 0, 255).astype(np.uint8).tobytes())
    writer.stdin.close()
    if reader.wait() or writer.wait():
        sys.exit("ffmpeg failed")


src, out = sys.argv[1:3]
w, h = probe(src)
render(src, out)

Find 3x winning ads

Recharm cuts your ad footage into tagged, searchable clips: hooks, testimonials, product shots. Find the shots that win, add the effect with Claude, and ship 3x more ad edits.

Start 14-day free trial