Skip to content
or4aclePublic

About

Offline media indexer for images, GIFs and videos (aka hentai lolll).

Resources

Stars

4 stars

Watchers

0 watching

Forks

Repository files navigation

Contributors Forks Stargazers Issues [License][license-url]


Vault34

Offline media indexer for images, GIFs and videos.
Local tagging with WD-14, perceptual duplicate detection and a searchable gallery.

No cloud. No API keys. No telemetry. After the one-time model download, it never touches the network.


Table of Contents
  1. About The Project
  2. Getting Started
  3. Verification
  4. Project Layout
  5. Troubleshooting
  6. Roadmap
  7. Contributing
  8. License
  9. Acknowledgments

About The Project

![Vault34 Screen Shot][product-screenshot]

Vault34 is a desktop application that builds a searchable local library out of a folder of images, GIFs and videos. Drop files into the inbox and it tags them with a neural network running entirely on your machine, works out which ones are duplicates, generates thumbnails, and gives you a gallery you can search by tags.

The tagger is WD-14 (wd-v1-4-moat-tagger-v2), the same model family used by popular tagging front-ends, exported to ONNX. It knows 9083 labels, including characters, ratings and a long tail of general concepts.

Everything runs locally:

  • No cloud. The only network access is the one-time model download in setup_assets.py.
  • No API keys. Nothing to sign up for.
  • No telemetry. The Flask server binds to 127.0.0.1 only.
  • No build step. Python plus a few wheels, nothing to compile.

Why

Existing tools forced a choice: send your images to a server, or accept coarse keyword metadata. Vault34 does the tagging locally, so a private or embarrassing library stays private, and you still get neural-net quality labels.

Duplicate detection is the other half of the problem. It hashes each file with SHA-256 and with two perceptual hashes (pHash + dHash). A re-encoded video, a resized JPEG or a screenshot of the same picture all land in media/duplicates/ instead of polluting the library, and the gallery tells you what each one duplicates.

(back to top)

Built With

  • Python 3.14 - application core
  • ONNX Runtime - neural inference for the tagger (CPU or CUDA)
  • WD-14 v1.4 Moat tagger - 9083-label classifier, MIT licensed
  • SQLite + FTS5 - storage, indexes and full-text search in one file
  • Pillow - image decoding, perceptual hashing, thumbnails
  • OpenCV - video probing and frame extraction when ffmpeg is absent
  • watchdog - native filesystem watching
  • Flask - loopback JSON API
  • pywebview - native desktop window
  • WebView2 / Chromium - the UI

(back to top)

How It Works

media/inbox/ ──watchdog──▶ stability check ──▶ pipeline worker ──┬─▶ media/library/
                                                                  └─▶ media/duplicates/
                                                                     media/thumbs/
                                                                         │
                                                                    SQLite + FTS5
                                                                         │
                                                                 Flask ─▶ pywebview UI
  1. Watch. watchdog fires the instant a directory entry changes, which is too early - a file being copied in is not finished. So the handler only schedules the path; a background thread waits until size and mtime stop changing (3 consecutive polls, 0.5 s apart) before handing it to the pipeline. A slow tag never blocks the observer.
  2. Probe. Dimensions, frame count and duration. ffprobe is used when available, otherwise OpenCV.
  3. Hash. SHA-256 for exact duplicates, pHash + dHash for perceptual ones. Video gets the same treatment on its representative frame, so a re-encoded copy of a clip is caught even though it shares no bytes with the original.
  4. Tag. The frame is resized to 448x448 (white padding, raw 0-255 pixels) and fed to the ONNX graph. Ratings and characters are split out using the category column of the shipped vocabulary.
  5. File away. Unique files move to media/library/, duplicates to media/duplicates/ with a pointer to the item they duplicate.
  6. Index. Rows in media, tags in normalised tables, text in an FTS5 table for filename and tag search.

Ingest runs on a single worker thread, so a slow file cannot stall the UI, and the progress bar always reflects real work in flight rather than just queue depth.

(back to top)

Supported Formats

Type Extensions
Images .jpg .jpeg .png .webp .gif .bmp .tif .tiff .avif .jxl
Video .mp4 .webm .mov .avi .mkv .m4v .wmv .flv .mpg .mpeg .ts

Animated files (.gif, .webp) are flagged as such and get a still thumbnail.

(back to top)

API

The Flask server is bound to 127.0.0.1 and files are served by media id, never by a client-supplied path, so a stray request cannot read arbitrary files off the machine.

Method Route Purpose
GET /api/stats totals, byte count, live progress
GET /api/status model state, providers, thresholds, paths
GET /api/search q, tag, kind, min_confidence, sort, limit, offset
GET /api/tags/autocomplete tag suggestions by usage
GET /api/tags/top most-used tags
GET /api/tags/related co-occurring tags
GET /api/media/<id> full record with tags
GET /api/media/<id>/thumb JPEG thumbnail
GET /api/media/<id>/file original bytes
GET /api/duplicates groups of duplicate files
GET /api/progress current phase and item
POST /api/scan queue everything in the inbox
POST /api/ingest queue a folder ({"folder": "..."}) or the inbox

Tags combine with AND: ?q=sunset+landscape returns items carrying both. Free text matches filenames and tags through FTS5.

(back to top)


Getting Started

Prerequisites

  • Python 3.11+ (developed and verified on 3.14)
  • ~1 GB of free disk for the model and thumbnails
  • Optionally ffmpeg on PATH for better video support - Vault34 works without it

Installation

  1. Install dependencies

    git clone https://github.com/or4acle/vault34.git
    cd vault34
    python -m pip install -r requirements.txt
  2. Fetch the model (one-time, ~326 MB, the only step that uses the network)

    python setup_assets.py

    This downloads wd14.onnx and selected_tags.csv into models/. To confirm the download is valid before you ever start the app:

    python setup_assets.py --check

    --check inspects the ONNX graph, verifies the input tensor is NHWC 448x448 with raw 0-255 pixels, confirms the vocabulary has 9083 entries, and runs a real inference. If it prints a pass, tagging will work.

  3. Run it

    python main.py
  4. Add media. Drop files into media/inbox/. The watcher notices them, waits until they stop changing, tags them, and files them into the library. Or press Rescan inbox in the sidebar.

    Headless / no-window mode, useful for a quick check:

    python main.py --headless

(back to top)

Usage

  1. Drop files into media/inbox/.
  2. Watch the progress bar at the bottom while they are tagged.
  3. Search by typing: sunset landscape finds items with both tags, plain words match filenames.
  4. Click a thumbnail to open the lightbox, with full metadata, tags and a Show in folder button.
  5. Switch to the Duplicates tab to review what was detected and why - each entry records whether it was an exact or a perceptual match, and points at its original.

(back to top)

Configuration

Every path derives from the project root, so the whole folder can be moved or renamed without breaking anything. Override defaults with environment variables (prefix VAULT34_) or by editing config.json in the project root:

Setting Default Meaning
general_threshold 0.35 minimum confidence for a general tag
character_threshold 0.40 higher, because character false-positives are noisy
max_tags 60 cap on tags kept per item
max_phash_distance 4 Hamming tolerance for perceptual matches
max_dhash_distance 6 secondary check, deliberately looser
thumb_size 480 longest thumbnail edge, in pixels
stability_checks 3 polls a file must be unchanged before ingest
stability_interval 0.5 seconds between those polls
organize true move files out of the inbox into the library
host 127.0.0.1 API bind address
port 8734 API port
ffmpeg_path auto-detected explicit path to the ffmpeg binary
providers [] ONNX execution providers
VAULT34_PORT=9000 VAULT34_PROVIDERS=CUDAExecutionProvider python main.py

Tighten or loosen duplicate detection without touching code:

VAULT34_MAX_PHASH_DISTANCE=2 VAULT34_MAX_DHASH_DISTANCE=3 python main.py

Changing the inbox or library folder from the UI writes to config.json and restarts the watcher against the new location.

(back to top)

GPU Acceleration

onnxruntime defaults to CPU. For NVIDIA CUDA:

python -m pip uninstall -y onnxruntime
python -m pip install onnxruntime-gpu

Then set VAULT34_PROVIDERS=CUDAExecutionProvider,CPUExecutionProvider. The app prints the active providers at startup and falls back to CPU if the requested one is unavailable, so a wrong setting degrades instead of crashing.

(back to top)

FFmpeg (optional)

Videos work without ffmpeg - OpenCV handles probing and frame extraction. Installing ffmpeg gives you broader codec coverage and faster, more accurate thumbnails. Put it on PATH or point at it with VAULT34_FFMPEG_PATH.

(back to top)


Verification

Four suites cover the paths that are easy to get quietly wrong. They are real tests against real files, not mocks.

python verify_pipeline.py   # ingest, tagging, dedup, file placement, search, thumbs
python verify_api.py        # every HTTP route, plus a live watcher drop
python verify_misc.py       # config persistence, desktop bridge, matcher rebuild
python verify_ui.py         # overlay visibility and the hidden-attribute cascade

A few of these exist because they caught an actual bug:

  • file placement asserts that every database row points at a file that really moved out of the inbox. An inverted Path.replace() once left library/ empty while every row claimed a file that did not exist, and no amount of database assertions had noticed.
  • A re-encoded video is included on purpose: it shares no bytes with its source and is a different resolution, so only frame hashing catches it.
  • A slow write is written in chunks with delays, proving the watcher waits for a file to stop changing before touching it.
  • verify_ui.py guards a subtle CSS trap: the hidden attribute is only a browser-default rule, so any author rule setting display silently defeats it. That shipped as a full-screen lightbox permanently stuck on top of the app with a non-functional close button.

Note

verify_pipeline.py resets the database and rebuilds a synthetic inbox. Run it on a scratch copy if you have real data indexed.

(back to top)


Project Layout

vault34/
├── main.py               entry point, desktop bridge, lifecycle
├── setup_assets.py       one-time model download + validation
├── make_readme_assets.py generates images/logo.png and the sample content
├── verify_*.py           the four verification suites
├── vault34/
│   ├── config.py         paths, thresholds, env / config.json overrides
│   ├── db.py             schema, FTS5, search, duplicate queries
│   ├── tagger.py         WD-14 ONNX inference
│   ├── hashing.py        sha256, phash, dhash, DuplicateMatcher
│   ├── media.py          classify, probe, thumbnail, frame extraction
│   ├── pipeline.py       single ingest worker
│   ├── watcher.py        watchdog + file-stability settling
│   └── server.py         Flask API, static hosting
├── web/                  index.html, app.js, style.css
├── images/               logo.png, screenshot.png
├── models/               wd14.onnx, selected_tags.csv   (downloaded)
├── media/                inbox, library, duplicates, thumbs
└── vault34.db            SQLite database

(back to top)


Troubleshooting

Symptom Cause and fix
[tagger] model missing run python setup_assets.py
port 8734 is already in use another instance is running, or set VAULT34_PORT
ffmpeg not found harmless - OpenCV handles video
Files sit in the inbox a file is still being written; check last_error in /api/stats
An item has no tags it indexed but inference failed; last_error says why
Nothing appears after copying press Rescan inbox, or check the console for [watch]
A window will not open open the printed URL in a browser; everything but reveal in Explorer works
Duplicates are missed lower VAULT34_MAX_PHASH_DISTANCE (at the cost of false positives)

(back to top)


Roadmap

  • Offline WD-14 tagging for images, GIFs and video
  • SHA-256 + pHash + dHash duplicate detection, including re-encoded video
  • FTS5 search, tag autocomplete and co-occurrence suggestions
  • Native window via pywebview with a browser fallback
  • Four verification suites
  • Configurable tag presets (general / danbooru / e621 style vocabularies)
  • Face-region detection so character tags can be weighted per region
  • Optional CLIP-style embeddings for "find something like this"
  • Sidecar .json export for interoperability with other tools
  • Multi-language UI

(back to top)


Contributing

Contributions are welcome. A few things that will help:

  • Keep it offline. No feature may require a network call at runtime. If it needs a model or data, fetch it once through setup_assets.py.
  • Add a check to a verification suite for anything you fix. Every significant bug in this project was found by a test that asserted a real property of the system - on disk, in the database, or computed in a browser - rather than by inspection.
  • Do not raise the test bar by weakening it. If a check is wrong, say why in the commit message.
  1. Fork the project
  2. Create your feature branch (git checkout -b feature/AmazingFeature)
  3. Commit your changes (git commit -m 'Add some AmazingFeature')
  4. Push to the branch (git push origin feature/AmazingFeature)
  5. Open a pull request

(back to top)


License

Distributed under the MIT License. See LICENSE for more information.

The bundled tagger, WD-14 v1.4 Moat, is MIT licensed and is downloaded separately by setup_assets.py, not vendored into this repository.

(back to top)


Acknowledgments

  • SmilingWolf for the WD-14 tagger and its vocabulary - this project would not exist without it
  • ONNX Runtime for making local inference practical
  • watchdog for reliable filesystem events
  • SQLite and FTS5 for search in a single file
  • Best-README-Template, which this README is based on
  • Img Shields for the badges above
  • Everyone who files an issue with a real file that misbehaves

(back to top)


[license-url]: https://github.com/or4acle/vault34/blob/main/LICENSE e4e33e1 (Harden indexing, stop losing work on shutdown, and lock the API to loopback) [product-screenshot]: images/screenshot.png

About

Offline media indexer for images, GIFs and videos (aka hentai lolll).

Resources

Stars

4 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages