How to add an elastic widescreen stretch with Claude

The black side bars of a 4:3 picture vanish as its left and right edges snap outward to fill the 16:9 frame, while the bottle in the middle keeps its exact shape.

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 elastic widescreen stretch to input.mp4
    Shape of the picture before it's stretched: 4 / 3
    Second the stretch begins: 1.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 an elastic widescreen stretch 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
ASPECT4 / 3Shape of the picture before it's stretched.
START1.0Second the stretch begins.
STRETCH0.8Seconds it takes to reach the frame edges.
BOUNCE1.2How far it swings past the edges before settling. 0 for none.

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
input.mp4 is 960x540. Treat its middle 720x540 (columns 120 to 840) as a 4:3 picture: for the first second show only that, with black bars over the 120 pixels at each side. From 1.0 to 1.8 seconds, stretch the 4:3 picture out to fill the whole 960 pixel width, nonlinearly: with x the output column's distance from the center (-480 to 480) divided by the picture's current half-width, take the source column at 480 + 360 * (k*x + (1 - k)*x^3), where k is the current half-width divided by 360. That keeps the middle at its original scale and puts all the stretch at the edges. Grow the half-width from 360 to 480 with an ease-out-back curve (overshoot 1.2) so it swings slightly past the frame edges and settles, then hold it full width to the end. Use Python with OpenCV (cv2.remap) 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 elastic-widescreen-stretch.py and run:

python3 elastic-widescreen-stretch.py input.mp4 output.mp4
Show the script
#!/usr/bin/env python3
"""Elastic widescreen stretch: a pillarboxed 4:3 picture stretches out to fill the 16:9 frame, edges only.

Usage: python3 elastic-widescreen-stretch.py input.mp4 output.mp4
Needs ffmpeg, OpenCV and numpy (pip install opencv-python-headless numpy).
The 4:3 picture is the middle of the frame, full height; the sides start as black bars.
"""
import json
import subprocess
import sys

import cv2
import numpy as np

ASPECT = 4 / 3   # shape of the picture before it's stretched
START = 1.0      # second the stretch begins
STRETCH = 0.8    # seconds it takes to reach the frame edges
BOUNCE = 1.2     # overshoot past the edges before settling (0 = none)


def ease_out_back(p):
    """0 -> 1, swinging a little past 1 before settling, so the picture snaps out like elastic."""
    p = min(max(p, 0), 1)
    return 1 + (BOUNCE + 1) * (p - 1) ** 3 + BOUNCE * (p - 1) ** 2


def source_columns(t):
    """For every output column, the column of the 4:3 picture it shows (-1 for the black bars).

    The picture's half-width grows from its own size to half the frame. Inside it, a cubic curve
    maps output to source with a slope of exactly 1 at the center, so the middle keeps its shape
    and all the stretch goes to the edges."""
    half_src = h * ASPECT / 2
    half = half_src + (w / 2 - half_src) * ease_out_back((t - START) / STRETCH)
    k = half / half_src
    u = (np.arange(w) + 0.5 - w / 2) / half
    x = w / 2 + half_src * (k * u + (1 - k) * u ** 3) - 0.5
    return np.where(np.abs(u) <= 1, x, -1).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 = np.repeat(np.arange(h, dtype=np.float32)[:, None], w, axis=1)
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)
    cols = np.repeat(source_columns(i / fps)[None, :], h, axis=0)
    img = cv2.remap(img, cols, rows, cv2.INTER_CUBIC, borderMode=cv2.BORDER_CONSTANT, borderValue=0)
    writer.stdin.write(img.tobytes())
    i += 1
writer.stdin.close()
if reader.wait() or writer.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