image-to-video · git:20260919.28d30f9 · 2026-09-19 · sha256 2857df3fd9cb7bcc

image-to-video git:20260919.28d30f9A

Immutable. This exact content is served forever at /api/v1/blob/2857df3fd9cb7bcc.

---
name: image-to-video
promoted_to: video-editing
description: "FFmpeg-based video creation from image and audio."
user-invocable: false
allowed-tools:
  - Read
  - Write
  - Bash
  - Grep
  - Glob
  - Edit
routing:
  triggers:
    - image to video
    - audio visualization
    - static video
    - mp4 from image
    - music video
    - podcast video
    - video from image
    - combine image audio
    - album art video
    - cover art video
  pairs_with:
    - workflow
  complexity: simple
  category: video-creation
---

# Image to Video Skill

Combine a static image with an audio file to produce an MP4 via FFmpeg. Supports resolution presets, audio visualization overlays, and batch processing. For image generation, use `image-gen`.

## Deep References

| Signal | Load | Why |
|---|---|---|
| FFmpeg filter graphs for visualization modes | `references/ffmpeg-filters.md` | Scale/pad, showwaves, showspectrum, overlay filters |

## Phase 1: VALIDATE

1. Check FFmpeg: `ffmpeg -version`. If missing, stop with install instructions.
2. Verify both input files exist with absolute paths and non-zero size. Supported: PNG/JPG/JPEG/GIF/WEBP/BMP (image), MP3/WAV/M4A/OGG/FLAC (audio).
3. Determine parameters from the user's request -- do not default to static when the user requested a visualization.

| Preset | Dimensions | Platform |
|--------|------------|----------|
| `1080p` | 1920x1080 | YouTube HD (default) |
| `720p` | 1280x720 | Standard HD |
| `square` | 1080x1080 | Instagram, social |
| `vertical` | 1080x1920 | Stories, Reels, TikTok |

Visualization modes (off unless requested): `waveform`, `spectrum`, `cqt`, `bars`.

**Gate**: FFmpeg installed, both files exist, parameters resolved.

## Phase 2: PREPARE

Use the user's output path or derive from audio filename (`/same/dir/filename.mp4`). Verify directory is writable.

## Phase 3: ENCODE

Defaults: libx264 preset medium, CRF 23, yuv420p, 192k AAC.

```bash
python3 $HOME/vexjoy-agent/skills/content/image-to-video/scripts/image_to_video.py \
  --image /path/to/image.png --audio /path/to/audio.mp3 \
  --output /path/to/output.mp4 --resolution 1080p --visualization static
```

Batch mode (matched pairs in `workspace/input/`):

```bash
python3 $HOME/vexjoy-agent/skills/content/image-to-video/scripts/image_to_video.py \
  --process-workspace --visualization waveform
```

**Gate**: Script exits 0.

## Phase 4: VERIFY

FFmpeg can exit 0 but produce a corrupt file. Always probe:

```bash
ffprobe -v error -show_entries format=duration,size -show_entries stream=codec_name,width,height \
  -of default=noprint_wrappers=1 /path/to/output.mp4
```

Confirm video duration matches audio (within 1s). Report: file path, size, duration, resolution, visualization mode.

## Error Handling

| Error | Cause | Solution |
|-------|-------|----------|
| FFmpeg not found | Not installed | `apt install ffmpeg` or `brew install ffmpeg` |
| Image/audio not found | Wrong or relative path | Use absolute paths; check with `ls -la` |
| FFmpeg filter errors | Minimal build lacks showwaves/showcqt | Install full FFmpeg; fall back to `--visualization static` |
| Cannot determine audio duration | Corrupted audio file | Test with `ffprobe`; convert: `ffmpeg -i input -acodec pcm_s16le output.wav` |