Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,53 @@ may change what the API or the configuration means, and says so here.
is made with wants some 4 GB at its last step, more than a Docker VM shared
with other containers may have. A build from source takes the same
variable; CI and the published image are unchanged.
- A **Reading** panel on the profile page: whether the posters of works for
adults are shown as they are or veiled, kept with the account
(`reader.adultPosters`) so the choice follows it from one browser to the
next. The switch in the bar's *Language and reading* menu is the same
choice for whoever is signed in, and stays this browser's for a visitor.
- Turning the WebP or the AVIF copies off, while some are kept, asks whether
to delete them too. `DELETE /api/v1/media/variants/{format}` deletes every
copy of a format once it is off, in the background, which the Media page
follows (`purging` in `GET /api/v1/media/status`).

### Changed

- The WebP copies are off until `media.webp` is set: the pages are given the
pictures themselves — with an AVIF `<source>` over them where the night's
copies reach as wide — and an address of a WebP copy answers the copy kept
or sends to the original, making none. A server that wants the lighter
pages turns them on, on the Media page. `media.variants` prepares the
placeholders, and the first copies only while the WebP copies are on;
`GET /api/v1/auth/options` says whether they are (`webp`).
- The front page no longer offers an administrator to write a programmer's
note there: the administration's page writes it, as do the pages of a
selection, a genre, a network, a year and a decade.
- Radarr's search leaves out the films it could not add, those without a
TMDB id that were not entered by hand. A `tvdb:` term from Sonarr resolves
every id this server gives, those made of a TMDB id or a Fankai one too.
- An administrator's own page among the accounts no longer offers to reset
their password, which the server refuses there, and points to their account
page instead. Closing every session of their own there asks first, since it
ends this one too.

### Fixed

- A work entered by hand reaches Sonarr and Radarr. It is given a number of
its own, from 1,000,000,000 up, worked out from its catalogue id so that it
never changes: Sonarr's searches list it under that number and its lookups
answer it, Radarr takes it as the film's `TmdbId`, and the change
notifications and the seeding find it by it. Fankai's numbers end at
999,999,999. Plex was given such a work whole already: an agent that also
lists Plex's own provider lets Plex guess it by its title, which
[docs/plex.md](docs/plex.md) explains.
- Signing out, or changing one's password, shows a visitor at once: the bar
kept the account's *Admin* until the page was reloaded. So does closing
one's own sessions, or untying one's own account from the identity
provider, from the administration. A session that ends somewhere else — it
runs out, or is closed from another browser or by another administrator —
is noticed at the next request it is refused, or when who is signed in is
next asked: the interface then forgets the account and shows a visitor.

## [0.10.0] — 2026-10-25

Expand Down
8 changes: 8 additions & 0 deletions docs/editing.md
Original file line number Diff line number Diff line change
Expand Up @@ -450,6 +450,14 @@ edited. Its identifiers are what Sonarr and Radarr find it by — TheTVDB's for
for a film — and where its links lead. An identifier that another entry already claims is
refused with a `409`.

Without those identifiers it is found all the same. Sonarr and Radarr find it by its title
and keep it under a number this server makes of its id here, the same for good — see
[Works entered by hand](sonarr-radarr.md#works-entered-by-hand). Plex matches it by its title
and year, under the catalogue's own id; Plex's own provider, when the agent lists it beside
this one, can only guess at it by its title, and lays what it guessed over it — see
[A work entered by hand](plex.md#a-work-entered-by-hand). An identifier the work does have
elsewhere is worth giving it: Plex's provider is then given the work itself.

## Duplicates

Two records of one work turn up in a catalogue now and then: a film entered by hand before a
Expand Down
101 changes: 77 additions & 24 deletions docs/media.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,10 @@ default: nothing is kept until it is asked for.
pictures as they were fetched — JPEG and PNG, whole — at the copies'
addresses when `AMS_PUBLIC_URL` is set, and at the providers' when it is
not, since only this server's own pages can follow a path. This server's
own pages are given smaller copies instead: see below. A bucket's media
go through this server, or, with **Sent to the bucket's own address**, a
reader is redirected to a presigned address good for an hour.
own pages are given the same pictures, or smaller copies once those are
turned on: see below. A bucket's media go through this server, or, with
**Sent to the bucket's own address**, a reader is redirected to a
presigned address good for an hour.
- **By hand.** From a work's page, a picture or a theme can be uploaded —
read for what it is, never for what it is called; SVG is refused, since a
document that can carry a script must not be served from here — and a
Expand Down Expand Up @@ -66,10 +67,10 @@ default: nothing is kept until it is asked for.

A poster kept is two to three thousand pixels tall and a background up to
four thousand wide: what a client that keeps one picture wants, and ten
times what a card or a page draws. So this server's own pages are given
copies, made of each picture at the widths it is drawn at, once and twice
over for a dense screen, never wider than the picture and never wider than
1920 pixels:
times what a card or a page draws. So this server can give its own pages
WebP copies instead, made of each picture at the widths it is drawn at,
once and twice over for a dense screen, never wider than the picture and
never wider than 1920 pixels:

| A picture drawn as | Widths |
|---|---|
Expand All @@ -80,6 +81,14 @@ over for a dense screen, never wider than the picture and never wider than
| an episode's still | 320, 640 |
| a photograph of the cast | 120, 240 |

They are off until somebody turns them on — **WebP copies for the pages**
on the Media page, the setting `media.webp` — and until then the pages are
given the pictures themselves, whole, with the AVIF copies over them where
the night made some (see [AVIF](#avif)): no WebP copy is made. A page
learns which with the site's other options (`webp` in
`GET /api/v1/auth/options`) — the catalogue's pages before they draw
anything — and asks for the right files from its first picture.

- **WebP.** Lossy, at quality 80, through libwebp's encoder — a poster's
colours converted with its sharper method, so that thin lettering keeps
its edges, and a transparency kept exact beside the colours; lossless
Expand All @@ -92,13 +101,22 @@ over for a dense screen, never wider than the picture and never wider than
the original.
- **Made once, when first asked for.** A copy is named by its picture's
hash and its width — `/media/<hash>-480.webp` — and kept beside the
picture in the store, in a directory of its own. The copy a page asks for
first is made as the picture is stored: a poster's 480, a background's
1280. Any other is made the first time a page asks for it — a few hundred
milliseconds for a large background — and served from the store after.
A width no page asks for is refused, so a picture has a dozen copies at
the most. The thumbnails earlier versions made are still served; none is
made any more.
picture in the store, in a directory of its own. While the copies are
on, the copy a page asks for first is made as the picture is stored: a
poster's 480, a background's 1280. Any other is made the first time a
page asks for it — a few hundred milliseconds for a large background —
and served from the store after. A width no page asks for is refused, so
a picture has a dozen copies at the most. The thumbnails earlier versions
made are still served; none is made any more.
- **Turned on.** The pages ask for the copies at once, and the pictures
kept already have theirs made as they are asked for: a catalogue's first
pages after the switch are a little slower to fill.
- **Turned off.** The pages are given the pictures themselves again, and
no copy is made. A page opened before, which still asks for one, is
given the copy where it is kept, and sent to the picture where it is not,
for that time only. The copies made stay in the store, ready should they
be turned back on, until they are deleted: see
[Deleting the copies of a format](#deleting-the-copies-of-a-format).
- **Read safely.** A picture is decoded in Rust, never by libwebp: a
picture past 16,384 pixels a side or 100 million pixels is not decoded
at all, nor one whose pixels would take more than 256 MB, and a decoder
Expand All @@ -121,14 +139,15 @@ proportions before a byte of it is there.

### Preparing what was kept before

Pictures stored before this version have neither a placeholder nor their
first copy. Their copies are made all the same, as pages ask for them; the
task **Prepare the pictures** (`media.variants`, on **Tasks** and on the
Media page, which says how many are left) works out their placeholders and
makes their first copies, a few at a time. It runs only when somebody runs
it — never at start: a catalogue's pictures are hours of decoding on a small
machine. It can be stopped; each picture is noted as it is done, so the
next run takes up where the last one stopped.
Pictures stored before this version have no placeholder, and no first
copy: a page draws them without one, and — while the WebP copies are on —
has their copies made as it asks for them. The task **Prepare the
pictures** (`media.variants`, on **Tasks** and on the Media page, which
says how many are left) works out their placeholders and, while the WebP
copies are on, makes their first copies, a few at a time. It runs only
when somebody runs it — never at start: a catalogue's pictures are hours of
decoding on a small machine. It can be stopped; each picture is noted as it
is done, so the next run takes up where the last one stopped.

### AVIF

Expand All @@ -144,6 +163,13 @@ background's 1920 takes about a second and a half at night. No browser is asked
what it reads: the addresses are fixed, and every browser the interface
supports reads both.

With the WebP copies off, a page offers the same AVIF copies over the
picture itself, by the same rule: where they reach as wide as the role the
picture is drawn in, a browser takes the AVIF of the width it needs rather
than the whole picture, and where they do not, the picture. An AVIF whose
file has gone is answered with the WebP of its width while those are on,
and with the picture while they are off.

Measured on fifty posters from TMDB, on one core of an Apple M-series
processor, at quality 68 and rav1e's speed 6:

Expand All @@ -158,9 +184,36 @@ feature; NASM assembles it on x86_64); a build from source has it with
`cargo build --release --features avif-asm`. Making them takes about 170 MB
of memory at its peak.

### Deleting the copies of a format

Turning the WebP or the AVIF copies off on the Media page while some are
kept asks what becomes of them, and says how many there are and what they
weigh. **Cancel** changes nothing. **Turn off and keep them** turns them
off and keeps the copies: a WebP copy is ready should they be turned back
on, and an AVIF copy is still given to the pages, though no other is made.
**Turn off and delete them** turns them off and deletes every copy of that
format, to have the space back. With none kept, the switch simply turns
off.

The deletion runs in the background, a few hundred copies at a time — each
file taken from the store, then its row — and the Media page says
**Deleting copies now** until it is over. Turned on again meanwhile, the
setting stops it where it is. The pictures themselves are never touched,
nor the squares Plex is given.

Through the API, an administrator's: `DELETE /api/v1/media/variants/webp`
(or `avif`), once that format's setting is off. It answers at once, `202`,
with how many copies there were and what they weighed (`copies`, `bytes`),
and the store's status says `purging` while it runs
(`GET /api/v1/media/status`). It is refused with `409` while the setting
is still on, while the AVIF copies are being made, or while another
deletion runs, and with `503` when no store is configured. Of several
instances, the one asked may not have heard yet that the setting is off:
the Media page asks once more a second later.

The providers' terms still apply to what is kept: TMDB, TheTVDB and
Fanart.tv each say what may be done with their pictures.

The switches of the **Media** page — store, how a bucket's copies are served, the cast's
photographs, the themes, the AVIF copies — are settings: see [Media](settings.md#media) in
the settings reference.
photographs, the themes, the WebP copies, the AVIF copies — are settings: see
[Media](settings.md#media) in the settings reference.
33 changes: 32 additions & 1 deletion docs/plex.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,8 @@ were fetched, JPEG and PNG, never the WebP copies this server's own pages are gi
A rating key names a work by the identifier it is best known by, so that it
survives the work being fetched again, or a new database: a film by TMDB's id,
then IMDb's; a series by TheTVDB's, then TMDB's, then Fankai's; either by the
catalogue's own id when it has none of those, as an entry made by hand may.
catalogue's own id when it has none of those, as an entry made by hand may (see
[A work entered by hand](#a-work-entered-by-hand)).

```
movie-tmdb-603 show-tvdb-81189
Expand Down Expand Up @@ -172,6 +173,36 @@ not see.
A season is matched by its series and its number; an episode by its series and
its season and number, or by the day it aired.

### A work entered by hand

A work [entered by hand](editing.md#entering-a-work-by-hand) is matched by its
title and year like any work the catalogue holds and, given none of the ids a
[rating key](#rating-keys) is made of, keyed by the catalogue's own id:
`show-id-01a17789-…`, `movie-id-…`, and its seasons' and episodes' keys after
it. Plex Media Server takes them like any other — on 1.43.5, matched when the
library is scanned and through *Fix Match*, with its record, its seasons and
episodes and its pictures. Its `Guid` lists the ids it does have elsewhere, and
nothing when it has none.

With nothing to map it by, Plex's own provider cannot know it. An agent that
lists *Plex Series* or *Plex Movie* beside this provider — Plex suggests it for
the cast — has Plex look such a work up in its own catalogue by title and year
alone, and take what it finds: in one test a series entered by hand came back as
a talk show of a title close to its own, whose cast, studio, country, poster and
background were laid over it — and its title too, with Plex's provider listed
first. The item is linked to the work guessed, and an app that follows the link
asks Plex Media Server for a work its library does not hold: its log then says
`Invalid library metadata ID <24 hexadecimal digits> passed.`, the id being
Plex's own for the guess, never one this server hands out. Nothing in this
server's answer stops the guess — Plex maps
another provider's work by IMDb's, TMDB's and TheTVDB's ids alone, and a work
entered by hand has none of them to give.

So give the work the ids it has elsewhere when it has some, and Plex's provider
is given that very work; or keep Plex's own provider out of the agent of a
library that holds works entered by hand. Once it is out, a refresh of the item
in Plex puts this provider's record back.

## What Plex is given

- **Language.** `X-Plex-Language` (a header, else a query parameter), then the
Expand Down
17 changes: 13 additions & 4 deletions docs/settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -630,6 +630,7 @@ On the **Media** page, and read only when a store is configured (`AMS_MEDIA_STOR
| [`media.serve`](#mediaserve) | `proxy` or `redirect` | `proxy` | server |
| [`media.people`](#mediapeople) | `true` or `false` | `true` | server |
| [`media.audio`](#mediaaudio) | `true` or `false` | `true` | server |
| [`media.webp`](#mediawebp) | `true` or `false` | unset | server |
| [`media.avif`](#mediaavif) | `true` or `false` | unset | server |

### `media.store`
Expand All @@ -650,12 +651,20 @@ The cast's photographs too: one per person, many per work.

The themes: a Fan-Kai's, a few megabytes each.

### `media.webp`

Make the WebP copies the pages ask for, at the widths they ask for them — the first as a
picture is stored, any other the first time a page asks — and give the pages those rather
than the pictures themselves. Off until it is set, and the pages are given the originals,
whole; the copies made while it was on are kept until an administrator deletes them. See
[The copies the pages are given](media.md#the-copies-the-pages-are-given).

### `media.avif`

Make an AVIF copy of every poster, at 480 and 960 pixels, and of every background, at 1280,
each night at 03:00 UTC — the task `media.avif`, also run by hand from **Tasks**. A third
lighter than the WebP the interface is given otherwise, and a second or two of a processor
each to make: off until it is set. See [AVIF](media.md#avif).
Make an AVIF copy of every poster, at 480 and 960 pixels, and of every background, at 1280
and 1920, each night at 03:00 UTC — the task `media.avif`, also run by hand from **Tasks**.
A third lighter than a WebP copy of the same width, and a second or two of a processor each
to make: off until it is set. See [AVIF](media.md#avif).

## Cache

Expand Down
Loading
Loading