How to add a glitter text with Claude

"SHINE" fades in above the bottle in plum-outlined letters packed with gold glitter flakes that keep flashing white, while little four-point stars pop on and off across the word.

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 glitter text to input.mp4
    Text: SHINE
    Golds: four golds from (184, 120, 24) to (255, 226, 130)

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 glitter text 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
TEXTSHINEThe word filled with glitter.
START0.5Second the text appears.
GOLDSfour golds from (184, 120, 24) to (255, 226, 130)Glitter flake shades; swap in silvers or pinks for another glitter.
FLAKE3Glitter flake size in pixels.
TWINKLE0.8Seconds for each flake to dim, flash and dim again.
STARS5Star glints on the letters at any moment.

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
Add glitter text to input.mp4: the word "SHINE" in Arial Black (or another heavy bold font), sized so it spans 84% of the frame width, centered horizontally 16% of the way down. Fill the letters with gold glitter: a grid of 3x3 pixel flakes, each randomly one of (184, 120, 24), (222, 166, 44), (250, 204, 80) or (255, 226, 130), and each twinkling on its own random phase over a 0.8 second cosine cycle, from 40% to 100% of its color, turning (255, 250, 225) for the brightest 4% of its cycle. Give the letters a 5 pixel dark plum (70, 24, 60) outline. Over the letters, keep 5 white four-point star glints going at random spots inside the letters, each growing to 44 pixels across and shrinking away over 0.6 seconds, staggered so a new one starts every 0.12 seconds, with a soft gold glow behind each. Nothing shows before 0.5 seconds; the text fades in linearly from 0.5 to 0.9 seconds and then stays to the end. Use Python with Pillow and numpy to draw it 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 glitter-text.py and run:

python3 glitter-text.py input.mp4 output.mp4
Show the script
#!/usr/bin/env python3
"""Glitter text: "SHINE" in heavy letters filled with twinkling gold glitter flakes, with star glints popping on them.

Usage: python3 glitter-text.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

TEXT = "SHINE"
START = 0.5              # second the text appears
FADE = 0.4               # seconds the fade-in takes
WIDTH = 0.84             # text width as a share of the frame width
Y = 0.16                 # text center, 0-1 down the frame
OUTLINE = (70, 24, 60)   # dark plum edge around the letters
OUTLINE_WIDTH = 5        # pixels
GOLDS = [(184, 120, 24), (222, 166, 44), (250, 204, 80), (255, 226, 130)]  # glitter flake shades
FLAKE = 3                # glitter flake size in pixels
TWINKLE = 0.8            # seconds for each flake to go dim, bright and dim again
SPARK = (255, 250, 225)  # color of a flake at the peak of its twinkle
STARS = 5                # star glints on the letters at any moment
STAR_LIFE = 0.6          # seconds each star glint lasts
STAR_SIZE = 44           # star glint size in pixels at its biggest
FONTS = ["/System/Library/Fonts/Supplemental/Arial Black.ttf",    # macOS
         "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf",  # Linux
         "C:/Windows/Fonts/ariblk.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 letter_masks():
    """Masks for the letters' fill and their outline, sized to the text, and where the text's top-left sits."""
    probe_font = ImageFont.truetype(FONT_FILE, 100)
    left, _, right, _ = probe_font.getbbox(TEXT)
    font = ImageFont.truetype(FONT_FILE, round(100 * WIDTH * w / (right - left)))
    left, top, right, bottom = font.getbbox(TEXT, stroke_width=OUTLINE_WIDTH)
    size = (right - left, bottom - top)
    fill, edge = Image.new("L", size), Image.new("L", size)
    ImageDraw.Draw(fill).text((-left, -top), TEXT, font=font, fill=255)
    ImageDraw.Draw(edge).text((-left, -top), TEXT, font=font, fill=255, stroke_width=OUTLINE_WIDTH, stroke_fill=255)
    return fill, edge, (round((w - size[0]) / 2), round(h * Y - size[1] / 2))


def glitter(t):
    """The gold flake pattern at time t: every flake brightens and dims on its own cycle, peaking near white."""
    wave = np.cos(2 * math.pi * t / TWINKLE + phase)
    flakes = shades * (0.7 + 0.3 * wave)[..., None]
    flakes[wave > 0.96] = SPARK
    big = flakes.repeat(FLAKE, axis=0).repeat(FLAKE, axis=1)[:fill.height, :fill.width]
    return Image.fromarray(big.clip(0, 255).astype(np.uint8))


def star(size):
    """A white four-point star with a soft gold halo."""
    d = STAR_SIZE * 2
    c, r, waist = d / 2, size / 2, size / 9
    points = [(c, c - r), (c + waist, c - waist), (c + r, c), (c + waist, c + waist),
              (c, c + r), (c - waist, c + waist), (c - r, c), (c - waist, c - waist)]
    halo = Image.new("L", (d, d))
    ImageDraw.Draw(halo).ellipse([c - r / 3, c - r / 3, c + r / 3, c + r / 3], fill=255)
    img = Image.new("RGBA", (d, d), GOLDS[3] + (0,))
    img.putalpha(halo.filter(ImageFilter.GaussianBlur(size / 8)))
    ImageDraw.Draw(img).polygon(points, fill=(255, 255, 255, 255))
    return img


def overlay(t):
    img = Image.new("RGBA", (w, h))
    fade = min(max((t - START) / FADE, 0), 1)
    if fade == 0:
        return img
    text = Image.new("RGBA", fill.size, OUTLINE)
    text.putalpha(edge)
    gold = glitter(t).convert("RGBA")
    gold.putalpha(fill)
    text.alpha_composite(gold)
    img.alpha_composite(text, corner)
    for i in range(STARS):
        age = t - START + i * STAR_LIFE / STARS
        cycle, p = divmod(age / STAR_LIFE, 1)
        # a new spot inside the letters for each star, each time it comes round
        y, x = spots[(int(cycle) * 7919 + i * 104729) % len(spots)]
        s = star(STAR_SIZE * math.sin(math.pi * p))
        img.alpha_composite(s, (corner[0] + int(x) - s.width // 2, corner[1] + int(y) - s.height // 2))
    img.putalpha(img.getchannel("A").point(lambda a: round(a * fade)))
    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):
    """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)
fill, edge, corner = letter_masks()
rng = np.random.default_rng(7)
grid = (math.ceil(fill.height / FLAKE), math.ceil(fill.width / FLAKE))
shades = np.array(GOLDS, np.float32)[rng.integers(len(GOLDS), size=grid)]
phase = rng.uniform(0, 2 * math.pi, grid)
spots = np.argwhere(np.asarray(fill) > 200)
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