How to add a film strip border with Claude

The video plays inside one frame of a black film strip, with cream sprocket holes running down both edges and slivers of the next frames peeking in above and below.

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 a film strip border to input.mp4
    Edge text: RCH 400  12A

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 film strip border 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
RAIL0.12Width of each sprocket rail as a share of the frame width.
PICTURE0.72Picture width as a share of the frame width.
GAP0.03Black frame line between film frames.
HOLE_W, HOLE_H0.05, 0.07Sprocket hole size as a share of the frame width.
PITCH0.12Spacing between sprocket holes.
FILM(18, 16, 14)Film base color.
HOLE(250, 247, 240)Sprocket hole color.
EDGE_TEXTRCH 400 12AEdge print up the left rail.
EDGE_COLOR(235, 165, 40)Edge print color.

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
Put input.mp4 inside a film strip. Scale the video to 72% of the frame width (388x690 on a 540x960 clip, keeping its aspect) and center it, with corners rounded to 1.2% of the frame width. Fill everything around it with film base color (18, 16, 14). Repeat the same scaled video directly above and below it with a 3%-of-width (16 px) black frame line between them, so slivers of the neighboring film frames show at the top and bottom edges. Down each side, in a rail 12% of the frame width, draw a column of cream (250, 247, 240) sprocket holes 5% of the frame width wide and 7% tall with slightly rounded corners, one every 12% of the frame width, centered in the rail and spread evenly from top to bottom. Add small amber (235, 165, 40) edge print "RCH 400  12A" in a bold mono font at 2.6% of the frame width, rotated to read up the left side just inside the rail, beside the middle of the picture. Keep the strip on screen for the whole clip. Use Python with Pillow to draw the strip (draw any text with Pillow, not ffmpeg's drawtext) and ffmpeg to composite it, keep the audio and the original size, and save it as output.mp4.

Run it without Claude

This is the exact script we tested. Save it as film-strip-border.py and run:

python3 film-strip-border.py input.mp4 output.mp4
Show the script
#!/usr/bin/env python3
"""Film strip border: the video plays in one frame of a black film strip, with sprocket holes down both sides.

Usage: python3 film-strip-border.py input.mp4 output.mp4
Needs ffmpeg and Pillow (pip install pillow).
"""
import subprocess
import sys
import tempfile
from pathlib import Path

from PIL import Image, ImageDraw, ImageFont

RAIL = 0.12                  # width of each sprocket rail as a share of the frame width
PICTURE = 0.72               # picture width as a share of the frame width (centered, original aspect)
GAP = 0.03                   # black frame line between film frames, as a share of the frame width
HOLE_W, HOLE_H = 0.05, 0.07  # sprocket hole size as a share of the frame width
PITCH = 0.12                 # distance from one hole to the next, as a share of the frame width
FILM = (18, 16, 14)          # film base color
HOLE = (250, 247, 240)       # sprocket holes (light shining through)
EDGE_TEXT = "RCH 400  12A"   # edge print running up the left rail
EDGE_COLOR = (235, 165, 40)  # amber edge print
FONTS = ["/System/Library/Fonts/Supplemental/Courier New Bold.ttf",       # macOS
         "/usr/share/fonts/truetype/dejavu/DejaVuSansMono-Bold.ttf",      # Linux
         "C:/Windows/Fonts/courbd.ttf"]                                   # Windows
FONT_FILE = next((f for f in FONTS if Path(f).exists()), None) or sys.exit("No mono font found: add one to FONTS")


def strip(w, h, pic_w, pic_h, top):
    """The film around the picture: rails with holes, frame lines, and a clear window for each film frame."""
    img = Image.new("RGBA", (w, h), FILM)
    draw = ImageDraw.Draw(img)
    x0, pitch = (w - pic_w) // 2, pic_h + round(w * GAP)
    for y in range(top - pitch, h, pitch):  # this frame and the neighbors above and below it
        draw.rounded_rectangle([x0, y, x0 + pic_w - 1, y + pic_h - 1], radius=round(w * 0.012), fill=(0, 0, 0, 0))
    hw, hh, step = round(w * HOLE_W), round(w * HOLE_H), w * PITCH
    for cx in (w * RAIL / 2, w * (1 - RAIL / 2)):
        y = (h % step) / 2 + (step - hh) / 2
        while y + hh <= h:
            draw.rounded_rectangle([cx - hw / 2, y, cx + hw / 2, y + hh], radius=round(hw * 0.2), fill=HOLE)
            y += step
    font = ImageFont.truetype(FONT_FILE, round(w * 0.026))
    box = font.getbbox(EDGE_TEXT)
    label = Image.new("RGBA", (box[2], box[3]))
    ImageDraw.Draw(label).text((0, 0), EDGE_TEXT, font=font, fill=EDGE_COLOR)
    label = label.rotate(90, expand=True)  # reads up the left rail, beside the middle of the picture
    img.alpha_composite(label, (round(w * (RAIL + 0.005)), top + (pic_h - label.height) // 2))
    return img


src, out = sys.argv[1:3]
w, h = map(int, subprocess.check_output(["ffprobe", "-v", "error", "-select_streams", "v:0", "-show_entries",
                                         "stream=width,height", "-of", "csv=p=0", src], text=True).strip().split(","))
pic_w = round(w * PICTURE) // 2 * 2
pic_h = round(pic_w * h / w) // 2 * 2
top, pitch = (h - pic_h) // 2, pic_h + round(w * GAP)
x0 = (w - pic_w) // 2
with tempfile.TemporaryDirectory() as tmp:
    film = f"{tmp}/film.png"
    strip(w, h, pic_w, pic_h, top).save(film)
    # the picture, plus copies above and below so the neighboring film frames peek in at the edges
    subprocess.run(["ffmpeg", "-y", "-v", "error", "-i", src, "-loop", "1", "-i", film, "-filter_complex",
                    f"[0:v]fps=30,scale={pic_w}:{pic_h},split=3[p][a][b];"
                    f"color=black:s={w}x{h}:r=30[bg];"
                    f"[bg][p]overlay={x0}:{top}:shortest=1[v1];"
                    f"[v1][a]overlay={x0}:{top - pitch}[v2];"
                    f"[v2][b]overlay={x0}:{top + pitch}[v3];"
                    f"[v3][1:v]overlay=0:0:shortest=1,format=yuv420p[v]",
                    "-map", "[v]", "-map", "0:a?", "-c:v", "libx264", "-crf", "18", "-c:a", "copy", out], check=True)

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