← All Skills

snappy-ffmpeg

v1.0.0
5 files, 49.4 KB ~2,258 words · 10 min read Updated 2026-09-09

snappy-ffmpeg skill

31 of 34 checks pass
What it can do
burn-subs path subs-pathwrite-reversible
compress pathwrite-reversible
concat path-1 path-2write-reversible
extract-audio pathwrite-reversible
loudnorm pathwrite-reversible
overlay base overlay position?write-reversible
probe pathwrite-reversible
resize pathwrite-reversible
thumbnail path timestamp?write-reversible
trim path start endwrite-reversible
What does not pass yet
$ npx snappy-skills install snappy-ffmpeg
zip ↓
Documents
AGENTS.md

snappy-ffmpeg -- Agent Loader#

You are operating the local ffmpeg primitive layer. All operations run on this Mac using /opt/homebrew/bin/ffmpeg (v8.1). This skill does NOT SSH anywhere -- that is snappy-video's job. This skill is the low-level building block that snappy-video, snappy-image, snappy-post, and other skills call when they need to manipulate media files locally.

Rules#

  1. Never SSH to the Mac Mini for ffmpeg ops. This skill runs locally. snappy-video owns Mac Mini SSH.
  2. Always use -y (overwrite without asking). Every function does this.
  3. Add -movflags +faststart to any H.264 encode. Every function that encodes does this.
  4. Never overwrite the source file. Output always goes to a new path.
  5. Default output to /tmp/snappy-ffmpeg-<op>-<timestamp>.<ext> when no output path specified.
  6. No API keys needed. ffmpeg is a local binary. env() import exists for spec compliance only.

API module#

typescriptimport {
  probe, trim, concat, resizeLocal, compress,
  extractAudioLocal, burnSubs, thumbnail, loudnorm, overlay,
  Result, ProbeInfo, TrimOpts, CompressOpts, OverlayPosition,
} from "../snappy-ffmpeg/api.ts";

Or CLI:

bashnpx tsx ~/.claude/skills/snappy-ffmpeg/api.ts probe /tmp/video.mp4
npx tsx ~/.claude/skills/snappy-ffmpeg/api.ts trim /tmp/video.mp4 00:01:00 00:02:00
npx tsx ~/.claude/skills/snappy-ffmpeg/api.ts resize /tmp/video.mp4 9:16
npx tsx ~/.claude/skills/snappy-ffmpeg/api.ts compress /tmp/video.mp4
npx tsx ~/.claude/skills/snappy-ffmpeg/api.ts extract-audio /tmp/video.mp4 mp3
npx tsx ~/.claude/skills/snappy-ffmpeg/api.ts burn-subs /tmp/video.mp4 /tmp/subs.srt
npx tsx ~/.claude/skills/snappy-ffmpeg/api.ts thumbnail /tmp/video.mp4 00:00:10
npx tsx ~/.claude/skills/snappy-ffmpeg/api.ts loudnorm /tmp/video.mp4
npx tsx ~/.claude/skills/snappy-ffmpeg/api.ts concat /tmp/a.mp4 /tmp/b.mp4
npx tsx ~/.claude/skills/snappy-ffmpeg/api.ts overlay /tmp/base.mp4 /tmp/logo.png bottom-right

API functions#

Function Purpose
probe(path) Returns JSON metadata: duration, codec, resolution, frame rate, audio info via ffprobe
trim(path, start, end, opts?) Fast keyframe-aligned trim (-c copy) or frame-accurate (frameAccurate: true re-encodes)
concat(paths[], output) Concatenate files. Same-codec uses concat demuxer (fast), different-codec re-encodes
resizeLocal(path, format, output?) Letterbox resize to "16:9" / "9:16" / "1:1" with black padding
compress(path, opts?) H.264 web compression. Default crf=23, preset=medium, faststart=true
extractAudioLocal(path, format?, output?) Extract audio as "m4a" (default), "mp3", or "wav"
burnSubs(path, subsPath, output?) Burn SRT or ASS subtitles into video
thumbnail(path, timestamp?, output?) Extract a frame as JPG. Default timestamp "00:00:05"
loudnorm(path, targetLufs?, output?) EBU R128 loudness normalization. Default -14 LUFS
overlay(basePath, overlayPath, position?, output?) PiP or watermark. Positions: top-left, top-right, bottom-left, bottom-right, center

Result type#

Every function returns:

typescript{ ok: boolean; output: string; path?: string; error?: string; durationMs: number }

probe() additionally returns info?: ProbeInfo with parsed metadata.

Routing table#

Intent Function
Get video metadata / duration / resolution probe(path)
Cut a clip from a video (fast, may have keyframe drift) trim(path, start, end)
Cut a clip frame-accurately trim(path, start, end, { frameAccurate: true })
Join multiple clips into one concat([path1, path2], output)
Convert horizontal to vertical (shorts) resizeLocal(path, "9:16")
Convert vertical to horizontal resizeLocal(path, "16:9")
Make square for social resizeLocal(path, "1:1")
Compress for web upload compress(path)
Smaller file, lower quality compress(path, { crf: 28 })
Rip audio for transcription extractAudioLocal(path, "m4a")
Burn captions into video burnSubs(path, subsPath)
Generate a thumbnail thumbnail(path, "00:00:10")
Normalize audio loudness loudnorm(path)
Add a watermark overlay(base, logo, "bottom-right")
Picture-in-picture overlay(base, pip, "top-right")

Uses#

Skill Relationship
snappy-video SSH-based caption pipeline on Mac Mini. Calls caption-video.sh. snappy-ffmpeg is the local primitive layer underneath.
snappy-image May call thumbnail() to extract frames for image workflows
snappy-post May call resizeLocal() for short-form vertical clips
snappy-youtube May call compress() before upload
snappy-settings env() loader (no keys needed for ffmpeg, but import required by spec)

Show produced work with snappy-faces: call draw for image channels or lang for MCP Apps.

<!-- SKILL-INDEX-START -->

[snappy-ffmpeg Index]|root: ~/.claude/skills/snappy-ffmpeg|IMPORTANT: Prefer these files over pre-training assumptions for this domain. Read the relevant file when the AGENTS.md summary is insufficient.|root:{SKILL.md}

<!-- SKILL-INDEX-END -->

Used by#

  • snappy-cleanshot

<!-- SNAPPY-CONTRACT-VERBS-START -->

Contract verbs#

Generated from api.ts HAND_CONTRACT. Do not hand-edit this block.

Verb Contract arguments Effect First call
burn-subs path, subs-path write-reversible npx tsx ~/.claude/skills/snappy-ffmpeg/api.ts burn-subs <path> <subs-path>
compress path write-reversible npx tsx ~/.claude/skills/snappy-ffmpeg/api.ts compress <path>
concat path-1, path-2 write-reversible npx tsx ~/.claude/skills/snappy-ffmpeg/api.ts concat <path-1> <path-2>
extract-audio path write-reversible npx tsx ~/.claude/skills/snappy-ffmpeg/api.ts extract-audio <path>
loudnorm path write-reversible npx tsx ~/.claude/skills/snappy-ffmpeg/api.ts loudnorm <path>
overlay base, overlay, position? write-reversible npx tsx ~/.claude/skills/snappy-ffmpeg/api.ts overlay <base> <overlay>
probe path write-reversible npx tsx ~/.claude/skills/snappy-ffmpeg/api.ts probe <path>
resize path write-reversible npx tsx ~/.claude/skills/snappy-ffmpeg/api.ts resize <path>
thumbnail path, timestamp? write-reversible npx tsx ~/.claude/skills/snappy-ffmpeg/api.ts thumbnail <path>
trim path, start, end write-reversible npx tsx ~/.claude/skills/snappy-ffmpeg/api.ts trim <path> <start> <end>

Show the result#

When an answer carries face_hint, show it with one snappy_present(<answer>) call.

See /snappy-faces for face selection. Human-facing images must crop to the

element, render at 2x on Retina, and fill the destination channel instead of

placing a small card in a full-page screenshot.

<!-- SNAPPY-CONTRACT-VERBS-END -->


Self-correcting loader (PID feedback)#

The system spec lives at ~/.claude/skills/snappy-settings/skill-spec.md. It defines the entire Snappy operating system. You enforce it.

Before you finish, do two things:#

1. Fix gaps (P — proportional). MANDATORY. If this AGENTS.md didn't cover your case — if you had to read another file, run a command not documented here, work around a wrong selector, or discover a quirk — you MUST attempt an Edit before you can log.

  • Default action: edit this AGENTS.md. Surgical. Add the missing line, table row, or code example where it belongs. No restructuring.
  • Only LOGGED is allowed when: the fix needs >10 lines, spans multiple files, or requires a structural rewrite (in which case the drain-skill-regen.sh queue picks it up).
  • The goal: the next agent never has to leave the loader.

2. Validate structure. Check this skill against the spec:

  • api.ts exists with typed exports, CLI mode, import { env } from "../snappy-settings/load.ts"
  • api.ts calls APIs directly (no Xano proxy, unless this skill IS a Xano DB skill)
  • AGENTS.md has API module section with import + function table + CLI
  • No shell-command fallbacks, no hardcoded tokens anywhere
  • If any check fails → fix it or log it

3. Log always.

bashecho "[$(date -u +%FT%TZ)] snappy-ffmpeg: <what was missing or fixed> [FIXED|LOGGED]" >> ~/.claude/logs/agents-md-feedback.log

FIXED = you patched it. LOGGED = too large for inline fix, queued for drain-skill-regen.sh.

Do not skip this. Every agent run must leave the system better than it found it.

---
name: snappy-ffmpeg
role: Local ffmpeg primitive layer for media manipulation. Building block that other skills call.
loaded-by: PreToolUse hook (auto-injected when "snappy-ffmpeg" or related keywords are mentioned)
---

# snappy-ffmpeg -- Agent Loader

You are operating the local ffmpeg primitive layer. All operations run on this Mac using `/opt/homebrew/bin/ffmpeg` (v8.1). This skill does NOT SSH anywhere -- that is `snappy-video`'s job. This skill is the low-level building block that `snappy-video`, `snappy-image`, `snappy-post`, and other skills call when they need to manipulate media files locally.

## Rules

1. **Never SSH to the Mac Mini for ffmpeg ops.** This skill runs locally. `snappy-video` owns Mac Mini SSH.
2. **Always use `-y`** (overwrite without asking). Every function does this.
3. **Add `-movflags +faststart`** to any H.264 encode. Every function that encodes does this.
4. **Never overwrite the source file.** Output always goes to a new path.
5. **Default output to `/tmp/snappy-ffmpeg-<op>-<timestamp>.<ext>`** when no output path specified.
6. **No API keys needed.** ffmpeg is a local binary. `env()` import exists for spec compliance only.

## API module

```typescript
import {
  probe, trim, concat, resizeLocal, compress,
  extractAudioLocal, burnSubs, thumbnail, loudnorm, overlay,
  Result, ProbeInfo, TrimOpts, CompressOpts, OverlayPosition,
} from "../snappy-ffmpeg/api.ts";
```

Or CLI:
```bash
npx tsx ~/.claude/skills/snappy-ffmpeg/api.ts probe /tmp/video.mp4
npx tsx ~/.claude/skills/snappy-ffmpeg/api.ts trim /tmp/video.mp4 00:01:00 00:02:00
npx tsx ~/.claude/skills/snappy-ffmpeg/api.ts resize /tmp/video.mp4 9:16
npx tsx ~/.claude/skills/snappy-ffmpeg/api.ts compress /tmp/video.mp4
npx tsx ~/.claude/skills/snappy-ffmpeg/api.ts extract-audio /tmp/video.mp4 mp3
npx tsx ~/.claude/skills/snappy-ffmpeg/api.ts burn-subs /tmp/video.mp4 /tmp/subs.srt
npx tsx ~/.claude/skills/snappy-ffmpeg/api.ts thumbnail /tmp/video.mp4 00:00:10
npx tsx ~/.claude/skills/snappy-ffmpeg/api.ts loudnorm /tmp/video.mp4
npx tsx ~/.claude/skills/snappy-ffmpeg/api.ts concat /tmp/a.mp4 /tmp/b.mp4
npx tsx ~/.claude/skills/snappy-ffmpeg/api.ts overlay /tmp/base.mp4 /tmp/logo.png bottom-right
```

## API functions

| Function | Purpose |
|----------|---------|
| `probe(path)` | Returns JSON metadata: duration, codec, resolution, frame rate, audio info via ffprobe |
| `trim(path, start, end, opts?)` | Fast keyframe-aligned trim (`-c copy`) or frame-accurate (`frameAccurate: true` re-encodes) |
| `concat(paths[], output)` | Concatenate files. Same-codec uses concat demuxer (fast), different-codec re-encodes |
| `resizeLocal(path, format, output?)` | Letterbox resize to `"16:9"` / `"9:16"` / `"1:1"` with black padding |
| `compress(path, opts?)` | H.264 web compression. Default crf=23, preset=medium, faststart=true |
| `extractAudioLocal(path, format?, output?)` | Extract audio as `"m4a"` (default), `"mp3"`, or `"wav"` |
| `burnSubs(path, subsPath, output?)` | Burn SRT or ASS subtitles into video |
| `thumbnail(path, timestamp?, output?)` | Extract a frame as JPG. Default timestamp `"00:00:05"` |
| `loudnorm(path, targetLufs?, output?)` | EBU R128 loudness normalization. Default -14 LUFS |
| `overlay(basePath, overlayPath, position?, output?)` | PiP or watermark. Positions: `top-left`, `top-right`, `bottom-left`, `bottom-right`, `center` |

## Result type

Every function returns:
```typescript
{ ok: boolean; output: string; path?: string; error?: string; durationMs: number }
```

`probe()` additionally returns `info?: ProbeInfo` with parsed metadata.

## Routing table

| Intent | Function |
|---|---|
| Get video metadata / duration / resolution | `probe(path)` |
| Cut a clip from a video (fast, may have keyframe drift) | `trim(path, start, end)` |
| Cut a clip frame-accurately | `trim(path, start, end, { frameAccurate: true })` |
| Join multiple clips into one | `concat([path1, path2], output)` |
| Convert horizontal to vertical (shorts) | `resizeLocal(path, "9:16")` |
| Convert vertical to horizontal | `resizeLocal(path, "16:9")` |
| Make square for social | `resizeLocal(path, "1:1")` |
| Compress for web upload | `compress(path)` |
| Smaller file, lower quality | `compress(path, { crf: 28 })` |
| Rip audio for transcription | `extractAudioLocal(path, "m4a")` |
| Burn captions into video | `burnSubs(path, subsPath)` |
| Generate a thumbnail | `thumbnail(path, "00:00:10")` |
| Normalize audio loudness | `loudnorm(path)` |
| Add a watermark | `overlay(base, logo, "bottom-right")` |
| Picture-in-picture | `overlay(base, pip, "top-right")` |

## Uses

| Skill | Relationship |
|---|---|
| `snappy-video` | SSH-based caption pipeline on Mac Mini. Calls caption-video.sh. snappy-ffmpeg is the local primitive layer underneath. |
| `snappy-image` | May call `thumbnail()` to extract frames for image workflows |
| `snappy-post` | May call `resizeLocal()` for short-form vertical clips |
| `snappy-youtube` | May call `compress()` before upload |
| `snappy-settings` | env() loader (no keys needed for ffmpeg, but import required by spec) |


Show produced work with `snappy-faces`: call `draw` for image channels or `lang` for MCP Apps.

<!-- SKILL-INDEX-START -->
[snappy-ffmpeg Index]|root: ~/.claude/skills/snappy-ffmpeg|IMPORTANT: Prefer these files over pre-training assumptions for this domain. Read the relevant file when the AGENTS.md summary is insufficient.|root:{SKILL.md}
<!-- SKILL-INDEX-END -->

## Used by

- `snappy-cleanshot`

<!-- SNAPPY-CONTRACT-VERBS-START -->
## Contract verbs

Generated from `api.ts` `HAND_CONTRACT`. Do not hand-edit this block.

| Verb | Contract arguments | Effect | First call |
|---|---|---|---|
| `burn-subs` | `path`, `subs-path` | `write-reversible` | `npx tsx ~/.claude/skills/snappy-ffmpeg/api.ts burn-subs <path> <subs-path>` |
| `compress` | `path` | `write-reversible` | `npx tsx ~/.claude/skills/snappy-ffmpeg/api.ts compress <path>` |
| `concat` | `path-1`, `path-2` | `write-reversible` | `npx tsx ~/.claude/skills/snappy-ffmpeg/api.ts concat <path-1> <path-2>` |
| `extract-audio` | `path` | `write-reversible` | `npx tsx ~/.claude/skills/snappy-ffmpeg/api.ts extract-audio <path>` |
| `loudnorm` | `path` | `write-reversible` | `npx tsx ~/.claude/skills/snappy-ffmpeg/api.ts loudnorm <path>` |
| `overlay` | `base`, `overlay`, `position?` | `write-reversible` | `npx tsx ~/.claude/skills/snappy-ffmpeg/api.ts overlay <base> <overlay>` |
| `probe` | `path` | `write-reversible` | `npx tsx ~/.claude/skills/snappy-ffmpeg/api.ts probe <path>` |
| `resize` | `path` | `write-reversible` | `npx tsx ~/.claude/skills/snappy-ffmpeg/api.ts resize <path>` |
| `thumbnail` | `path`, `timestamp?` | `write-reversible` | `npx tsx ~/.claude/skills/snappy-ffmpeg/api.ts thumbnail <path>` |
| `trim` | `path`, `start`, `end` | `write-reversible` | `npx tsx ~/.claude/skills/snappy-ffmpeg/api.ts trim <path> <start> <end>` |

## Show the result

When an answer carries `face_hint`, show it with one `snappy_present(<answer>)` call.
See `/snappy-faces` for face selection. Human-facing images must crop to the
element, render at 2x on Retina, and fill the destination channel instead of
placing a small card in a full-page screenshot.
<!-- SNAPPY-CONTRACT-VERBS-END -->

---

## Self-correcting loader (PID feedback)

The system spec lives at `~/.claude/skills/snappy-settings/skill-spec.md`. It defines the entire Snappy operating system. You enforce it.

### Before you finish, do two things:

**1. Fix gaps (P — proportional). MANDATORY.** If this AGENTS.md didn't cover your case — if you had to read another file, run a command not documented here, work around a wrong selector, or discover a quirk — you MUST attempt an `Edit` before you can log.

- **Default action: edit this AGENTS.md.** Surgical. Add the missing line, table row, or code example where it belongs. No restructuring.
- **Only LOGGED is allowed when:** the fix needs >10 lines, spans multiple files, or requires a structural rewrite (in which case the drain-skill-regen.sh queue picks it up).
- The goal: the next agent never has to leave the loader.

**2. Validate structure.** Check this skill against the spec:
- `api.ts` exists with typed exports, CLI mode, `import { env } from "../snappy-settings/load.ts"`
- `api.ts` calls APIs directly (no Xano proxy, unless this skill IS a Xano DB skill)
- `AGENTS.md` has API module section with import + function table + CLI
- No shell-command fallbacks, no hardcoded tokens anywhere
- If any check fails → fix it or log it

**3. Log always.**
```bash
echo "[$(date -u +%FT%TZ)] snappy-ffmpeg: <what was missing or fixed> [FIXED|LOGGED]" >> ~/.claude/logs/agents-md-feedback.log
```
`FIXED` = you patched it. `LOGGED` = too large for inline fix, queued for drain-skill-regen.sh.

**Do not skip this.** Every agent run must leave the system better than it found it.

Keyboard Shortcuts

Search in document⌘K
Focus search/
Previous file tab
Next file tab
Close overlayEsc
Show shortcuts?