---
title: "How to add a podcast clip stack with Claude (tested prompt and script)"
description: "Two cameras, tagged HOST and GUEST, are stacked in a tall frame with a black band between them where the speech appears three words at a time, the spoken word in yellow. Install one skill and ask Claude Code, or run the Python script we tested yourself."
url: https://www.recharm.com/effects/podcast-clip-stack
---

# How to add a podcast clip stack with Claude

Two cameras, tagged HOST and GUEST, are stacked in a tall frame with a black band between them where the speech appears three words at a time, the spoken word in yellow.

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

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

   ```
   Make a podcast clip stack from top.mp4 and bottom.mp4
   Model: "base.en"
   Top name, bottom name: HOST, GUEST
   ```

## 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 podcast clip stack 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                                                                     |
| ----------------------- | ----------------- | -------------------------------------------------------------------------------- |
| `MODEL`                 | `"base.en"`       | Whisper model; bigger ones are more accurate but slower.                         |
| `BAND`                  | `0.15`            | Height of the caption band between the panels, as a share of the frame height.   |
| `BAND_COLOR`            | `(16, 16, 16)`    | Caption band color.                                                              |
| `SIZE`                  | `0.075`           | Caption text height as a share of the frame width.                               |
| `MAX_WORDS`             | `3`               | Most words on one caption line.                                                  |
| `HOLD`                  | `0.3`             | Seconds a line stays up after its last word, unless the next line starts sooner. |
| `TEXT`                  | `(255, 255, 255)` | Caption color.                                                                   |
| `ACTIVE`                | `(255, 210, 0)`   | Color of the word being spoken.                                                  |
| `TOP_NAME, BOTTOM_NAME` | `HOST, GUEST`     | Name tags in each panel's top-left corner.                                       |

## Without the skill

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

```
Make a vertical podcast clip from top.mp4 (the host, whose audio we hear) and bottom.mp4 (the guest). The output is 540x960, 30fps, as long as top.mp4. Put top.mp4 in a 540x408 panel at the top (y=0 to 408) and bottom.mp4 in a 540x408 panel at the bottom (y=552 to 960), each scaled to fill its panel and cropped equally from top and bottom (centered), never stretched. Between them, from y=408 to 552, is a solid (16, 16, 16) band. Transcribe top.mp4's audio with faster-whisper (model "base.en", device cpu, compute_type int8, word_timestamps=True) and group the words in order into lines of at most 3 words. Show each line in the band in Arial Bold 40 pixels tall, centered horizontally with its vertical center at y=480, from its first word's start until 0.3 seconds after its last word ends, or until the next line starts if that is sooner; nothing in the band after the last line. The words are white (255, 255, 255) except the word most recently started, which is yellow (255, 210, 0). In each panel's top-left corner, 16 pixels in from the panel's left and top edges, put a small name tag: "HOST" on the top panel and "GUEST" on the bottom panel, white Arial Bold 19 pixels tall on a black rounded rectangle at alpha 170, padded by 45% of the text height (about 9 pixels) on every side. Use Python with Pillow to draw the text (don't use ffmpeg's drawtext or subtitles filter) and ffmpeg to stack the clips and put the drawing on top, keep top.mp4's audio, and save it as output.mp4.
```

## Run it without Claude

This is the exact script we tested. Save it as `podcast-clip-stack.py` and run:

```
python3 podcast-clip-stack.py top.mp4 bottom.mp4 output.mp4
```

```
#!/usr/bin/env python3
"""Podcast clip stack: two speakers' cameras stacked top and bottom, with Whisper captions in a dark band between them.

Usage: python3 podcast-clip-stack.py top.mp4 bottom.mp4 output.mp4
top.mp4 is the speaker you hear; its size, length and sound set the output. Each camera fills its panel,
cropped from the center. No subtitle file needed: faster-whisper transcribes top.mp4 and times every word.
Needs ffmpeg, faster-whisper and Pillow (pip install faster-whisper pillow). The first run downloads
the "base.en" speech model (about 140 MB).
"""
import json
import math
import subprocess
import sys
from pathlib import Path

from faster_whisper import WhisperModel
from PIL import Image, ImageDraw, ImageFont

MODEL = "base.en"          # Whisper model: bigger ("small.en") is more accurate but slower
BAND = 0.15                # height of the caption band between the panels, as a share of the frame height
BAND_COLOR = (16, 16, 16)  # caption band color
SIZE = 0.075               # caption text height as a share of the frame width
MAX_WORDS = 3              # most words on one caption line
HOLD = 0.3                 # seconds a line stays up after its last word, unless the next line starts sooner
TEXT = (255, 255, 255)     # caption color
ACTIVE = (255, 210, 0)     # color of the word being spoken
TOP_NAME, BOTTOM_NAME = "HOST", "GUEST"  # name tags in each panel's top-left corner
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 transcribe(path):
    """Every spoken word as (text, start, end), times in seconds."""
    model = WhisperModel(MODEL, device="cpu", compute_type="int8")
    segments, _ = model.transcribe(path, word_timestamps=True)
    return [(word.word.strip(), word.start, word.end) for segment in segments for word in segment.words]

def caption_lines(words):
    """Group the words into lines of up to MAX_WORDS that fit the frame: [(words, start, end)]."""
    groups = []
    for word in words:
        longer = " ".join(text for text, _, _ in (groups[-1] if groups else []) + [word])
        if groups and len(groups[-1]) < MAX_WORDS and font.getlength(longer) <= w * 0.9:
            groups[-1].append(word)
        else:
            groups.append([word])
    starts = [group[0][1] for group in groups[1:]] + [math.inf]
    return [(group, group[0][1], min(group[-1][2] + HOLD, next_start)) for group, next_start in zip(groups, starts)]

def name_tag(draw, x, y, name):
    pad = tag_font.size * 0.45
    *_, x1, y1 = draw.textbbox((x + pad, y + pad), name, font=tag_font)
    draw.rounded_rectangle([x, y, x1 + pad, y1 + pad], radius=pad, fill=(0, 0, 0, 170))
    draw.text((x + pad, y + pad), name, font=tag_font, fill="white")

def overlay(t):
    """Name tags always; in the band, the current line with the word being spoken in ACTIVE."""
    img = Image.new("RGBA", (w, h))
    draw = ImageDraw.Draw(img)
    margin = round(w * 0.03)
    name_tag(draw, margin, margin, TOP_NAME)
    name_tag(draw, margin, h - panel + margin, BOTTOM_NAME)
    for group, start, end in lines:
        if start <= t < end:
            active = max(i for i, (_, word_start, _) in enumerate(group) if word_start <= t)
            x = (w - font.getlength(" ".join(text for text, _, _ in group))) / 2
            for i, (text, _, _) in enumerate(group):
                draw.text((x, h / 2), text, font=font, anchor="lm", fill=ACTIVE if i == active else TEXT)
                x += font.getlength(text + " ")
    return img

def probe(path):
    info = json.loads(subprocess.check_output(["ffprobe", "-v", "error", "-select_streams", "v:0", "-show_entries",
                                               "stream=width,height:format=duration", "-of", "json", path]))
    s = info["streams"][0]
    return s["width"], s["height"], float(info["format"]["duration"])

def render(top, bottom, out):
    """Stack the two panels around the band, draw overlay(t) on every frame and save to out with top's audio."""
    fill = f"scale={w}:{panel}:force_original_aspect_ratio=increase,crop={w}:{panel},setsar=1,fps=30"
    ff = subprocess.Popen(["ffmpeg", "-y", "-v", "error", "-i", top, "-i", bottom,
                           "-f", "rawvideo", "-pix_fmt", "rgba", "-s", f"{w}x{h}", "-r", "30", "-i", "-",
                           "-filter_complex",
                           f"[0:v]{fill},pad={w}:{h}:0:0:color=#{bytes(BAND_COLOR).hex()}[t];[1:v]{fill}[b];"
                           f"[t][b]overlay=0:{h - panel}[s];[s][2: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 * 30)):
        ff.stdin.write(overlay(i / 30).tobytes())
    ff.stdin.close()
    if ff.wait():
        sys.exit("ffmpeg failed")

top, bottom, out = sys.argv[1:4]
w, h, duration = probe(top)
panel = round(h * (1 - BAND) / 4) * 2  # each panel's height, even
font = ImageFont.truetype(FONT_FILE, round(w * SIZE))
tag_font = ImageFont.truetype(FONT_FILE, round(w * 0.035))
lines = caption_lines(transcribe(top))
render(top, bottom, out)
```

## More layouts and reframing

- [Background replacement **Background replacement**](https://www.recharm.com/effects/background-replacement)
- [Binocular view **Binocular view**](https://www.recharm.com/effects/binocular-view)
- [Blur region **Blur region**](https://www.recharm.com/effects/blur-region)
- [Cinematic bars **Cinematic bars**](https://www.recharm.com/effects/cinematic-bars)
- [Comic panel layout **Comic panel layout**](https://www.recharm.com/effects/comic-panel-layout)

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