Skip to content

v0.4.0

Choose a tag to compare

@deeplook deeplook released this 31 Mar 09:51
· 232 commits to main since this release

Added

  • MP4 video output — all three animation commands (watch --animate, git --animate,
    replay) now 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.

    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
  • @ref suffix for dirplot git: local repository paths now accept an optional
    @ref suffix to target a specific branch, tag, or commit SHA without needing
    --range (e.g. dirplot git .@my-branch -o out.apng --animate). --range takes
    precedence when both are provided.

  • dirplot git subcommand — 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 as watch --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 to git 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-duration is 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-duration the bar is linear as before.

  • Debounced watch (--debounce SECONDS, default 0.5): the watch subcommand now
    collects rapid file-system event bursts and regenerates the treemap once per quiet
    period instead of on every raw event. A git checkout touching 100 files triggers
    exactly one render after the activity settles. Pass --debounce 0 to 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 has timestamp, type, path, and dest_path fields.
    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 # comments in tree output 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.

  • --depth for watch: the watch subcommand now accepts --depth N to limit
    recursion depth, matching the behaviour of dirplot map.

    dirplot watch . --output treemap.png --depth 3
  • dirplot replay subcommand — replays a JSONL filesystem event log (as produced
    by dirplot 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
    matching watch --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 git accepts GitHub URLs — pass a github://owner/repo[@branch] or
    https://github.com/owner/repo URL directly to dirplot git. dirplot clones the
    repository into a temporary directory (shallow when --max-commits is 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 showndirplot git now 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 + Link header). For local repos git rev-list --count HEAD is used.

  • --github-token ($GITHUB_TOKEN): added to dirplot git for private GitHub
    repos or to raise the API rate limit when fetching the total commit count.

Changed

  • libarchive-c is 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 / .tzst support. The base install works without it; a clear
    error is shown if you try to open one of these formats without the extra.

  • --animate writes 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 reads Captured frame N;
    the final Wrote N-frame APNG → … line confirms the file was written on exit.

Fixed

  • Initial scan progress: the watch subcommand now prints Scanning <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.

  • --animate race 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.

  • --animate Pillow APNG regression: passing pnginfo alongside save_all=True
    caused Pillow to silently write a static PNG instead of an APNG. The pnginfo
    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 uint16 delay_num field when delay_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 FILE or stdin pipe): the map
    subcommand now accepts a list of paths produced by tree or find — either piped via
    stdin or read from a file with --paths-from. Format is auto-detected: tree output
    (detected by ├── / └── box-drawing characters) or find output (one path per line).
    Handles tree -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 watch accepts multiple directories: the watch subcommand 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 map accepts 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