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:8080There 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.
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 | shThis 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 shOr build from source with cargo install dir2web.
/<path>/— directory index (sortable by name, mtime, size; dotfiles hidden unless--hidden), or the directory's ownindex.htmlif it has one. AREADME.mdin 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.
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'- Scrubbing beyond the transcoded portion (publish the full VOD playlist up
front and produce segments on demand with
-ss). - Authentication.
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.