Skip to content

Repository files navigation

dir2web

Browse a directory tree over HTTP with Apache-style index pages. Videos get a thumbnail in the listing, and clicking one opens a player that starts a low-resolution preview within a second or so, even for multi-GB files.

cargo install dir2web   # or, from a checkout: cargo build --release
dir2web /path/to/base --listen 0.0.0.0:8080

There is no authentication: anyone who can reach the port can read every file under the base directory. The default --listen is 127.0.0.1:37326; only bind wider on a trusted LAN. See --help for the preview and concurrency knobs.

Requires ffmpeg and ffprobe (with libx264) in PATH.

Install

Prebuilt binaries for Linux (x86_64 and aarch64, glibc or static musl) and macOS (Intel and Apple silicon) are attached to each GitHub release. Download and unpack the archive for your platform, or use the installer script:

curl --proto '=https' --tlsv1.2 -LsSf https://github.com/astraw/dir2web/releases/latest/download/dir2web-installer.sh | sh

This installs for the current user into ~/.cargo/bin and adds that to PATH in your shell's startup files, which takes effect in new shells. To install system-wide into /usr/local/bin instead, which is already on PATH, set DIR2WEB_UNMANAGED_INSTALL:

curl --proto '=https' --tlsv1.2 -LsSf https://github.com/astraw/dir2web/releases/latest/download/dir2web-installer.sh | sudo env DIR2WEB_UNMANAGED_INSTALL=/usr/local/bin sh

Or build from source with cargo install dir2web.

How it works

  • /<path>/ — directory index (sortable by name, mtime, size; dotfiles hidden unless --hidden), or the directory's own index.html if it has one. A README.md in the directory is rendered below the listing. Paths resolving outside the base directory, including via symlinks, are 404.
  • /<path> — the original file, with HTTP range support (so browsers can seek in directly playable files).
  • /<path>?view — a Markdown file (.md, .markdown, …) rendered as GitHub-flavoured HTML, up to 16 MiB. Raw HTML is passed through unsanitized, since the served files are trusted. Headings get GitHub-style anchors, and relative links to other Markdown files open rendered too.
  • /<path>?view — a 3D model (.glb, .gltf) in an interactive viewer (rotate, zoom, pan; animations play), using Google's <model-viewer>. Draco- and meshopt-compressed meshes and KTX2 textures work; their decoders are embedded too, so nothing is fetched from the internet. A .gltf's external buffers and textures are loaded from beside it.
  • /<path>?thumb — JPEG thumbnail from ~10% into the video, extracted lazily and cached.
  • /<path>?play — player page. Starts (or joins) a transcode of the video to an HLS event playlist of 2 s fMP4 segments (H.264 ≤640 px wide, ≤30 fps, AAC). The page polls /_dir2web/status/<key> and starts playback once the first segment exists; the part transcoded so far is seekable. A finished preview is complete-seekable and reused across restarts.

The cache lives in ~/.cache/dir2web ($XDG_CACHE_HOME, or --cache-dir). Entries are keyed by a hash of the canonical path, size, mtime and output settings, so changed files or settings produce new entries. The cache is kept under --max-cache-size (default 10G; 0 for no limit) by evicting the least recently used thumbnails and previews, down to 90% of the limit. Entries used in the last 5 minutes and transcodes in progress are never evicted, so the cache can briefly exceed the limit by what is in active use.

Safari plays the HLS preview natively. Other browsers use a vendored copy of hls.js (light build, Apache-2.0, in static/), compiled into the binary. Chrome's own native HLS is deliberately not used: it mishandles the growing playlist (no duration, seeks ignored). Append &player=hlsjs or &player=native to a player URL to force either.

Browser test

tests/browser/play.mjs drives the player page in headless Chromium, Firefox or (Linux) WebKit via Playwright, logging playback start, seeks and buffering while the transcode runs. It expects a video of at least 240 s.

cd tests/browser && npm i --no-save playwright && npx playwright install chromium firefox webkit
node play.mjs firefox 'http://127.0.0.1:37326/some.mp4?play'

tests/browser/model.mjs loads a 3D model view page, waits for the model to load, fails if any request left the dir2web server, and can save a screenshot. Headless Firefox has no WebGL without a GPU; run it headed under Xvfb instead:

node model.mjs chromium 'http://127.0.0.1:37326/model.glb?view' shot.png
HEADED=1 xvfb-run -a node model.mjs firefox 'http://127.0.0.1:37326/model.glb?view'

Not yet

  • Scrubbing beyond the transcoded portion (publish the full VOD playlist up front and produce segments on demand with -ss).
  • Authentication.

License

Licensed under either of Apache License, Version 2.0 or MIT license at your option.

The JavaScript embedded from static/, such as hls.js (Apache-2.0), keeps its own license; see THIRD-PARTY-LICENSES, which also covers the Rust dependencies.

About

Serve a directory tree over HTTP with Apache-style indices, video thumbnails and instant transcoded HLS previews

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages