How to add a comic panel layout with Claude

Three clips snap one by one into black-bordered panels on a white page, a wide one across the top and a tall and a narrow one below, then a yellow "MEANWHILE..." caption box lands in the top panel's corner.

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

    Make a comic panel layout from main.mp4 and second.mp4 and wide.mp4
    Caption: MEANWHILE...

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 comic panel layout 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
GUTTER0.03White gap between panels and round the page, as a share of the frame width.
BORDER6Black panel border in pixels.
TOP_SHARE, LEFT_SHARE0.42, 0.56Where the top panel ends down the frame and the bottom-left panel ends across it.
APPEAR(0.0, 0.5, 1.0)Seconds the top, bottom-left and bottom-right panels snap in.
CAPTIONMEANWHILE...Text in the caption box.
CAPTION_AT1.5Second the caption box appears.
CAPTION_COLOR(250, 215, 0)Caption box yellow.

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 a comic-page layout from main.mp4, second.mp4 and wide.mp4, the same size as main.mp4 (540x960), 30fps, 6 seconds. Start from a white page. Lay out three panels with 16px white gutters between them and round the page edge: a wide top panel from (16, 16) to (524, 403), a bottom-left panel from (16, 419) to (302, 944) and a narrow bottom-right panel from (318, 419) to (524, 944) (pixel corners, x then y). Each panel has a 6px black border on the inside of its box, and the clip fills the area inside the border, scaled to cover it and cropped from the center. wide.mp4 goes in the top panel and appears at 0 seconds, main.mp4 in the bottom-left panel snaps in at 0.5 seconds, second.mp4 in the bottom-right panel snaps in at 1.0 second; before a panel snaps in its space is plain white page. At 1.5 seconds, a caption box appears flush in the top panel's top-left corner (its top-left at (16, 16)): a yellow (250, 215, 0) rectangle with a 6px black outline and "MEANWHILE..." in black bold text 27px tall, with 11px padding, and it stays to the end. All clips keep playing. Draw the caption box with Python and Pillow (not ffmpeg's drawtext filter), keep main.mp4's audio, and save it as output.mp4.

Run it without Claude

This is the exact script we tested. Save it as comic-panel-layout.py and run:

python3 comic-panel-layout.py main.mp4 second.mp4 wide.mp4 output.mp4
Show the script
#!/usr/bin/env python3
"""Comic panel layout: three clips snap one by one into black-bordered panels on a white page, then a yellow caption box.

Usage: python3 comic-panel-layout.py main.mp4 second.mp4 wide.mp4 output.mp4
main.mp4 sets the size and the sound and fills the big bottom-left panel; second.mp4 fills the narrow
bottom-right panel; wide.mp4 fills the wide top panel.
Needs ffmpeg and Pillow (pip install pillow).
"""
import json
import subprocess
import sys
import tempfile
from pathlib import Path

from PIL import Image, ImageDraw, ImageFont

GUTTER = 0.03            # white gap between panels and round the page, as a share of the frame width
BORDER = 6               # black panel border in pixels
TOP_SHARE = 0.42         # the top panel ends this far down the frame
LEFT_SHARE = 0.56        # the bottom-left panel ends this far across the frame
APPEAR = (0.0, 0.5, 1.0)  # seconds the top, bottom-left and bottom-right panels snap in
CAPTION = "MEANWHILE..."
CAPTION_AT = 1.5         # second the caption box appears in the top panel's top-left corner
CAPTION_COLOR = (250, 215, 0)  # caption box yellow
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 panels():
    """Boxes (x0, y0, x1, y1) in pixels for the top, bottom-left and bottom-right panels."""
    g = round(w * GUTTER)
    split_y, split_x = round(h * TOP_SHARE), round(w * LEFT_SHARE)
    return [(g, g, w - g, split_y),
            (g, split_y + g, split_x, h - g),
            (split_x + g, split_y + g, w - g, h - g)]


def caption_box(path):
    font = ImageFont.truetype(FONT_FILE, round(w * 0.05))
    left, top, right, bottom = font.getbbox(CAPTION)
    pad = round(w * 0.02)
    img = Image.new("RGBA", (right - left + 2 * pad, bottom - top + 2 * pad))
    draw = ImageDraw.Draw(img)
    draw.rectangle([0, 0, img.width - 1, img.height - 1], fill=CAPTION_COLOR, outline="black", width=BORDER)
    draw.text((pad - left, pad - top), CAPTION, font=font, fill="black")
    img.save(path)


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]))
    s = info["streams"][0]
    return s["width"], s["height"], float(info["format"]["duration"])


main, second, wide, out = sys.argv[1:5]
w, h, duration = probe(main)
boxes = panels()
with tempfile.TemporaryDirectory() as tmp:
    caption = str(Path(tmp) / "caption.png")
    caption_box(caption)
    # each clip is cropped to fill its panel inside the border, then padded out with the black border
    graph = [f"color=white:s={w}x{h}:r=30:d={duration}[page]"]
    for i, (x0, y0, x1, y1) in enumerate(boxes):
        pw, ph = x1 - x0, y1 - y0
        graph.append(f"[{i}:v]fps=30,scale={pw - 2 * BORDER}:{ph - 2 * BORDER}:force_original_aspect_ratio=increase,"
                     f"crop={pw - 2 * BORDER}:{ph - 2 * BORDER},pad={pw}:{ph}:{BORDER}:{BORDER}:black,setsar=1[p{i}]")
    layer = "page"
    for i, (x0, y0, _, _) in enumerate(boxes):
        graph.append(f"[{layer}][p{i}]overlay={x0}:{y0}:enable='gte(t,{APPEAR[i]})'[l{i}]")
        layer = f"l{i}"
    x0, y0 = boxes[0][:2]
    graph.append(f"[{layer}][3:v]overlay={x0}:{y0}:enable='gte(t,{CAPTION_AT})',format=yuv420p[v]")
    # panel order on the page: wide on top, main bottom-left, second bottom-right
    subprocess.run(["ffmpeg", "-y", "-v", "error", "-i", wide, "-i", main, "-i", second, "-loop", "1", "-i", caption,
                    "-filter_complex", ";".join(graph), "-map", "[v]", "-map", "1:a?", "-t", str(duration),
                    "-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