using System.Collections.Generic; using Jellyfin.Plugin.JRay.Models; namespace Jellyfin.Plugin.JRay.Services; /// /// Answers "which actors are in the scene at time t" from a truth file. /// /// /// This is the unit that decides presence, so the scene-scoped semantics live /// here rather than being spread through the controller. /// /// /// A window is a claim about scene membership, not a recognition event. /// An actor who turns away, is occluded, or is off-camera while the shot cuts to /// whoever they are speaking to is still present. Two windows mean a genuine /// departure and return, not a break in detection — so this code reads windows /// exactly as given and never merges, splits, trims, or reorders them. /// /// /// Bounds are inclusive at both ends, matching the format's definition. That /// makes adjacent windows such as [0,10] and [10,20] both contain /// t = 10; reporting the actor present once is correct, and is not a /// reason to merge the windows. /// /// // TRACES: JR-004, JR-005, JR-006 | SR-002 public static class PresenceLookup { /// /// Determines whether an actor is present in the scene at . /// /// The actor entry from a truth file. /// The timestamp, in seconds. /// true when any window contains . public static bool IsPresentAt(TruthActor actor, double t) { if (actor is null) { return false; } // A full scan, deliberately: windows may be numerous, but correctness // must not depend on the producer having honoured the sortedness // guarantee. An early exit on `start > t` would be faster and would // silently under-report the moment one file arrived out of order — // trading a correctness risk for a saving that does not matter at this // scale (see JR-006). foreach (var window in actor.Scenes) { if (window.Length == 2 && window[0] <= t && t <= window[1]) { return true; } } return false; } /// /// Lists the actors present in the scene at , in the /// order the truth file lists them. /// /// The truth file. /// The timestamp, in seconds. /// The actors whose windows contain . public static IEnumerable ActorsPresentAt(TruthFile truth, double t) { if (truth is null) { yield break; } foreach (var actor in truth.Actors) { if (IsPresentAt(actor, t)) { yield return actor; } } } /// /// Determines whether an actor's windows are sorted by start time, as the /// truth-file format requires of producers. /// /// /// Presence lookup does not depend on this — it is a diagnostic. A file that /// fails it is still read correctly, but it signals a producer bug worth /// surfacing rather than absorbing silently. /// /// The actor entry from a truth file. /// true when every window starts at or after its predecessor. public static bool WindowsAreSorted(TruthActor actor) { if (actor is null) { return true; } double previousStart = double.NegativeInfinity; foreach (var window in actor.Scenes) { if (window.Length != 2) { continue; } if (window[0] < previousStart) { return false; } previousStart = window[0]; } return true; } }