Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/rivenmedia/riven/llms.txt

Use this file to discover all available pages before exploring further.

When Riven marks an item as Downloaded, there is nothing on your disk yet. The torrent exists only on the debrid provider’s servers. RivenVFS bridges that gap: it mounts a virtual directory that looks to your media server like a normal filesystem, but every read is transparently proxied to the debrid provider over HTTP. Files are never downloaded in full — they stream on demand.

How It Works

RivenVFS is implemented in program/services/filesystem/vfs/rivenvfs.py and extends pyfuse3.Operations — the Python binding for the Linux FUSE kernel interface.
Media server reads /mnt/riven/movies/Inception (2010)/Inception (2010).mkv


   FUSE kernel module


   RivenVFS.read(fh, offset, size)
          │  1. Resolve VFSFile node from inode
          │  2. Look up original_filename → streaming URL in VFSDatabase
          │  3. Open / reuse a MediaStream for this file handle

   MediaStream.read(request_start, request_end, request_size)
          │  HTTP Range request to debrid CDN

   Bytes returned to FUSE → kernel → media server
The VFS tree is held entirely in memory as a hierarchy of VFSDirectory and VFSFile nodes indexed by inode. Path lookups are O(depth), not O(n), making directory listings fast even with large libraries.

Mount Path

The FUSE filesystem is mounted at the path configured by filesystem.mount_path:
{
  "filesystem": {
    "mount_path": "/mnt/riven"
  }
}
FilesystemService creates the RivenVFS instance and passes the mount path to pyfuse3.init(). If the path is already mounted (e.g., from a previous crash), RivenVFS attempts a graceful unmount with fusermount3/fusermount before re-mounting.
The container running Riven must have --privileged or the SYS_ADMIN capability and access to /dev/fuse. The mount point on the host must be propagated with rshared so that other containers (such as your media server) can see the FUSE mount.

Chunk Cache

Streaming every byte on-the-fly would re-fetch identical data on every seek. RivenVFS maintains a configurable chunk cache — backed by an in-memory or tmpfs directory — so that recently read regions are served locally.
SettingDefaultDescription
filesystem.cache_dir/dev/shm/riven-cacheDirectory for cached chunks (tmpfs recommended)
filesystem.cache_max_size_mb10240 (10 GiB)Maximum total cache size. Clamped to 90 % of available free space if necessary
filesystem.cache_ttl_seconds7200 (2 hours)Time-to-live per cached chunk (used by TTL eviction)
filesystem.cache_evictionLRUEviction policy: LRU (least-recently-used) or TTL (time-based)
filesystem.cache_metricstrueLog cache hit/miss statistics
{
  "filesystem": {
    "cache_dir": "/dev/shm/riven-cache",
    "cache_max_size_mb": 10240,
    "cache_ttl_seconds": 7200,
    "cache_eviction": "LRU",
    "cache_metrics": true
  }
}
/dev/shm is a RAM-backed tmpfs on most Linux systems, making it ideal for the cache. If your host has limited RAM, point cache_dir to a fast SSD path and lower cache_max_size_mb accordingly.

Directory Structure and Library Profiles

RivenVFS always creates two top-level directories:
/mnt/riven/
├── movies/
└── shows/
Library profiles let you create additional filtered views of the same library. A profile is a named set of filter rules (genres, content ratings, is_anime, etc.) paired with a VFS path:
{
  "filesystem": {
    "library_profiles": {
      "anime": {
        "name": "Anime",
        "library_path": "/anime",
        "enabled": true,
        "filter_rules": { "is_anime": true }
      }
    }
  }
}
With the above profile enabled, every anime item appears under both /mnt/riven/shows/ and /mnt/riven/anime/shows/. Non-anime items only appear under the default paths. Profile directories are never removed, even when empty.
A single item can match multiple profiles and will appear in each matching path. The underlying stream is shared — there is no data duplication.

Naming Templates

File and directory names inside the VFS are rendered from configurable Jinja-style templates:
SettingDefault templateExample output
movie_dir_template{title} ({year}) {{tmdb-{tmdb_id}}}Inception (2010) {tmdb-27205}
movie_file_template{title} ({year})Inception (2010).mkv
show_dir_template{title} ({year}) {{tvdb-{tvdb_id}}}Breaking Bad (2008) {tvdb-81189}
season_dir_templateSeason {season:02d}Season 01
episode_file_template{show[title]} - s{season:02d}e{episode:02d}Breaking Bad - s01e01.mkv
Available template variables include title, year, tmdb_id, tvdb_id, imdb_id, resolution, codec, hdr, audio, quality, season, and episode.

Subtitle Support

RivenVFS also surfaces subtitle files alongside media files. Subtitles are stored in the database (not fetched over HTTP) and served directly from there when a media client reads the .srt or similar file. Subtitle nodes share the parent directory of their associated video file.

Stream Lifecycle

Each open file handle in the VFS maps to a MediaStream object. The stream lifecycle is:
  1. open() — a file handle is created; no HTTP connection is opened yet.
  2. read() — on the first byte request, RivenVFS looks up the streaming URL from VFSDatabase, creates a MediaStream, and issues an HTTP Range request to the debrid CDN.
  3. release() — when the media client closes the file, the stream is shut down and its resources are freed.
A background task (_monitor_stream_timeouts) checks every 60 seconds for streams that have not received a read request recently and closes them proactively to reclaim memory and CDN connections.

API Endpoints

List VFS Files

Returns a flat map of all files currently visible in the VFS mount:
GET /api/v1/mount
{
  "files": {
    "Inception (2010).mkv": "/mnt/riven/movies/Inception (2010) {tmdb-27205}/Inception (2010).mkv"
  }
}

VFS Statistics

Returns per-file streaming statistics (open count, bytes read, errors) collected by opener_stats:
GET /api/v1/vfs_stats
{
  "stats": {
    "/mnt/riven/movies/Inception (2010) {tmdb-27205}/Inception (2010).mkv": {
      "opens": 3,
      "bytes_read": 4294967296
    }
  }
}

Container Setup

A typical Docker Compose configuration for Riven alongside Plex looks like this:
services:
  riven:
    image: ghcr.io/rivenmedia/riven:latest
    privileged: true          # Required for FUSE
    devices:
      - /dev/fuse:/dev/fuse
    volumes:
      - /mnt/riven:/mnt/riven:rshared   # rshared so Plex can see the mount

  plex:
    image: plexinc/pms-docker:latest
    volumes:
      - /mnt/riven:/mnt/riven:rslave    # rslave: sees Riven's mount propagation
rshared on the Riven container means mount events inside the container are propagated to the host. rslave on the Plex container means it receives those propagated mounts without creating its own.
If Plex (or Jellyfin/Emby) reports that library files are missing after a Riven restart, the most common cause is that the FUSE mount was not re-established before the media server checked. Riven will re-mount automatically on startup, but the media server may need a library scan triggered via POST /api/v1/updater/update or the media server’s own interface.

Build docs developers (and LLMs) love