Files
flan 997c257442 Add Audience plugin: rating repair and audience tagging
Jellyfin treats a rating string it cannot parse the same as no rating at all: the item takes the unrated path and stays visible to every profile, including one with an age ceiling set. This library carried eighteen such items. Six were Singapore adult ratings (M18, NC16, R21) that no age ceiling would have caught, and two (TP, L) mean "all ages" and were being withheld from children for the opposite reason.

The task remaps those strings to their US equivalents using published board equivalences, and locks the repaired value against provider refresh. Ratings are never inferred from genre or synopsis; an unmapped string is reported as a warning instead. A wrong guess there fails open, which is the one outcome parental controls exist to prevent.

The audience tag backs a per-user Allowed Tags whitelist. That check fails closed, so new content is invisible to the filtered account until it is tagged, which is why the work has to recur rather than run once. A ledger records every id the task has tagged; an id present in the ledger but no longer carrying the tag was untagged deliberately and is never tagged again.

Running in-process as a scheduled task means no API key, no credential file and no container. It also sidesteps three defects in the 12.0 REST item-update path, which assigns Name, Overview, ProductionYear, OfficialRating and CustomRating unconditionally, silently unlocks an item when LockData is omitted, and rejects its own serialized Trickplay block on the way back in.
2026-09-19 22:29:58 +00:00

4.4 KiB

Jellyfin Audience

A Jellyfin plugin that repairs ratings the server cannot parse, and maintains an audience tag so a user account can be narrowed to a manageable subset of a large library.

It runs in-process as a scheduled task. It needs no API key, no credential file and no external service.

Why it exists

Ratings Jellyfin cannot parse are a hole in parental controls

Jellyfin resolves an OfficialRating string to a numeric score. When it cannot, the item takes the same path as an unrated one, and unrated items are visible to everyone by default unless an account explicitly blocks unrated content.

That is not a theoretical problem. A real library scanned for this contained Singapore board ratings (M18, NC16, R21) on adult titles. Jellyfin could not parse any of them, so no age ceiling would have hidden them. It cuts the other way too: TP (Spain) and L (Brazil) both mean "all ages", and titles carrying them were being hidden from children as though unrated.

This plugin remaps those strings to their documented US equivalents and locks the result.

Allowed Tags fails closed

Narrowing an account with Allowed Tags is the right mechanism, but it is a whitelist: anything without the tag is invisible. A library that keeps growing would leave that account frozen at whatever was tagged on the day it was set up. So the tagging pass has to run on a schedule, not once.

What it does not do

It does not guess a rating from genre, synopsis, or anything else about the content. A wrong guess fails open, and failing open is the one outcome parental controls exist to prevent. Ratings it cannot map are logged as warnings and left untouched, for a person to decide.

Configuration

Dashboard, then Plugins, then Audience.

Setting Default Meaning
Enabled on Master switch for the scheduled task
RepairRatings on Remap unparseable board ratings
ApplyTags on Maintain the audience tag
TagName grandma Tag applied to matching titles
MaxRatingScore 14 Rating ceiling for tagging. Inclusive
MinCommunityRatingWhenUnrated 6.0 Community-rating floor for titles with no usable rating
RespectManualUntag on Honour the ledger, so manual untagging sticks

The US rating ladder is 0 G and TV-G, 7 TV-Y7, 10 PG and TV-PG, 13 PG-13, 14 TV-14, 17 R and TV-MA. Note that Jellyfin 10.11 rescored TV-PG from 13 to 10, so a ceiling carried over from an older install will not behave the way it used to.

MinCommunityRatingWhenUnrated is a weak gate on obscure titles, where a perfect score can rest on a handful of votes. It is a starting filter, not a verdict.

Curating the result

The rule is a first pass, not an opinion. To remove something, untag it in the Jellyfin UI. The ledger records that the item was tagged once, so the next run sees a deliberate removal and leaves it alone.

Turning off RespectManualUntag makes every run re-apply the rule, which undoes curation. It exists for the case where you want to reset and start again.

Building

Needs the .NET 9 SDK. On a machine where it is a user-local install rather than a system package, it will not be on the PATH of a non-interactive shell:

export DOTNET_ROOT="$HOME/.dotnet"
export PATH="$DOTNET_ROOT:$PATH"
dotnet build Jellyfin.Plugin.Audience/Jellyfin.Plugin.Audience.csproj -c Release

The output is Jellyfin.Plugin.Audience/bin/Release/net9.0/Jellyfin.Plugin.Audience.dll.

Installing

Copy the DLL and meta.json into a folder named Audience_1.0.0.0 inside Jellyfin's plugins directory, then restart the server. The plugin appears under Dashboard, Plugins, and its task appears under Scheduled Tasks as "Repair ratings and apply audience tags".

targetAbi is 10.11.0.0. That is deliberate and is not a mistake on a 12.x server: a plugin built against Jellyfin.Controller 10.11.11 loads and runs on Jellyfin 12.0. Do not raise it without confirming the newer package exists and the result still loads.

Releasing

To publish through a Jellyfin plugin catalog, zip the DLL and meta.json, host the zip, and add a repository manifest entry whose sourceUrl points at it. The sourceUrl must end in .zip, and the checksum field must be the MD5 of the actual published archive. No manifest is committed here, because a manifest carrying a placeholder checksum or a URL with nothing behind it produces a broken install for anyone who adds it.

Licence

GPL-3.0.