v0.4.0
Added
-
MP4 video output — all three animation commands (
watch --animate,git --animate,
replay) now write MP4 video when the output path ends in.mp4or.mov. Quality is
controlled via--crf(Constant Rate Factor: 0 = lossless, 51 = worst, default 23) and
--codec(libx264H.264 orlibx265H.265). MP4 files are typically 10–100× smaller
than equivalent APNGs. Requiresffmpegon PATH.dirplot git . -o history.mp4 --animate dirplot git . -o history.mp4 --animate --crf 18 --codec libx265 dirplot replay events.jsonl -o replay.mp4 --total-duration 30 dirplot watch . -o treemap.mp4 --animate
-
@refsuffix fordirplot git: local repository paths now accept an optional
@refsuffix to target a specific branch, tag, or commit SHA without needing
--range(e.g.dirplot git .@my-branch -o out.apng --animate).--rangetakes
precedence when both are provided. -
dirplot gitsubcommand — replays a git repository's commit history as an
animated treemap. Each commit becomes one frame; changed tiles receive the same
colour-coded highlight borders aswatch --animate(green = created, blue = modified,
red = deleted). The commit SHA and local timestamp are shown in the root tile header,
and a progress bar at the top of each frame advances as the animation plays.# Animate all commits, write APNG dirplot git . --output history.apng --animate --exclude .git # Last 50 commits on main, 30-second animation with time-proportional frame durations dirplot git . --output history.apng --animate \ --range main~50..main --total-duration 30 # Live-updating static PNG (last frame wins; useful with an auto-refreshing viewer) dirplot git /path/to/repo --output treemap.png --max-commits 100
-
--range(-r): git revision range passed directly togit log
(e.g.main~50..main,v1.0..HEAD). Defaults to the full history of the current branch. -
--max-commits(-n): cap the number of commits processed. -
--frame-duration: fixed frame display time in ms when--total-durationis not set
(default: 1000 ms). -
--total-duration: target total animation length in seconds. Frame durations are
scaled proportionally to the real elapsed time between commits, so quiet periods in
development history map to longer pauses and burst activity to rapid flips. A 200 ms
floor prevents very fast commits from being invisible; durations are capped at 65 535 ms
(APNG uint16 limit). A summary line reports the actual range:
Proportional timing: 200–7553 ms/frame (total ~30.1s). -
--workers(-w): number of parallel render workers in animate mode (default: all
CPU cores). Rendering is memory-bandwidth bound, so 4–8 workers is typically optimal;
use this flag to tune for your hardware. -
Time-proportional progress bar: a 2 px bar at the top of each frame advances in
proportion to animation time consumed, not frame count — so a burst of closely-spaced
commits produces only a small movement while a long quiet period advances it visibly.
With fixed--frame-durationthe bar is linear as before. -
Debounced watch (
--debounce SECONDS, default0.5): thewatchsubcommand now
collects rapid file-system event bursts and regenerates the treemap once per quiet
period instead of on every raw event. Agit checkouttouching 100 files triggers
exactly one render after the activity settles. Pass--debounce 0to restore the
old immediate-fire behaviour.dirplot watch . --output treemap.png # 500 ms debounce (default) dirplot watch . --output treemap.png --debounce 1.0 # 1 s quiet window dirplot watch . --output treemap.png --debounce 0 # immediate, as before
-
Event log (
--event-log FILE): on Ctrl-C exit, all raw file-system events
recorded during the session are written as newline-delimited JSON (JSONL) to the
given file. Each line hastimestamp,type,path, anddest_pathfields.
The log is written only if there are events to record.dirplot watch src --output treemap.png --event-log events.jsonl # Ctrl-C, then: cat events.jsonl | python3 -m json.tool
-
File-change highlights (
--animate): each APNG frame now draws colour-coded
borders around tiles that changed since the previous frame — green for created,
blue for modified, red for deleted, orange for moved. Deleted files are highlighted
retroactively on the previous frame (since the tile no longer exists in the current
one), so the animation clearly shows both the disappearance and the appearance of files.
Moved files appear as a deletion at the old path and a creation at the new path. -
Graceful finalization: Ctrl-C now flushes any pending debounced render before
stopping the observer, so the output file always reflects the final state of the
watched tree. A second Ctrl-C during APNG writing is ignored so the file can finish
being written. -
Tree comment stripping: trailing
# commentsintreeoutput are now ignored
by the path-list parser, so annotated tree listings (e.g.├── config.json # app config)
are parsed correctly. Filenames containing#without a leading space are preserved. -
scripts/apng_frames.py: utility script to list frame durations, dimensions, and
offsets in an APNG file. -
scripts/watch_events.py: utility script to watch directories and log filesystem
events to a CSV file (or stdout) in real time using watchdog. -
--depthforwatch: thewatchsubcommand now accepts--depth Nto limit
recursion depth, matching the behaviour ofdirplot map.dirplot watch . --output treemap.png --depth 3 -
dirplot replaysubcommand — replays a JSONL filesystem event log (as produced
bydirplot watch --event-log) as an animated treemap APNG. Events are grouped into
time buckets (one frame per bucket, default 60 s), with colour-coded highlight borders
matchingwatch --animate. Only files referenced in the event log appear in the
treemap; the common ancestor of all paths is used as the tree root. Frame durations
can be uniform (--frame-duration, default 500 ms) or proportional to the real time
gaps between buckets (--total-duration). Frames are rendered in parallel.# Replay an event log with 60-second buckets, 30-second total animation dirplot replay events.jsonl --output replay.apng --total-duration 30 # Smaller buckets for fine-grained activity, fixed frame duration dirplot replay events.jsonl --output replay.apng --bucket 10 --frame-duration 200
-
dirplot gitaccepts GitHub URLs — pass agithub://owner/repo[@branch]or
https://github.com/owner/repoURL directly todirplot git. dirplot clones the
repository into a temporary directory (shallow when--max-commitsis set, full
otherwise), runs the full history pipeline locally, and removes the clone on exit.
No permanent local copy is created.# Animate the last 50 commits of a GitHub repo — no local clone needed dirplot git github://owner/repo --output history.png --animate --max-commits 50 # Specific branch dirplot git github://owner/repo@main --output history.png --animate --max-commits 50
-
Total commit count shown —
dirplot gitnow reports the total number of commits
available alongside the number being animated, so you can gauge how much history
exists before committing to a longer run:Replaying 20 of 147 commit(s) (increase --max-commits to process more) ...For GitHub URLs the count is fetched with a single cheap API request (one commit
object +Linkheader). For local reposgit rev-list --count HEADis used. -
--github-token($GITHUB_TOKEN): added todirplot gitfor private GitHub
repos or to raise the API rate limit when fetching the total commit count.
Changed
-
libarchive-cis now an optional dependency. Install it with
pip install 'dirplot[libarchive]'(plus the system library:
brew install libarchive/apt install libarchive-dev) to enable
.iso,.cpio,.rpm,.cab,.lha,.xar,.pkg,.dmg,.a,.ar,
and.tar.zst/.tzstsupport. The base install works without it; a clear
error is shown if you try to open one of these formats without the extra. -
--animatewrites the APNG once on exit instead of reading and rewriting the
entire file on every render. Frames are accumulated as raw PNG bytes in memory and
flushed as a single multi-frame APNG when the watcher stops (Ctrl-C). This removes
an O(N²) disk-I/O pattern where frame K required reading a K-frame APNG just to
append one more frame. Status output during a session now readsCaptured frame N;
the finalWrote N-frame APNG → …line confirms the file was written on exit.
Fixed
-
Initial scan progress: the
watchsubcommand now printsScanning <roots> …
before the first render and starts the filesystem observer only after the initial
treemap has been generated, avoiding spurious events during the first scan. -
--animaterace condition: the debounce timer thread was marked as daemon,
causing an in-progress render to be killed when the main thread exited after
observer.join(). The timer is no longer a daemon thread;flush()joins any
in-flight render before stopping. -
--animatePillow APNG regression: passingpnginfoalongsidesave_all=True
caused Pillow to silently write a static PNG instead of an APNG. Thepnginfo
argument is now omitted from multi-frame saves (cross-process timing metadata is
no longer needed since frames are held in memory for the lifetime of the process). -
APNG frame duration overflow: restoring the inter-session frame duration from
stored metadata could produce a value exceeding 65 535 ms — the maximum expressible
by APNG's uint16delay_numfield whendelay_den = 1000— causing Pillow to raise
cannot write duration. Durations are now capped at 65 535 ms (≈ 65 s). -
Path-list input from
tree/find(--paths-from FILEor stdin pipe): themap
subcommand now accepts a list of paths produced bytreeorfind— either piped via
stdin or read from a file with--paths-from. Format is auto-detected:treeoutput
(detected by├──/└──box-drawing characters) orfindoutput (one path per line).
Handlestree -s/tree -h(size columns),tree -f(full embedded paths), and the
default indented name format. Ancestor/descendant duplicates are collapsed automatically
so only the minimal set of roots is passed to the scanner.# Implicit stdin — no flag needed tree src/ | dirplot map tree -s src/ | dirplot map # with file sizes in tree output find . -name "*.py" | dirplot map # Explicit file tree src/ > paths.txt && dirplot map --paths-from paths.txt # Explicit stdin tree src/ | dirplot map --paths-from -
Positional path arguments and path-list input are mutually exclusive — combining them
exits with a clear error. Only local paths are supported (remote backends such as
docker://,s3://,ssh://remain positional-arg only). -
dirplot watchaccepts multiple directories: thewatchsubcommand now takes
one or more positional path arguments and schedules a filesystem observer for each,
regenerating the treemap from all roots on every change.dirplot watch src tests --output treemap.png
-
dirplot mapaccepts multiple file paths as roots: previously, multi-root mode
required every argument to be a directory. Individual files can now be passed as roots;
each is treated as a leaf node and displayed under the common parent directory.dirplot map src/main.py src/util.py --no-show
-
stdout output (
--output -): passing-as the output path writes the PNG or SVG
bytes to stdout, enabling piping to other tools. Header and progress lines are
automatically redirected to stderr to keep the binary stream clean.dirplot map . --output - --no-show | convert - -resize 50% small.png dirplot map . --output - --format svg --no-show > treemap.svg