Backends

September 2, 2026 · View on GitHub

load_media_classifier(config=None, voc_match_func=None) returns one classifier. Every backend implements AbstractMediaClassifier, so callers treat them identically, the choice only affects accuracy, dependencies and what signal is available.

The package ships several backends. The keyword backend is the default and has no ML dependencies; ONNX, the learned embedding-router (embedding-router.md) and NER are opt-in extras. Any opm.media.classifier plugin can also be loaded by name.

Selection

load_media_classifier(config) picks a backend in this order; the first matching config key wins, and any load failure falls back to the keyword classifier so the zero-dependency default is always available:

OrderBackendSelected byExtra
1external pluginconfig["media_classifier_plugin"]a plugin package
2ONNXconfig["media_classifier_onnx_model"][onnx]
3embedding-router (hybrid)config["media_classifier_embedding_router"][onnx]
4NERconfig["media_classifier_entities"], media_classifier_wordlists, or media_classifier_ner_csv[ner]
5keyword (default)nothing else setcore
from ovos_media_classifier import load_media_classifier

load_media_classifier()                                                   # keyword (default)
load_media_classifier({"media_classifier_onnx_model": "/models/ocp"})     # ONNX bundle dir
load_media_classifier({"media_classifier_embedding_router": "/models/router",  # learned router (hybrid)
                       "media_classifier_entity_library": {"anime_title": ["Attack on Titan"]}})
load_media_classifier({"media_classifier_entities": {"entity_lists": [...]}})  # NER
load_media_classifier({"media_classifier_plugin": "my-classifier"})       # external plugin

Keyword classifier (default, zero-dep)

clf = load_media_classifier()                                # bundled locale
clf = load_media_classifier(voc_match_func=self.voc_match)   # pipeline mode

Matches the query against bundled per-language .voc files (ovos_media_classifier/locale/<lang>/<Vocab>.voc), using word-boundary matching from ovos-spec-tools. No ML dependencies, no model files, fully offline. In pipeline mode the OCP pipeline plugin owns the .voc files and passes its voc_match method as voc_match_func; in standalone mode the classifier reads the bundled files directly. Matching runs in a fixed priority order (e.g. MusicVideoKeyword before MusicKeyword) and surfaces genre tags (anime, adult, …) via classify_genres().

It predicts the coarse axes (playback_type, structure) from their own .voc cues and chooses the leaf MediaType leaf-first, so classify_full() returns a complete multi-axis result with no model file. How and why this differs from a trained head's soft-gating is in classification-model.md.

Because it derives the domain from the media-type result, the keyword classifier reports OCP_PLAY / NOT_OCP and does not detect OCP_CONTROL.

Use when: you want the offline default with no setup. Use pipeline mode when you are inside the OCP pipeline and already have a voc_match function; otherwise use the no-arg bundled mode.

Languages bundled: ca-es, da-dk, de-de, en-us, es-es, eu-es, fr-fr, gl-es, it-it, nl-nl, pl-pl, pt-br, pt-pt.

NER classifier ([ner] extra)

"NER" here is the user-facing name for exact entity-list matching, not statistical named-entity recognition: the backend is AhocorasickMediaClassifier, which compiles the configured entity lists into an Aho-Corasick automaton and matches the user's actual library (artists, titles, stations) verbatim. No model guesses spans — an entity either is in a registered list or it does not match.

pip install ovos-media-classifier[ner]
from ovos_media_classifier.entities import EntitiesContainer
from ovos_media_classifier.ahocorasick import AhocorasickMediaClassifier

container = EntitiesContainer.from_sources(["/data/library.csv"])
clf = AhocorasickMediaClassifier.from_container(container)
clf.classify("play inception", "en-us")     # -> (<MediaType.MOVIE: 'movie'>, ...)

The NER backend matches the user's real media, their artists, titles and stations, captured as entity lists (label → list of strings), with an Aho-Corasick automaton for fast exact substring matching. Each entity label maps to a media type via NER_LABEL_TO_MEDIA_TYPE (and to genres via NER_LABEL_TO_GENRES), so "play Inception" resolves to MOVIE because Inception is in the library, high confidence, language-agnostic, zero linguistic guessing.

It is selected by config (media_classifier_entities / media_classifier_wordlists / media_classifier_ner_csv) or built directly via AhocorasickMediaClassifier.from_container / from_wordlists / from_csv. The entity lists, their source specs (.csv / .tsv / .jsonl / HuggingFace / media-server / inline / runtime) and the perf/memory tradeoff of loading more entities are documented in entity-lists.md.

Use when: the user has a known library (Jellyfin, the *arr stack, Music Assistant, a static roster) and you want titles to resolve exactly. See contextual-classification.md.

ONNX classifier ([onnx] extra)

pip install ovos-media-classifier[onnx]
load_media_classifier({"media_classifier_onnx_model": "/models/ocp-bundle"})

A trained classifier with one ONNX head per axis, domain, media_type, playback_type, structure, explicitness, content_form, programme_format, variant (single-label) and content_form_genres, content_genres, accessibility (multi-label), plus numpy. It depends on raw onnxruntime + numpy only. The factory loads it from a self-describing model-bundle directory of <axis>.onnx files plus a meta.json manifest (which carries the ordered feature names, the per-head index→label maps and the multi-label thresholds, so the runtime makes no hard-coded assumptions and loads whichever heads the bundle ships, a missing head simply derives its axis). Set media_classifier_onnx_model to that directory. The full bundle layout and retrain contract are in model.md.

A trained model predicts each axis with its own head and can capture per-utterance nuance the keyword backend cannot, see classification-model.md. The bundle format is documented in ovos_media_classifier/onnx.py.

Use when: you have a trained model bundle and want real-query accuracy beyond the keyword floor.

External plugins

Any subclass of AbstractMediaClassifier registered under the opm.media.classifier entry-point group is loadable by name via config["media_classifier_plugin"]. load_media_classifier() falls back to the keyword classifier if it fails to load, so an external plugin never hard-fails the pipeline. The full recipe, registering, loading by name, and which methods to override, is in external-plugins.md.


← Taxonomy · Home · Embedding router →