Communitygithub.com

ericrisco/rsc-harness

Use when you have raw 9:16 footage and need it cut into a post-ready vertical short — dead air removed, fast jump cuts, karaoke word-by-word burned-in captions from a transcript, cuts and zooms snapped to the music beat, safe-area placement so captions clear the platform UI, and a platform-correct MP4 export. NOT writing the script, hook, beats or caption text (that is `video-shorts`), NOT building a Remotion React composition codebase (that is `remotion-video`), NOT deciding when or where to post (that is `social-publisher`).

What is rsc-harness?

rsc-harness is a Claude Code agent skill that use when you have raw 9:16 footage and need it cut into a post-ready vertical short — dead air removed, fast jump cuts, karaoke word-by-word burned-in captions from a transcript, cuts and zooms snapped to the music beat, safe-area placement so captions clear the platform UI, and a platform-correct MP4 export. NOT writing the script, hook, beats or caption text (that is `video-shorts`), NOT building a Remotion React composition codebase (that is `remotion-video`), NOT deciding when or where to post (that is `social-publisher`).

Works with✓Claude Code~Codex CLI~Cursor
npx skills add https://github.com/ericrisco/rsc-harness/tree/HEAD/skills/shortform-editing

Ask in your favorite AI

Open a new chat with this agent skill pre-loaded.

Documentation

Shortform Editing — Cut the Footage Into a Posted-Ready Short

You are the edit-bay technician. Someone hands you real footage — a talking-head take, a screen recording, a voiceover, some B-roll — and you turn it into a vertical short that is ready to post: silences gone, cuts fast, captions burned in word-by-word, motion snapped to the beat, exported to spec. You encode pixels and timing. You do not write the words or pick the hook.

The rigor is a checkable file: a 1080x1920 H.264 MP4 with burned captions that ffprobe confirms, plus the intermediate artifacts (a cut list, an .ass/.srt, a beat grid). If you cannot ffprobe the result and see the specs, you are not done.

Scope: three skills touch "a vertical video" — know your lane

The askGoes toArtifact
Write the script, hook, beats, on-screen caption text, edit decision sheet../video-shorts/SKILL.mda .md script
Cut/caption/export real footage, tool-agnostichereedited .mp4 + caption/cut files
Build a Remotion React composition project (<TransitionSeries>, headless render)../remotion-video/SKILL.mda .tsx project

One-line router: footage in → cut, captioned, exported → this skill. Words or a plan → video-shorts. A React codebase → remotion-video. You are the only one of the three that is tool-portable.

Pick your route first

Do not start cutting before you have chosen the tool. The choice is about who runs it and whether it must reproduce.

SituationRouteWhy
Reproducible / batch / CI / "do this to 40 clips"ffmpeg + WhisperXscriptable, deterministic, no GUI — same input, same output
The repo is already a Remotion project@remotion/captionsreuse the existing composition graph; don't shell out
A human is polishing one video by handCapCut click-pathfastest for a single hands-on pass with auto-captions + auto beat markers

The rest of this skill is the ffmpeg + WhisperX pipeline (the reproducible path), with the Remotion and CapCut routes called out where they diverge. Long command blocks live in references/ffmpeg-pipeline.md; the caption detail lives in references/captions.md.

The reproducible pipeline, in order

This is the spine. Run it top to bottom; each step feeds the next.

1. Extract a clean mono 16 kHz audio track for transcription.

ffmpeg -i in.mp4 -vn -ac 1 -ar 16000 a.wav

Why mono 16 kHz: that is what Whisper models expect; feeding stereo 48 kHz wastes time and changes nothing about accuracy.

2. Transcribe with word-level timestamps. Segment timestamps are not enough — word-by-word captions need per-word timing.

whisperx a.wav --model large-v3 --output_format json --highlight_words True

WhisperX (v3.8.5) runs faster-whisper plus wav2vec2 forced alignment, giving word timing accurate to under 100 ms — the floor for karaoke highlight. faster-whisper alone with word_timestamps=True works (~10% overhead) but drifts more on word boundaries. Why forced alignment: Whisper's native word stamps are interpolated and jitter; forced alignment pins each word to the audio. See references/captions.md for the install and the JSON shape.

3. Detect silence, then cut video AND audio together. This is the step people get wrong.

# Pass 1: find the silent ranges (speech threshold)
ffmpeg -i in.mp4 -af silencedetect=noise=-25dB:d=0.3 -f null - 2> silence.log

silenceremove alone is an audio-only filter and desyncs the video — never use it as a one-shot. Parse the silence_start / silence_end lines from silence.log, invert them into keep-ranges, and build a select + aselect filter (or a trim+concat graph) that drops the same ranges from both streams. Thresholds: speech noise=-25dB:d=0.3; music beds noise=-40dB:d=1. The filter-generation snippet is in references/ffmpeg-pipeline.md.

4. Build the beat grid (only if you are cutting to music).

python -c "import librosa; y,sr=librosa.load('music.wav'); \
_,b=librosa.beat.beat_track(y=y,sr=sr,units='time'); print(*b,sep='\n')" > beats.txt

beats.txt is a list of beat times in seconds. CapCut drops these markers on the waveform automatically; librosa (or aubio) gives you the same grid in a script.

5. Snap cut points and zoom keyframes to the nearest beat. Round each cut time to its closest value in beats.txt. A zoom-punch on the beat reads as energy; see Beat sync below for when NOT to.

6. Burn the karaoke .ass captions.

ffmpeg -i tightened.mp4 -vf "ass=captions.ass" -c:a copy captioned.mp4

The .ass carries the per-word \k timing built from step 2's word timestamps and the safe-zone margins. Generation detail and the template are in Karaoke captions below.

7. Export the platform master (see Export specs).

Karaoke captions

Word-by-word captions are the signature look. Build them from the word timestamps, not from segment timestamps.

  • Timing. Each word's on-screen highlight comes from its forced-alignment start/end. In .ass, the \k<centiseconds> tag advances the karaoke fill per word. Word-by-word from segment timing jitters and lands the highlight on the wrong word — always use the per-word stamps from step 2.
  • Legibility for sound-off. Big bold sans, heavy outline/stroke, optional drop shadow. People watch muted: if it is not readable at a glance on a phone, it failed. Keep it to ≤ 2 lines / ~6 words on screen at once.
  • Safe zone — the non-obvious one. Place captions in the vertical middle band, not the bottom. Platform UI (right-rail icons, bottom caption/CTA/profile) covers the bottom ~15–20% and the right edge. Keep captions and key text inside the central ~80% of the 1080x1920 frame. Captions burned at the bottom get hidden behind the TikTok buttons — this is the #1 "my captions are broken" cause.

Minimal .ass style block (full template in references/captions.md):

[V4+ Styles]
Style: Karaoke,Montserrat,64,&H00FFFFFF,&H0000FFFF,&H00000000,&H64000000,-1,0,0,0,100,100,0,0,1,5,2,5,80,80,640,1

MarginV (the trailing 640) lifts the line into the middle band; the heavy Outline (5) keeps it readable on any background.

Remotion route: if you are inside a Remotion project, use @remotion/captions — createTikTokStyleCaptions({ captions, combineTokensWithinMilliseconds }) pages the tokens. Low (200–500 ms) = word-by-word pop; high (1200–2000 ms) = phrase pages. Page durationMs was added in v4.0.261. CapCut route: Text → Auto captions → Generate, then restyle.

Beat sync

A beat grid (step 4) is a list of seconds. Snap your cut points and zoom keyframes to the nearest beat so motion lands on the music.

  • Bad → Good. Cutting mid-word to land a cut exactly on a beat reads as a mistake → nudge the cut to the nearest beat that also respects the speech boundary. Honor the words first, the beat second.
  • When NOT to beat-cut. A talking-head explainer where the words carry the value: clarity beats rhythm. Beat-cutting a person mid-sentence chops their delivery. Reserve beat sync for B-roll montages, transitions, and music-led segments.
  • Transitions stay cheap and fast. Hard cut is the default. Whip-pan or zoom-punch only on a beat. Avoid 1-second crossfades in a 30-second short — they kill the pace; a short lives on fast hard cuts.

Export specs

The universal vertical master — one file that plays cleanly across all three platforms:

ffmpeg -i captioned.mp4 \
  -vf "scale=1080:1920:force_original_aspect_ratio=decrease,pad=1080:1920:(ow-iw)/2:(oh-ih)/2,setsar=1" \
  -c:v libx264 -profile:v high -pix_fmt yuv420p -r 30 \
  -c:a aac -b:a 192k -ar 48000 -ac 2 \
  -movflags +faststart out.mp4

yuv420p (broad playback), +faststart (web streaming starts before full download), AAC-LC 48 kHz. Use 30 fps; bump to 60 fps for high-motion Shorts.

PlatformVideo bitrateNotes
TikTok~2,000–4,000 kbpslightest of the three
Instagram Reels~3,500–4,500 kbps
YouTube Shorts (1080p)~8,000–15,000 kbpsup to ~53,000 for 4K/60

A single 1080x1920 H.264 MP4 @30 fps under ~250 MB is safe everywhere. Verify before you ship:

ffprobe -v error -select_streams v:0 \
  -show_entries stream=width,height,codec_name,avg_frame_rate out.mp4

Expect width=1080 height=1920 codec_name=h264. The full per-platform encode flags and ffprobe assertions are in references/ffmpeg-pipeline.md.

Anti-patterns

BadWhy it breaksGood
silenceremove as a one-shot filterIt is audio-only; the video keeps playing → audio/video desyncsilencedetect → invert ranges → select+aselect cut both streams
Word captions from segment timestampsHighlight jitters and lands on the wrong wordForced-alignment per-word stamps (WhisperX / word_timestamps)
Captions burned in the bottom 15–20%Platform UI (buttons, CTA, profile) covers themPlace in the vertical middle band, central ~80%
One export reused for every platformOver/under the bitrate target somewherePer-platform bitrate, or the safe universal master
1-second crossfades in a 30s shortKills the pace; reads slowHard cuts; whip/zoom only on a beat
Beat-cutting a talking-head mid-sentenceChops the delivery; clarity lostBeat-sync B-roll/montage only; honor speech boundaries
Re-running the encode to "check the specs"Wastes minutes; the file already existsffprobe the output; verify, don't re-render
Writing the caption copy or the hook hereThat is the script, not the cutRoute to ../video-shorts/SKILL.md; you only burn it in
ffmpeg overwriting the source in placeOne bad flag and the original is goneAlways write to a new file; keep in.mp4 untouched

Verify & references

  • bash scripts/verify.sh <output.mp4> — runs ffprobe and asserts width=1080, height=1920 (9:16), video codec h264, audio codec aac, fps in {30, 60}, and that a sibling .srt/.ass caption file exists and is non-empty. With no ffprobe it falls back to a path/extension check. Read-only; exits 0 on an empty/clean target so it never false-fails. The "does it feel on-beat" judgment is not mechanical — that lives in the capability eval.
  • references/ffmpeg-pipeline.md — the full silence-detect → keep-range inversion → select/aselect filter generation that cuts video+audio together, the concat/trim approach, per-platform encode flags, ffprobe assertions, and desync/audio-drift/faststart troubleshooting.
  • references/captions.md — WhisperX install + run, the word-timestamp JSON shape, the full .ass karaoke template with safe-zone margins, the Remotion createTikTokStyleCaptions() snippet with combineTokensWithinMilliseconds guidance, and the faster-whisper vs WhisperX tradeoff.

Individual skills in this repo

This repo contains 9 individual skills — each has its own dedicated page.

ericrisco/rsc-harness

Use when writing the words on a single conversion page — the hero headline and subhead, the offer, the proof and testimonials, and one primary CTA — or diagnosing a page that gets traffic and does not convert, as a copy problem rather than a layout one. NOT the paid ad that drives the click (that is `ads`), NOT the visual layout the words sit in (that is `design`), NOT the experiment that picks the winning variant (that is `ab-testing`).

ericrisco/rsc-harness

Use when you need to render an actual video file with Remotion — React compositions, the Composition/Sequence/TransitionSeries graph, transitions, burned-in word-by-word captions from a transcript, automatic silence removal, b-roll overlays, headless CI renders, and a final MP4 or MOV. NOT writing the script, hook, beats or caption text (that is `video-shorts`), NOT mastering audio to LUFS or building an RSS feed (that is `podcast`), NOT structuring the narrative arc (that is `course-storytelling`).

ericrisco/rsc-harness

Use when a Reels/TikTok/Shorts account needs its next video ideas scored into a ranked backlog, grounded in its own performance log plus dated trending sounds, each bet logged so its outcome feeds the next batch. NOT scripting a chosen idea (that is `video-shorts`), NOT pillars or cadence (that is `shortform-strategy`), NOT packaging a finished cut (that is `shortform-packaging`).

ericrisco/rsc-harness

Use when a vertical short is shot or scripted and you need the upload-form copy and cover that win the feed — the hook line, the first-frame on-screen text, the search-led caption, a tight hashtag set, and the cover frame; it learns from what performed via the 02-DOCS log. NOT inventing the idea (that is `shortform-ideation`), NOT scripting or directing the cuts (that is `video-shorts`), NOT executing the edit (that is `shortform-editing`), NOT scheduling the post (that is `social-publisher`).

ericrisco/rsc-harness

Use when the unit of work is a TikTok/Reels account, not one clip: positioning, sustainable cadence and format mix, ride-or-skip on a trend or sound, recurring series, diagnosing a flat account, and the dated learning loop. NOT the single script or hook variants (that is `video-shorts`), NOT the idea backlog (that is `shortform-ideation`).

ericrisco/rsc-harness

Use when planning a posting schedule, building a multi-platform content calendar, or reshaping one source asset into channel-native posts across X, LinkedIn, Instagram, Threads, Bluesky, TikTok, Facebook or YouTube — cadence, best-time slots, per-platform format limits, API publish caps, and a dated CSV or JSON calendar a scheduler can import. NOT the brand voice the posts speak in (that is `brand-voice`), NOT the editorial strategy of what to make (that is `content-engine`).

ericrisco/rsc-harness

Use when connecting a real TikTok account to code via the Content Posting, Display and Business Account APIs — OAuth, chunked video publish with status polling, and pulling views, watch time and impression sources, then logging that performance into the wiki as a dated feedback record. Covers short-lived tokens breaking a cron, unverified pull-from-URL ownership, and rate limits. NOT what to post or how to package it (that is `shortform-strategy` and `shortform-packaging`).

ericrisco/rsc-harness

Use when scripting or directing the edit of a single vertical short that has to hold attention from the first frame — turning a topic, a clip or a long video into a shot-by-shot script and an edit decision sheet, fixing a hook that dies in second three, generating hook variants to test, and planning cuts and on-screen text. NOT scheduling or cross-platform cadence (that is `social-publisher`), NOT running the editorial calendar (that is `content-engine`), NOT defining the durable brand tone (that is `brand-voice`).

ericrisco/rsc-harness

Use to judge whether a short-form clip is worth publishing — scores a reel, short or podcast excerpt 0-100 on ten weighted criteria with automatic penalties, then ranks a batch and says where the cut-off falls. Run it on transcript candidates before rendering. NOT writing the hook or caption (that is `shortform-packaging`), NOT inventing the idea (that is `shortform-ideation`), NOT executing the edit (that is `shortform-editing`).

Related Skills