Repository navigation
Releases: Dim145/plex-auto-genres
Release list
v2.7.0 — arr-metadata-server by key
arr-metadata-server 0.8.1 lets its Plex provider ask for keys (AMS_PLEX_AUTH=apikey)
without shutting Plex out: Plex is given the provider's address with a key at its end,
…/plex/<key>. This release follows it: this app can now be let in by a key as well as by
its address.
Setting it up
With AMS_PLEX_AUTH=apikey on the server:
- Under Keys & network, issue this app a key of its own, one that only reads.
- Set it as
ARR_METADATA_API_KEY. The address of the Plex panel,…/plex/<key>,
works asARR_METADATA_URLtoo, but the server's guide keeps that key for Plex alone:
revoking it would shut both out. - Config › Checks says whether the server takes it.
The key travels in an X-Api-Key header, never in an address. An address carrying one is
shown with /plex/*** — in the console, the doctor and the logs — and so is a key in its
query. A key carries its own settings on the server, the language the genres come in among
them.
A server that lets callers in by address, its default, needs nothing new.
What else changes
- A refusal says which way in failed, and which setting to change: a key missing, or
one the server does not know; an address not on its list, or a key switched off or run
out; a key whose account is no longer active. A refusal the server did not send — from a
reverse proxy in front of it, and the password it asks for — is now said to come from
there, rather than blamed on the server. ARR_METADATA_URLandARR_METADATA_API_KEYnaming two different keys are refused, with
the reason, by the libraries that read fromamsand by the doctor.
Upgrading
Pull and restart: nothing to change.
v2.6.0 — your own catalogue as a source
A library can now read its genres from an arr-metadata-server: the self-hosted
metadata server that answers Sonarr, Radarr and Plex from a catalogue of its own, in which
every correction locked by hand wins over TMDB, TheTVDB and the arr services. Those
corrections now reach the genres this app writes. It holds films, series and anime, so any
library can read from it, alone or ahead of the others.
Setting it up
ARR_METADATA_URLis the server's address as this app reaches it — two containers
on one Docker network reach it by name,http://arr-metadata-server:8080. The address
its Keys & network page gives Plex, ending in/plex, works too.- Let this app in. The server's Plex provider admits callers by address: add the
address the server sees this app call from under Keys & network › Allowed
addresses, beside Plex's. A server that asks for a key instead
(AMS_PLEX_AUTH=apikey) takes one issued there inARR_METADATA_API_KEY. - Add
amsto a library's sources. In the console, Reads from is now an ordered
list: move, drop or add each source. Config › Checks asks the server and says what
is still in the way — a refused address, a host name it does not answer to, a provider
switched off, a redirect.
{ "library": "Films", "type": "standard-movie", "providers": ["ams", "tmdb"] }An item is asked for by the ids Plex gives it — a film by its TMDB or IMDb id, a series by
its TheTVDB or TMDB id, an item matched by the server's own agent by the key that agent
gave it — and by title and year otherwise. Asking keeps the work in the server's
catalogue, as when Plex asks, so a title search takes a match only with the item's title,
accents aside, from a year either side. An item with no year, or a work found under
another of its titles, is left to the next source, or to a binding.
What else changes
- A source that refuses this app — an address the server does not let in, a wrong
TMDB key — sits out the rest of the run after saying why, once in the log and on the
run's report. It used to be asked again for every title, and its titles were failed and
kept behind the retry backoff. A title the other sources all miss now waits for the next
run; in a library that merges its sources, a title that needs the refusing one waits
too, without the others being asked for it. - TMDB asks for an item as what Plex holds it as. In an anime library that is a Plex
film section, it looked among its series, so a film's id could fetch another work. - Bindings: the picker searches as what Plex holds the item as, an anime library
readingamstakes TheTVDB and IMDb pins alike, and AniDB pins are offered only where
Jikan or AniList can read them. - Stopping a run — a cancel, or the give-up — no longer loses an item whose write was
already with Plex: it finishes, and is counted and cached. A server stop arriving right
after a job was cancelled no longer hangs.
Upgrading
Pull and restart: nothing to change unless you want the new source.
A run asks again only for what changed on this side. Titles already tagged keep their
genres until they are asked again — Forget them in the library browser, or run the
library with --force — which matters in two cases:
- an anime library that is a Plex film section and reads TMDB, to have its films read as
films; - a correction locked in arr-metadata-server after a title was tagged, or a title another
source answered while arr-metadata-server refused this app.
v2.5.0 — the database lives beside the config
The state database moves out of /logs into /config, beside config.json. Upgrading
is a pull and one start with the old /logs mount still in place; after that the mount
can go.
Why
The database holds the only copy of the bindings and the genres decided by hand. It
lived in /logs: a directory whose name says it can be thrown away, and a second volume
to get right. A /logs mounted from the host's /tmp came back empty after a reboot,
and the app opened a new, empty database there without a word. Every binding and every
decision was gone, while config.json and its rules survived.
What changes
- The database is
plex-auto-genres.dbbeside the config file:
/config/plex-auto-genres.dbin the image,config/plex-auto-genres.dbnext to
config/config.jsonoutside Docker.--db/PAG_DBstill point anywhere. /configis the only volume the compose file needs (/postersstays optional). It
holds everything worth keeping, so it is the one to back up.- The container checks at every start that
/configwill survive, and warns when it
will not: onlyconfig.jsonmounted into it, a tmpfs, or a directory under the host's
/tmp.
Upgrading
Stop the old container, then start the new one with your /logs mount still in place.
The database moves to /config on its own. It is copied through SQLite, so whatever the
last run left in its WAL comes along, then checked, and the old file is kept as
plex-auto-genres.db.moved. The log says:
Moved the state database from /logs/plex-auto-genres.db to /config/plex-auto-genres.db, beside the config; nothing is written to /logs any more. The old file is kept as plex-auto-genres.db.moved.
Then remove the /logs line from your compose file, and PAG_DB if a tool added it
(2.4's own default is read as unset).
The start stops, with the reason, rather than go on with an empty database when:
- the old database is still open, because the previous container is still running;
- it cannot be read or copied, or the copy does not check out. SQLite writes beside a
database to open it, so a read-only/logscounts; - a file at
/config/plex-auto-genres.dbis not one of this app's databases; /configwould not keep it (see above).
An empty database already in /config, from a doctor run before /logs was mounted
for instance, gives way. One that holds anything is never touched: the log names both,
with what each holds, and how to keep the older one.
To go back to 2.4 afterwards, set PAG_DB=/config/plex-auto-genres.db.
v2.4.1 — replaced and ignored tags come off
A fix for libraries that replace their genres. Upgrading is a pull, followed by one
forced run of each library that uses "Replace existing genres".
Replaced and ignored tags stayed in Plex
With "Replace existing genres" (clearGenres) on, names covered by a rename or an
ignore rule could stay on an item after a run. The rules were applied; the write was
not what it should have been.
Plex's library listing returns only the first few tags of each item, typically two
genres of four, and every write was planned from that listing. The tags to take off
were therefore only the ones it showed: every older tag past the cut stayed on the
item, which is exactly where a renamed or ignored name tends to sit once a library
writes keywords. An item whose visible tags already matched was even reported as
unchanged.
A write that takes tags off now reads the item's own page first. That covers
"Replace existing genres", names refused by hand, and names added by hand. A write
that only adds does not need to, because Plex appends: merging libraries and rating
collections cost no extra request.
The Genres… dialog reads the item's full list too. The list it showed came from
the same listing, and fixing an item's genres from it would have dropped the ones it
did not show.
After upgrading
Items written by an earlier version keep their hidden tags until they are written
again, and the cache has no reason to rewrite them. Run each affected library once
with Force (the console's Run menu), or:
plex-auto-genres run --library "<library>" --forcev2.4.0 — genres decided by hand, and bindings that find their item
Two changes: genres you decide by hand, and bindings by title that actually apply.
Upgrading is a pull.
Genres decided by hand
Any item can now have its genres (or collections) decided by hand, and nothing
automatic undoes the decision until you hand the item back. There are three
instructions, the same ones Kometa
uses for this job:
- Always write: added whatever the sources say, even when they have nothing for
the title. - Never write: kept off even when a source returns it, and taken off the item if
it is already there. - Set exactly: the sources are no longer asked, and the genres are exactly yours.
In a library that writes collections this is Stop asking: collections are never
cleared, so the names you add are written, the ones you refuse are taken off, and
the item's other collections stay.
In the console: Genres… on any item of a library, a By hand filter, and an
Overrides page (formerly Bindings) listing every pin and every decision. From the CLI:
plex-auto-genres manual Anime "Monster" --add Psychological --remove Kids --note "the sources lump it in"
plex-auto-genres manual Anime "Monster (2004)" --lock --add Psychological --add Thriller
plex-auto-genres manuals --library Anime
plex-auto-genres unmanual Anime "Monster"Worth knowing:
- A refused name does not use up one of the
maxGenresslots, and a name you add is
never cut by the cap. - A decision is part of what the cache remembers about the item. It applies on the
next run, even when it was saved while a run was going, and editing only its note
asks the sources nothing. - A name matches a tag with or without
PLEX_COLLECTION_PREFIX, so you can refuse a
tag the app wrote, one you made, or a star-rating collection alike. The
rating-collections pass honours refusals too. - Handing an item back asks the sources again from the next run. Where a library
merges rather than replaces (noclearGenres, and every collections library), the
names a decision added stay on the item like any other tag: refuse them first to
take them off. - The API is
GET/PUT/DELETE /api/v1/manual.
bind by title applies now
bind stored the typed title as the item's key, while items are looked up by the GUID
Plex gives them. So bind Lib 'Some Title' tmdb 123 said "bound" and was never
applied, and failures suggested exactly that command.
bind and unbind now find the item the way manual does. Give a title as Plex shows
it (add the year when two items share it), or the item's key as the console and
failures show it, such as mal://1234. A title no run has seen is refused rather
than stored and ignored.
- Pins no longer drop the item's cached result: they are part of its fingerprint. A
pin saved while a run is under way is no longer lost, and the item keeps its name in
the lists. failuresshows each item's key, suggests an id scheme the library can read, and
still lists when the config does not load.failures --retryand the console's
retry keep the rows they reset.
If you ever ran bind with a title before this version, that pin was probably
never applied. doctor now lists pins on keys no run has seen, and bindings marks
them. Remove one with unbind and that exact key, then bind the item again by title.
Upgrading
The database gains one table, for decisions, when it is first opened. The first run after
the upgrade re-resolves each item that has a pin, once, since pins are now part of its
cache fingerprint.
v2.3.0 — keywords as a preference, and a tag threshold you can set
Three changes to what a keyword-tagged library writes, from a merging anime library in
use. Upgrading is a pull.
useKeywords is a preference now, not an ultimatum
It meant keywords instead of genres, and nothing at all when the source had no keyword
for that title. AniList counts only the tags its community agrees on strongly enough,
and a great many TMDB entries carry no keyword, so each source went quiet on a large
share of titles. Where both did, the title was tagged with nothing and recorded as a
failure under backoff.
It now reads as a preference: the finer vocabulary where a source has one, that
source's own genres where it does not. A merging library writes something for every
title again, from both of its sources.
AniList's tag threshold is yours to set
AniList shows a tag with the share of its community that agreed with it. Only tags at
70% or more were written, a figure hard-coded with nothing anywhere to say so — which is
why a tag plainly visible on AniList's own page could fail to reach Plex.
ANILIST_TAG_RANK="20" # default 70Lower it to take much more of what AniList knows, and accept the noise that comes with
it. It joins the settings fingerprint when it is not the default, so changing it re-tags
the libraries that read AniList.
A failure message that pointed at the wrong thing
"returned no usable genres (all filtered by your ignore rules?)" was shown even when the
rules were innocent. When they really are the cause it now says so as a fact, with how
many names they dropped. To see what a source returns for one title without running a
pass:
plex-auto-genres query "<title>" --type anime --keywordsAlso
The example config and several docstrings use invented names for their examples. Two
genre posters nothing referred to are gone; a collection with no poster file is
reported as missing and skipped, as always.
v2.2.2 — AniList paced at what it actually serves
AniList refusing a large library turned out to be our fault, not its rate limit.
Upgrading is a pull; nothing to configure.
AniList was being asked three times too fast
Its documentation says 90 requests a minute. The API has been "in a degraded state" for
a long time and actually serves 30, which is what its X-RateLimit-Limit header
reports on every response. Our limiter was built for the documented figure, so a library
reading AniList was paced three times over what it would accept, and got refused for it.
It now starts at what AniList serves, and every response retunes the pacing — up or
down — for any source that advertises a limit and documents the window that figure
counts over. The day AniList is restored to 90, the next run speeds back up on its first
answer, with no release and no setting to change.
Expect AniList to be slower than before: one request every two seconds, answering,
instead of a faster one being turned away. Falling back, it is only asked about the
titles MyAnimeList missed, so a run barely changes length. Merging, it is asked about
every title and it will set the pace.
No API key would have helped
The question that started this was whether an AniList key could raise the limit, the way
TMDB_API_KEY unlocks TMDB. It could not. AniList's OAuth exists to read and write a
user's own lists, not to buy throughput, and a rate-limit raise is a manual request its
maintainers say they are not currently accepting.
Jikan and TMDB were checked too, and are right as they are
Both were verified against their live endpoints and their documentation:
| Source | Sends a limit header | Paced at |
|---|---|---|
| AniList | yes, per minute | what it reports, 30 today |
| Jikan | no | 3 a second, 60 a minute |
| TMDB | no | 40 a second |
Jikan answers with cache headers alone and documents exactly the figures we use. TMDB
retired its old cap in 2019 and now documents "somewhere in the 40 requests per second
range", with nothing on the wire and an instruction to respect the 429 — which this
tool has always done. Neither had anything to adapt.
Worth knowing when Jikan refuses anyway, from its own documentation: "It's still
possible to get rate limited from MyAnimeList.net instead." No pacing on this side
changes that; a second source does.
One hardening
Reading any header called X-RateLimit-Limit and assuming it means "per minute" is a
guess. The same name counting over ten seconds would pace us six times too fast, exactly
when a service is asking for less. A source now declares the window its header counts
over, and the header is read only from a source that has said what it means.
v2.2.1 — what a review and a tool pass found in 2.2.0
A correction release: a full review of everything since 2.1.1, plus semgrep, bandit,
gitleaks, trivy, hadolint, shellcheck, actionlint and the dependency audits over the
repository. Fifteen defects, all from 2.2.0, all fixed. Upgrading is a pull.
If you run TMDB in a language other than English, upgrade
2.2.0 compared genre names on ASCII letters and digits alone, so a genre written in
Japanese, Cyrillic, Greek or Korean reduced to nothing and was dropped. With
TMDB_LANGUAGE set to one of those, every title in the library lost its genres and
was cached as a permanent failure, under a message blaming ignore rules you never
wrote. Letters are now whatever the writing system calls them.
Those cached failures are still sitting behind their retry backoff. Clear them, and only
them, then run again:
plex-auto-genres failures --library "<your library>" --retryIf you have manual bindings, upgrade
The migration that widened bindings to one id per source ran as a script, where every
statement commits on its own. An interruption between dropping the old table and
renaming the new one — a container restart is enough — lost every pinned id in
silence, and a leftover scratch table made the database refuse to open at all. It is
one transaction now, and it cleans up after an interrupted attempt.
A pinned id is no longer overridden by a guess
The rule that a hand-pinned title is only ever asked of the sources that can read its id
was written into the merge path alone. In the default mode, a pinned source that was
rate limited handed the title to the next source, which searched by name and wrote back
exactly the match the binding existed to override. The rule now holds in both modes, and
a pin no configured source can read says so instead of caching the title as dead.
The rest, in the order a title meets them
- An answer carrying no genres stopped the fallback chain, so a title whose AniList tags
were all below the agreement threshold failed for "no usable genres" without the
sources below it ever being asked. - A merge that lost a source to a transient error was cached as complete, freezing that
title on a subset of its sources for good. It is deferred and completed next run. - A merged result took its rating from whichever source had one, which could write one
record's score beside another record's id. - The parallel merge discarded answers already in hand when one source failed in an
unexpected way, and the connection pool was sized for one request per title while a
merge asks every source at once — enough to time out and stand a healthy source down. - The Plex writer still compared tags by case alone, so on a collection library the
duplicate collections 2.2.0 set out to remove were appended straight back. - The bindings page removed every pin on an item when asked to remove one row, and the
config editor kept a merge mode alive after hiding its control. - Two rename rules that differ only in punctuation cancelled each other in silence; that
is refused now, with both keys named.
Also
TMDB_LANGUAGE joins the settings fingerprint, since it decides the very strings
written to Plex: if yours is not en-US, this release re-tags those libraries once, on
purpose. doctor warns when useKeywords sits behind a source that has no keywords and
will answer first.
From the tools: semgrep found two shell injections in the release workflow, where a ref
name reached a shell through interpolation; those values go through the environment now.
Trivy found six advisories in the image, all against pip, which nothing at runtime
invokes — it is gone from the final stage, with 6.7 MB and a package installer. Bandit,
gitleaks over the tree and the whole history, hadolint, shellcheck, actionlint,
pip-audit and pnpm audit came back clean, and semgrep now reports nothing anywhere in
the repository.
v2.2.0 — merge several sources, AniList tags, and one collection per genre
Three changes to what ends up written on a title, all from a library in use. Upgrading
is a pull: the defaults are the old behaviour, and no cached item is invalidated by the
upgrade itself.
Several sources at once, not just one after another
providers has always been a preference order. providerMode now says what that order
means:
| Mode | What a title costs | What it returns |
|---|---|---|
fallback (default, and what 2.1 did) |
one request, until a source answers | the first answer |
merge |
one request per source | every source's genres, pooled |
{ "library": "Animes", "type": "anime",
"providers": ["jikan", "anilist", "tmdb"], "providerMode": "merge",
"overrides": { "maxGenres": 10 } }In the console it is a switch under the source presets, and with it on, the TMDB
fallback reads "…and TMDB" rather than "…then TMDB", because that is what it now does.
Merging is worth a cap — three sources can hand back thirty names for one title. One
rule is worth knowing: a title you bound by hand is only asked of the sources that can
read the id you pinned. The others would search by title and merge back the very match
the binding exists to override.
Keywords are a property of the source, not of the library type
useKeywords meant TMDB's keywords. AniList's community tags are the same idea, and
its genre list is a dozen broad buckets that say little across a whole library. AniList
now returns its tags instead of those genres when keywords are asked for, still
filtered to high-agreement, non-spoiler tags.
So the rule is no longer about the library type: useKeywords needs a source that has
keywords — TMDB or AniList — and the error says which sources the library actually
reads. MyAnimeList has none, its themes and demographics being part of the genres it
returns already.
One genre, one collection
Two sources spell one idea differently and Plex grows two collections: Boys Love
beside Boys' Love. Genre names are now compared on letters and digits alone, so
case, accents, punctuation and spacing stop splitting a genre in two. The first
spelling a source returned is the one written, and a rename rule reaches every spelling
of its key — one "sci fi" line catches Sci-Fi, SciFi and sci fi.
Words that genuinely differ cannot be guessed, so a localized source beside
English-only ones still needs a line each:
"defaults": { "anime": { "replace": { "comedie": "Comedy" } } }One line covers Comédie, comedie and COMÉDIE. The other way out is to stop the
mismatch at the source: TMDB_LANGUAGE=en-US makes TMDB agree with MyAnimeList and
AniList, which are English-only.
Items already in the cache keep the tags they have — the upgrade deliberately does not
invalidate them. To re-tag a library with the folding applied, run it once with
--force; collections left empty afterwards are Plex's to tidy.
v2.1.1 — TMDB keywords reach an anime library that falls back to TMDB
One correction, reported from the console: useKeywords was refused on any anime
library, so the option was hidden in the library editor and rejected on save.
That rule was right while nothing anime-shaped could produce keywords. It went out of
date in 2.1.0, which let an anime library end its chain with TMDB as a last resort —
and TMDB has keywords.
The rule now follows the sources rather than the library type. useKeywords needs
tmdb among the library's providers, whatever that library is, and the error names the
sources it does read instead of talking about types. In the console the toggle appears
whenever TMDB is in the chain:
{ "library": "Animes", "type": "anime", "providers": ["jikan", "anilist", "tmdb"],
"useKeywords": true }It covers exactly the titles TMDB itself answered for. The anime sources above it are
untouched, and they still return their own genres. TMDB can return 50 keywords for one
title, so pair it with overrides.maxGenres — doctor warns when you have not.
Removing TMDB from the chain clears the flag with it, in the editor, so the pair that
cannot work is not one you can build by hand.
Nothing else changes. No configuration that was valid before becomes invalid, and
upgrading is a pull.