using System;
using System.Collections.Generic;
using System.Net.Mime;
using ConfusedPolarBear.Plugin.IntroSkipper.Configuration;
using ConfusedPolarBear.Plugin.IntroSkipper.Data;
using MediaBrowser.Common.Api;
using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Entities.TV;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace ConfusedPolarBear.Plugin.IntroSkipper.Controllers;
///
/// Skip intro controller.
///
[Authorize]
[ApiController]
[Produces(MediaTypeNames.Application.Json)]
public class SkipIntroController : ControllerBase
{
///
/// Initializes a new instance of the class.
///
public SkipIntroController()
{
}
///
/// Returns the timestamps of the introduction in a television episode. Responses are in API version 1 format.
///
/// ID of the episode. Required.
/// Timestamps to return. Optional. Defaults to Introduction for backwards compatibility.
/// Episode contains an intro.
/// Failed to find an intro in the provided episode.
/// Detected intro.
[HttpGet("Episode/{id}/IntroTimestamps")]
[HttpGet("Episode/{id}/IntroTimestamps/v1")]
public ActionResult GetIntroTimestamps(
[FromRoute] Guid id,
[FromQuery] AnalysisMode mode = AnalysisMode.Introduction)
{
var intro = GetIntro(id, mode);
if (intro is null || !intro.Valid)
{
return NotFound();
}
return intro;
}
///
/// Updates the timestamps for the provided episode.
///
/// Episode ID to update timestamps for.
/// New timestamps Introduction/Credits start and end times.
/// New timestamps saved.
/// Given ID is not an Episode.
/// No content.
[Authorize(Policy = Policies.RequiresElevation)]
[HttpPost("Episode/{Id}/Timestamps")]
public ActionResult UpdateTimestamps([FromRoute] Guid id, [FromBody] TimeStamps timestamps)
{
// only update existing episodes
var rawItem = Plugin.Instance!.GetItem(id);
if (rawItem == null || rawItem is not Episode and not Movie)
{
return NotFound();
}
if (timestamps?.Introduction.End > 0.0)
{
var tr = new TimeRange(timestamps.Introduction.Start, timestamps.Introduction.End);
Plugin.Instance!.Intros[id] = new Segment(id, tr);
}
if (timestamps?.Credits.End > 0.0)
{
var cr = new TimeRange(timestamps.Credits.Start, timestamps.Credits.End);
Plugin.Instance!.Credits[id] = new Segment(id, cr);
}
Plugin.Instance!.SaveTimestamps(AnalysisMode.Introduction);
Plugin.Instance!.SaveTimestamps(AnalysisMode.Credits);
return NoContent();
}
///
/// Gets the timestamps for the provided episode.
///
/// Episode ID.
/// Sucess.
/// Given ID is not an Episode.
/// Episode Timestamps.
[HttpGet("Episode/{Id}/Timestamps")]
[ActionName("UpdateTimestamps")]
public ActionResult GetTimestamps([FromRoute] Guid id)
{
// only get return content for episodes
var rawItem = Plugin.Instance!.GetItem(id);
if (rawItem == null || rawItem is not Episode and not Movie)
{
return NotFound();
}
var times = new TimeStamps();
if (Plugin.Instance!.Intros.TryGetValue(id, out var introValue))
{
times.Introduction = introValue;
}
if (Plugin.Instance!.Credits.TryGetValue(id, out var creditValue))
{
times.Credits = creditValue;
}
return times;
}
///
/// Gets a dictionary of all skippable segments.
///
/// Media ID.
/// Skippable segments dictionary.
/// Dictionary of skippable segments.
[HttpGet("Episode/{id}/IntroSkipperSegments")]
public ActionResult> GetSkippableSegments([FromRoute] Guid id)
{
var segments = new Dictionary();
if (GetIntro(id, AnalysisMode.Introduction) is Intro intro)
{
segments[AnalysisMode.Introduction] = intro;
}
if (GetIntro(id, AnalysisMode.Credits) is Intro credits)
{
segments[AnalysisMode.Credits] = credits;
}
return segments;
}
/// Lookup and return the skippable timestamps for the provided item.
/// Unique identifier of this episode.
/// Mode.
/// Intro object if the provided item has an intro, null otherwise.
private static Intro? GetIntro(Guid id, AnalysisMode mode)
{
try
{
var timestamp = Plugin.GetIntroByMode(id, mode);
// Operate on a copy to avoid mutating the original Intro object stored in the dictionary.
var segment = new Intro(timestamp);
var config = Plugin.Instance!.Configuration;
segment.IntroEnd -= config.RemainingSecondsOfIntro;
if (config.PersistSkipButton)
{
segment.ShowSkipPromptAt = segment.IntroStart;
segment.HideSkipPromptAt = segment.IntroEnd;
}
else
{
segment.ShowSkipPromptAt = Math.Max(0, segment.IntroStart - config.ShowPromptAdjustment);
segment.HideSkipPromptAt = Math.Min(
segment.IntroStart + config.HidePromptAdjustment,
segment.IntroEnd);
}
return segment;
}
catch (KeyNotFoundException)
{
return null;
}
}
///
/// Erases all previously discovered introduction timestamps.
///
/// Mode.
/// Erase cache.
/// Operation successful.
/// No content.
[Authorize(Policy = Policies.RequiresElevation)]
[HttpPost("Intros/EraseTimestamps")]
public ActionResult ResetIntroTimestamps([FromQuery] AnalysisMode mode, [FromQuery] bool eraseCache = false)
{
if (mode == AnalysisMode.Introduction)
{
Plugin.Instance!.Intros.Clear();
}
else if (mode == AnalysisMode.Credits)
{
Plugin.Instance!.Credits.Clear();
}
if (eraseCache)
{
FFmpegWrapper.DeleteCacheFiles(mode);
}
Plugin.Instance!.EpisodeStates.Clear();
Plugin.Instance!.SaveTimestamps(mode);
return NoContent();
}
///
/// Gets the user interface configuration.
///
/// UserInterfaceConfiguration returned.
/// UserInterfaceConfiguration.
[HttpGet]
[Route("Intros/UserInterfaceConfiguration")]
public ActionResult GetUserInterfaceConfiguration()
{
var config = Plugin.Instance!.Configuration;
return new UserInterfaceConfiguration(
config.SkipButtonVisible,
config.SkipButtonIntroText,
config.SkipButtonEndCreditsText,
config.AutoSkip,
config.AutoSkipCredits,
config.ClientList);
}
}