← back to Voice Clone
initial scaffold (gitify-all 2026-05-06)
8a62f1f039c85a5434485ff775c42ff5132b19b2 · 2026-05-06 10:25:51 -0700 · Steve Abrams
Files touched
A .gitignoreA clone.shA record.shA say.sh
Diff
commit 8a62f1f039c85a5434485ff775c42ff5132b19b2
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Wed May 6 10:25:51 2026 -0700
initial scaffold (gitify-all 2026-05-06)
---
.gitignore | 12 +++++++++++
clone.sh | 67 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
record.sh | 57 ++++++++++++++++++++++++++++++++++++++++++++++++++++
say.sh | 35 ++++++++++++++++++++++++++++++++
4 files changed, 171 insertions(+)
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..7e6a9c3
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,12 @@
+node_modules/
+.env
+.env.local
+.env.*.local
+.env.*
+tmp/
+*.log
+.DS_Store
+dist/
+build/
+.next/
+*.bak
diff --git a/clone.sh b/clone.sh
new file mode 100755
index 0000000..532f059
--- /dev/null
+++ b/clone.sh
@@ -0,0 +1,67 @@
+#!/bin/bash
+# clone.sh — upload sample.wav to ElevenLabs as Instant Voice Clone "Steve",
+# capture the voice_id, route it through the secrets manager so every skill
+# (app-demo-video, reviewed-demo-video, use-voice) can grab it from env.
+#
+# Free tier: 10 voice slots, IVC enabled, ~90K chars/mo TTS budget.
+
+set -euo pipefail
+SAMPLE=${1:-$HOME/Projects/voice-clone/sample.wav}
+NAME=${2:-Steve}
+
+[ -f "$SAMPLE" ] || { echo "[abort] sample not found: $SAMPLE (run ./record.sh first)"; exit 1; }
+KEY=$(grep '^ELEVENLABS_API_KEY=' ~/Projects/secrets-manager/.env | cut -d= -f2)
+[ -z "$KEY" ] && { echo "[abort] ELEVENLABS_API_KEY missing in secrets-manager .env"; exit 1; }
+
+echo "Uploading $(basename $SAMPLE) ($(du -h $SAMPLE | awk '{print $1}')) as voice '$NAME'..."
+
+RESP=$(curl -s -X POST https://api.elevenlabs.io/v1/voices/add \
+ -H "xi-api-key: $KEY" \
+ -F "name=$NAME" \
+ -F "description=Steve Abrams narrator voice — used for project walkthroughs and reviewed-demo-videos" \
+ -F "files=@$SAMPLE" \
+ -F 'labels={"use_case":"narration","style":"editorial"}')
+
+VOICE_ID=$(echo "$RESP" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('voice_id',''))" 2>/dev/null || echo "")
+
+if [ -z "$VOICE_ID" ]; then
+ echo "[fail] no voice_id in response:"
+ echo "$RESP"
+ exit 2
+fi
+
+echo "✓ Voice created — id: $VOICE_ID"
+echo ""
+
+# Persist to .env (master + desktop copy)
+for ENVFILE in ~/Projects/secrets-manager/.env ~/Desktop/site-factory.env; do
+ if [ -f "$ENVFILE" ]; then
+ grep -v '^ELEVENLABS_VOICE_STEVE=' "$ENVFILE" > "$ENVFILE.tmp" || true
+ echo "ELEVENLABS_VOICE_STEVE=$VOICE_ID" >> "$ENVFILE.tmp"
+ mv "$ENVFILE.tmp" "$ENVFILE"
+ echo " wrote ELEVENLABS_VOICE_STEVE → $ENVFILE"
+ fi
+done
+
+# Run a quick sanity TTS to verify the clone
+SAMPLE_TEXT="This is Steve, narrating a walkthrough. Open the archive for every house."
+echo ""
+echo "Test render (8 words, ~3 sec audio):"
+curl -s -X POST "https://api.elevenlabs.io/v1/text-to-speech/$VOICE_ID" \
+ -H "xi-api-key: $KEY" -H "Content-Type: application/json" \
+ -d "$(python3 -c "import json,sys; print(json.dumps({'text':'$SAMPLE_TEXT','model_id':'eleven_multilingual_v2','voice_settings':{'stability':0.5,'similarity_boost':0.85}}))")" \
+ -o ~/Projects/voice-clone/test.mp3
+
+if [ -s ~/Projects/voice-clone/test.mp3 ]; then
+ echo "✓ Test MP3: ~/Projects/voice-clone/test.mp3"
+ echo " Listen: afplay ~/Projects/voice-clone/test.mp3"
+else
+ echo "✗ Test render failed"
+fi
+
+echo ""
+echo "═══════════════════════════════════════════════════════════"
+echo " Voice ID saved as ELEVENLABS_VOICE_STEVE in .env files"
+echo " Use in any script: export VOICE_ID=\$ELEVENLABS_VOICE_STEVE"
+echo " Generate narration: ./say.sh \"some text\" out.mp3"
+echo "═══════════════════════════════════════════════════════════"
diff --git a/record.sh b/record.sh
new file mode 100755
index 0000000..c665b03
--- /dev/null
+++ b/record.sh
@@ -0,0 +1,57 @@
+#!/bin/bash
+# record.sh — record a 90-second voice sample for ElevenLabs cloning.
+# First run will trigger macOS mic-permission prompt (Terminal needs Microphone access).
+#
+# Usage: ./record.sh [seconds=90] [output=~/Projects/voice-clone/sample.wav]
+#
+# What to read for the best clone (60-90sec, varied tone):
+# "Hi, I'm Steve. This is a voice sample for the Steve narrator voice on the
+# WhoLivedThere project. I'm reading at a normal pace, with natural pauses,
+# the way I'd actually narrate a product walkthrough. Open the archive for
+# every house. Public records, private memories, real addresses. Editorial
+# seriousness without being precious. Quick brown foxes and lazy dogs and
+# forty-two and three-thousand-eight-hundred-and-twelve."
+# (Add another 30-40sec of any natural speech. Whisper, normal, slightly
+# excited — variety helps the clone capture range.)
+
+set -euo pipefail
+SECONDS_TO_RECORD=${1:-90}
+OUT=${2:-$HOME/Projects/voice-clone/sample.wav}
+mkdir -p "$(dirname "$OUT")"
+
+# discover audio device index
+AUDIO_DEV=$(ffmpeg -hide_banner -f avfoundation -list_devices true -i "" 2>&1 \
+ | awk '/AVFoundation audio/{found=1; next} found && /\[[0-9]+\]/{print; exit}' \
+ | grep -oE '\[[0-9]+\]' | head -1 | tr -d '[]')
+AUDIO_DEV=${AUDIO_DEV:-0}
+
+echo ""
+echo "═════════════════════════════════════════════════════════════════"
+echo " Voice sample recording — $SECONDS_TO_RECORD seconds"
+echo " output: $OUT"
+echo " device: avfoundation index=$AUDIO_DEV"
+echo "═════════════════════════════════════════════════════════════════"
+echo ""
+echo "Tips for a great ElevenLabs clone:"
+echo " • Quiet room, no fan/AC noise, no echo"
+echo " • Same distance from mic the whole time (~6 inches)"
+echo " • Natural pace — read like you're narrating a walkthrough"
+echo " • Vary tone: matter-of-fact, slightly excited, calm explanatory"
+echo " • 60-90sec total, English only for first clone"
+echo ""
+read -p "Press ENTER to start recording (3-sec countdown), Ctrl-C to abort... "
+for n in 3 2 1; do echo -n "$n... "; sleep 1; done
+echo "RECORDING — speak now."
+echo ""
+
+ffmpeg -hide_banner -loglevel warning \
+ -f avfoundation -i ":$AUDIO_DEV" \
+ -t "$SECONDS_TO_RECORD" -ac 1 -ar 44100 -c:a pcm_s16le \
+ -y "$OUT"
+
+echo ""
+echo "✓ Saved: $OUT ($(du -h "$OUT" | awk '{print $1}'))"
+echo ""
+echo "Listen back: afplay '$OUT'"
+echo "If happy: ./clone.sh"
+echo "If not: ./record.sh # try again"
diff --git a/say.sh b/say.sh
new file mode 100755
index 0000000..72510e1
--- /dev/null
+++ b/say.sh
@@ -0,0 +1,35 @@
+#!/bin/bash
+# say.sh — generate narration MP3 in Steve's cloned voice.
+# Usage: ./say.sh "text to narrate" output.mp3
+# Env: ELEVENLABS_API_KEY, ELEVENLABS_VOICE_STEVE (auto-loaded from secrets-manager)
+
+set -euo pipefail
+TEXT=${1:?usage: say.sh \"text\" [output.mp3]}
+OUT=${2:-narration.mp3}
+
+KEY=$(grep '^ELEVENLABS_API_KEY=' ~/Projects/secrets-manager/.env | cut -d= -f2)
+VID=$(grep '^ELEVENLABS_VOICE_STEVE=' ~/Projects/secrets-manager/.env | cut -d= -f2)
+[ -z "$KEY" ] && { echo "[abort] ELEVENLABS_API_KEY missing"; exit 1; }
+[ -z "$VID" ] && { echo "[abort] ELEVENLABS_VOICE_STEVE not set — run clone.sh first"; exit 1; }
+
+CHARS=${#TEXT}
+echo "rendering $CHARS chars → $OUT (voice=$VID)"
+
+curl -s -X POST "https://api.elevenlabs.io/v1/text-to-speech/$VID" \
+ -H "xi-api-key: $KEY" -H "Content-Type: application/json" \
+ -d "$(python3 -c "
+import json,sys
+print(json.dumps({
+ 'text': '''$TEXT''',
+ 'model_id': 'eleven_multilingual_v2',
+ 'voice_settings': {'stability': 0.45, 'similarity_boost': 0.85, 'style': 0.15, 'use_speaker_boost': True}
+}))")" \
+ -o "$OUT"
+
+if [ -s "$OUT" ]; then
+ SIZE=$(du -h "$OUT" | awk '{print $1}')
+ echo "✓ $OUT ($SIZE)"
+ command -v ffprobe >/dev/null && DUR=$(ffprobe -v error -show_entries format=duration -of default=nw=1:nk=1 "$OUT" 2>/dev/null) && echo " duration: ${DUR}s"
+else
+ echo "✗ render failed"; exit 2
+fi
(oldest)
·
back to Voice Clone
·
(newest)