openmoderate
Open-source, self-hosted image and video content moderation for Python. Detect nudity, sexual content, violence, gore, weapons, hate symbols, drugs, self-harm, alcohol and tobacco in uploaded images and videos — with one pip install, no cloud account, no per-image fees, and no ML experience needed.
Think of it as an open alternative to Amazon Rekognition's DetectModerationLabels that runs inside your own app. Under the hood it runs CLIP, a deep-learning vision-language model, entirely on your machine — no API calls, no data leaves your servers. Its accuracy is measured on a public benchmark.
from openmoderate import Moderator
mod = Moderator()
result = mod.analyze("upload.jpg")
if not result.is_safe:
print(result.violations)
# [Label(name='weapons', confidence=0.9612, parent='violence', threshold=0.5)]
Documentation · Benchmarks · Changelog · Security
Install
pip install openmoderate
Python 3.9+. The first call downloads the CLIP model weights (~600 MB) once; after that everything runs offline, on CPU, CUDA or Apple Silicon. Extras: openmoderate[server], [video], [fastapi], [flask], [django]. See the install notes for CPU-only servers.
How it works: the machine-learning model
openmoderate is built on OpenCLIP, an open implementation of CLIP: a deep-learning model with a Vision Transformer image encoder and a text encoder, trained on LAION-2B (about 2 billion image–text pairs). CLIP places images and sentences in the same vector space, so "how close is this photo to a photo of a gun?" becomes a number.
For every image:
- Embed the image. Letterbox bars and padding are trimmed, then the Vision Transformer turns the image into a vector (an embedding).
- Embed the policy. Each category is described by short text prompts ("a photo of a gun", "a nazi swastika flag"), alongside a pool of everyday safe prompts and category-specific look-alikes (kitchen knives, national flags, medicine). These are embedded once and cached.
- Compare. Cosine similarity between the image vector and every prompt vector.
- Score. For each category, the margin between its best-matching prompt and the best safe or look-alike prompt goes through a calibrated sigmoid, giving a 0–1 confidence.
- Decide. A confidence at or above the category's threshold is a violation.
This is zero-shot classification: it needs no labelled training data, and a new category is a few lines of text. When you do have labelled images, openmoderate train adds supervised learning on top: a class-balanced logistic-regression head trained on frozen CLIP embeddings, which can add new labels or sharpen existing ones.
Three model sizes are available as presets:
| Preset | CLIP model | Parameters | Download | CPU, per image | Apple GPU, per image |
|---|---|---|---|---|---|
fast (default) |
ViT-B/32 | ~150 M | ~600 MB | 10 ms | 6 ms |
balanced |
ViT-B/16 | ~150 M | ~600 MB | 32 ms | 16 ms |
accurate |
ViT-L/14 | ~430 M | ~1.7 GB | 129 ms | 68 ms |
Speeds are batched, on an Apple M-series machine (4 CPU threads, or the built-in GPU); CUDA GPUs are used automatically when available.
What you can do with it
Pick the accuracy you need — Moderator(preset="accurate") uses a larger model: 82% recall with 4.2% of safe images flagged, versus 77.5% / 6.5% for the default (details below).
Tune the policy — thresholds, categories and your own categories as plain text, no training data needed (guide):
mod = Moderator(thresholds={"weapons": 0.3}, exclude=["alcohol", "tobacco"])
mod = Moderator("policy.yaml") # custom categories like "gambling" in YAML
Scan from the command line — folders, URLs and videos; --fail-on-violation for CI (guide):
openmoderate scan ./uploads
Train on your own images for better accuracy in your domain (guide):
openmoderate train ./dataset -o my_head.omh # dataset/<label>/*.jpg + dataset/safe/
Moderate videos with timestamped segments (guide):
Moderator().analyze_video("clip.mp4").segments # weapons 0:03–0:06, violence 0:09–0:12
Protect uploads in FastAPI, Flask or Django with one line (guide):
async def avatar(file: ModeratedFile): ... # FastAPI
avatar = models.ImageField(validators=[ModerationValidator()]) # Django
Call it over HTTP from any language — REST server and Docker image (guide):
docker run -p 8000:8000 -e OPENMODERATE_API_KEY=change-me ghcr.io/karammm/openmoderate
curl -H "X-API-Key: change-me" -F file=@upload.jpg localhost:8000/v1/moderate
Accuracy
Accuracy is measured on a public benchmark of 475 openly licensed photos from Wikimedia Commons, each reviewed by eye (about half of the candidates were removed as unclear or mislabelled). 215 of them are safe, including deliberate look-alikes such as kitchen knives, national flags, milk and medicine. Anyone can reproduce the numbers with python benchmark/run.py --preset <name> --split all.
Recall is the share of each category's images that get flagged; safe images flagged is the false-positive rate on the 215 safe photos.
fast (default) |
balanced |
accurate |
|
|---|---|---|---|
alcohol |
76% | 82% | 87% |
drugs |
85% | 83% | 94% |
hate_symbols |
65% | 65% | 73% |
tobacco |
68% | 67% | 72% |
violence |
87% | 78% | 87% |
weapons |
84% | 88% | 80% |
| Average recall | 77.5% | 77% | 82% |
| Safe images flagged | 6.5% | 4.7% | 4.2% |
| Cross-validated average recall | 73.5% | 79.5% | 82.4% |
How these numbers were produced:
- Calibration follows one rule for every model: the highest recall at which at most ~5% of safe images are flagged (
fastkeeps its original 1.0 calibration). - Cross-validation. The images are split into two halves. A single tuning/test split proved unreliable at this size, so the last row scores each half with the calibration chosen on the other half and averages the two. It is the fairest estimate of how each model generalises.
- Precision is high. On the default model's held-out half, precision is 100% for alcohol, hate symbols, violence and weapons, and 82–89% for drugs and tobacco (full results).
Limits to keep in mind:
- The sets are small (23–56 images per category), so treat each number as roughly ±10 points.
- Nudity, sexual content, gore and self-harm are not benchmarked: the maintainers chose not to collect such images. Evaluate them on your own data with
openmoderate evaluate. - Commons skews towards clear, well-lit photos; screenshots, memes and phone photos are harder.
- Zero-shot moderation is strong on clear-cut cases and will miss subtle ones. For high-stakes decisions, send borderline scores to human review.
Benchmark method and licensing · How it works and its limits
Contributing
Contributions are welcome — better prompts, new categories, benchmark images and bug reports especially. See CONTRIBUTING.md. Report security issues privately (SECURITY.md).
Responsible use
openmoderate is meant to help keep platforms safe. Do not use it to surveil individuals or to profile people by protected characteristics. Moderation models make mistakes; give users a way to appeal automated decisions.
License
Apache 2.0. Benchmark images keep their own licences (see benchmark/manifest.csv).
Metadata
Release files for openmoderate 1.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| openmoderate-1.1.1.tar.gz | 426.3 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| openmoderate-1.1.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 479.7 kB
Release files / openmoderate-1.1.1.tar.gz
| Download URL | openmoderate-1.1.1.tar.gz |
|---|---|
| Size | 426.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
505037124e8359b8d62d98d465fe38666e2bfaaa56acb8467a4662c54ff1c5d7
|
|
BLAKE2b-256 checksum How to use checksums |
d7eab132ff6e8ff9b9f89636f99bfb23c72096a1b6454882172d5835a0bb3c79
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 15, 2026.
Transparency logRelease files / openmoderate-1.1.1-py3-none-any.whl
| Download URL | openmoderate-1.1.1-py3-none-any.whl |
|---|---|
| Size | 53.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
e16cbfad279b28214e9748092867a259c005da6a0a1914a0cc06046921c8e1dd
|
|
BLAKE2b-256 checksum How to use checksums |
b7afca555404d55e17415d0749fdd3e75f39f927b146c4c9007ed301d14fb425
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 15, 2026.
Transparency log