---
title: "How to add a packaging mockup with Claude (tested prompt and script)"
description: "A flat strip of yellow, navy and cream carton panels appears over the blurred clip, folds into a box with a lid and spins a full turn to show its front, sides and \"How to use\" back. Install one skill and ask Claude Code, or run the Python script we tested yourself."
url: https://www.recharm.com/effects/packaging-mockup
---

# How to add a packaging mockup with Claude

A flat strip of yellow, navy and cream carton panels appears over the blurred clip, folds into a box with a lid and spins a full turn to show its front, sides and "How to use" back.

Made by the script on this page, from our own test clip.

## Get this effect

1. ### Switch to Code in your [Claude Desktop App](https://claude.com/download)

2. ### Install the skill (once)

   ```
   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 packaging mockup to input.mp4
   Brand: AURELLE
   ```

## 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 packaging mockup 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.

| Setting               | Default                                        | What it does                                                         |
| --------------------- | ---------------------------------------------- | -------------------------------------------------------------------- |
| `BRAND`               | `AURELLE`                                      | Name on the front and back panels; its first letter goes on the lid. |
| `START`               | `0.8`                                          | Second the flat artwork appears.                                     |
| `FOLD`                | `(1.3, 2.3)`                                   | Seconds the panels fold into a box.                                  |
| `TURN`                | `(2.5, 5.5)`                                   | Seconds the box spins one full turn.                                 |
| `REST_YAW`            | `-28`                                          | Degrees the box is turned at rest, to show a side.                   |
| `TILT`                | `20`                                           | Degrees the camera looks down on the box.                            |
| `BOX_W, BOX_H`        | `0.32, 0.72`                                   | Panel width and box height as shares of the frame width.             |
| `YELLOW, NAVY, CREAM` | `(250, 176, 0), (28, 34, 78), (246, 240, 226)` | Carton colors: front and lid, sides and text, back.                  |
| `BLUR, DIM`           | `14, 0.6`                                      | Background blur radius and brightness behind the box.                |

## Without the skill

Don’t want to install the skill? Paste this prompt into Claude Code and Claude writes the script itself.

```
Make a packaging mockup on input.mp4 (540x960, 30fps, 6 seconds). Draw the artwork for a square carton with Pillow, four panels each 173 pixels wide (32% of the frame width) and 389 pixels tall (72% of the width), plus a 173x173 lid: a yellow (250, 176, 0) front reading "AURELLE" near the top in bold navy (28, 34, 78), a white citrus slice (navy outline, 8 yellow spokes) in the middle and "VITAMIN C", "BRIGHTENING SERUM" and "30 ml" below; a navy right side with "20%" in yellow over "VITAMIN C" and "+ HYALURONIC" in white; a cream (246, 240, 226) back reading "HOW TO USE", "3 drops", "every morning" and a small "AURELLE" in navy; a navy left side with "GLOW" in yellow over "IN 14 DAYS" in white; a yellow lid with a navy "A"; the inside of the cardboard kraft brown (190, 150, 102). Leave the clip untouched until 0.8 seconds. From 0.8 to 1.3 seconds blur the clip (Gaussian, radius rising to 14) and darken it to 60% brightness, and fade in the artwork as a flat dieline centered in the frame, in the order left side, front, right side, back, with the lid standing up off the top of the back panel, growing from 62% to 72% of full size. From 1.3 to 2.3 seconds fold the panels back along their shared edges into a closed box (side panels at 90 degrees to the front), growing to full size and turning to -28 degrees, and from 2.0 to 2.5 seconds fold the lid down over the top. From 2.5 to 5.5 seconds spin the box one full turn (360 degrees) around its vertical axis, so the back faces the camera around 4.1 seconds, then hold at -28 degrees with the front and right side showing. Center the box at 50% across and 50% down, look down on it from 20 degrees above with a slight perspective, light the panels facing the upper left brighter, and draw a soft blurred black ellipse shadow under it. Use cubic ease-in-out for every move. Use Python with Pillow and numpy for the drawing and 3D projection (draw all text with Pillow, not ffmpeg's drawtext) and ffmpeg to read and write the video, keep the audio, and save it as output.mp4.
```

## Run it without Claude

This is the exact script we tested. Save it as `packaging-mockup.py` and run:

```
python3 packaging-mockup.py input.mp4 output.mp4
```

```
#!/usr/bin/env python3
"""Packaging mockup: a flat carton dieline appears over the blurred clip, folds into a box and turns a full circle.

Usage: python3 packaging-mockup.py input.mp4 output.mp4
Needs ffmpeg, Pillow and numpy (pip install pillow numpy).
"""
import json
import math
import subprocess
import sys
from pathlib import Path

import numpy as np
from PIL import Image, ImageDraw, ImageFilter, ImageFont

BRAND = "AURELLE"                   # name on the front panel
START = 0.8                         # second the flat artwork appears
FOLD = (1.3, 2.3)                   # seconds the side panels fold back into a box
LID = (2.0, 2.5)                    # seconds the top flap folds shut
TURN = (2.5, 5.5)                   # seconds the box takes to turn a full circle
REST_YAW = -28                      # degrees the box is turned at rest, to show a side
TILT = 20                           # degrees the camera looks down on the box
BOX_W, BOX_H = 0.32, 0.72           # panel width and box height as shares of the frame width
CENTER = (0.5, 0.50)                # box center (0-1 across and down the frame)
YELLOW = (250, 176, 0)              # front panel and lid
NAVY = (28, 34, 78)                 # side panels and text
CREAM = (246, 240, 226)             # back panel
KRAFT = (190, 150, 102)             # inside of the cardboard
BLUR, DIM = 14, 0.6                 # background blur radius and brightness once the artwork is in
SS = 2                              # supersampling for smooth box edges
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(size):
    return ImageFont.truetype(FONT_FILE, round(size))

def panel(color, lines, height=None):
    """One flat panel of the carton artwork: a color with centered (text, size, y, color) lines."""
    img = Image.new("RGB", (pw, height or ph), color)
    draw = ImageDraw.Draw(img)
    for text, size, y, fill in lines:
        draw.text((pw / 2, img.height * y), text, font=font(pw * size), anchor="mm", fill=fill)
    return img

def artwork():
    front = panel(YELLOW, [(BRAND, 0.17, 0.16, NAVY), ("VITAMIN C", 0.11, 0.70, NAVY),
                           ("BRIGHTENING SERUM", 0.065, 0.78, NAVY), ("30 ml", 0.07, 0.92, NAVY)])
    draw = ImageDraw.Draw(front)            # a citrus slice in the middle of the front
    r, cx, cy = pw * 0.26, pw / 2, ph * 0.43
    draw.ellipse([cx - r, cy - r, cx + r, cy + r], fill="white", outline=NAVY, width=round(pw * 0.025))
    for k in range(8):
        a = math.radians(k * 45)
        draw.line([cx, cy, cx + r * 0.9 * math.cos(a), cy + r * 0.9 * math.sin(a)], fill=YELLOW, width=round(pw * 0.03))
    return {
        "front": front,
        "right": panel(NAVY, [("20%", 0.22, 0.40, YELLOW), ("VITAMIN C", 0.10, 0.50, "white"),
                              ("+ HYALURONIC", 0.075, 0.60, "white")]),
        "back": panel(CREAM, [("HOW TO USE", 0.11, 0.30, NAVY), ("3 drops", 0.09, 0.45, NAVY),
                              ("every morning", 0.08, 0.53, NAVY), (BRAND, 0.08, 0.88, NAVY)]),
        "left": panel(NAVY, [("GLOW", 0.20, 0.42, YELLOW), ("IN 14 DAYS", 0.10, 0.52, "white")]),
        "lid": panel(YELLOW, [(BRAND[0], 0.40, 0.5, NAVY)], height=pw),
        "inside": Image.new("RGB", (pw, ph), KRAFT),
    }

def ease(p):
    p = min(max(p, 0), 1)
    return 4 * p ** 3 if p < 0.5 else 1 - (-2 * p + 2) ** 3 / 2

def progress(t, span):
    return ease((t - span[0]) / (span[1] - span[0]))

def faces(fold, lid):
    """Each panel's four corners (top-left, top-right, bottom-right, bottom-left of its artwork) in 3D,
    for a fold angle in radians: 0 is the flat dieline, pi/2 a closed box. x right, y up, z to the camera."""
    W, H = BOX_W * w, BOX_H * w
    z0 = W / 2                                       # the front sits half a box in front of the turning axis
    shift = np.array([-W / 2 * math.cos(fold), 0, 0])  # keeps the flat dieline centered
    right_dir = np.array([math.cos(fold), 0, -math.sin(fold)])
    back_dir = np.array([math.cos(2 * fold), 0, -math.sin(2 * fold)])
    left_dir = np.array([-math.cos(fold), 0, -math.sin(fold)])
    up = np.array([0, H / 2, 0])
    fl, fr = np.array([-W / 2, 0, z0]) + shift, np.array([W / 2, 0, z0]) + shift
    rr = fr + W * right_dir
    br = rr + W * back_dir
    ll = fl + W * left_dir

    def quad(a, b):  # a vertical panel from edge a to edge b
        return [a + up, b + up, b - up, a - up]

    back = quad(rr, br)
    lid_dir = np.array([0, math.cos(lid), 0]) + math.sin(lid) * np.cross([0, 1, 0], back_dir)  # up, then over the top
    return {"left": quad(ll, fl), "front": quad(fl, fr), "right": quad(fr, rr), "back": back,
            "lid": [back[0] + W * lid_dir, back[1] + W * lid_dir, back[1], back[0]]}

def turn(p, yaw):
    x, y, z = p
    return np.array([x * math.cos(yaw) + z * math.sin(yaw), y, -x * math.sin(yaw) + z * math.cos(yaw)])

def project(points, yaw, scale):
    """Turn the box, tilt the camera and project to (SS times bigger) frame pixels."""
    ct, st = math.cos(math.radians(TILT)), math.sin(math.radians(TILT))
    focal = 2.4 * w
    out = []
    for p in points:
        x, y, z = turn(p, yaw)
        y, z = y * ct - z * st, y * st + z * ct
        k = focal / (focal - z * scale) * scale
        out.append(((CENTER[0] * w + x * k) * SS, (CENTER[1] * h - y * k) * SS, z))
    return out

def perspective(dst, size):
    """Coefficients for Image.transform that pull a size[0] x size[1] image onto the four dst corners."""
    src = [(0, 0), (size[0], 0), (size[0], size[1]), (0, size[1])]
    rows, rhs = [], []
    for (x, y), (u, v) in zip(dst, src):
        rows += [[x, y, 1, 0, 0, 0, -u * x, -u * y], [0, 0, 0, x, y, 1, -v * x, -v * y]]
        rhs += [u, v]
    return np.linalg.solve(np.array(rows, float), np.array(rhs, float))

def box_layer(t):
    """The carton (and its shadow) at time t, as a frame-sized RGBA image."""
    appear = progress(t, (START, FOLD[0]))
    fold = math.pi / 2 * progress(t, FOLD)
    yaw = math.radians(REST_YAW * progress(t, FOLD) + 360 * progress(t, TURN))
    scale = 0.62 + 0.10 * appear + 0.28 * progress(t, FOLD)
    layer = Image.new("RGBA", (w * SS, h * SS))
    light = np.array([-0.5, 0.5, 1.0]) / np.linalg.norm([-0.5, 0.5, 1.0])
    drawn = []
    for name, quad in faces(fold, math.pi / 2 * progress(t, LID)).items():
        pts = project(quad, yaw, scale)
        a, b, c = (np.array(p[:2]) for p in pts[:3])
        facing = (b - a)[0] * (c - b)[1] - (b - a)[1] * (c - b)[0] > 0  # corners run clockwise on screen
        normal = turn(np.cross(quad[1] - quad[0], quad[3] - quad[0]), yaw)
        shade = 0.62 + 0.38 * abs(float(normal @ light)) / np.linalg.norm(normal)  # panels facing the light are brighter
        drawn.append((sum(p[2] for p in pts) / 4, name if facing else "inside", pts, shade))
    for _, name, pts, shade in sorted(drawn, key=lambda d: d[0]):
        tex = Image.eval(art[name], lambda v: v * shade).convert("RGBA")
        corners = [p[:2] for p in pts]
        x0, y0 = (math.floor(min(c[i] for c in corners)) for i in (0, 1))
        x1, y1 = (math.ceil(max(c[i] for c in corners)) for i in (0, 1))
        local = [(x - x0, y - y0) for x, y in corners]
        piece = tex.transform((x1 - x0, y1 - y0), Image.PERSPECTIVE, tuple(perspective(local, tex.size)), Image.BICUBIC)
        mask = Image.new("L", piece.size)
        ImageDraw.Draw(mask).polygon(local, fill=round(255 * appear))
        layer.paste(piece, (x0, y0), mask)
    layer = layer.resize((w, h), Image.LANCZOS)
    shadow = Image.new("RGBA", (w, h))                     # a soft shadow on the floor under the box
    floor_y = CENTER[1] * h + BOX_H * w / 2 * scale
    half = BOX_W * w * scale * (1.3 - 0.6 * progress(t, FOLD))
    ImageDraw.Draw(shadow).ellipse([w / 2 - half, floor_y - 10, w / 2 + half, floor_y + 30], fill=(0, 0, 0, round(170 * appear)))
    shadow = shadow.filter(ImageFilter.GaussianBlur(12))
    shadow.alpha_composite(layer)
    return shadow

def background(img, t):
    p = progress(t, (START, FOLD[0]))
    if p <= 0:
        return img
    img = img.filter(ImageFilter.GaussianBlur(BLUR * p))
    return Image.eval(img, lambda v: v * (1 - (1 - DIM) * p))

def probe(path):
    info = json.loads(subprocess.check_output(["ffprobe", "-v", "error", "-select_streams", "v:0", "-show_entries",
                                               "stream=width,height,r_frame_rate", "-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)

def render(src, out):
    """Blur the background and draw the box on every frame of src, keeping the audio."""
    reader = subprocess.Popen(["ffmpeg", "-v", "error", "-i", src, "-f", "rawvideo", "-pix_fmt", "rgb24", "-"],
                              stdout=subprocess.PIPE)
    writer = subprocess.Popen(["ffmpeg", "-y", "-v", "error", "-f", "rawvideo", "-pix_fmt", "rgb24", "-s", f"{w}x{h}",
                               "-r", rate, "-i", "-", "-i", src, "-map", "0:v", "-map", "1:a?", "-c:v", "libx264",
                               "-crf", "18", "-pix_fmt", "yuv420p", "-c:a", "copy", "-shortest", out],
                              stdin=subprocess.PIPE)
    i = 0
    while raw := reader.stdout.read(w * h * 3):
        t = i / fps
        frame = background(Image.frombytes("RGB", (w, h), raw), t).convert("RGBA")
        if t >= START:
            frame.alpha_composite(box_layer(t))
        writer.stdin.write(frame.convert("RGB").tobytes())
        i += 1
    writer.stdin.close()
    if reader.wait() or writer.wait():
        sys.exit("ffmpeg failed")

src, out = sys.argv[1:3]
w, h, rate, fps = probe(src)
pw, ph = round(BOX_W * w * SS), round(BOX_H * w * SS)
art = artwork()
render(src, out)
```

## More product shots

- [Animated feature callouts **Animated feature callouts**](https://www.recharm.com/effects/animated-feature-callouts)
- [Before/after slider **Before/after slider**](https://www.recharm.com/effects/before-after-slider)
- [Best seller badge **Best seller badge**](https://www.recharm.com/effects/best-seller-badge)
- [Callout arrows and circles **Callout arrows and circles**](https://www.recharm.com/effects/callout-arrows-and-circles)
- [Carousel swipe **Carousel swipe**](https://www.recharm.com/effects/carousel-swipe)

## 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](https://www.recharm.com/get-started)
