All guides

Deconstruct Any Viral Video Into a Script You Can Imitate

Paste a reel URL. Get back the transcript, a breakdown of why the video performed, three hook options, a full word-for-word imitation script, and filming notes — all saved to a Notion page. Runs locally on your Mac. No OpenAI key, no usage fees.

If you'd rather not read the whole guide, skip to the bottom — there's a one-shot setup prompt you paste into Claude Code.

What this is

This is the upgraded version of the basic transcribe pipeline. The basic version gives you a transcript. This version gives you a transcript + a structured breakdown that helps you actually use the video as a template.

The output is a single Notion page with four sections:

  1. Source URL — link back to the original
  2. Original Transcript — the full Whisper output
  3. Why This Worked — 3-5 bullets covering hook, structure, pacing, CTA
  4. Imitation Script — three hook options, the chosen hook, a word-for-word spoken script, and filming notes

Page title is the chosen hook. Status, priority, and platform tag are pre-filled so it slots straight into your content board.

Architecture

code
You paste URL into Claude Code
    |
    v
/transcribe slash command
    |
    v
yt-dlp (download audio)
    |
    v
ffmpeg (16kHz mono WAV)
    |
    v
mlx-whisper (local Whisper, Apple Silicon)
    |
    v
claude --print (Sonnet 4.6)  <--  brand voice + framework files (optional)
    |
    v
POST /v1/pages -> Notion content board

Five components: a slash command, a shell orchestrator, two Python helpers, and an optional brand voice file. Everything sits in ~/.claude/.

Prerequisites

  • Apple Silicon Mac (M1/M2/M3/M4)
  • Claude Code installed (claude --version works)
  • Homebrew installed
  • A Notion workspace and a database to post to
  • ~10 minutes for setup

Part 1: Install dependencies

bash
brew install yt-dlp ffmpeg
pip3 install --break-system-packages mlx-whisper

Verify:

bash
yt-dlp --version
ffmpeg -version | head -1
python3 -c "import mlx_whisper; print('ok')"

Part 2: Set up Notion

2a. Create the database

In Notion, create a new database (full page). Add these properties:

NameTypeNotes
Task nameTitleDefault — the chosen hook goes here
StatusStatusAdd option "Not started"
PrioritySelectAdd option "High"
Task typeMulti-selectAdd options "Reel/Short" and "Youtube"

You can rename "Task name" to "Title" if you prefer — just update the corresponding key in transcribe-notion-post.py later.

2b. Create the integration

  1. Go to notion.so/my-integrations
  2. Click "New integration"
  3. Name it (e.g. "Video Deconstructor"), select your workspace, click Save
  4. Copy the Internal Integration Token — looks like ntn_...

2c. Connect it to your database

  1. Open your database in Notion
  2. Click the ... menu in the top right -> Connections -> add your integration
  3. Grab the database ID from the URL: https://www.notion.so/<DATABASE_ID>?v=... (the 32-char string before the ?)

You now have a token and a database ID. Keep them handy.

Part 3: Create the orchestrator script

bash
mkdir -p ~/.claude/tools ~/.claude/commands

Create ~/.claude/tools/transcribe-video.sh:

bash
#!/bin/bash
set -euo pipefail

URL=""
EXTRA_CONTEXT=""
SEND_NOTION=true

while [[ $# -gt 0 ]]; do
  case "$1" in
    --no-notion)   SEND_NOTION=false; shift ;;
    --context)     EXTRA_CONTEXT="${2:-}"; shift 2 ;;
    -*)            echo "Unknown flag: $1" >&2; exit 1 ;;
    *)             URL="$1"; shift ;;
  esac
done

if [[ -z "$URL" ]]; then
  echo "Usage: transcribe-video.sh <URL> [--context \"...\"] [--no-notion]" >&2
  exit 1
fi

YTDLP="$(command -v yt-dlp)"
FFMPEG="$(command -v ffmpeg)"
TOOLS="$HOME/.claude/tools"

# Notion config — read from env
NOTION_TOKEN="${NOTION_TOKEN:-}"
NOTION_DB="${NOTION_DB:-}"
NOTION_API="https://api.notion.com/v1/pages"
NOTION_VERSION="2022-06-28"

if [[ "$SEND_NOTION" == "true" && ( -z "$NOTION_TOKEN" || -z "$NOTION_DB" ) ]]; then
  echo "Set NOTION_TOKEN and NOTION_DB in your shell or pass --no-notion" >&2
  exit 1
fi

TMPDIR_WORK="$(mktemp -d)"
trap 'rm -rf "$TMPDIR_WORK"' EXIT

detect_platform() {
  local url="$1"
  if [[ "$url" =~ (youtube\.com|youtu\.be) ]]; then echo "Youtube"
  elif [[ "$url" =~ instagram\.com ]]; then echo "Reel/Short"
  elif [[ "$url" =~ tiktok\.com ]]; then echo "Reel/Short"
  else echo "Youtube"
  fi
}
TASK_TYPE="$(detect_platform "$URL")"

echo "Fetching title..." >&2
VIDEO_TITLE="$("$YTDLP" --print title "$URL" 2>/dev/null || echo "Unknown Title")"
echo "Title: $VIDEO_TITLE" >&2

echo "Downloading audio..." >&2
"$YTDLP" -x --audio-format wav -o "$TMPDIR_WORK/audio_raw.%(ext)s" "$URL" 2>&1 | tail -3 >&2
AUDIO_FILE="$(find "$TMPDIR_WORK" -name 'audio_raw.*' -type f | head -1)"
[[ -z "$AUDIO_FILE" ]] && { echo "Audio download failed" >&2; exit 1; }

AUDIO_WAV="$TMPDIR_WORK/audio_16k.wav"
"$FFMPEG" -y -i "$AUDIO_FILE" -ar 16000 -ac 1 -c:a pcm_s16le "$AUDIO_WAV" 2>/dev/null

echo "Transcribing..." >&2
TRANSCRIPT="$(AUDIO_PATH="$AUDIO_WAV" python3 -c "
import mlx_whisper, os
r = mlx_whisper.transcribe(os.environ['AUDIO_PATH'], path_or_hf_repo='mlx-community/whisper-base-mlx')
print(r['text'].strip())
")"
bash

[[ -z "$TRANSCRIPT" ]] && { echo "Transcription empty" >&2; exit 1; }
echo "Got ${#TRANSCRIPT} chars" >&2

echo "Building analysis prompt..." >&2
export _TV_URL2="$URL" _TV_TITLE2="$VIDEO_TITLE" _TV_TRANSCRIPT2="$TRANSCRIPT"
export _TV_CONTEXT2="$EXTRA_CONTEXT" _TV_PROMPT_OUT="$TMPDIR_WORK/prompt.txt"
python3 "$TOOLS/transcribe-build-prompt.py"

echo "Generating analysis..." >&2
FULL_OUTPUT="$(claude --dangerously-skip-permissions --no-session-persistence \
  --print --model claude-sonnet-4-6 \
  -- "$(cat "$TMPDIR_WORK/prompt.txt")" 2>/dev/null)"

if [[ -z "$FULL_OUTPUT" || "${#FULL_OUTPUT}" -lt 100 ]]; then
  echo "Analysis empty — saving transcript only" >&2
  FULL_OUTPUT=""
fi

if [[ "$SEND_NOTION" == "true" ]]; then
  echo "Posting to Notion..." >&2
  export _TV_URL="$URL" _TV_TITLE="$VIDEO_TITLE" _TV_TASK_TYPE="$TASK_TYPE"
  export _TV_TRANSCRIPT="$TRANSCRIPT" _TV_FULL_OUTPUT="$FULL_OUTPUT"
  export _TV_NOTION_TOKEN="$NOTION_TOKEN" _TV_NOTION_DB="$NOTION_DB"
  export _TV_NOTION_API="$NOTION_API" _TV_NOTION_VERSION="$NOTION_VERSION"
  NOTION_URL="$(python3 "$TOOLS/transcribe-notion-post.py")"
  [[ -n "$NOTION_URL" ]] && echo "Notion: $NOTION_URL" >&2
fi

echo ""
echo "=== $VIDEO_TITLE ==="
echo ""
echo "$TRANSCRIPT"
echo ""
echo "---"
echo ""
echo "$FULL_OUTPUT"

Make it executable:

bash
chmod +x ~/.claude/tools/transcribe-video.sh

Part 4: Create the prompt builder

Create ~/.claude/tools/transcribe-build-prompt.py:

python
#!/usr/bin/env python3
"""Build the Sonnet prompt for analysis + imitation script."""
import os

url        = os.environ['_TV_URL2']
title      = os.environ['_TV_TITLE2']
transcript = os.environ['_TV_TRANSCRIPT2']
extra      = os.environ.get('_TV_CONTEXT2', '').strip()
out_file   = os.environ['_TV_PROMPT_OUT']
base       = os.path.expanduser('~/.claude')

def read_file(path):
    try:
        with open(path) as f: return f.read().strip()
    except: return ''

# Optional brand customisation — drop a brand/voice.md to personalise the script
voice = read_file(base + '/brand/voice.md')

parts = [
    "You are deconstructing a viral video and writing an imitation script.",
    "",
    f"Source URL: {url}",
    f"Title: {title}",
    "",
    "TRANSCRIPT:",
    transcript,
    "",
    "---",
]

if extra:
    parts += [
        "## ANGLE — adapt the imitation around this topic",
        "",
        "The viral structure comes from the source video. The SUBJECT and CONCRETE EXAMPLES of the imitation must come from this brief:",
        "",
        extra,
        "",
        "---",
    ]

if voice:
    parts += [
        "## BRAND VOICE — write in this voice",
        "",
        voice,
        "",
        "---",
    ]

parts += [
    "## TASK",
    "",
    "First: analyse why this video performed well — hook, structure, pacing, retention beats, CTA.",
    "Then: produce a full imitation script. Pick the format that matches (contrarian take, before/after, experiment, hidden truth, etc.).",
    "",
    "## OUTPUT FORMAT (exact, no deviations)",
    "",
    "WHY THIS WORKED:",
    "[3-5 bullets]",
    "",
    "HOOK 1: [7 words max]",
    "HOOK 2: [7 words max]",
    "HOOK 3: [7 words max]",
    "",
    "CHOSEN HOOK: [restate the best one]",
    "",
    "SPOKEN SCRIPT:",
    "[Full word-for-word script. End on an open loop or CTA.]",
    "",
    "FILMING NOTES:",
    "[2-4 lines. Tone, pacing, key emphasis moments.]",
]

with open(out_file, 'w') as f:
    f.write('\n'.join(parts
python
))

Part 5: Create the Notion poster

Create ~/.claude/tools/transcribe-notion-post.py:

python
#!/usr/bin/env python3
"""Post transcript + analysis to a single Notion page."""
import json, os, sys, urllib.request, re

url          = os.environ["_TV_URL"]
title        = os.environ["_TV_TITLE"]
task_type    = os.environ["_TV_TASK_TYPE"]
transcript   = os.environ["_TV_TRANSCRIPT"]
full_output  = os.environ.get("_TV_FULL_OUTPUT", "")
notion_token = os.environ["_TV_NOTION_TOKEN"]
notion_db    = os.environ["_TV_NOTION_DB"]
notion_api   = os.environ["_TV_NOTION_API"]
notion_version = os.environ["_TV_NOTION_VERSION"]

def chunk(text, size=2000):
    return [text[i:i+size] for i in range(0, len(text), size)] or [""]

def para(text):
    return {"object": "block", "type": "paragraph", "paragraph": {
        "rich_text": [{"type": "text", "text": {"content": text}}]
    }}

def heading(text):
    return {"object": "block", "type": "heading_2", "heading_2": {
        "rich_text": [{"type": "text", "text": {"content": text}}]
    }}

def divider():
    return {"object": "block", "type": "divider", "divider": {}}

# Title = chosen hook if available
page_title = f"Transcript: {title[:200]}"
if full_output:
    m = re.search(r"CHOSEN HOOK:\s*(.+)", full_output)
    if m: page_title = m.group(1).strip()[:200]

children = [para(f"Source: {url}"), divider(), heading("Original Transcript")]
for c in chunk(transcript): children.append(para(c))

if full_output:
    children += [divider(), heading("Why This Worked")]
    why = re.search(r"WHY THIS WORKED:(.*?)(?=HOOK 1:|$)", full_output, re.DOTALL)
    if why:
        for c in chunk(why.group(1).strip()): children.append(para(c))

    children += [divider(), heading("Imitation Script")]
    script = re.search(r"(HOOK 1:.*)", full_output, re.DOTALL)
    body = script.group(1).strip() if script else full_output
    for c in chunk(body): children.append(para(c))

children = children[:100]

payload = {
    "parent": {"database_id": notion_db},
    "properties": {
        "Task name": {"title": [{"text": {"content": page_title}}]},
    
python
    "Status": {"status": {"name": "Not started"}},
        "Priority": {"select": {"name": "High"}},
        "Task type": {"multi_select": [{"name": task_type}]},
    },
    "children": children,
}

req = urllib.request.Request(
    notion_api, data=json.dumps(payload).encode("utf-8"),
    headers={
        "Authorization": f"Bearer {notion_token}",
        "Content-Type": "application/json",
        "Notion-Version": notion_version,
    },
    method="POST",
)
try:
    resp = urllib.request.urlopen(req)
    print(json.loads(resp.read()).get("url", ""))
except Exception as e:
    print(f"NOTION_ERROR: {e}", file=sys.stderr)

Part 6: Create the slash command

Create ~/.claude/commands/transcribe.md:

markdown
---
name: transcribe
description: Transcribe a video URL and post analysis + imitation script to Notion
user-invocable: true
---

Transcribe a video. The user provided: $ARGUMENTS

Parse $ARGUMENTS:
- Find the URL (the http(s)://... token)
- Everything else is EXTRA_CONTEXT — the angle they want the imitation built around. Trim filler like "transcribe this:".

Run the script. With context:

\``bash
bash ~/.claude/tools/transcribe-video.sh "<URL>" --context "<EXTRA_CONTEXT>"
\`

Without context:

\`bash
bash ~/.claude/tools/transcribe-video.sh "<URL>"
\``

After the command completes, the transcript and analysis are printed to stdout. Tell the user "Transcribed and saved to Notion." If you passed extra context, add: "Anchored the imitation to: [short paraphrase]."

If $ARGUMENTS is empty, ask for a URL.

(Strip the backslashes before the triple backticks — they're escaping for this guide only.)

Part 7: Set Notion credentials

Add to your ~/.zshrc (or ~/.bashrc):

bash
export NOTION_TOKEN="ntn_your_token_here"
export NOTION_DB="your_database_id_here"

Then source ~/.zshrc (or restart your terminal).

Part 8: Test it

Restart Claude Code so it picks up the new slash command. Then:

code
/transcribe https://www.instagram.com/reel/<some-id>/

With angle:

code
/transcribe https://www.instagram.com/reel/<some-id>/ make this about how juniors should learn to code with AI

You'll see progress in the terminal:

code
Title: <video title>
Downloading audio...
Got 1247 chars
Building analysis prompt...
Generating analysis...
Notion: https://www.notion.so/<page-id>

A new page lands in your Notion content board with the four sections.

Optional: Customise the voice

Drop a markdown file at ~/.claude/brand/voice.md describing how you write — phrases you use, ones you don't, tone, sentence length, audience. The prompt builder picks it up automatically and the imitation script will sound like you.

Example:

markdown
# My Voice

- Australian, terse, no corporate filler
- I write for solo builders and creators, not enterprise
- Short sentences. No "in today's world" openings.
- I never say "leverage", "synergy", "solutions", "dive deep"
- Hooks land hard or get cut

If the file doesn't exist, the script falls back to a generic creator voice.

One-shot setup prompt

Paste this into Claude Code and it'll build the whole thing. Have your Notion token and database ID ready — Claude will ask for them.

code
Set up the viral video deconstructor on this Mac. End state: I run /transcribe <url> in Claude Code and a full breakdown lands in Notion.

What I need:
1. Install yt-dlp, ffmpeg via Homebrew, and mlx-whisper via pip3 (use --break-system-packages)
2. Ask me for my Notion integration token (ntn_...) and database ID, then add NOTION_TOKEN and NOTION_DB exports to my ~/.zshrc
3. Create ~/.claude/tools/transcribe-video.sh — a bash orchestrator that:
   - Takes a URL, optional --context "...", optional --no-notion
   - Detects platform (Youtube, Reel/Short)
   - yt-dlp downloads audio as wav, ffmpeg converts to 16kHz mono PCM
   - mlx-whisper transcribes locally with mlx-community/whisper-base-mlx
   - Calls a prompt builder, then runs claude --print --model claude-sonnet-4-6 to generate analysis
   - POSTs to Notion (single page with Source / Transcript / Why This Worked / Imitation Script sections)
   - Prints transcript + full analysis to stdout
4. Create ~/.claude/tools/transcribe-build-prompt.py — builds the Sonnet prompt; reads ~/.claude/brand/voice.md if it exists; injects extra context if passed; outputs in this exact format: WHY THIS WORKED bullets, HOOK 1/2/3, CHOSEN HOOK, SPOKEN SCRIPT, FILMING NOTES
5. Create ~/.claude/tools/transcribe-notion-post.py — POSTs one page to Notion. Title = chosen hook. Properties: Task name (title), Status="Not started", Priority="High", Task type=Reel/Short or Youtube. Body sections in order: Source URL, Original Transcript, Why This Worked, Imitation Script. Chunk text into 2000-char blocks, cap children at 100.
6. Create ~/.claude/commands/transcribe.md — slash command with frontmatter (name: transcribe, user-invocable: true). Parses $ARGUMENTS into URL + extra context, runs the bash script, reports back.
7. chmod +x the bash script
8. Test by running /transcribe https://www.youtube.com/watch?v=jNQXAC9IVRw

My Notion database has these properties already: Task name (title), Status (status with "Not started" option), Priority (s
code
elect with "High" option), Task type (multi-select with "Reel/Short" and "Youtube" options). If mine doesn't, tell me what to add.

Use the architecture from this guide: https://[wherever you publish this]

That's the full system. From this point you can:

  • Drop any reel link and have the script written for you in 60 seconds
  • Build a swipe file of viral structures by category in Notion
  • Add your brand voice file to make the imitation sound like you
  • Wire it into a Telegram bot if you want to send links from your phone (separate guide)

Learn this inside the community

The full course, templates, and the people building this, free in the Skool community.

Join the community