---
title: "How to add a social post card with Claude (tested prompt and script)"
description: "A dark social post from \"Priya Raman @priyaglows\", with a purple avatar, a blue verified check and 12.4K likes, pops up over the bottom of the frame, saying her dark spots are fading. Install one skill and ask Claude Code, or run the Python script we tested yourself."
url: https://www.recharm.com/effects/social-post-card
---

# How to add a social post card with Claude

A dark social post from "Priya Raman @priyaglows", with a purple avatar, a blue verified check and 12.4K likes, pops up over the bottom of the frame, saying her dark spots are fading.

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 social post card to input.mp4
   Name, handle: Priya Raman, priyaglows
   Post: Two weeks on this vitamin C serum and my dark spots are actually fading. Not sponsored, just obsessed.
   ```

## 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 social post card 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                                  |
| -------------- | -------------------------------------------------------------------------------------------------------- | --------------------------------------------- |
| `NAME, HANDLE` | `Priya Raman, priyaglows`                                                                                | Display name and @handle on the post.         |
| `POST`         | `Two weeks on this vitamin C serum and my dark spots are actually fading. Not sponsored, just obsessed.` | The post's text.                              |
| `COUNTS`       | `("386", "1.2K", "12.4K")`                                                                               | Replies, reposts and likes on the bottom row. |
| `START`        | `0.5`                                                                                                    | Second the card pops up.                      |
| `WIDTH`        | `0.86`                                                                                                   | Card width as a share of the frame width.     |
| `TOP`          | `0.64`                                                                                                   | Card top edge as a share of the frame height. |
| `CARD`         | `(21, 24, 28)`                                                                                           | Card color.                                   |
| `AVATAR`       | `(120, 80, 220)`                                                                                         | Avatar circle color.                          |

## Without the skill

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

```
Add a social media post card to input.mp4 (540x960). Draw a dark (21, 24, 28) rounded rectangle 86% of the frame width across (464 px), centered horizontally with its top edge 64% down the frame, corner radius 4.5% of the card width, with a soft dark drop shadow. Inside, with 5% padding: at the top left a purple (120, 80, 220) circle 12% of the card width across with a white bold "P"; to its right the name "Priya Raman" in white bold at 4.8% of the card width followed by a blue (29, 155, 240) verified circle with a white check mark, and under the name "@priyaglows · 2h" in gray (139, 152, 165) at 4% of the card width. Below the avatar, the post "Two weeks on this vitamin C serum and my dark spots are actually fading. Not sponsored, just obsessed." in white at 4.8% of the card width, wrapped to fit. Along the bottom, spread evenly across the card, gray outline icons with counts: a speech bubble with "386", two arrows for reposts with "1.2K" and a heart with "12.4K". Nothing shows before 0.5 seconds; at 0.5 seconds the card pops up over 0.45 seconds, rising from 8% of the frame height lower, growing from 85% size with a slight overshoot and fading in, then stays to the end. Leave the rest of the video untouched. Use Python with Pillow to draw the card and ffmpeg to put it on the video (don't rely on ffmpeg's drawtext filter), keep the audio, and save it as output.mp4.
```

## Run it without Claude

This is the exact script we tested. Save it as `social-post-card.py` and run:

```
python3 social-post-card.py input.mp4 output.mp4
```

```
#!/usr/bin/env python3
"""Social post card: a dark social media post with avatar, name, handle, text and counts pops up over the video.

Usage: python3 social-post-card.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, ImageFilter, ImageFont

NAME = "Priya Raman"     # display name on the post
HANDLE = "priyaglows"    # @handle shown after the name
POST = "Two weeks on this vitamin C serum and my dark spots are actually fading. Not sponsored, just obsessed."
COUNTS = ("386", "1.2K", "12.4K")  # replies, reposts, likes on the bottom row
START = 0.5              # second the card pops up
POP = 0.45               # seconds the pop takes
WIDTH = 0.86             # card width as a share of the frame width (centered across)
TOP = 0.64               # card top edge, as a share of the frame height
CARD = (21, 24, 28)      # card color
AVATAR = (120, 80, 220)  # avatar circle color
BADGE = (29, 155, 240)   # verified badge color
GRAY = (139, 152, 165)   # handle and count 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
REGULAR_FONTS = ["/System/Library/Fonts/Supplemental/Arial.ttf",
                 "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
                 "C:/Windows/Fonts/arial.ttf"]
FONT_FILE = next((f for f in FONTS if Path(f).exists()), None) or sys.exit("No bold font found: add one to FONTS")
REGULAR_FILE = next((f for f in REGULAR_FONTS if Path(f).exists()), None) or sys.exit("No font found: add one to REGULAR_FONTS")

def wrap(draw, text, font, width):
    lines = [""]
    for word in text.split():
        trial = f"{lines[-1]} {word}".strip()
        if draw.textlength(trial, font=font) <= width or not lines[-1]:
            lines[-1] = trial
        else:
            lines.append(word)
    return lines

def card():
    """The post: avatar, name with a verified badge, @handle, the text and a row of reply/repost/like counts."""
    cw = round(w * WIDTH)
    pad, av = round(cw * 0.05), round(cw * 0.12)
    bold = ImageFont.truetype(FONT_FILE, round(cw * 0.048))
    small = ImageFont.truetype(REGULAR_FILE, round(cw * 0.04))
    body = ImageFont.truetype(REGULAR_FILE, round(cw * 0.048))
    measure = ImageDraw.Draw(Image.new("RGB", (1, 1)))
    lines = wrap(measure, POST, body, cw - 2 * pad)
    line_h = round(body.size * 1.3)
    text_top = pad + av + round(pad * 0.6)
    row_top = text_top + line_h * len(lines) + round(pad * 0.5)
    ch = row_top + small.size + pad
    shadow = round(cw * 0.04)

    img = Image.new("RGBA", (cw + 2 * shadow, ch + 2 * shadow))
    shape = Image.new("L", img.size)
    ImageDraw.Draw(shape).rounded_rectangle([shadow, shadow, shadow + cw, shadow + ch], radius=round(cw * 0.045), fill=255)
    img.paste((0, 0, 0, 140), (0, round(shadow * 0.4)), shape.filter(ImageFilter.GaussianBlur(shadow / 2)))
    img.paste(CARD + (255,), (0, 0), shape)

    draw = ImageDraw.Draw(img)
    x0, y0 = shadow + pad, shadow + pad
    draw.ellipse([x0, y0, x0 + av, y0 + av], fill=AVATAR)
    draw.text((x0 + av / 2, y0 + av / 2), NAME[0], font=bold, anchor="mm", fill="white")
    nx = x0 + av + round(pad * 0.6)
    draw.text((nx, y0 + av * 0.08), NAME, font=bold, fill="white")
    bx, b = nx + draw.textlength(NAME, font=bold) + bold.size * 0.3, bold.size
    draw.ellipse([bx, y0 + av * 0.08, bx + b, y0 + av * 0.08 + b], fill=BADGE)
    by = y0 + av * 0.08
    draw.line([(bx + b * 0.27, by + b * 0.52), (bx + b * 0.44, by + b * 0.68), (bx + b * 0.74, by + b * 0.34)],
              fill="white", width=max(2, round(b / 8)), joint="curve")
    draw.text((nx, y0 + av * 0.58), f"@{HANDLE} · 2h", font=small, fill=GRAY)
    for i, line in enumerate(lines):
        draw.text((x0, shadow + text_top + i * line_h), line, font=body, fill="white")

    # bottom row: reply, repost and like, each an icon followed by its count, spread across the card
    s, ry = small.size, shadow + row_top
    for i, count in enumerate(COUNTS):
        ix = x0 + i * (cw - 2 * pad) / 3
        lw = max(2, round(s / 10))
        if i == 0:
            draw.rounded_rectangle([ix, ry, ix + s, ry + s * 0.75], radius=s * 0.3, outline=GRAY, width=lw)
            draw.polygon([(ix + s * 0.2, ry + s * 0.7), (ix + s * 0.45, ry + s * 0.7), (ix + s * 0.18, ry + s)], fill=GRAY)
        elif i == 1:
            draw.line([(ix, ry + s * 0.3), (ix + s, ry + s * 0.3)], fill=GRAY, width=lw)
            draw.polygon([(ix + s * 0.7, ry + s * 0.08), (ix + s, ry + s * 0.3), (ix + s * 0.7, ry + s * 0.52)], fill=GRAY)
            draw.line([(ix, ry + s * 0.75), (ix + s, ry + s * 0.75)], fill=GRAY, width=lw)
            draw.polygon([(ix + s * 0.3, ry + s * 0.53), (ix, ry + s * 0.75), (ix + s * 0.3, ry + s * 0.97)], fill=GRAY)
        else:
            pts = [(ix + s / 2 + 16 * math.sin(a) ** 3 * s / 34,
                    ry + s * 0.42 - (13 * math.cos(a) - 5 * math.cos(2 * a) - 2 * math.cos(3 * a) - math.cos(4 * a)) * s / 34)
                   for a in [k * 2 * math.pi / 60 for k in range(61)]]
            draw.line(pts, fill=GRAY, width=lw, joint="curve")
        draw.text((ix + s * 1.4, ry), count, font=small, fill=GRAY)
    return img

def overlay(t):
    """The card rising into place from 8% lower down while it fades and grows in, with a slight overshoot."""
    img = Image.new("RGBA", (w, h))
    p = min(max((t - START) / POP, 0), 1)
    if p > 0:
        scale = 0.85 + 0.15 * ease_out_back(p)
        c = full.resize((round(full.width * scale), round(full.height * scale)), Image.LANCZOS)
        c.putalpha(c.getchannel("A").point(lambda a: round(a * min(p * 2.5, 1))))
        y = h * TOP + full.height / 2 + h * 0.08 * (1 - ease_out_back(p))
        img.alpha_composite(c, (round(w / 2 - c.width / 2), round(y - c.height / 2)))
    return img

def ease_out_back(p):
    """0 -> 1 with a small overshoot past 1 before settling, which reads as a 'pop'."""
    return 1 + 2.70158 * (p - 1) ** 3 + 1.70158 * (p - 1) ** 2

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):
    """Draw overlay(t) on every frame of src and save to out, keeping the audio."""
    ff = subprocess.Popen(["ffmpeg", "-y", "-v", "error", "-i", src,
                           "-f", "rawvideo", "-pix_fmt", "rgba", "-s", f"{w}x{h}", "-r", rate, "-i", "-",
                           "-filter_complex", "[0:v][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)
full = card()
render(src, out)
```

## More ugc and social proof

- [Comment reply overlay **Comment reply overlay**](https://www.recharm.com/effects/comment-reply-overlay)
- [Day counter **Day counter**](https://www.recharm.com/effects/day-counter)
- [Emoji rain **Emoji rain**](https://www.recharm.com/effects/emoji-rain)
- [Floating hearts **Floating hearts**](https://www.recharm.com/effects/floating-hearts)
- [Follow button animation **Follow button animation**](https://www.recharm.com/effects/follow-button-animation)

## 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)
