Get this effect
Switch to Code in your Claude Desktop App
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/skillsAsk for the effect
Add a live stream overlay to input.mp4 Comments: 5 (username, message) pairs
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 live stream overlay 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 |
|---|---|---|
COMMENTS | 5 (username, message) pairs | The comments, in the order they arrive. |
START | 0.5 | Second the badge and viewer count appear. |
FIRST, GAP | 1.0, 0.9 | When the first comment arrives and the seconds between comments. |
SHOWN | 4 | Comments on screen at once. |
VIEWERS, RISE | 1248, 60 | Starting viewer count and how much it climbs a second. |
RED | (230, 30, 45) | LIVE badge color. |
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
Make input.mp4 look like a live stream. From 0.5 seconds (fading in over 0.25 seconds), put a red (230, 30, 45) pill badge reading "LIVE" in white with a white dot before it, 7.2% of the frame width tall, with its top-left corner 4% across and 3.5% down; right next to it, 2% of the width further right, a translucent black pill with a white eye icon and a viewer count that starts at 1,248 and climbs by 60 a second. Then add five comments, one every 0.9 seconds from 1.0 seconds: jess_r "is it good for oily skin?", dani.b "just ordered two", sam_lee "the glow is unreal", priya.m "link please!!", tom.w "restocked yet?". Each is a translucent black pill 11% of the frame width tall, left edge 4% across, with a colored circle avatar showing the username's first letter, the username in small light gray bold text above the message in white bold text. The newest comment slides up into place with its bottom at 90% down over 0.25 seconds and pushes the older ones up one row; keep four on screen and fade out the oldest. Use Python with Pillow 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 live-stream-overlay.py and run:
python3 live-stream-overlay.py input.mp4 output.mp4Show the script
#!/usr/bin/env python3
"""Live stream overlay: a red LIVE badge, a climbing viewer count and comments that scroll up the left side.
Usage: python3 live-stream-overlay.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, ImageFont
COMMENTS = [("jess_r", "is it good for oily skin?"), # (username, message), one arriving every GAP seconds
("dani.b", "just ordered two"),
("sam_lee", "the glow is unreal"),
("priya.m", "link please!!"),
("tom.w", "restocked yet?")]
START = 0.5 # second the badge and viewer count appear
FIRST = 1.0 # second the first comment arrives
GAP = 0.9 # seconds between comments
SLIDE = 0.25 # seconds a comment takes to slide in
SHOWN = 4 # comments on screen at once; older ones fade out at the top
VIEWERS = 1248 # viewer count at START; it climbs by RISE a second
RISE = 60
RED = (230, 30, 45) # LIVE badge 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
FONT_FILE = next((f for f in FONTS if Path(f).exists()), None) or sys.exit("No bold font found: add one to FONTS")
AVATARS = [(255, 159, 67), (72, 219, 251), (29, 209, 161), (156, 136, 255), (254, 202, 87)]
def pill(size, fill):
img = Image.new("RGBA", size)
ImageDraw.Draw(img).rounded_rectangle([0, 0, size[0] - 1, size[1] - 1], radius=size[1] // 2, fill=fill)
return img
def live_badge():
font = ImageFont.truetype(FONT_FILE, round(w * 0.042))
bh = round(w * 0.072)
img = pill((round(font.getlength("LIVE") + bh * 1.35), bh), RED + (255,))
draw = ImageDraw.Draw(img)
r = bh * 0.13
draw.ellipse([bh * 0.42 - r, bh / 2 - r, bh * 0.42 + r, bh / 2 + r], fill="white")
draw.text((bh * 0.72, bh / 2), "LIVE", font=font, anchor="lm", fill="white")
return img
def viewer_pill(count):
font = ImageFont.truetype(FONT_FILE, round(w * 0.038))
bh = round(w * 0.072)
text = f"{count:,}"
img = pill((round(font.getlength(text) + bh * 1.45), bh), (0, 0, 0, 140))
draw = ImageDraw.Draw(img)
cx, cy = bh * 0.5, bh / 2
draw.ellipse([cx - bh * 0.22, cy - bh * 0.13, cx + bh * 0.22, cy + bh * 0.13], outline="white", width=2)
draw.ellipse([cx - bh * 0.07, cy - bh * 0.07, cx + bh * 0.07, cy + bh * 0.07], fill="white")
draw.text((bh * 0.9, cy), text, font=font, anchor="lm", fill="white")
return img
def comment(i):
user, message = COMMENTS[i]
name_font = ImageFont.truetype(FONT_FILE, round(w * 0.032))
text_font = ImageFont.truetype(FONT_FILE, round(w * 0.04))
bh = round(w * 0.11)
width = round(bh * 1.15 + max(name_font.getlength(user), text_font.getlength(message)) + bh * 0.4)
img = pill((width, bh), (0, 0, 0, 120))
draw = ImageDraw.Draw(img)
draw.ellipse([bh * 0.14, bh * 0.14, bh * 0.86, bh * 0.86], fill=AVATARS[i % len(AVATARS)])
draw.text((bh / 2, bh / 2), user[0].upper(), font=name_font, anchor="mm", fill="white")
draw.text((bh * 1.05, bh * 0.3), user, font=name_font, anchor="lm", fill=(205, 205, 205))
draw.text((bh * 1.05, bh * 0.68), message, font=text_font, anchor="lm", fill="white")
return img
def ease(p):
p = min(max(p, 0), 1)
return 1 - (1 - p) ** 3
def faded(img, alpha):
if alpha >= 1:
return img
out = img.copy()
out.putalpha(out.getchannel("A").point(lambda a: round(a * alpha)))
return out
def overlay(t):
img = Image.new("RGBA", (w, h))
if t < START:
return img
shown = ease((t - START) / SLIDE)
x, y = round(w * 0.04), round(h * 0.035)
img.alpha_composite(faded(badge, shown), (x, y))
img.alpha_composite(faded(viewer_pill(VIEWERS + int((t - START) * RISE)), shown), (x + badge.width + round(w * 0.02), y))
bottom, row = h * 0.9, bubbles[0].height * 1.18
arrivals = [FIRST + i * GAP for i in range(len(COMMENTS))]
for i, bubble in enumerate(bubbles):
if t < arrivals[i]:
continue
pushed = sum(ease((t - a) / SLIDE) for a in arrivals[i + 1:] if t >= a) # rows later comments pushed it up
came_in = ease((t - arrivals[i]) / SLIDE)
alpha = came_in * min(max(SHOWN - pushed, 0), 1)
if alpha > 0.01:
top = bottom - bubble.height - pushed * row + (1 - came_in) * row * 0.6
img.alpha_composite(faded(bubble, alpha), (x, round(top)))
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)
badge = live_badge()
bubbles = [comment(i) for i in range(len(COMMENTS))]
render(src, out)





