using System; using System.Threading; using System.Threading.Tasks; using Jellyfin.Plugin.JRay.Models; using Jellyfin.Plugin.JRay.Services; using Jellyfin.Plugin.JRay.Services.Interfaces; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; namespace Jellyfin.Plugin.JRay.Controllers; /// /// Exposes scene-actor-extraction "truth" data: which actors are present in /// the scene at a given timestamp. /// /// /// Presence is scene-scoped, not instantaneous: a window is a claim about /// scene membership, not a recognition event, so an actor who is off-camera /// during a reverse shot is still present. Windows are served exactly as stored /// — never merged, split or trimmed. /// [ApiController] [Route("Plugins/JRay/Items/{itemId}")] [Authorize] // TRACES: JR-004, JR-005, JR-010, JR-012, JR-013, JR-014 | SR-002 public class ActorsController : ControllerBase { private readonly ITruthDataService _truthDataService; /// /// Initializes a new instance of the class. /// /// The truth data service. public ActorsController(ITruthDataService truthDataService) { _truthDataService = truthDataService; } /// /// Gets the full actor timeline (every actor with their scene-presence windows) for a movie. /// /// The Jellyfin item id. /// Cancellation token. /// The truth file contents, or 404 if no truth data exists for this item. [HttpGet("Timeline")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task> GetTimeline(Guid itemId, CancellationToken cancellationToken) { var truth = await _truthDataService.GetTruthAsync(itemId, cancellationToken).ConfigureAwait(false); if (truth is null) { return NotFound(); } return Ok(truth); } /// /// Gets how this item's truth data was obtained. /// /// /// Separate from Timeline on purpose: provenance is metadata *about* /// the claim, and folding it into the truth file would mean the bytes served /// back are not the bytes the producer wrote (JR-004). /// /// The Jellyfin item id. /// The provenance, or 404 if no truth data exists for this item. [HttpGet("Provenance")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] public ActionResult GetProvenance(Guid itemId) { var provenance = _truthDataService.GetProvenance(itemId); return provenance is null ? NotFound() : Ok(provenance); } /// /// Gets the JRay context (currently: the actors in the scene) at a given timestamp. /// This is an extensible envelope — future fields (locations, trivia, etc.) /// will be added here without changing the route. /// /// The Jellyfin item id. /// The timestamp, in seconds from the start of the movie. /// Cancellation token. /// The JRay context at , or 404 if no truth data exists for this item. [HttpGet("jray")] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task> GetContext(Guid itemId, [FromQuery] double t, CancellationToken cancellationToken) { var truth = await _truthDataService.GetTruthAsync(itemId, cancellationToken).ConfigureAwait(false); if (truth is null) { return NotFound(); } var context = new JRayContext(); foreach (var actor in PresenceLookup.ActorsPresentAt(truth, t)) { context.Actors.Add(new ActorInScene { Name = actor.Name, ImdbId = actor.ImdbId, TmdbId = actor.TmdbId, JellyfinId = actor.JellyfinId }); } return Ok(context); } }