---
title: "How to add a funhouse mirror with Claude (tested prompt and script)"
description: "For four seconds the bottle bulges fat in one band and pinches thin in the next, an hourglass shape that keeps rolling down the frame, then it settles back to its true shape. Install one skill and ask Claude Code, or run the Python script we tested yourself."
url: https://www.recharm.com/effects/funhouse-mirror
---

# How to add a funhouse mirror with Claude

For four seconds the bottle bulges fat in one band and pinches thin in the next, an hourglass shape that keeps rolling down the frame, then it settles back to its true shape.

Made by the script on this page, from our own test clip.

## Get this effect

1. ### Switch to Code in your [Claude Desktop App](https://claude.com/download)

2. ### Install the skill (once)

   ```
   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 funhouse mirror to input.mp4
   Second the mirror warp starts: 1.0
   Second it's gone again: 5.0
   ```

## 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 funhouse mirror 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                                                       |
| ------- | ------- | ------------------------------------------------------------------ |
| `START` | `1.0`   | Second the mirror warp starts.                                     |
| `END`   | `5.0`   | Second it's gone again.                                            |
| `EASE`  | `0.5`   | Seconds it takes to build up at the start and fade out at the end. |
| `BULGE` | `0.8`   | Extra width in the fattest band. 0.8 is 80% wider.                 |
| `BANDS` | `2.0`   | Fat-and-thin waves from the top of the frame to the bottom.        |
| `ROLL`  | `2.5`   | Seconds for the bands to roll down one wave. Bigger is slower.     |

## Without the skill

Don’t want to install the skill? Paste this prompt into Claude Code and Claude writes the script itself.

```
Add a funhouse mirror effect to input.mp4 from 1.0 to 5.0 seconds; before 1.0 and after 5.0 seconds the video is unchanged. Strength k eases from 0 to 1 over the first 0.5 seconds and back to 0 over the last 0.5 seconds (smoothstep k = u²(3 − 2u), where u = min(t − 1, 5 − t, 0.5) / 0.5). Each row is stretched sideways around the frame's vertical center line by its own amount: output pixel (x, y) shows the input pixel at column w/2 + (x − w/2) / s on the same row, with s = 1 + 0.8 × k × (1 + sin(2π × (2 × y / h − (t − 1) / 2.5))) / 2, where w and h are the frame width and height. So there are 2 fat-and-thin waves from top to bottom, the fattest rows are 1.8x wider at full strength, the thinnest keep their width, and the bands roll down one wave every 2.5 seconds. Only columns move, never rows, and the center column stays put. Use Python with OpenCV (cv2.remap, cubic interpolation) and ffmpeg, 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 `funhouse-mirror.py` and run:

```
python3 funhouse-mirror.py input.mp4 output.mp4
```

```
#!/usr/bin/env python3
"""Funhouse mirror: the picture swells fat and pinches thin in bands that roll down the frame, like a carnival mirror.

Usage: python3 funhouse-mirror.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

START = 1.0      # second the mirror warp starts
END = 5.0        # second it's gone again
EASE = 0.5       # seconds it takes to build up at the start and fade out at the end
BULGE = 0.8      # extra width in the fattest band (0.8 = 80% wider); the thinnest band keeps its width
BANDS = 2.0      # fat-and-thin waves from the top of the frame to the bottom
ROLL = 2.5       # seconds for the bands to roll down one wave

def strength(t):
    """0 outside the effect, easing up to 1 and back down over EASE seconds at each end."""
    u = max(min(t - START, END - t, EASE) / EASE, 0)
    return u * u * (3 - 2 * u)

def source_columns(t):
    """For every pixel, the input column it shows: each row is scaled across the frame's center line
    by its own amount, between 1 (thin bands) and 1 + BULGE (fat bands)."""
    wave = np.sin(2 * math.pi * (BANDS * (rows[:, :1] + 0.5) / h - (t - START) / ROLL))
    scale = 1 + strength(t) * BULGE * (1 + wave) / 2
    return (w / 2 + (cols - w / 2) / scale).astype(np.float32)

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"], s["r_frame_rate"], float(num) / float(den)

src, out = sys.argv[1:3]
w, h, rate, fps = probe(src)
rows, cols = np.mgrid[0:h, 0:w].astype(np.float32) + 0.5
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", rate, "-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)
i = 0
while True:
    buf = reader.stdout.read(w * h * 3)
    if len(buf) < w * h * 3:
        break
    img = np.frombuffer(buf, np.uint8).reshape(h, w, 3)
    t = i / fps
    if START < t < END:
        img = cv2.remap(img, source_columns(t) - 0.5, rows - 0.5, cv2.INTER_CUBIC, borderMode=cv2.BORDER_REFLECT)
    writer.stdin.write(img.tobytes())
    i += 1
writer.stdin.close()
if reader.wait() or writer.wait():
    sys.exit("ffmpeg failed")
```

## More distortion and glitch

- [Anaglyph 3D **Anaglyph 3D**](https://www.recharm.com/effects/anaglyph-3d)
- [Black hole **Black hole**](https://www.recharm.com/effects/black-hole)
- [Bulge and pinch **Bulge and pinch**](https://www.recharm.com/effects/bulge-pinch)
- [Datamosh **Datamosh**](https://www.recharm.com/effects/datamosh)
- [Disintegration **Disintegration**](https://www.recharm.com/effects/disintegration)

## 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](https://www.recharm.com/get-started)
