---
title: "How to add a space crawl text with Claude (tested prompt and script)"
description: "The shot fades into a starfield, then yellow text rises from the bottom edge on a tilted plane and slides away toward the horizon, shrinking and narrowing as it recedes. Install one skill and ask Claude Code, or run the Python script we tested yourself."
url: https://www.recharm.com/effects/space-crawl-text
---

# How to add a space crawl text with Claude

The shot fades into a starfield, then yellow text rises from the bottom edge on a tilted plane and slides away toward the horizon, shrinking and narrowing as it recedes.

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 space crawl text to input.mp4
   Lines: [("EPISODE C", 0.065), ("THE GLOW AWAKENS", 0.085), ...]
   ```

## 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 space crawl 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                                                                       |
| ------------------------- | ---------------------------------------------------------- | ---------------------------------------------------------------------------------- |
| `LINES`                   | `[("EPISODE C", 0.065), ("THE GLOW AWAKENS", 0.085), ...]` | The crawl's lines with their heights as a share of the frame width, top to bottom. |
| `FADE_START`              | `0.4`                                                      | Second the clip starts fading into space.                                          |
| `FADE`                    | `0.6`                                                      | Seconds the fade takes.                                                            |
| `CRAWL_START`             | `1.0`                                                      | Second the first line enters at the bottom.                                        |
| `SPEED`                   | `0.22`                                                     | How far the text slides up the plane each second, in frame widths.                 |
| `HORIZON`                 | `0.20`                                                     | Where the plane vanishes, as a share of the frame height.                          |
| `TOP_WIDTH, BOTTOM_WIDTH` | `0.24, 1.40`                                               | Widths of the plane's far and near edges, in frame widths.                         |
| `DEPTH`                   | `2.6`                                                      | Length of the visible plane, in frame widths.                                      |
| `COLOR`                   | `(255, 205, 50)`                                           | Text color.                                                                        |
| `STARS`                   | `220`                                                      | Number of stars.                                                                   |

## Without the skill

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

```
Add a space opening crawl to input.mp4 (540x960). Play the clip untouched until 0.4 seconds, then cross-fade it into a starfield between 0.4 and 1.0 seconds: a black frame with 220 small round stars at fixed random spots, radius 0.6 to 1.4 pixels, gray levels 120 to 255 with a slight blue tint. From 1.0 seconds, yellow (255, 205, 50) text in Arial Bold crawls up the screen on a tilted plane. Draw the text flat with Pillow on a transparent strip 540 pixels wide: 1404 pixels of empty space, then these lines centered, top-anchored, each line's slot 1.35 times its font size tall: "EPISODE C" (35 px), "THE GLOW AWAKENS" (46 px), an empty 35 px line, then "It is a period of dull,", "tired skin. A bright", "new serum, made in", "a hidden lab, has", "won its first battle", "against dark spots." (38 px each), then another 1404 pixels of empty space. Each frame, take a 540x1404 window of the strip starting 118.8 pixels further down for every second after 1.0 seconds (starting at the top), and warp it with OpenCV's perspective transform so its top edge lands from (205.2, 192) to (334.8, 192) and its bottom edge from (-108, 960) to (648, 960): text enters at the bottom wider than the frame and shrinks toward a vanishing line 20% down. Fade the text's opacity from 0 at y = 192 to full at y = 403 so it dissolves near the horizon. Composite it over the starfield. Use Python with Pillow to draw the text (don't rely on ffmpeg's drawtext filter), OpenCV and numpy for the warp, ffmpeg to encode, keep the audio and the 6 second length at 30fps, and save it as output.mp4.
```

## Run it without Claude

This is the exact script we tested. Save it as `space-crawl-text.py` and run:

```
python3 space-crawl-text.py input.mp4 output.mp4
```

```
#!/usr/bin/env python3
"""Space crawl text: the clip fades into a starfield and yellow text scrolls up and away into the distance on a tilted plane.

Usage: python3 space-crawl-text.py input.mp4 output.mp4
Needs ffmpeg, Pillow, OpenCV and numpy (pip install pillow opencv-python-headless numpy).
"""
import json
import math
import random
import subprocess
import sys
from pathlib import Path

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

LINES = [("EPISODE C", 0.065),             # (text, height as a share of the frame width), top to bottom
         ("THE GLOW AWAKENS", 0.085),
         ("", 0.065),
         ("It is a period of dull,", 0.070),
         ("tired skin. A bright", 0.070),
         ("new serum, made in", 0.070),
         ("a hidden lab, has", 0.070),
         ("won its first battle", 0.070),
         ("against dark spots.", 0.070)]
FADE_START = 0.4           # second the clip starts fading into space
FADE = 0.6                 # seconds the fade takes
CRAWL_START = 1.0          # second the first line enters at the bottom
SPEED = 0.22               # how far the text slides up the plane each second, in frame widths
HORIZON = 0.20             # where the plane vanishes, as a share of the frame height
TOP_WIDTH = 0.24           # width of the plane's far edge, as a share of the frame width
BOTTOM_WIDTH = 1.40        # width of the plane's near edge (wider than the frame, so lines run off the sides)
DEPTH = 2.6                # length of the visible plane, in frame widths
COLOR = (255, 205, 50)     # crawl yellow
STARS = 220                # number of stars
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 crawl_strip():
    """The text drawn flat on a tall transparent strip as wide as the frame, with a plane's length of empty space above
    and below so it can enter at the near edge and leave at the far one."""
    depth = round(w * DEPTH)
    heights = [round(w * size * 1.35) for _, size in LINES]
    strip = Image.new("RGBA", (w, depth * 2 + sum(heights)))
    draw = ImageDraw.Draw(strip)
    y = depth
    for (line, size), line_h in zip(LINES, heights):
        draw.text((w / 2, y), line, font=ImageFont.truetype(FONT_FILE, round(w * size)), anchor="mt", fill=COLOR)
        y += line_h
    return np.array(strip)

def starfield():
    """Black sky with small white and pale blue stars at fixed random spots."""
    sky = Image.new("RGB", (w, h))
    draw = ImageDraw.Draw(sky)
    rng = random.Random(7)
    for _ in range(STARS):
        x, y, r = rng.uniform(0, w), rng.uniform(0, h), rng.choice([0.6, 0.8, 1.0, 1.4])
        level = rng.randint(120, 255)
        draw.ellipse([x - r, y - r, x + r, y + r], fill=(level, level, min(255, level + 20)))
    return np.array(sky).astype(np.float32)

def plane():
    """Perspective warp from the strip's visible window onto a trapezoid narrowing toward the horizon, and a mask
    that fades the text out as it nears the horizon."""
    depth = round(w * DEPTH)
    window = np.float32([[0, 0], [w, 0], [w, depth], [0, depth]])
    top, bottom = w * TOP_WIDTH / 2, w * BOTTOM_WIDTH / 2
    trapezoid = np.float32([[w / 2 - top, h * HORIZON], [w / 2 + top, h * HORIZON],
                            [w / 2 + bottom, h], [w / 2 - bottom, h]])
    rows = np.arange(h, dtype=np.float32)[:, None, None]
    fade = np.clip((rows - h * HORIZON) / (h * 0.22), 0, 1)
    return cv2.getPerspectiveTransform(window, trapezoid), fade

def frame_at(t, video):
    """The clip faded into the starfield, with the strip's window slid SPEED frame widths further each second."""
    fade = min(max((t - FADE_START) / FADE, 0), 1)
    img = video * (1 - fade) + sky * fade
    if t >= CRAWL_START:
        top = round((t - CRAWL_START) * SPEED * w)
        window = strip[top:top + round(w * DEPTH)]
        text = cv2.warpPerspective(window, warp, (w, h), flags=cv2.INTER_AREA).astype(np.float32)
        alpha = text[:, :, 3:] / 255 * horizon_fade
        img = img * (1 - alpha) + text[:, :, :3] * alpha
    return img.astype(np.uint8)

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):
    """Read src frame by frame, draw frame_at(t) and save to out, 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)
    for i in range(math.ceil(duration * fps)):
        raw = reader.stdout.read(w * h * 3)
        if len(raw) < w * h * 3:
            break
        video = np.frombuffer(raw, np.uint8).reshape(h, w, 3).astype(np.float32)
        writer.stdin.write(frame_at(i / fps, video).tobytes())
    writer.stdin.close()
    reader.stdout.close()
    if writer.wait() or reader.wait():
        sys.exit("ffmpeg failed")

src, out = sys.argv[1:3]
w, h, rate, fps, duration = probe(src)
strip, sky = crawl_strip(), starfield()
warp, horizon_fade = plane()
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)
- [Burning text **Burning text**](https://www.recharm.com/effects/burning-text)
- [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)

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