---
title: "How to add a burning text with Claude (tested prompt and script)"
description: "Charred \"HOT DROP\" letters with a glowing orange rim catch fire, and tongues of flame flicker up from the top of every letter. Install one skill and ask Claude Code, or run the Python script we tested yourself."
url: https://www.recharm.com/effects/burning-text
---

# How to add a burning text with Claude

Charred "HOT DROP" letters with a glowing orange rim catch fire, and tongues of flame flicker up from the top of every letter.

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 burning text to input.mp4
   Text: HOT DROP
   ```

## 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 burning 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.

| Setting   | Default        | What it does                                                   |
| --------- | -------------- | -------------------------------------------------------------- |
| `TEXT`    | `HOT DROP`     | The headline.                                                  |
| `START`   | `0.5`          | Second the letters appear and catch fire.                      |
| `IGNITE`  | `0.6`          | Seconds the flames take to grow to full height.                |
| `FLAME`   | `0.12`         | Tallest flame tongue, as a share of the frame height.          |
| `RISE`    | `1.4`          | How fast the flames lick upwards, in frame heights per second. |
| `LETTERS` | `(30, 18, 12)` | Charred letter color.                                          |
| `SEED`    | `7`            | Change for a different pattern of flames.                      |

## Without the skill

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

```
Add burning text to input.mp4: "HOT DROP" in near-black (30, 18, 12) Arial Black (or another heavy bold font) at an 81 pixel font size (15% of the frame width), centered horizontally 20% of the way down, with a 3 pixel orange (255, 120, 20) outline blurred by 1.5 pixels so the letters glow at the edges. Nothing shows before 0.5 seconds; at 0.5 seconds the letters appear and flames start rising from the top edge of every letter, growing linearly to full height by 1.1 seconds and burning to the end. Draw the flames with numpy: for each pixel, measure how far it sits above the nearest letter pixel below it in the same column; flame tongues reach up to 12% of the frame height (115 pixels), each column's height set by smooth random noise (about 30 cells across the frame) that scrolls upward at 1.4 frame heights per second so the flames flicker and lick upwards, with the tips leaning up to 2% of the frame width left and right. Heat is 1 at the letter edge and 0 at the tip; color it from near white (255, 240, 190) through amber (255, 180, 40), orange (250, 100, 10) and red (190, 30, 0) to dark ember (60, 8, 0), soft only at the tips, never hotter than the pixel just below so no specks float free, and blur the flames by 1.5 pixels. Draw the letters on top of the flames, and keep fire off the rest of the frame. Use Python with Pillow to draw the text (don't rely on ffmpeg's drawtext filter), numpy for the flames and ffmpeg to put it on 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 `burning-text.py` and run:

```
python3 burning-text.py input.mp4 output.mp4
```

```
#!/usr/bin/env python3
"""Burning text: charred letters with a glowing ember rim catch fire, and flames flicker up from their tops.

Usage: python3 burning-text.py input.mp4 output.mp4
Needs ffmpeg, Pillow and numpy (pip install pillow numpy). The flames are drawn from random noise, so no stock fire clip is needed.
"""
import json
import math
import subprocess
import sys
from pathlib import Path

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

TEXT = "HOT DROP"          # the headline
START = 0.5                # second the letters appear
IGNITE = 0.6               # seconds the flames take to grow to full height after START
Y = 0.2                    # text center, 0-1 down the frame
SIZE = 0.15                # font size as a share of the frame width
LETTERS = (30, 18, 12)     # charred letter color
RIM = (255, 120, 20)       # ember glow around the letters
FLAME = 0.12               # tallest flame tongue, as a share of the frame height
RISE = 1.4                 # how fast the flame texture climbs, in frame heights per second
SWAY = 0.04                # how far the flame tips lean left and right, as a share of the frame width
HEAT = [0, 0.25, 0.5, 0.75, 1.0]                                                    # flame heat...
COLORS = [(60, 8, 0), (190, 30, 0), (250, 100, 10), (255, 180, 40), (255, 240, 190)]  # ...and its color: ember, red, orange, amber, near white
SEED = 7                   # change for different flames
FONTS = ["/System/Library/Fonts/Supplemental/Arial Black.ttf",    # macOS
         "/System/Library/Fonts/Supplemental/Arial Bold.ttf",
         "/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 letters():
    """The charred letters with their ember rim (RGBA), and their shape as a 0/1 mask."""
    font = ImageFont.truetype(FONT_FILE, round(w * SIZE))
    shape = Image.new("L", (w, h))
    ImageDraw.Draw(shape).text((w / 2, h * Y), TEXT, font=font, anchor="mm", fill=255)
    rim = Image.new("L", (w, h))
    ImageDraw.Draw(rim).text((w / 2, h * Y), TEXT, font=font, anchor="mm", fill=255, stroke_width=3)
    img = Image.new("RGBA", (w, h), RIM)
    img.putalpha(rim.filter(ImageFilter.GaussianBlur(1.5)))
    fill = Image.new("RGBA", (w, h), LETTERS)
    fill.putalpha(shape)
    img.alpha_composite(fill)
    return img, np.asarray(shape) > 127

def height_above(mask):
    """For every pixel, how far up it sits from the nearest letter below it, in frame heights (0 on a letter,
    infinite with no letter below)."""
    d = np.full(mask.shape, np.inf, np.float32)
    for y in range(h - 2, -1, -1):
        d[y] = np.where(mask[y], 0, d[y + 1] + 1)
    return d / h

def noise(x, y, cols, rows, layer):
    """Smooth random values in 0-1 at x, y (0-1 across and down; y may run past 1), on a cols x rows grid."""
    grid = np.random.default_rng([SEED, layer]).random((64, cols + 1))
    gx, gy = x * cols, y * rows
    ix, iy = np.floor(gx).astype(int), np.floor(gy).astype(int)
    fx, fy = gx - ix, gy - iy
    fx, fy = fx * fx * (3 - 2 * fx), fy * fy * (3 - 2 * fy)
    iy0, iy1, ix1 = iy % 64, (iy + 1) % 64, np.minimum(ix + 1, cols)
    top = grid[iy0, ix] * (1 - fx) + grid[iy0, ix1] * fx
    bottom = grid[iy1, ix] * (1 - fx) + grid[iy1, ix1] * fx
    return top * (1 - fy) + bottom * fy

def flames(t):
    """The fire above the letters at time t, as an RGBA image."""
    grow = min(max((t - START) / IGNITE, 0), 1)
    climb = ys + RISE * t                                     # the texture scrolls up, so the flames lick upwards
    flicker = 0.6 * noise(xs, climb, 30, 6, 1) + 0.4 * noise(xs, climb * 1.7, 70, 14, 2)
    reach = FLAME * grow * (0.25 + 1.2 * flicker ** 2)        # each tongue of flame reaches a different height
    lean = SWAY * w * (noise(xs, climb * 0.6, 6, 3, 3) - 0.5) * np.clip((Y * h - ys * h) / (FLAME * h), 0, 1)
    cols = np.clip(np.round(px + lean), 0, w - 1).astype(int)  # higher up, read the height from a column off to the side
    d = np.take_along_axis(above, cols, axis=1)
    air = (d > 0) & np.isfinite(d)                            # above a letter, not on one
    heat = np.where(air, np.clip(1 - d / np.maximum(reach, 1e-6), 0, 1), 1)
    heat = np.minimum.accumulate(heat[::-1], axis=0)[::-1] * air  # never hotter than just below: no floating specks
    color = np.stack([np.interp(heat, HEAT, [c[k] for c in COLORS]) for k in range(3)], axis=2)
    alpha = np.clip(heat / 0.3, 0, 1)                         # soft only at the very tips
    fire = Image.fromarray(np.dstack([color, alpha * 255]).astype(np.uint8))
    return fire.filter(ImageFilter.GaussianBlur(1.5))

def overlay(t):
    if t < START:
        return blank
    img = flames(t)
    img.alpha_composite(text)
    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)
ys, xs = np.mgrid[0:h, 0:w] / np.array([h, w]).reshape(2, 1, 1)
px = xs * w
text, mask = letters()
above = height_above(mask)
blank = Image.new("RGBA", (w, h))
render(src, out)
```

## More text and titles

- [Animated text frame **Animated text frame**](https://www.recharm.com/effects/animated-text-frame)
- [Brush stroke text reveal **Brush stroke text reveal**](https://www.recharm.com/effects/brush-stroke-text-reveal)
- [Drop shadow text **Drop shadow text**](https://www.recharm.com/effects/drop-shadow-text)
- [Echo text **Echo text**](https://www.recharm.com/effects/echo-text)
- [3D extruded text **3D extruded text**](https://www.recharm.com/effects/extruded-3d-text)

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