[object Object]

← back to Voice Clone

initial scaffold (gitify-all 2026-05-06)

8a62f1f039c85a5434485ff775c42ff5132b19b2 · 2026-05-06 10:25:51 -0700 · Steve Abrams

Files touched

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)