How to add an infinite zoom with Claude

At one second a white-framed copy of the shot appears in its own center with smaller copies nested inside it, and the camera keeps zooming into copy after copy, one a second, until five seconds.

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 an infinite zoom to input.mp4
    Second the zoom starts: 1.0
    How many copies the camera flies into: 4

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 an infinite zoom 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
START1.0Second the zoom starts. The copies fade in just before.
LOOPS4How many copies the camera flies into.
LOOP_TIME1.0Seconds to zoom from one copy to the next. Smaller is faster.
FADE0.2Seconds the copies take to fade in and out.
SCALE0.5Size of each copy as a share of the one around it.
DEPTH6How many copies are nested inside each other.
BORDER4Width of the frame round each copy, in pixels at full size. 0 hides it.
BORDER_COLOR(245, 245, 245)Color of the frame round each copy.

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 an infinite zoom (Droste effect) to input.mp4 with Python and Pillow, decoding and encoding the frames with ffmpeg. For each frame F (540x960), build a nested picture D: start with F, then 6 times paste the current picture, shrunk to half size (270x480, Lanczos), centered on a fresh copy of F, after first filling a rectangle 4 px larger on every side with (245, 245, 245) as a border. Between 1.0s and 5.0s, zoom into the center with zoom z = 2^frac((t - 1) / 1.0), where t is the time in seconds, so z goes 1 -> 2 once a second (4 loops). Each output frame is F scaled up z times about its center and cropped back to 540x960 (bicubic), with D shrunk to (0.5 x z) of the frame size pasted in the center inside a border of 4 x z px of (245, 245, 245). At z = 2 the copy fills the screen and looks exactly like z = 1, so the loop has no seam. Fade the copies (border included) in from 0.8s to 1.0s and out from 5.0s to 5.2s by blending with the plain frame; before 0.8s and after 5.2s the frames are untouched. Keep the size, 30fps and the audio, and save it as output.mp4.

Run it without Claude

This is the exact script we tested. Save it as infinite-zoom.py and run:

python3 infinite-zoom.py input.mp4 output.mp4
Show the script
#!/usr/bin/env python3
"""Infinite zoom: a framed copy of the shot appears in its own center, and the camera zooms into it copy after copy.

Usage: python3 infinite-zoom.py input.mp4 output.mp4
Needs ffmpeg and Pillow (pip install pillow).
"""
import json
import subprocess
import sys

from PIL import Image, ImageDraw

START = 1.0                   # second the zoom starts (the copies fade in just before)
LOOPS = 4                     # how many copies the camera flies into
LOOP_TIME = 1.0               # seconds to zoom from one copy to the next
FADE = 0.2                    # seconds the copies take to fade in before START and out after the last loop
SCALE = 0.5                   # size of each copy, as a share of the one around it
DEPTH = 6                     # how many copies are nested inside each other
BORDER = 4                    # frame round each copy, in pixels at full size
BORDER_COLOR = (245, 245, 245)
FPS = 30                      # output frame rate
END = START + LOOPS * LOOP_TIME


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 frames(path):
    """Decode a clip as RGB frames at FPS."""
    ff = subprocess.Popen(["ffmpeg", "-v", "error", "-i", path, "-vf", f"fps={FPS}",
                           "-f", "rawvideo", "-pix_fmt", "rgb24", "-"], stdout=subprocess.PIPE)
    while True:
        data = ff.stdout.read(w * h * 3)
        if len(data) < w * h * 3:
            return
        yield Image.frombytes("RGB", (w, h), data)


def framed_copy(base, inner, scale, alpha=1.0):
    """base with inner shrunk to `scale` of the frame in its center, inside a thin border."""
    cw, ch = round(w * scale), round(h * scale)
    x0, y0 = (w - cw) // 2, (h - ch) // 2
    b = max(1, round(BORDER * scale / SCALE))
    out = base.copy()
    ImageDraw.Draw(out).rectangle([x0 - b, y0 - b, x0 + cw + b - 1, y0 + ch + b - 1], fill=BORDER_COLOR)
    out.paste(inner.resize((cw, ch), Image.LANCZOS), (x0, y0))
    return Image.blend(base, out, alpha) if alpha < 1 else out


def nested(frame):
    """The frame with DEPTH copies of itself, each inside the last."""
    img = frame
    for _ in range(DEPTH):
        img = framed_copy(frame, img, SCALE)
    return img


def zoomed(img, z):
    """img scaled up z times about its center, cropped back to the frame size."""
    cw, ch = w / z, h / z
    box = ((w - cw) / 2, (h - ch) / 2, (w + cw) / 2, (h + ch) / 2)
    return img.resize((w, h), Image.BICUBIC, box=box)


def infinite_zoom(frame, t):
    """Zoom z runs from 1 to 1/SCALE once per loop: by then the first copy fills the screen and looks exactly
    like the start, so the loop repeats without a seam. The copy is drawn at its on-screen size to stay sharp."""
    alpha = min(max((t - START + FADE) / FADE, 0), max((END + FADE - t) / FADE, 0), 1)
    if alpha == 0:
        return frame
    loop = min(max(t - START, 0), END - START) / LOOP_TIME % 1
    z = (1 / SCALE) ** loop
    return framed_copy(zoomed(frame, z), nested(frame), SCALE * z, alpha)


src, out = sys.argv[1:3]
w, h = probe(src)
enc = 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?",
                        "-c:v", "libx264", "-crf", "18", "-pix_fmt", "yuv420p", "-c:a", "copy", "-shortest", out],
                       stdin=subprocess.PIPE)
for i, frame in enumerate(frames(src)):
    enc.stdin.write(infinite_zoom(frame, i / FPS).tobytes())
enc.stdin.close()
if enc.wait():
    sys.exit("ffmpeg failed")

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