Get this effect
Switch to Code in your Claude Desktop App
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/skillsAsk for the effect
Join clipA.mp4 and clipB.mp4 with a stinger transition Lines: ["NEW", "DROP"] Panels: yellow, pink, navy
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 stinger transition.
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 |
|---|---|---|
LINES | ["NEW", "DROP"] | The text on the top panel, one entry per line. |
D | 1.2 | Length of the transition in seconds; the cut sits halfway. |
SWEEP | 0.45 | Seconds each panel takes to slide in, and again to slide out. |
STAGGER | 0.07 | Seconds between one panel and the next. |
POINT | 0.5 | How sharp the arrow point is (sideways pixels per pixel up or down). |
PANELS | yellow, pink, navy | Panel colors from bottom to top; the last one covers the cut. |
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.2 second stinger transition that starts 1.2 seconds before the end of clipA.mp4. The stinger is three right-pointing arrow-shaped (chevron) bands drawn with Pillow and numpy, stacked bottom to top: yellow (255, 200, 40), pink (255, 70, 160) and navy (22, 26, 64). Each band is 1155 pixels wide on a 540x960 frame (the frame width plus 480 plus 25% of the width); its leading edge is an arrow whose tip sits on the frame's vertical center, and each row's edge sits 0.5 pixels further back for every pixel it is above or below the center, and its trailing edge is the same notched arrow shape. Each band starts fully off the left edge (its center 577 pixels left of the frame), slides in to center on the frame in 0.45 seconds with an ease-out cubic, rests there, then slides off the right edge (center 1357 pixels from the left) in 0.45 seconds with an ease-in cubic. Bands enter 0.07 seconds apart, yellow first (yellow at 0, pink at 0.07, navy at 0.14 seconds into the transition), and leave in the reverse order, navy first (navy at 0.61, pink at 0.68, yellow at 0.75 seconds), so the navy band covers the whole frame from about 0.4 to 0.8 seconds into the transition. On the navy band draw "NEW" over "DROP" in white Arial Black, 130 pixels tall, centered on the band's center and moving with it, the two lines 136 pixels apart around the frame's vertical center. Cut from clip A to clip B under full cover, halfway through the transition (0.6 seconds in; B picks up 0.6 seconds into its own clip), so the output is 10.8 seconds long. Crossfade the audio over the whole 1.2 seconds. Match B's size and frame rate to A, output 30fps H.264 yuv420p. Draw everything with Pillow (not ffmpeg's drawtext), composite it with ffmpeg, keep the audio and save it as output.mp4.Run it without Claude
This is the exact script we tested. Save it as stinger-transition.py and run:
python3 stinger-transition.py clipA.mp4 clipB.mp4 output.mp4Show the script
#!/usr/bin/env python3
"""Stinger transition: yellow, pink and navy arrow-shaped panels sweep across, the navy one carrying "NEW DROP", and the cut hides under them.
Usage: python3 stinger-transition.py clipA.mp4 clipB.mp4 output.mp4 (both clips need an audio track)
Needs ffmpeg, numpy and Pillow (pip install numpy pillow).
"""
import json
import math
import subprocess
import sys
from pathlib import Path
import numpy as np
from PIL import Image, ImageDraw, ImageFont
LINES = ["NEW", "DROP"] # text on the top panel
D = 1.2 # transition length in seconds; the cut sits halfway, under full cover
SWEEP = 0.45 # seconds each panel takes to slide in, and again to slide out
STAGGER = 0.07 # seconds between one panel and the next
POINT = 0.5 # how sharp the arrow point is: sideways pixels per pixel up or down
PANELS = [(255, 200, 40), # bottom to top: yellow leads in and trails out,
(255, 70, 160), # then pink,
(22, 26, 64)] # then navy, which covers the whole frame at the cut
TEXT_COLOR = (255, 255, 255)
TEXT_SIZE = 0.24 # text height as a share of the frame width
FONTS = ["/System/Library/Fonts/Supplemental/Arial Black.ttf", # macOS
"/System/Library/Fonts/Supplemental/Arial Bold.ttf", # macOS
"/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", # Linux
"C:/Windows/Fonts/arialbd.ttf"] # Windows
FONT_FILE = next((f for f in FONTS if Path(f).exists()), None) or sys.exit("No bold font found: add one to FONTS")
def probe(path):
info = json.loads(subprocess.check_output(["ffprobe", "-v", "error", "-select_streams", "v:0", "-show_entries",
"stream=width,height,r_frame_rate:format=duration", "-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), float(info["format"]["duration"])
def title():
"""The text, drawn once, to ride in the middle of the top panel."""
font = ImageFont.truetype(FONT_FILE, round(w * TEXT_SIZE))
img = Image.new("RGBA", (w, h))
draw = ImageDraw.Draw(img)
gap = w * TEXT_SIZE * 1.05
for i, line in enumerate(LINES):
draw.text((w / 2, h / 2 + (i - (len(LINES) - 1) / 2) * gap), line, font=font, anchor="mm", fill=TEXT_COLOR)
return img
def center_x(t, i):
"""Where panel i's middle is at t seconds into the transition: it eases in to the frame's center,
rests there, then speeds off to the right. Panels enter bottom first and leave top first."""
t_in, t_out = i * STAGGER, D - SWEEP - i * STAGGER
if t < t_out:
p = min(max((t - t_in) / SWEEP, 0), 1)
return start + (w / 2 - start) * (1 - (1 - p) ** 3)
p = min((t - t_out) / SWEEP, 1)
return w / 2 + (end - w / 2) * p ** 3
def overlay(t):
"""The stinger at t seconds into the transition: arrow-shaped bands, each pointing right."""
img = np.zeros((h, w, 4), np.uint8)
for i, color in enumerate(PANELS):
c = center_x(t, i)
inside = (xs > c - band / 2 - point) & (xs < c + band / 2 - point)
img[inside] = (*color, 255)
frame = Image.fromarray(img)
frame.alpha_composite(text.transform((w, h), Image.AFFINE, (1, 0, w / 2 - center_x(t, len(PANELS) - 1), 0, 1, 0)))
return frame
A, B, out = sys.argv[1:4]
w, h, rate, fps, length = probe(A)
xs = np.arange(w)[None, :]
point = POINT * np.abs(np.arange(h)[:, None] - h / 2) # how far each row's edge sits behind the arrow's tip
band = w + 2 * POINT * h / 2 + 0.25 * w # wide enough to cover the frame with room to spare
start, end = -band / 2, w + band / 2 + POINT * h / 2 # fully off the left, fully off the right
text = title()
cut = length - D / 2
# A up to the cut, then B from D/2 seconds in (the stinger covers the moment between), audio crossfaded over D
ff = subprocess.Popen([
"ffmpeg", "-y", "-v", "error", "-i", A, "-i", B,
"-f", "rawvideo", "-pix_fmt", "rgba", "-s", f"{w}x{h}", "-r", rate, "-i", "-",
"-filter_complex",
f"[0:v]trim=0:{cut},setpts=PTS-STARTPTS,fps={rate},setsar=1[a];"
f"[1:v]scale={w}:{h}:force_original_aspect_ratio=increase,crop={w}:{h},fps={rate},setsar=1,"
f"trim=start={D / 2},setpts=PTS-STARTPTS[b];"
f"[a][b]concat=n=2:v=1:a=0[base];"
f"[2:v]setpts=PTS+{length - D}/TB[sting];"
f"[base][sting]overlay=eof_action=pass,format=yuv420p[v];"
f"[0:a][1:a]acrossfade=d={D}[au]",
"-map", "[v]", "-map", "[au]", "-r", rate, "-c:v", "libx264", "-crf", "18", "-c:a", "aac", out],
stdin=subprocess.PIPE)
for n in range(math.ceil(D * fps)):
ff.stdin.write(overlay(n / fps).tobytes())
ff.stdin.close()
if ff.wait():
sys.exit("ffmpeg failed")





