$npx -y skills add tuan3w/obsidian-vault-agent --skill lectureExtract transcript and key slides from a local video file, then create a vault-formatted lecture note. Use this skill whenever a user provides a local video file (.mp4, .mov, .mkv, .avi, .webm) and wants notes from it. Triggers on "lecture", "take notes", "I have a recording of",
| 1 | <Purpose> |
| 2 | Transcribe a local video lecture using mlx-whisper (Apple Silicon), extract key |
| 3 | slide frames with timestamps, then synthesize into a vault-formatted lecture note |
| 4 | with embedded screenshots. Auto-detects transcript language — works with any language, |
| 5 | outputs English notes. |
| 6 | </Purpose> |
| 7 | |
| 8 | <Use_When> |
| 9 | - User provides a local video file and wants lecture notes |
| 10 | - User says "take notes from this lecture/video" |
| 11 | - User uses /lecture with a file path |
| 12 | - User has an MP4/MOV/MKV file to process |
| 13 | </Use_When> |
| 14 | |
| 15 | <Do_Not_Use_When> |
| 16 | - User has a YouTube URL (use /youtube instead) |
| 17 | - User wants to process an existing vault note (use /process) |
| 18 | - User wants audio-only transcription without note synthesis |
| 19 | </Do_Not_Use_When> |
| 20 | |
| 21 | <Prerequisites> |
| 22 | - `ffmpeg` installed (for audio extraction and frame capture) |
| 23 | - `uv` installed (`brew install uv` or `curl -LsSf https://astral.sh/uv/install.sh | sh`) |
| 24 | - Apple Silicon Mac (mlx-whisper is optimized for M-series chips) |
| 25 | </Prerequisites> |
| 26 | |
| 27 | <Execution_Policy> |
| 28 | - Extract first, synthesize second, integrate third |
| 29 | - Always check vault for existing notes on the same topic before creating |
| 30 | - Create note as type: lecture with processing_status: inbox |
| 31 | - The note is a starting point — user can /process it later for deeper engagement |
| 32 | - Transcription can take several minutes for long videos — inform the user |
| 33 | </Execution_Policy> |
| 34 | |
| 35 | <Steps> |
| 36 | |
| 37 | ## Stage 1: EXTRACT |
| 38 | |
| 39 | Parse the video file path from $ARGUMENTS. If no path provided, ask the user. |
| 40 | Verify the file exists and is a video format (mp4, mov, mkv, avi, webm). |
| 41 | |
| 42 | Run the extraction script: |
| 43 | |
| 44 | ```bash |
| 45 | SKILL_DIR="${CLAUDE_SKILL_DIR}" |
| 46 | LECTURE_OUTPUT="temp/lecture-extract-output.json" |
| 47 | uv run "$SKILL_DIR/scripts/extract_lecture.py" "VIDEO_PATH" > "$LECTURE_OUTPUT" 2>&1 & |
| 48 | ``` |
| 49 | |
| 50 | **IMPORTANT:** This script takes time (several minutes for a 30-60 min video). |
| 51 | Inform the user: "Extracting audio and transcribing — this will take a few minutes for a [duration] video." |
| 52 | |
| 53 | Run it and wait for completion. Then read the output JSON. |
| 54 | |
| 55 | The JSON contains: |
| 56 | - `filename`, `duration`, `duration_seconds`, `width`, `height` |
| 57 | - `transcript.full_text`, `transcript.segments` (with start/end times), `transcript.language` |
| 58 | - `transcript.error` (null if success) |
| 59 | - `frames[]` — array of `{path, timestamp_seconds, timestamp}` for each extracted slide |
| 60 | - `output_dir` — temp directory with extracted frames |
| 61 | |
| 62 | **If transcript.error is not null:** inform the user and stop. Check if mlx-whisper is installed. |
| 63 | |
| 64 | **If transcript is very long (>80,000 chars):** warn the user. Send first 60,000 chars to the agent |
| 65 | with a note about total length. |
| 66 | |
| 67 | ## Stage 2: PREPARE FRAMES |
| 68 | |
| 69 | Copy the extracted frames to the vault's assets directory with a descriptive naming scheme: |
| 70 | |
| 71 | ```bash |
| 72 | # Generate a slug from the video filename |
| 73 | SLUG=$(echo "VIDEO_FILENAME" | sed 's/\.[^.]*$//' | tr '[:upper:]' '[:lower:]' | tr ' ' '-' | sed 's/[^a-z0-9-]//g' | cut -c1-30) |
| 74 | ASSETS_DIR="assets" |
| 75 | |
| 76 | for frame in FRAME_PATHS; do |
| 77 | FRAME_NUM=$(basename "$frame" | grep -o '[0-9]*') |
| 78 | cp "$frame" "$ASSETS_DIR/lecture-${SLUG}-${FRAME_NUM}.jpg" |
| 79 | done |
| 80 | ``` |
| 81 | |
| 82 | Build a frame manifest for the noter agent — each frame gets: |
| 83 | - Its vault filename (for `![[embedding]]`) |
| 84 | - Its timestamp in the video (e.g., "12:30") |
| 85 | |
| 86 | Example manifest: |
| 87 | ``` |
| 88 | FRAMES WITH TIMESTAMPS: |
| 89 | - lecture-risk-mgmt-01.jpg (timestamp: 0:10) |
| 90 | - lecture-risk-mgmt-02.jpg (timestamp: 2:00) |
| 91 | - lecture-risk-mgmt-03.jpg (timestamp: 4:00) |
| 92 | ... |
| 93 | ``` |
| 94 | |
| 95 | ## Stage 3: SYNTHESIZE |
| 96 | |
| 97 | Read the agent definition: |
| 98 | ``` |
| 99 | Read("${CLAUDE_SKILL_DIR}/agents/lecture-noter.md") |
| 100 | ``` |
| 101 | |
| 102 | Search the vault for existing notes related to the lecture's topics using the MCP tool: |
| 103 | ``` |
| 104 | search_notes(query="KEYWORD", limit=20) |
| 105 | ``` |
| 106 | Or fall back to Grep if MCP is unavailable: |
| 107 | ``` |
| 108 | Grep(pattern="KEYWORD", path="notes/", glob="*.md", head_limit=20) |
| 109 | ``` |
| 110 | |
| 111 | **Review each frame** using the Read tool to see what's on each slide. |
| 112 | Build a brief description of each frame's content (1 line each) to include in the agent prompt. |
| 113 | |
| 114 | Launch the lecture-noter agent: |
| 115 | |
| 116 | ``` |
| 117 | Agent( |
| 118 | subagent_type="general-purpose", |
| 119 | model="sonnet", |
| 120 | run_in_background=false, |
| 121 | prompt="You are Lecture Noter. Follow these instructions exactly: |
| 122 | |
| 123 | [INSERT FULL CONTENT OF agents/lecture-noter.md HERE] |
| 124 | |
| 125 | VIDEO METADATA: |
| 126 | - Filename: [filename] |
| 127 | - Duration: [duration] |
| 128 | - Transcript language: [language from extraction JSON] |
| 129 | |
| 130 | FRAMES WITH TIMESTAMPS AND DESCRIPTIONS: |
| 131 | - lecture-slug-01.jpg (timestamp: 0:10) — Title slide showing cou |