Skip to content

Commit 1a9931e

Browse files
committed
feat(mp4): add MP4 video output for watch, git, and replay animations
All three animation commands now support writing .mp4/.mov output via ffmpeg (concat demuxer, variable frame durations). New --crf and --codec options control H.264/H.265 quality. Odd-dimension images are padded to even width/height automatically.
1 parent b5607c0 commit 1a9931e

8 files changed

Lines changed: 347 additions & 49 deletions

File tree

CHANGELOG.md

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
## [0.4.0] - 2026-03-28
11+
1012
### Added
1113

14+
- **MP4 video output** — all three animation commands (`watch --animate`, `git --animate`,
15+
`replay`) now write MP4 video when the output path ends in `.mp4` or `.mov`. Quality is
16+
controlled via `--crf` (Constant Rate Factor: 0 = lossless, 51 = worst, default 23) and
17+
`--codec` (`libx264` H.264 or `libx265` H.265). MP4 files are typically 10–100× smaller
18+
than equivalent APNGs. Requires `ffmpeg` on PATH.
19+
```bash
20+
dirplot git . -o history.mp4 --animate
21+
dirplot git . -o history.mp4 --animate --crf 18 --codec libx265
22+
dirplot replay events.jsonl -o replay.mp4 --total-duration 30
23+
dirplot watch . -o treemap.mp4 --animate
24+
```
1225
- **`@ref` suffix for `dirplot git`**: local repository paths now accept an optional
1326
`@ref` suffix to target a specific branch, tag, or commit SHA without needing
1427
`--range` (e.g. `dirplot git .@my-branch -o out.apng --animate`). `--range` takes
1528
precedence when both are provided.
16-
17-
## [0.4.0] - 2026-03-28
18-
19-
### Added
20-
2129
- **`dirplot git` subcommand** — replays a git repository's commit history as an
2230
animated treemap. Each commit becomes one frame; changed tiles receive the same
2331
colour-coded highlight borders as `watch --animate` (green = created, blue = modified,

README.md

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,10 @@
2626
- Exclude paths with `--exclude` (repeatable), or focus on specific subtrees with `--subtree` / `-s` (allowlist complement, supports nested paths like `src/dirplot/fonts`).
2727
- Pass multiple local paths (`dirplot map src tests`) to scan each independently and display them under their common parent, ignoring all other siblings. Individual files are also accepted as roots (`dirplot map main.py util.py`).
2828
- **Pipe `tree` or `find` output directly**: `tree src/ | dirplot map` and `find . -name "*.py" | dirplot map` are both supported. The format is auto-detected (`tree -s`, `tree -f`, and plain `find` output all work). Use `--paths-from FILE` to read from a file instead of stdin.
29-
- **Live watch mode** (`dirplot watch`) — monitors one or more directories and regenerates the treemap automatically. Rapid bursts of events (e.g. `git checkout`) are debounced into a single render after a configurable quiet period (`--debounce`, default 0.5 s). With `--animate`, each render is captured as a frame and the complete APNG is written on Ctrl-C exit; changed tiles receive colour-coded highlight borders (green = created, blue = modified, red = deleted, orange = moved). All events can be logged to a JSONL file with `--event-log`.
30-
- **Event log replay** (`dirplot replay`) — replays a JSONL filesystem event log (produced by `dirplot watch --event-log`) as an animated treemap APNG. Events are grouped into time buckets (one frame per bucket, default 60 s); only files referenced in the log appear in the treemap. Frame durations can be uniform or proportional to real elapsed time between buckets (`--total-duration`). Frames are rendered in parallel.
31-
- **Git history replay** (`dirplot git`) — renders a git repository's commit history as an animated treemap APNG. Each commit becomes one frame, with colour-coded change highlights matching `watch --animate`. Frame durations can be uniform (`--frame-duration`) or proportional to real elapsed time between commits (`--total-duration`). Frames are rendered in parallel across CPU cores for speed. Accepts a local path or a **GitHub URL** (`github://owner/repo[@branch]` or `https://github.com/owner/repo`) — dirplot clones the repo into a temporary directory and removes it when done. The total number of commits available is always reported so you can tune `--max-commits` before committing to a long render.
29+
- **Live watch mode** (`dirplot watch`) — monitors one or more directories and regenerates the treemap automatically. Rapid bursts of events (e.g. `git checkout`) are debounced into a single render after a configurable quiet period (`--debounce`, default 0.5 s). With `--animate`, each render is captured as a frame and the complete APNG or **MP4** is written on Ctrl-C exit; changed tiles receive colour-coded highlight borders. All events can be logged to a JSONL file with `--event-log`.
30+
- **Event log replay** (`dirplot replay`) — replays a JSONL filesystem event log (produced by `dirplot watch --event-log`) as an animated treemap APNG or **MP4**. Events are grouped into time buckets (one frame per bucket, default 60 s); only files referenced in the log appear in the treemap. Frame durations can be uniform or proportional to real elapsed time between buckets (`--total-duration`). Frames are rendered in parallel.
31+
- **Git history replay** (`dirplot git`) — renders a git repository's commit history as an animated treemap APNG or **MP4**. Each commit becomes one frame, with colour-coded change highlights. Frame durations can be uniform (`--frame-duration`) or proportional to real elapsed time between commits (`--total-duration`). Frames are rendered in parallel across CPU cores for speed. Accepts a local path (with optional `@ref` suffix), or a **GitHub URL**.
32+
- **MP4 output** (`--output file.mp4`) — all three animation commands (`watch --animate`, `git --animate`, `replay`) write MP4 video when the output path ends in `.mp4` or `.mov`. Quality is controlled via `--crf` (Constant Rate Factor: 0 = lossless, 51 = worst, default 23) and `--codec` (`libx264` H.264 or `libx265` H.265). MP4 files are typically 10–100× smaller than equivalent APNGs. Requires `ffmpeg` on PATH.
3233
- Works on macOS, Linux, and Windows; WSL2 fully supported.
3334
- Scan remote hosts over SSH (`pip install "dirplot[ssh]"`), AWS S3 buckets (`pip install "dirplot[s3]"`), any public/private GitHub repository (including specific branch, tag, commit SHA, or subdirectory), **running Docker containers**, or **Kubernetes pods** — all without extra dependencies beyond the respective CLI/SDK. See [EXAMPLES.md](docs/EXAMPLES.md).
3435
- Optional **file-count legend** (`--legend`) — a corner overlay listing the top extensions by number of files, with coloured swatches and counts, automatically sized to fit the image.
@@ -159,17 +160,32 @@ dirplot watch src --output treemap.png --event-log events.jsonl
159160
# Watch and build an animated APNG — one frame per debounced render, written on Ctrl-C
160161
dirplot watch . --output treemap.png --animate
161162

163+
# Watch and build an MP4 video instead (requires ffmpeg)
164+
dirplot watch . --output treemap.mp4 --animate
165+
dirplot watch . --output treemap.mp4 --animate --crf 18 # higher quality
166+
dirplot watch . --output treemap.mp4 --animate --codec libx265 # smaller file (H.265)
167+
162168
# Replay a filesystem event log as an animated APNG (60-second buckets, 30-second animation)
163169
dirplot replay events.jsonl --output replay.apng --total-duration 30
164170

171+
# Replay as MP4 instead
172+
dirplot replay events.jsonl --output replay.mp4 --total-duration 30
173+
dirplot replay events.jsonl --output replay.mp4 --crf 18 # higher quality
174+
dirplot replay events.jsonl --output replay.mp4 --codec libx265 # smaller file
175+
165176
# Smaller buckets for finer-grained activity
166177
dirplot replay events.jsonl --output replay.apng --bucket 10 --frame-duration 200
167178

168179
# Replay full git history as an animated APNG
169180
dirplot git . --output history.apng --animate --exclude .git
170181

182+
# Replay as MP4 instead
183+
dirplot git . --output history.mp4 --animate
184+
dirplot git . --output history.mp4 --animate --crf 18 # higher quality
185+
dirplot git . --output history.mp4 --animate --codec libx265 # smaller file (H.265)
186+
171187
# Replay a specific local branch
172-
dirplot git .@my-branch --output history.apng --animate
188+
dirplot git .@my-branch --output history.mp4 --animate
173189

174190
# Last 50 commits, 30-second animation with time-proportional frame durations
175191
dirplot git . --output history.apng --animate --range main~50..main --total-duration 30
@@ -214,10 +230,12 @@ These options are specific to the `watch` subcommand.
214230

215231
| Flag | Default | Description |
216232
|---|---|---|
217-
| `--output` / `-o` | required | Output file (`.png` or `.svg`) updated on each change |
233+
| `--output` / `-o` | required | Output file (`.png`, `.apng`, `.mp4`, or `.svg`) updated on each change |
218234
| `--debounce` | `0.5` | Seconds of quiet after the last event before regenerating; `0` disables |
219235
| `--event-log` || Write all raw events as JSONL to this file on Ctrl-C exit |
220-
| `--animate` / `--no-animate` | off | Capture one frame per debounced render; write the complete APNG on Ctrl-C exit |
236+
| `--animate` / `--no-animate` | off | Capture one frame per debounced render; write the complete APNG or MP4 on Ctrl-C exit |
237+
| `--crf` | `23` | MP4 quality: Constant Rate Factor (0 = lossless, 51 = worst). Ignored for APNG |
238+
| `--codec` | `libx264` | MP4 video codec: `libx264` (H.264) or `libx265` (H.265, smaller files) |
221239
| `--log` / `--no-log` | off | Use log of file sizes for layout |
222240
| `--size` | terminal size | Output dimensions as `WIDTHxHEIGHT` |
223241
| `--depth` || Maximum recursion depth |
@@ -232,10 +250,12 @@ These options are specific to the `replay` subcommand.
232250

233251
| Flag | Default | Description |
234252
|---|---|---|
235-
| `--output` / `-o` | required | Output `.png` or `.apng` file |
253+
| `--output` / `-o` | required | Output `.png`, `.apng`, or `.mp4` file |
236254
| `--bucket` | `60.0` | Time bucket size in seconds; one frame per bucket |
237255
| `--frame-duration` | `500` | Frame display time in ms (when `--total-duration` is not set) |
238256
| `--total-duration` || Target total animation length in seconds; frame durations scale proportionally to real time gaps between buckets |
257+
| `--crf` | `23` | MP4 quality: Constant Rate Factor (0 = lossless, 51 = worst). Ignored for APNG |
258+
| `--codec` | `libx264` | MP4 video codec: `libx264` (H.264) or `libx265` (H.265, smaller files) |
239259
| `--workers` / `-w` | all CPU cores | Parallel render workers |
240260
| `--log` / `--no-log` | off | Use log of file sizes for layout |
241261
| `--size` | terminal size | Output dimensions as `WIDTHxHEIGHT` |
@@ -260,9 +280,11 @@ is set, full otherwise) and removes it on exit.
260280
| `--output` / `-o` | required | Output PNG file |
261281
| `--range` / `-r` | all commits | Git revision range (e.g. `main~50..main`, `v1.0..HEAD`). When using a GitHub URL, `--range` works without `--max-commits`; if both are set, `--max-commits` controls the shallow clone depth and must be large enough to reach the range's base commit. |
262282
| `--max-commits` / `-n` || Maximum number of commits to process |
263-
| `--animate` / `--no-animate` | off | Build an animated APNG; without this flag each commit overwrites the output PNG |
283+
| `--animate` / `--no-animate` | off | Build an animated APNG or MP4; without this flag each commit overwrites the output PNG |
264284
| `--frame-duration` | `1000` | Frame display time in ms (when `--total-duration` is not set) |
265285
| `--total-duration` || Target total animation length in seconds; frame durations scale proportionally to real time gaps between commits |
286+
| `--crf` | `23` | MP4 quality: Constant Rate Factor (0 = lossless, 51 = worst). Ignored for APNG |
287+
| `--codec` | `libx264` | MP4 video codec: `libx264` (H.264) or `libx265` (H.265, ~40% smaller at same quality) |
266288
| `--workers` / `-w` | all CPU cores | Parallel render workers; 4–8 is typically optimal due to memory-bandwidth limits |
267289
| `--log` / `--no-log` | off | Use log of file sizes for layout |
268290
| `--size` | terminal size | Output dimensions as `WIDTHxHEIGHT` |

0 commit comments

Comments
 (0)