JR-004, JR-005, JR-006: scene-scoped read path
Presence was decided by a LINQ predicate inline in the controller, so the semantics SR-002 sets were nowhere stated in code -- the read path complied by accident rather than by requirement. PresenceLookup is now the unit that decides, tagged, with the reasoning next to it. JR-004: windows are served exactly as given. UT-021 pins that [0,10] and [10,20] are not merged despite looking mergeable -- two windows mean a genuine departure and return, and collapsing them answers a different question from the one the truth file asked. UT-022 pins a byte-identical round trip. JR-005: bounds inclusive at both ends, zero-length windows are real sightings rather than degenerate ones to discard, overlaps resolve. The wording was the larger half of JR-005. The overlay rendered a bare list: it asserted nothing, but told the viewer nothing either, and the default reading of a paused frame is "these people are on screen" -- exactly what SR-002 forbids. It now carries an "In this scene" heading. ActorAtTime became ActorInScene, and README no longer contains "on screen" anywhere; it stated the forbidden reading outright in seven places, including the opening sentence. JR-006: measured rather than assumed. UT-023 builds 50 actors x 1000 windows and asserts the response is bounded by actor count, never window count. The lookup is a full scan on purpose -- an early exit on `start > t` would exploit the sortedness the format requires, but would silently under-report the moment one producer emitted windows out of order. UT-020 pins that unsorted input still resolves; WindowsAreSorted is a diagnostic, not a correctness dependency. Third mutation check: making the end bound exclusive fails UT-016 and UT-018 and nothing else. One character turns an inclusive window into a half-open one, dropping an actor at exactly the moment a scene ends. TRACES: UT-016, UT-017, UT-018, UT-019, UT-020, UT-021, UT-022, UT-023 TRACES: JR-004, JR-005, JR-006 | SR-002 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,11 +1,11 @@
|
||||
# JRay
|
||||
|
||||
A Jellyfin plugin that brings an actor-overlay (think Amazon "X-Ray") feature to your media: pause a movie and JRay shows you which actors are on screen at that exact moment.
|
||||
A Jellyfin plugin that brings an actor-overlay (think Amazon "X-Ray") feature to your media: pause a movie and JRay shows you which actors are in the scene you paused in.
|
||||
|
||||
JRay reads "truth" files produced offline by the
|
||||
[scene-actor-extraction](https://github.com/dtourolle/scene-actor-extraction)
|
||||
pipeline (face detection + recognition) and exposes an API to query which actors
|
||||
are visible at a given timestamp. A small overlay, injected into the Jellyfin web
|
||||
are present in the scene at a given timestamp. A small overlay, injected into the Jellyfin web
|
||||
client, displays the result when you pause playback.
|
||||
|
||||
## Status
|
||||
@@ -47,7 +47,7 @@ patch on startup, so there is nothing to clean up by hand. See
|
||||
## Features
|
||||
|
||||
- **Pause overlay** — pause a movie or episode in the web client and see the
|
||||
actors currently on screen, without leaving the player.
|
||||
actors in the current scene, without leaving the player.
|
||||
- **Sidecar truth files** — drop a `Movie.jray.json` next to `Movie.mkv` and JRay
|
||||
picks it up automatically (suffix configurable).
|
||||
- **Remote truth push** — for servers that can't run the extraction pipeline
|
||||
@@ -82,12 +82,12 @@ patch on startup, so there is nothing to clean up by hand. See
|
||||
```
|
||||
|
||||
1. The extraction pipeline analyses a film offline and emits a **truth file**
|
||||
listing each detected actor and the time windows they're on screen.
|
||||
listing each actor and the time windows they are present in the film.
|
||||
2. JRay loads that truth file either from a **sidecar** next to the media
|
||||
(`Movie.jray.json`) or from a **managed store** populated via the push API.
|
||||
3. On startup JRay injects a small `<script>` into the web client's `index.html`.
|
||||
When you pause, the script calls JRay for the current item and timestamp and
|
||||
renders the on-screen actors as an overlay.
|
||||
renders the scene's cast as an overlay.
|
||||
|
||||
## Truth File Format
|
||||
|
||||
@@ -112,8 +112,11 @@ configurable). Schema (`schema_version: 1`, minimal verbosity):
|
||||
}
|
||||
```
|
||||
|
||||
An actor is considered visible at timestamp `t` (seconds) if any of their
|
||||
`scenes` windows satisfies `start <= t <= end`. JRay prefers `jellyfin_id` (a
|
||||
An actor is present at timestamp `t` (seconds) if any of their `scenes` windows
|
||||
satisfies `start <= t <= end`. **A window is a claim about scene membership, not
|
||||
a recognition event** — an actor who has turned away or is off-camera during a
|
||||
reverse shot is still present, and two windows mean a genuine departure and
|
||||
return rather than a break in detection. JRay prefers `jellyfin_id` (a
|
||||
Jellyfin Person GUID) when present, otherwise resolves `imdb_id`/`tmdb_id`
|
||||
against the item's People `ProviderIds`.
|
||||
|
||||
@@ -127,7 +130,7 @@ the `X-Emby-Token: <token>` header or `Authorization: MediaBrowser Token="<token
|
||||
|
||||
| Method & Route | Auth | Description |
|
||||
| --- | --- | --- |
|
||||
| `GET /Items/{itemId}/jray?t={seconds}` | user | "Context at time t" envelope (on-screen actors), or `404`. |
|
||||
| `GET /Items/{itemId}/jray?t={seconds}` | user | "Context at time t" envelope (the scene's cast), or `404`. |
|
||||
| `GET /Items/{itemId}/Timeline` | user | Full truth file for an item, or `404` if none. |
|
||||
| `PUT /Items/{itemId}/Truth` | admin | Push managed truth data (schema v1). `204` on success, `400` on bad schema. |
|
||||
| `DELETE /Items/{itemId}/Truth` | admin | Remove managed truth data (idempotent, `204`). Falls back to sidecar. |
|
||||
@@ -148,12 +151,12 @@ the `X-Emby-Token: <token>` header or `Authorization: MediaBrowser Token="<token
|
||||
|
||||
JRay is designed so that *any* Jellyfin client (not just the bundled web overlay)
|
||||
can build an actor-overlay feature. The integration is two calls: figure out
|
||||
**what is playing and where**, then ask JRay **who is on screen**.
|
||||
**what is playing and where**, then ask JRay **who is in the scene**.
|
||||
|
||||
### 1. Query on-screen actors: `GET /Items/{itemId}/jray?t={seconds}`
|
||||
### 1. Query the scene's cast: `GET /Items/{itemId}/jray?t={seconds}`
|
||||
|
||||
Given a Jellyfin item id and a playback position in **seconds**, returns the
|
||||
actors visible at that timestamp. This is the only call most clients need.
|
||||
the actors in that scene. This is the only call most clients need.
|
||||
|
||||
**Request**
|
||||
|
||||
@@ -177,7 +180,7 @@ X-Emby-Token: <user-or-api-token>
|
||||
}
|
||||
```
|
||||
|
||||
- `actors` may be an **empty array** when no one is on screen at `t` — that's a
|
||||
- `actors` may be an **empty array** when no one is in the scene at `t` — that's a
|
||||
`200`, not a `404`.
|
||||
- `404 Not Found` means the item has **no truth data at all** (no managed upload
|
||||
and no sidecar file). Treat this as "JRay isn't available for this item" and
|
||||
@@ -193,7 +196,7 @@ X-Emby-Token: <user-or-api-token>
|
||||
|
||||
Returns the complete truth file (the [schema above](#truth-file-format)) — every
|
||||
actor with all their scene windows. Use this if you'd rather fetch once and
|
||||
compute "who's on screen" client-side (e.g. to drive a scrubber-bar heatmap)
|
||||
compute "who is in the scene" client-side (e.g. to drive a scrubber-bar heatmap)
|
||||
instead of polling `jray?t=` on each pause. `404` if no truth data exists.
|
||||
|
||||
### Reference implementation (web client)
|
||||
@@ -212,7 +215,7 @@ var s = sessions[0];
|
||||
var itemId = s.NowPlayingItem.Id;
|
||||
var t = (s.PlayState.PositionTicks || 0) / 10000000; // ticks → seconds
|
||||
|
||||
// 2. Ask JRay who is on screen. ApiClient adds the auth token for you.
|
||||
// 2. Ask JRay who is in the scene. ApiClient adds the auth token for you.
|
||||
var ctx = await ApiClient.ajax({
|
||||
url: ApiClient.getUrl('Plugins/JRay/Items/' + itemId + '/jray', { t: t }),
|
||||
type: 'GET', dataType: 'json'
|
||||
@@ -355,7 +358,7 @@ Jellyfin.Plugin.JRay/
|
||||
`<script>` tag in the web client's `index.html`, marked with `<!-- jray-overlay -->`
|
||||
so it's idempotent. Re-applied whenever configuration changes.
|
||||
5. **Overlay Script** (`jray-overlay.js`): listens for the player's pause event,
|
||||
calls `jray?t=`, and renders the on-screen actors.
|
||||
calls `jray?t=`, and renders the scene's cast.
|
||||
|
||||
## Important Notes
|
||||
|
||||
|
||||
Reference in New Issue
Block a user