[object Object]

← back to Crcp Pitch Video

CRCP video: cloned-voice narration (ElevenLabs, Steve Abrams) synced to scenes + 60s LinkedIn cut (CRCPShort) + Drive upload

bb76371dba10bdbdee07a45057e340510358b9ba · 2026-07-12 09:36:08 -0700 · Steve Abrams

Files touched

Diff

commit bb76371dba10bdbdee07a45057e340510358b9ba
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Sun Jul 12 09:36:08 2026 -0700

    CRCP video: cloned-voice narration (ElevenLabs, Steve Abrams) synced to scenes + 60s LinkedIn cut (CRCPShort) + Drive upload
---
 .gitignore    |  2 ++
 narrate.py    | 41 +++++++++++++++++++++++++++++++++++++++
 src/Root.tsx  | 14 +++++---------
 src/Video.tsx | 62 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 110 insertions(+), 9 deletions(-)

diff --git a/.gitignore b/.gitignore
index 125e883..b22a3c9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,5 @@
 node_modules/
 out/
 .DS_Store
+audio/
+audio_short/
diff --git a/narrate.py b/narrate.py
new file mode 100644
index 0000000..6073eb9
--- /dev/null
+++ b/narrate.py
@@ -0,0 +1,41 @@
+#!/usr/bin/env python3
+# Generate per-scene cloned-voice narration (ElevenLabs, Steve Abrams voice) timed to the video.
+import os, sys, json, subprocess, urllib.request
+
+VOICE = "Xa9qV4wNbvSkdUWsYLzq"  # Steve Abrams (cloned)
+KEY = None
+for p in [os.path.expanduser("~/Projects/secrets-manager/.env"), os.path.expanduser("~/.claude/skills/clone-voice/.env")]:
+    try:
+        for line in open(p):
+            if "ELEVENLABS_API_KEY" in line: KEY = line.strip().split("=", 1)[1].strip().strip('"'); break
+    except FileNotFoundError: pass
+    if KEY: break
+assert KEY, "no ElevenLabs key"
+
+# (start_sec, text) — each scene starts at its Sequence boundary in the 180s video
+SCENES = [
+    (0,   "This is C-R-C-P. Every licensed real estate agent, in America's most expensive markets — the prospecting platform built for loan officers, escrow, and title."),
+    (13,  "Your business runs on one thing: relationships with the agents who close the deals. But which agents? In which markets? And how do you reach them, before every other rep in town beats you to it?"),
+    (36,  "Meet C-R-C-P. Three hundred fifty-five thousand licensed agents. Twenty-one elite markets. The twenty priciest cities in the country — Beverly Hills, Manhattan, Aspen, Greenwich, Naples, and more. Every record is backed by real government license data. Verified, not guessed. Search any market, and see every agent, their brokerage, their license, and their listings."),
+    (79,  "Here's why that matters to you. If you're a loan officer, agents send you borrowers, so target the top producers in luxury markets, where a single deal is a jumbo loan. If you run escrow, agents choose you, so get in front of the highest-volume agents first. And if you're in title, it's all relationships. Build yours with the agents who control the priciest inventory in America. One platform. Every deal-maker. Before anyone else finds them."),
+    (127, "So how do you approach them? Four channels. Calls are warm and personal, so reference a recent listing, and save them for your highest-value agents. Email scales, so lead with value, keep it short, one ask. Postcards cut through the digital noise, because luxury agents still notice great physical mail. And LinkedIn is relationship-first: connect, engage, and comment on their wins before you ever pitch."),
+    (169, "Every record gives you the name, brokerage, license, and city. Enrich it, and reach out. Start with the agents closing the biggest deals, at C-R-C-P dot agent abrams dot com."),
+]
+
+os.makedirs("audio", exist_ok=True)
+total_chars = 0
+for i, (start, text) in enumerate(SCENES):
+    total_chars += len(text)
+    body = json.dumps({"text": text, "model_id": "eleven_multilingual_v2",
+                       "voice_settings": {"stability": 0.5, "similarity_boost": 0.8, "style": 0.15}}).encode()
+    req = urllib.request.Request(f"https://api.elevenlabs.io/v1/text-to-speech/{VOICE}",
+                                 data=body, headers={"xi-api-key": KEY, "Content-Type": "application/json", "Accept": "audio/mpeg"})
+    mp3 = f"audio/scene{i}.mp3"
+    with urllib.request.urlopen(req) as r, open(mp3, "wb") as f:
+        f.write(r.read())
+    # duration
+    dur = subprocess.check_output(["ffprobe", "-v", "error", "-show_entries", "format=duration", "-of", "csv=p=0", mp3]).decode().strip()
+    print(f"  scene{i}: start {start}s, {len(text)} chars, {float(dur):.1f}s audio -> {mp3}")
+
+json.dump([{"i": i, "start": s} for i, (s, t) in enumerate(SCENES)], open("audio/timing.json", "w"))
+print(f"TOTAL {total_chars} chars (~${total_chars/1000*0.30:.2f} ElevenLabs)")
diff --git a/src/Root.tsx b/src/Root.tsx
index 2d359bc..306c6c2 100644
--- a/src/Root.tsx
+++ b/src/Root.tsx
@@ -1,16 +1,12 @@
 import React from 'react';
 import { Composition } from 'remotion';
-import { CRCPPitch, TOTAL_FRAMES, FPS } from './Video';
+import { CRCPPitch, CRCPShort, TOTAL_FRAMES, SHORT_FRAMES, FPS } from './Video';
 
 export const RemotionRoot: React.FC = () => {
   return (
-    <Composition
-      id="CRCPPitch"
-      component={CRCPPitch}
-      durationInFrames={TOTAL_FRAMES}
-      fps={FPS}
-      width={1920}
-      height={1080}
-    />
+    <>
+      <Composition id="CRCPPitch" component={CRCPPitch} durationInFrames={TOTAL_FRAMES} fps={FPS} width={1920} height={1080} />
+      <Composition id="CRCPShort" component={CRCPShort} durationInFrames={SHORT_FRAMES} fps={FPS} width={1920} height={1080} />
+    </>
   );
 };
diff --git a/src/Video.tsx b/src/Video.tsx
index c9f88e1..3a0dfb5 100644
--- a/src/Video.tsx
+++ b/src/Video.tsx
@@ -262,6 +262,68 @@ const S6: React.FC = () => {
   );
 };
 
+// ═══════════════ 60-SECOND SOCIAL CUT ═══════════════
+export const SHORT_FRAMES = 1800; // 60s
+const ShortStats: React.FC = () => {
+  const cities = ['Beverly Hills', 'Manhattan', 'Aspen', 'Naples', 'Greenwich', 'Malibu', 'Palm Beach', 'The Hamptons', 'Vail', 'Miami'];
+  return (
+    <AbsoluteFill style={{ justifyContent: 'center', alignItems: 'center' }}>
+      <Bg />
+      <Kicker delay={2}>Meet CRCP</Kicker>
+      <div style={{ display: 'flex', gap: 90, marginTop: 34 }}>
+        <Stat big={<Counter to={355354} delay={10} />} label="Licensed agents" delay={8} />
+        <Stat big={<Counter to={20} delay={10} />} label="Priciest US cities" delay={16} />
+      </div>
+      <div style={{ display: 'flex', flexWrap: 'wrap', gap: 12, justifyContent: 'center', maxWidth: 1300, marginTop: 44 }}>
+        {cities.map((c, i) => {
+          const d = 40 + i * 6;
+          return <div key={c} style={{ fontFamily: SANS, fontSize: 24, color: C.gold, border: `1px solid ${C.goldD}`, borderRadius: 30, padding: '9px 20px', background: `${C.gold}12`, opacity: useFade(d), transform: `translateY(${useRise(d, 16)}px)` }}>{c}</div>;
+        })}
+      </div>
+      <div style={{ fontFamily: SANS, fontSize: 26, color: C.ink, marginTop: 42, opacity: useFade(120), maxWidth: 1200, textAlign: 'center' }}>
+        Every agent in America's priciest markets — <b style={{ color: C.green }}>government-verified.</b>
+      </div>
+    </AbsoluteFill>
+  );
+};
+const ShortRoles: React.FC = () => (
+  <AbsoluteFill style={{ justifyContent: 'center', alignItems: 'center' }}>
+    <Bg />
+    <Kicker delay={2}>Built for you</Kicker>
+    <div style={{ display: 'flex', flexDirection: 'column', gap: 22, marginTop: 40 }}>
+      <RoleCard icon="🏦" role="LOAN OFFICERS" delay={14} line="Target the top producers — where one deal is a jumbo loan." />
+      <RoleCard icon="📜" role="ESCROW OFFICERS" delay={40} line="Reach the highest-volume agents before your competitor does." />
+      <RoleCard icon="🏛️" role="TITLE REPS" delay={66} line="Build relationships with the agents controlling the priciest inventory." />
+    </div>
+  </AbsoluteFill>
+);
+const ShortChannels: React.FC = () => (
+  <AbsoluteFill style={{ justifyContent: 'center', alignItems: 'center' }}>
+    <Bg />
+    <Kicker delay={2}>Reach them</Kicker>
+    <div style={{ display: 'flex', gap: 20, marginTop: 40 }}>
+      {[['📞', 'CALL'], ['✉️', 'EMAIL'], ['📮', 'POSTCARD'], ['in', 'LINKEDIN']].map(([ic, nm], i) => (
+        <div key={nm} style={{ background: C.card, border: `1px solid ${C.line}`, borderRadius: 16, padding: '26px 30px', textAlign: 'center', opacity: useFade(14 + i * 8), transform: `scale(${interpolate(useFade(14 + i * 8), [0, 1], [0.85, 1])})` }}>
+          <div style={{ fontSize: 46 }}>{ic}</div>
+          <div style={{ fontFamily: SANS, fontSize: 26, color: C.blue, fontWeight: 700, marginTop: 8 }}>{nm}</div>
+        </div>
+      ))}
+    </div>
+    <div style={{ fontFamily: SANS, fontSize: 26, color: C.gold, marginTop: 40, opacity: useFade(70) }}>
+      LinkedIn → email → call → postcard.
+    </div>
+  </AbsoluteFill>
+);
+export const CRCPShort: React.FC = () => (
+  <AbsoluteFill style={{ background: C.bg }}>
+    <Sequence from={0} durationInFrames={150}><S1 /></Sequence>
+    <Sequence from={150} durationInFrames={540}><ShortStats /></Sequence>
+    <Sequence from={690} durationInFrames={600}><ShortRoles /></Sequence>
+    <Sequence from={1290} durationInFrames={300}><ShortChannels /></Sequence>
+    <Sequence from={1590} durationInFrames={210}><S6 /></Sequence>
+  </AbsoluteFill>
+);
+
 // ═══════════════ ROOT SEQUENCER ═══════════════
 export const CRCPPitch: React.FC = () => {
   return (

← d0a4745 CRCP 3-min Remotion pitch video — value to loan officers/esc  ·  back to Crcp Pitch Video  ·  auto-save: 2026-07-12T09:47:28 (10 files) — package-lock.jso 8c29909 →