Replaces the v1 shape rather than accepting both. `anneal_sec` and the top-level `sample_fps` are deleted, not zeroed; `extraction` and `cut` blocks arrive; `scenes` become objects carrying belief and route, so a window records how far to trust it instead of being a bare float pair. `TruthSchema.IsSupported` is the single gate and is applied on all four read paths — sidecar, managed store load, managed PUT, and converted manifest. Previously only the controller checked, so the version the plugin claimed to require and the one it would actually parse were free to drift. Rejections name the file and the version found, so an item that looks empty is distinguishable from one that was refused. `ManifestConverter` carries belief, route and both provenance blocks through: dropping them would silently downgrade every fetched manifest against a locally extracted one. TRACES: JR-002, JR-003 | SR-003
120 lines
4.0 KiB
C#
120 lines
4.0 KiB
C#
using System.Collections.Generic;
|
|
using Jellyfin.Plugin.JRay.Models;
|
|
|
|
namespace Jellyfin.Plugin.JRay.Services;
|
|
|
|
/// <summary>
|
|
/// Answers "which actors are in the scene at time <c>t</c>" from a truth file.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// This is the unit that decides presence, so the scene-scoped semantics live
|
|
/// here rather than being spread through the controller.
|
|
///
|
|
/// <para>
|
|
/// <b>A window is a claim about scene membership, not a recognition event.</b>
|
|
/// 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.
|
|
/// </para>
|
|
/// <para>
|
|
/// Bounds are inclusive at both ends, matching the format's definition. That
|
|
/// makes adjacent windows such as <c>[0,10]</c> and <c>[10,20]</c> both contain
|
|
/// <c>t = 10</c>; reporting the actor present once is correct, and is not a
|
|
/// reason to merge the windows.
|
|
/// </para>
|
|
/// </remarks>
|
|
// TRACES: JR-004, JR-005, JR-006 | SR-002
|
|
public static class PresenceLookup
|
|
{
|
|
/// <summary>
|
|
/// Determines whether an actor is present in the scene at <paramref name="t"/>.
|
|
/// </summary>
|
|
/// <param name="actor">The actor entry from a truth file.</param>
|
|
/// <param name="t">The timestamp, in seconds.</param>
|
|
/// <returns><c>true</c> when any window contains <paramref name="t"/>.</returns>
|
|
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 is not null && window.Start <= t && t <= window.End)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Lists the actors present in the scene at <paramref name="t"/>, in the
|
|
/// order the truth file lists them.
|
|
/// </summary>
|
|
/// <param name="truth">The truth file.</param>
|
|
/// <param name="t">The timestamp, in seconds.</param>
|
|
/// <returns>The actors whose windows contain <paramref name="t"/>.</returns>
|
|
public static IEnumerable<TruthActor> ActorsPresentAt(TruthFile truth, double t)
|
|
{
|
|
if (truth is null)
|
|
{
|
|
yield break;
|
|
}
|
|
|
|
foreach (var actor in truth.Actors)
|
|
{
|
|
if (IsPresentAt(actor, t))
|
|
{
|
|
yield return actor;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Determines whether an actor's windows are sorted by start time, as the
|
|
/// truth-file format requires of producers.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// 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.
|
|
/// </remarks>
|
|
/// <param name="actor">The actor entry from a truth file.</param>
|
|
/// <returns><c>true</c> when every window starts at or after its predecessor.</returns>
|
|
public static bool WindowsAreSorted(TruthActor actor)
|
|
{
|
|
if (actor is null)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
double previousStart = double.NegativeInfinity;
|
|
foreach (var window in actor.Scenes)
|
|
{
|
|
if (window is null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (window.Start < previousStart)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
previousStart = window.Start;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|