How to add an incoming call screen with Claude

The video opens as a phone call from "Your Skin", with red Decline and green Accept buttons over a blurred, dimmed clip, then the call screen fades away to reveal the shot.

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 incoming call screen to input.mp4
    Caller: Your Skin
    Status: incoming call...

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 incoming call screen 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
CALLERYour SkinName on the call screen.
STATUSincoming call...Small line above the name.
END2.5Second the call is answered and the screen starts to fade.
FADE0.3Seconds the fade takes.
DARKEN0.45How much the blurred clip behind the call is darkened (0-1).
ACCEPT, DECLINE(52, 199, 89), (255, 59, 48)Button colors.

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
Make input.mp4 open on a fake incoming phone call. From 0 to 2.5 seconds, show the clip blurred (box blur radius 18, 2 passes) and darkened to 55% brightness, with a call screen over it: "incoming call..." in light gray (235, 235, 240) bold text 4.5% of the frame width tall, centered at 14% down; the caller name "Your Skin" in white bold text 11% of the frame width tall, centered at 21% down; a gray (142, 142, 147) circle 30% of the frame width across centered at 36% down with a white "Y" in it; and two round buttons 20% of the frame width across centered at 82% down: a red (255, 59, 48) Decline button at 25% across and a green (52, 199, 89) Accept button at 75% across, each with a white phone handset icon and a white label ("Decline", "Accept", 4% of the frame width tall) just below. A thin green ring pulses out of the Accept button about once a second. At 2.5 seconds the call screen and the blur fade out over 0.3 seconds, and the clip plays on untouched. Use Python with Pillow to draw the call screen (don't rely on ffmpeg's drawtext filter) and ffmpeg for the blur and compositing, keep the audio, and save it as output.mp4.

Run it without Claude

This is the exact script we tested. Save it as incoming-call-screen.py and run:

python3 incoming-call-screen.py input.mp4 output.mp4
Show the script
#!/usr/bin/env python3
"""Incoming call screen: the clip opens as a phone call from "Your Skin", then the call screen fades away.

Usage: python3 incoming-call-screen.py input.mp4 output.mp4
Needs ffmpeg and Pillow (pip install pillow).
"""
import json
import math
import subprocess
import sys
from pathlib import Path

from PIL import Image, ImageDraw, ImageFont

CALLER = "Your Skin"         # name on the call screen
STATUS = "incoming call..."  # small line under the name
END = 2.5                    # second the call is answered and the screen starts to fade
FADE = 0.3                   # seconds the fade takes
DARKEN = 0.45                # how much the blurred clip behind the call screen is darkened (0-1)
ACCEPT = (52, 199, 89)       # answer button color
DECLINE = (255, 59, 48)      # decline button color
FONTS = ["/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 font(share):
    return ImageFont.truetype(FONT_FILE, round(w * share))


def handset(draw, cx, cy, r, angle):
    """A phone handset icon: a thick arc with an ear and a mouth piece, turned by `angle` degrees."""
    cx -= r * 0.36 * math.cos(math.radians(270 + angle))  # nudge it so the icon, not the arc's circle, is centered
    cy -= r * 0.36 * math.sin(math.radians(270 + angle))
    draw.arc([cx - r, cy - r, cx + r, cy + r], 200 + angle, 340 + angle, fill="white", width=round(r * 0.38))
    for a in (200 + angle, 340 + angle):
        ex, ey = cx + r * 0.86 * math.cos(math.radians(a)), cy + r * 0.86 * math.sin(math.radians(a))
        draw.ellipse([ex - r * 0.3, ey - r * 0.3, ex + r * 0.3, ey + r * 0.3], fill="white")


def call_screen():
    img = Image.new("RGBA", (w, h))
    draw = ImageDraw.Draw(img)
    draw.text((w / 2, h * 0.14), STATUS, font=font(0.045), anchor="mm", fill=(235, 235, 240))
    draw.text((w / 2, h * 0.21), CALLER, font=font(0.11), anchor="mm", fill="white")
    r = w * 0.15
    draw.ellipse([w / 2 - r, h * 0.36 - r, w / 2 + r, h * 0.36 + r], fill=(142, 142, 147))
    draw.text((w / 2, h * 0.36), CALLER[0], font=font(0.15), anchor="mm", fill="white")
    for x, color, label, angle in ((0.25, DECLINE, "Decline", 0), (0.75, ACCEPT, "Accept", -135)):
        cx, cy, br = w * x, h * 0.82, w * 0.1
        draw.ellipse([cx - br, cy - br, cx + br, cy + br], fill=color)
        handset(draw, cx, cy, br * 0.5, angle)
        draw.text((cx, cy + br + w * 0.05), label, font=font(0.04), anchor="mm", fill="white")
    return img


def overlay(t):
    """The call screen, with a ring pulsing out of the answer button, faded out after END."""
    img = screen.copy()
    pulse = (t * 1.2) % 1
    cx, cy, r = w * 0.75, h * 0.82, w * 0.1 * (1 + 0.45 * pulse)
    ring = Image.new("RGBA", (w, h))
    ImageDraw.Draw(ring).ellipse([cx - r, cy - r, cx + r, cy + r], outline=ACCEPT + (round(200 * (1 - pulse)),),
                                 width=max(2, w // 120))
    img = Image.alpha_composite(ring, img)
    alpha = min(max(1 - (t - END) / FADE, 0), 1)
    if alpha < 1:
        img.putalpha(img.getchannel("A").point(lambda a: round(a * alpha)))
    return img


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 render(src, out):
    """Blur and darken the clip behind the call screen until END, then fade both away, keeping the audio."""
    behind = (f"[0:v]split[clip][b];[b]boxblur=18:2,colorchannelmixer=rr={1 - DARKEN}:gg={1 - DARKEN}:bb={1 - DARKEN},"
              f"format=rgba,fade=t=out:st={END}:d={FADE}:alpha=1[blur];[clip][blur]overlay[bg];")
    ff = subprocess.Popen(["ffmpeg", "-y", "-v", "error", "-i", src,
                           "-f", "rawvideo", "-pix_fmt", "rgba", "-s", f"{w}x{h}", "-r", rate, "-i", "-",
                           "-filter_complex", behind + "[bg][1:v]overlay=shortest=1,format=yuv420p[v]",
                           "-map", "[v]", "-map", "0:a?", "-c:v", "libx264", "-crf", "18", "-c:a", "copy", out],
                          stdin=subprocess.PIPE)
    for i in range(math.ceil(duration * fps)):
        ff.stdin.write(overlay(i / fps).tobytes())
    ff.stdin.close()
    if ff.wait():
        sys.exit("ffmpeg failed")


src, out = sys.argv[1:3]
w, h, rate, fps, duration = probe(src)
screen = call_screen()
render(src, out)

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