Skip to main content

Xyran V1

Fast, local-first content safety for Python.

Xyran is an offline image and animation moderation SDK with bundled Owen-S ONNX inference. After installation, inference requires no API key, cloud moderation service, telemetry, Hugging Face login, or model download.

Xyran 1.1 adds frame-aware animated GIF/WebP moderation, real ONNX batching, recursive folder scanning, and JSON/Markdown/TXT/CSV reports.

Install

pip install xyran

On Windows x64 and Linux x86_64, the package intentionally installs onnxruntime-gpu[cuda,cudnn] so NVIDIA CUDA can work out of the box while the same runtime can fall back to CPU. On macOS and non-x64 Windows/Linux targets, the CPU ONNX Runtime package is selected.

ONNX Runtime's CPU and GPU Python distributions share the same import namespace. Use a clean virtual environment for the most predictable install.

Runtime conflict repair

If xyran doctor reports multiple ONNX Runtime distributions or a damaged onnxruntime namespace, use Xyran's built-in repair command:

xyran repair-runtime

Xyran will:

  1. inspect the current Python interpreter/environment;
  2. remove all overlapping ORT distributions (onnxruntime, onnxruntime-gpu, DirectML and OpenVINO variants);
  3. reinstall exactly one Xyran-compatible runtime;
  4. verify the result in a fresh Python process;
  5. run the bundled model smoke test and dynamic-batch smoke test.

Preview the exact commands without changing anything:

xyran repair-runtime --dry-run

For CI/Docker/non-interactive environments:

xyran repair-runtime --yes

Force CPU runtime:

xyran repair-runtime --runtime cpu

Force the NVIDIA GPU runtime on supported Windows/Linux x64 systems:

xyran repair-runtime --runtime gpu

The repair command always uses the interpreter that is currently running Xyran (sys.executable -m pip), so it does not silently repair a different Python installation. Network/package-index access is required to reinstall ORT.

A clean virtual environment remains the recommended production deployment.

Static image usage

from xyran import Moderator

mod = Moderator()  # model/session resident by default
result = mod.scan("image.jpg")

print(result.decision)      # ALLOW / REVIEW / BLOCK
print(result.scores.sexual)
print(result.scores.graphic)
print(result.scores.safe)

Animated GIF / WebP

scan() now auto-detects multi-frame GIF/WebP and switches to frame-aware moderation:

from xyran import Moderator, AnimationModerationResult

mod = Moderator()
result = mod.scan("animation.gif")

if isinstance(result, AnimationModerationResult):
    print(result.decision)
    print(result.processing.sampled_frames, result.processing.total_frames)
    print(result.processing.exhaustive)
    print(result.worst_frame.frame_index)
    print(result.worst_frame.scores)

Explicit animation API:

result = mod.scan_animation(
    "animation.webp",
    sampling="smart",  # smart | uniform | all
    max_samples=32,
    batch_size=16,
)

Smart sampling design

The default sampling="smart" is deterministic and combines:

  1. endpoints — always protects intro/outro coverage;
  2. duration-aware time coverage — samples by playback time rather than only frame number, so long-dwell frames receive appropriate representation;
  3. visual-change peaks — a cheap 48x48 RGB signature combines whole-frame change with extra weight on the most-changed local regions, improving sensitivity to smaller abrupt inserts;
  4. long-dwell frames — prioritizes frames visible for longer periods;
  5. coverage fill — fills any collisions with deterministic frame coverage.

If the animation has 32 frames or fewer, the default is automatically exhaustive. Longer animations are smart-sampled to at most 32 model-scanned frames by default.

Smart sampling reduces model inference cost, but it is not a mathematical guarantee that every unsafe frame is inspected. High-assurance workflows can request every frame:

result = mod.scan_animation("animation.gif", sampling="all")

CLI equivalent:

xyran scan-animation animation.gif --sampling all

Real frame batching

Selected frames are preprocessed independently, stacked into dynamic [batch, 3, 224, 224] tensors, and sent through one ONNX Runtime call per batch. The default frame batch size is 16.

The same real batching is available for static inputs:

results = mod.scan_batch(paths, batch_size=16)

Folder scanning

Scan a complete directory recursively:

from xyran import Moderator

mod = Moderator()
report = mod.scan_folder(
    "./uploads",
    recursive=True,
    batch_size=16,
    animation_sampling="smart",
    animation_max_samples=32,
)

print(report.summary)

Write multiple report formats from the same scan result:

report.write_reports(
    "./reports/xyran-report",
    formats=("json", "md", "txt", "csv"),
)

This creates:

xyran-report.json
xyran-report.md
xyran-report.txt
xyran-report.csv

CLI:

xyran scan-folder ./uploads \
  --output ./reports/xyran-report \
  --report-formats json md txt csv

Windows CMD can use one line:

xyran scan-folder .\uploads --output .\reports\xyran-report --report-formats json md txt csv

Folder scanning:

  • scans known raster-image extensions by default;
  • recursively scans subdirectories by default;
  • uses real ONNX batching for static images;
  • uses frame batching inside each animated GIF/WebP;
  • records per-file decode/runtime failures without silently dropping them;
  • can optionally probe every regular file with probe_unknown=True / --probe-unknown when filename extensions are untrusted.

The JSON report contains the full structured results, including sampled frame indices, selection reasons, per-frame scores, animation timing metadata, error details, provider/fallback status, and whether each animation scan was exhaustive.

Markdown/TXT provide human-readable summaries. CSV is intentionally flattened to one row per file; for animations it reports the worst sampled frame.

V1.1 defaults

preprocess              BlurPad + Lanczos3
input                    224 x 224 (generated internally)
tiling                   disabled
resident                 true
device                   auto
animation sampling       smart
animation max samples    32
animation batch size     16
folder static batch      16
runtime                  ONNX Runtime
network after install    not required
telemetry                disabled
cloud API                none

The default BlurPad + Lanczos3 preprocessing preserves source aspect ratio, places the fitted sharp image over a blurred full-canvas background, and sends a single 224x224 tensor per image/frame to Owen-S.

Optional alternate preprocessing:

mod = Moderator(preprocess="warp")  # pyvips Warp + Linear

No tiling is used in V1.1 inference.

Inputs and image formats

scan() accepts:

  • local file paths (str / pathlib.Path)
  • encoded image bytes (bytes / bytearray)
  • PIL.Image.Image

Xyran identifies common image families from file content where possible rather than trusting only filename extensions. EXIF orientation is applied. Alpha is flattened onto white consistently before preprocessing.

Core raster formats:

Format Extensions Xyran 1.1 policy
JPEG .jpg, .jpeg, .jpe static supported
PNG .png static supported; multi-frame/APNG remains fail-closed
WebP .webp static + animated supported
BMP .bmp, .dib static supported
TIFF .tif, .tiff single-page only
GIF .gif static + animated supported

Extended raster formats are accepted when the installed decoder exposes the codec:

  • HEIC / HEIF
  • AVIF
  • JPEG 2000
  • ICO

Multi-page families other than GIF/WebP remain fail-closed in V1.1. SVG, PDF and PSD are intentionally outside the normal raster-image moderation contract.

Inspect the actual local decoder/runtime support:

xyran formats
xyran formats --json

Animation safety limits

Defaults protect against unexpectedly large or adversarial animations:

maximum source frames    5,000
maximum effective time   600,000 ms
maximum pixels/frame     80,000,000

These are hard decode/analysis limits, not policy thresholds. They can be customized in scan_animation() when the caller explicitly needs larger input.

Model residency

Default:

mod = Moderator(resident=True)

The ONNX session is created once and remains resident until close()/unload(). This is recommended for servers, desktop apps, animation scanning and directory batches.

Memory-sensitive mode:

mod = Moderator(resident=False)

Device selection

Moderator(device="auto")  # default: CUDA when it really works, otherwise CPU
Moderator(device="cpu")   # strict CPU
Moderator(device="cuda")  # strict CUDA; raises if CUDA is unusable

device="auto" can fall back to CPU if CUDA is visible but unusable.

Policy

The bundled classifier produces:

NSFL -> graphic
NSFW -> sexual
SFW  -> safe

Development defaults:

sexual REVIEW  >= 0.35
sexual BLOCK   >= 0.85
graphic REVIEW >= 0.35
graphic BLOCK  >= 0.85

These thresholds are not universal safety policy. Real moderation policy is application-specific.

from xyran import Moderator, ModerationPolicy

policy = ModerationPolicy(
    sexual_review=0.40,
    sexual_block=0.90,
    graphic_review=0.35,
    graphic_block=0.85,
)
mod = Moderator(policy=policy)

For animations, each sampled frame receives an ordinary policy decision. The animation decision is the worst sampled frame by severity. worst_frame.scores is a real model probability vector. peak_scores is an aggregate convenience field: sexual/graphic are maxima across sampled frames and safe is the minimum, so peak_scores is not itself one probability distribution.

CLI

xyran doctor
xyran formats
xyran scan image.jpg
xyran scan animation.gif
xyran scan animation.webp --animation-sampling all
xyran scan-animation animation.gif --sampling smart --max-samples 32 --batch-size 16
xyran scan-folder ./uploads --output xyran-report --report-formats json md txt csv

Bundled model

Xyran V1 pins:

Repository: OwenElliott/image-safety-classifier-s
Source commit: eb8b0b203952b70db191e990217174af4af39767
File: onnx/image-safety-classifier-s.onnx
Size: 23,701,765 bytes
SHA256: fef443ed68ae25ed693b6fef9e456071692ed3963cff4168acb39c3de6f017e7
License metadata: MIT

See THIRD_PARTY_NOTICES.md and package xyran/third_party/.

Offline guarantee

After pip install xyran finishes successfully:

model download during inference: NO
API key:                         NO
cloud moderation API:           NO
telemetry:                       NO
network required for inference: NO

Limitations

Xyran helps classify content-safety risk. It is not a guarantee that every unsafe image/frame will be detected, nor that every flagged image/frame is unsafe. Smart animation sampling is deliberately non-exhaustive on long animations; use sampling="all" when exhaustive frame inspection is required. For high-stakes moderation, use human review and dataset-specific evaluation.

Metadata

Release files for xyran 1.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for xyran 1.2.0
File Size Uploaded
xyran-1.2.0.tar.gz 21.1 MB Details

Built distribution (wheel)

Table of built distributions (wheels) for xyran 1.2.0
File Interpreter ABI Platform
xyran-1.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 42.2 MB

Release files / xyran-1.2.0.tar.gz

Download URL xyran-1.2.0.tar.gz
Size 21.1 MB
Tags Source
SHA-256 checksum
How to use checksums
9ea4b44e26c2a856bdbf80cd171dd3af48509eb2a95a6a3fe513b59af46d7d47
BLAKE2b-256 checksum
How to use checksums
bd4f6ba09be6af8fde2f3740ee3429f0069e1d0d4cd9100f572026a80b5ac54a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.11.4

Release files / xyran-1.2.0-py3-none-any.whl

Download URL xyran-1.2.0-py3-none-any.whl
Size 21.1 MB
Tags Python 3
SHA-256 checksum
How to use checksums
d50c7dac8091d0abc29db746e652fb370ed033d2a10fd393d6a5eda89fb556c1
BLAKE2b-256 checksum
How to use checksums
4ef199371dc140d519b8ad9f2047a3ce26d9e0ddb96febf1851e5a315ea3a6dd
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.11.4

Release history Release notifications | RSS feed

1.3.0

2 release files

This release

1.2.0 This release

2 release files

1.1.0

2 release files

1.0.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page