← back to Tiktok Architect
scripts/tt-transcript.sh
78 lines
#!/usr/bin/env bash
# tt-transcript.sh — get the spoken text of ONE TikTok tip video, $0 and local.
#
# Most @tiktok_architect videos expose NO subtitles (verified — yt-dlp lists an
# empty subtitle set), and the actual "do this / never do that" detail is spoken
# aloud + shown on-screen, not in the caption. This tries, in order:
# 1) yt-dlp subtitles / auto-captions (instant, $0) — usually empty on TikTok
# 2) download audio + LOCAL whisper transcription ($0, no API) if a local
# whisper is installed (whisper / whisper-cli / mlx_whisper)
# 3) if no local whisper: leave the audio file and print its path + the next
# step, honestly — do NOT claim a transcript you don't have.
#
# Usage: bash tt-transcript.sh <tiktok-video-url> [outdir]
# Output: prints the transcript to stdout when it can produce one; otherwise
# prints WHERE the audio landed and what to run. Never fabricates text.
#
# NOTE: transcription is OPTIONAL. For most tips the oEmbed caption + the video
# premise is enough to identify the CLAIM to verify. Reach for this only when a
# tip's specifics can't be pinned down from the caption.
set -euo pipefail
URL="${1:-}"
OUT="${2:-$(mktemp -d -t ttarch)}"
[ -z "$URL" ] && { echo "usage: bash tt-transcript.sh <tiktok-video-url> [outdir]" >&2; exit 1; }
command -v yt-dlp >/dev/null 2>&1 || { echo "yt-dlp not found. brew install yt-dlp" >&2; exit 2; }
mkdir -p "$OUT"
# --- 1) try real / auto subtitles (instant, $0) ---
yt-dlp --skip-download --write-subs --write-auto-subs --sub-format vtt \
--sub-langs "en.*,en" -o "$OUT/%(id)s.%(ext)s" "$URL" >/dev/null 2>&1 || true
VTT="$(ls "$OUT"/*.vtt 2>/dev/null | head -1 || true)"
if [ -n "${VTT:-}" ] && [ -s "$VTT" ]; then
echo "# transcript source: yt-dlp subtitles ($VTT) cost: \$0" >&2
# strip WEBVTT header, cue timestamps, and de-dupe consecutive lines
grep -vE '^(WEBVTT|Kind:|Language:|[0-9]{2}:[0-9]{2}|$)' "$VTT" \
| sed -E 's/<[^>]+>//g' | awk '!seen[$0]++'
exit 0
fi
# --- 2) download audio, then LOCAL whisper if present ($0) ---
echo "# no subtitles on this video — downloading audio for local transcription…" >&2
yt-dlp -x --audio-format mp3 --audio-quality 5 -o "$OUT/%(id)s.%(ext)s" "$URL" >/dev/null 2>&1 || {
echo "audio download failed (TikTok throttle or missing curl_cffi impersonate target)." >&2
echo "see references/retrieval-playbook.md → impersonation note." >&2
exit 3
}
MP3="$(ls "$OUT"/*.mp3 2>/dev/null | head -1 || true)"
[ -z "${MP3:-}" ] && { echo "no audio file produced." >&2; exit 3; }
run_whisper() {
# try the common local whisper CLIs, cheapest/fastest first — all $0 local
if command -v mlx_whisper >/dev/null 2>&1; then
echo "# transcript source: mlx_whisper (local, \$0)" >&2
mlx_whisper "$MP3" --output-dir "$OUT" --output-format txt >/dev/null 2>&1 \
&& cat "${MP3%.mp3}.txt" 2>/dev/null && return 0
fi
if command -v whisper-cli >/dev/null 2>&1; then
echo "# transcript source: whisper-cli (local, \$0)" >&2
whisper-cli -f "$MP3" -otxt -of "${MP3%.mp3}" >/dev/null 2>&1 \
&& cat "${MP3%.mp3}.txt" 2>/dev/null && return 0
fi
if command -v whisper >/dev/null 2>&1; then
echo "# transcript source: openai-whisper (local, \$0)" >&2
whisper "$MP3" --model base --output_dir "$OUT" --output_format txt >/dev/null 2>&1 \
&& cat "$OUT/$(basename "${MP3%.mp3}").txt" 2>/dev/null && return 0
fi
return 1
}
if run_whisper; then exit 0; fi
# --- 3) honest fallback: no local transcriber available ---
echo "NO_TRANSCRIPT: no local whisper found (tried mlx_whisper / whisper-cli / whisper)." >&2
echo "Audio saved to: $MP3" >&2
echo "Next step (still \$0 local): install a local whisper, OR read the tip's CLAIM" >&2
echo "from the oEmbed caption + on-screen text instead of a full transcript." >&2
exit 4