How to add a burning paper transition with Claude

A hole burns open in the middle of clip A and spreads outward on a ragged, glowing orange edge ringed with scorched brown paper, until clip B fills the frame.

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

    Join clipA.mp4 and clipB.mp4 with a burning paper transition
    Ember colors: near white, orange, dark red

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.

Put a sale badge on @clip1.mp4, give @clip2.mp4 a VHS look, then join them with a burning paper transition.

Settings you can change

Ask Claude to change any of these, or edit them at the top of the script.

SettingDefaultWhat it does
D1.5Length of the transition in seconds.
CENTER(0.5, 0.5)Where the hole starts, as fractions across and down the frame.
RAGGED0.12How far the noise pushes the burn edge in and out, as a share of the frame height.
EMBER0.014Width of the glowing edge, as a share of the frame height.
CHAR0.045Width of the scorched brown band just outside the glowing edge.
GLOW0.03How far the orange glow reaches either side of the edge.
EMBER_COLORSnear white, orange, dark redThe glowing edge's colors from the burned side outward.
CHAR_COLOR(35, 20, 12)Color of the scorched paper next to the edge.
GLOW_COLOR(255, 110, 20)Color of the light the edge throws on both clips.
SEED3Change for a different burn shape.

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
Join clipA.mp4 and clipB.mp4 with a 1.5 second burning paper transition that starts 1.5 seconds before the end of A, so the output is 10.5 seconds long. Match B's size to A (scale to cover and center-crop) and output 30fps. Do it in Python with numpy and Pillow: read both clips' frames as RGB through ffmpeg pipes, composite each frame of the transition in numpy and pipe the result back to ffmpeg. Outside the 1.5 second window the picture must be untouched. Build a static burn map once, with h the frame height: dist = (distance in pixels from the frame center) / h + 0.24 * (n - 0.5), where n = 0.55*N(4x7) + 0.3*N(10x18) + 0.15*N(40x70) and N(cols x rows) is a fixed grid of uniform random values (0-1) upscaled to the frame with bicubic resampling. With EMBER = 0.014, CHAR = 0.045 and GLOW = 0.03, the burn front r runs linearly from min(dist) - EMBER - CHAR - 3*GLOW at the start of the transition to max(dist) + 3*GLOW at the end. For each pixel let e = dist - r. Where e < 0 show clip B, otherwise clip A. Where EMBER <= e < EMBER + CHAR, blend A toward the scorched color RGB (35, 20, 12) by clip(1 - (e - EMBER)/CHAR, 0, 1)^1.5. Where 0 <= e < EMBER, paint the glowing edge, interpolating e/EMBER = 0, 0.5, 1 to RGB (255, 235, 170), (255, 140, 30), (150, 40, 5). Then add an orange glow on both sides of the edge: add RGB (255, 110, 20) * 0.6 * exp(-(e/GLOW)^2) and clip to 0-255. Crossfade the audio over the same 1.5 seconds and keep it. Output H.264 yuv420p, and save it as output.mp4.

Run it without Claude

This is the exact script we tested. Save it as burning-paper-transition.py and run:

python3 burning-paper-transition.py clipA.mp4 clipB.mp4 output.mp4
Show the script
#!/usr/bin/env python3
"""Burning paper: a hole burns open in the middle of clip A and spreads out on a ragged, glowing edge to uncover clip B.

Usage: python3 burning-paper-transition.py clipA.mp4 clipB.mp4 output.mp4  (both clips need an audio track)
Needs ffmpeg, numpy and Pillow (pip install numpy pillow). The burn edge is drawn from random noise, so no stock overlay is needed.
"""
import json
import subprocess
import sys

import numpy as np
from PIL import Image

D = 1.5                        # transition length in seconds
CENTER = (0.5, 0.5)            # where the hole starts (0-1 across and down the frame)
RAGGED = 0.12                  # how far the noise pushes the burn edge in and out, as a share of the frame height
EMBER = 0.014                  # width of the glowing edge, as a share of the frame height
CHAR = 0.045                   # width of the scorched brown band beyond it
GLOW = 0.03                    # reach of the orange glow either side of the edge
EMBER_COLORS = [(255, 235, 170), (255, 140, 30), (150, 40, 5)]  # glowing edge, inside to outside: near white, orange, dark red
CHAR_COLOR = (35, 20, 12)      # color of the scorched paper next to the edge
GLOW_COLOR = (255, 110, 20)    # orange light the edge throws on both clips
SEED = 3                       # change for a different burn shape


def noise(cols, rows):
    """Smooth random field in 0-1: a cols x rows grid of random values scaled up to the frame."""
    grid = np.random.default_rng([SEED, cols]).random((rows, cols)).astype(np.float32)
    return np.asarray(Image.fromarray(grid).resize((w, h), Image.BICUBIC))


def burn(fa, fb, u):
    """Clip A frame fa burned away into clip B frame fb, u (0-1) of the way through the transition."""
    e = dist - (start + (end - start) * u)        # each pixel's distance from the burn edge, into the unburned paper
    out = np.where((e < 0)[..., None], fb, fa).astype(np.float32)
    char = np.clip(1 - (e - EMBER) / CHAR, 0, 1) ** 1.5 * (e >= EMBER)
    out += (np.array(CHAR_COLOR) - out) * char[..., None]
    ember = (e >= 0) & (e < EMBER)
    k = e[ember] / EMBER
    out[ember] = np.stack([np.interp(k, [0, 0.5, 1], [c[i] for c in EMBER_COLORS]) for i in range(3)], axis=1)
    out += np.array(GLOW_COLOR) * 0.6 * np.exp(-(e / GLOW) ** 2)[..., None]
    return np.clip(out, 0, 255).astype(np.uint8)


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


def frames(path, vf):
    """Read a clip's frames as RGB arrays."""
    ff = subprocess.Popen(["ffmpeg", "-v", "error", "-i", path, "-vf", vf, "-f", "rawvideo", "-pix_fmt", "rgb24", "-"],
                          stdout=subprocess.PIPE)
    while data := ff.stdout.read(w * h * 3):
        yield np.frombuffer(data, np.uint8).reshape(h, w, 3)


a, b, out = sys.argv[1:4]
w, h, len_a = probe(a)
len_b = probe(b)[2]
fps = 30
offset = len_a - D
# distance from CENTER in frame heights, pushed in and out by noise so the hole's edge is ragged like burning paper
ys, xs = np.mgrid[0:h, 0:w]
n = 0.55 * noise(4, 7) + 0.3 * noise(10, 18) + 0.15 * noise(40, 70)   # big lobes, smaller bites and a crinkled edge
dist = np.hypot(xs - CENTER[0] * w, ys - CENTER[1] * h) / h + RAGGED * 2 * (n - 0.5)
start = dist.min() - EMBER - CHAR - 3 * GLOW   # nothing shows yet at the first frame
end = dist.max() + 3 * GLOW                    # all of clip B, glow gone, at the last
ra = frames(a, f"fps={fps}")
rb = frames(b, f"scale={w}:{h}:force_original_aspect_ratio=increase,crop={w}:{h},fps={fps}")
ff = subprocess.Popen(["ffmpeg", "-y", "-v", "error", "-f", "rawvideo", "-pix_fmt", "rgb24", "-s", f"{w}x{h}", "-r", str(fps),
                       "-i", "-", "-i", a, "-i", b, "-filter_complex", f"[1:a][2:a]acrossfade=d={D}[au]",
                       "-map", "0:v", "-map", "[au]", "-c:v", "libx264", "-crf", "18", "-pix_fmt", "yuv420p", "-c:a", "aac", out],
                      stdin=subprocess.PIPE)
for i in range(round((len_a + len_b - D) * fps)):
    t = i / fps
    if t < offset:
        frame = next(ra)
    elif t < len_a:
        frame = burn(next(ra), next(rb), (t - offset) / D)
    else:
        frame = next(rb)
    ff.stdin.write(frame.tobytes())
ff.stdin.close()
sys.exit("ffmpeg failed" if ff.wait() else 0)

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