2022-05-01 00:33:22 -05:00
|
|
|
using System;
|
2022-06-13 01:52:41 -05:00
|
|
|
using System.Collections.Generic;
|
2022-05-01 00:33:22 -05:00
|
|
|
using System.Net.Mime;
|
2022-11-06 21:20:52 -06:00
|
|
|
using ConfusedPolarBear.Plugin.IntroSkipper.Configuration;
|
2024-05-13 23:50:51 +02:00
|
|
|
using MediaBrowser.Common.Api;
|
2022-07-29 03:34:55 -05:00
|
|
|
using MediaBrowser.Controller.Entities.TV;
|
2022-05-01 00:33:22 -05:00
|
|
|
using Microsoft.AspNetCore.Authorization;
|
|
|
|
using Microsoft.AspNetCore.Mvc;
|
|
|
|
|
|
|
|
namespace ConfusedPolarBear.Plugin.IntroSkipper.Controllers;
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Skip intro controller.
|
|
|
|
/// </summary>
|
|
|
|
[Authorize]
|
|
|
|
[ApiController]
|
|
|
|
[Produces(MediaTypeNames.Application.Json)]
|
|
|
|
public class SkipIntroController : ControllerBase
|
|
|
|
{
|
|
|
|
/// <summary>
|
2022-05-09 22:50:41 -05:00
|
|
|
/// Initializes a new instance of the <see cref="SkipIntroController"/> class.
|
2022-05-01 00:33:22 -05:00
|
|
|
/// </summary>
|
|
|
|
public SkipIntroController()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
2022-06-12 21:28:24 -05:00
|
|
|
/// Returns the timestamps of the introduction in a television episode. Responses are in API version 1 format.
|
2022-05-01 00:33:22 -05:00
|
|
|
/// </summary>
|
2022-05-01 01:24:57 -05:00
|
|
|
/// <param name="id">ID of the episode. Required.</param>
|
2022-11-25 00:40:02 -06:00
|
|
|
/// <param name="mode">Timestamps to return. Optional. Defaults to Introduction for backwards compatibility.</param>
|
2022-05-01 00:33:22 -05:00
|
|
|
/// <response code="200">Episode contains an intro.</response>
|
|
|
|
/// <response code="404">Failed to find an intro in the provided episode.</response>
|
2022-05-09 22:50:41 -05:00
|
|
|
/// <returns>Detected intro.</returns>
|
2022-05-01 00:33:22 -05:00
|
|
|
[HttpGet("Episode/{id}/IntroTimestamps")]
|
2022-06-12 21:28:24 -05:00
|
|
|
[HttpGet("Episode/{id}/IntroTimestamps/v1")]
|
2022-11-25 00:40:02 -06:00
|
|
|
public ActionResult<Intro> GetIntroTimestamps(
|
|
|
|
[FromRoute] Guid id,
|
|
|
|
[FromQuery] AnalysisMode mode = AnalysisMode.Introduction)
|
2022-05-01 00:33:22 -05:00
|
|
|
{
|
2022-11-25 00:40:02 -06:00
|
|
|
var intro = GetIntro(id, mode);
|
2022-05-01 00:33:22 -05:00
|
|
|
|
2022-06-12 21:28:24 -05:00
|
|
|
if (intro is null || !intro.Valid)
|
2022-05-01 00:33:22 -05:00
|
|
|
{
|
|
|
|
return NotFound();
|
|
|
|
}
|
|
|
|
|
|
|
|
return intro;
|
|
|
|
}
|
2022-06-07 12:18:03 -05:00
|
|
|
|
2023-03-04 00:15:26 -06:00
|
|
|
/// <summary>
|
|
|
|
/// Gets a dictionary of all skippable segments.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="id">Media ID.</param>
|
|
|
|
/// <response code="200">Skippable segments dictionary.</response>
|
|
|
|
/// <returns>Dictionary of skippable segments.</returns>
|
|
|
|
[HttpGet("Episode/{id}/IntroSkipperSegments")]
|
|
|
|
public ActionResult<Dictionary<AnalysisMode, Intro>> GetSkippableSegments([FromRoute] Guid id)
|
|
|
|
{
|
|
|
|
var segments = new Dictionary<AnalysisMode, Intro>();
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2022-11-25 00:40:02 -06:00
|
|
|
/// <summary>Lookup and return the skippable timestamps for the provided item.</summary>
|
2022-06-12 21:28:24 -05:00
|
|
|
/// <param name="id">Unique identifier of this episode.</param>
|
2022-11-25 00:40:02 -06:00
|
|
|
/// <param name="mode">Mode.</param>
|
2022-06-12 21:28:24 -05:00
|
|
|
/// <returns>Intro object if the provided item has an intro, null otherwise.</returns>
|
2022-11-25 00:40:02 -06:00
|
|
|
private Intro? GetIntro(Guid id, AnalysisMode mode)
|
2022-06-12 21:28:24 -05:00
|
|
|
{
|
2022-11-25 00:40:02 -06:00
|
|
|
try
|
|
|
|
{
|
2024-06-15 13:16:47 +02:00
|
|
|
var timestamp = Plugin.Instance!.GetIntroByMode(id, mode);
|
2022-11-25 00:40:02 -06:00
|
|
|
|
2023-03-04 00:15:26 -06:00
|
|
|
// Operate on a copy to avoid mutating the original Intro object stored in the dictionary.
|
|
|
|
var segment = new Intro(timestamp);
|
|
|
|
|
2024-04-20 12:21:07 +02:00
|
|
|
var config = Plugin.Instance.Configuration;
|
2024-03-02 14:01:34 -05:00
|
|
|
segment.IntroEnd -= config.SecondsOfIntroToPlay;
|
2024-03-01 09:19:12 -05:00
|
|
|
if (config.PersistSkipButton)
|
2024-03-01 18:29:37 +01:00
|
|
|
{
|
2024-04-20 12:29:40 +02:00
|
|
|
segment.ShowSkipPromptAt = segment.IntroStart;
|
2024-06-01 14:15:30 +02:00
|
|
|
segment.HideSkipPromptAt = segment.IntroEnd;
|
2024-03-01 18:29:37 +01:00
|
|
|
}
|
|
|
|
else
|
2024-03-01 09:19:12 -05:00
|
|
|
{
|
2024-04-20 12:29:40 +02:00
|
|
|
segment.ShowSkipPromptAt = Math.Max(0, segment.IntroStart - config.ShowPromptAdjustment);
|
|
|
|
segment.HideSkipPromptAt = Math.Min(
|
|
|
|
segment.IntroStart + config.HidePromptAdjustment,
|
2024-06-01 14:15:30 +02:00
|
|
|
segment.IntroEnd);
|
2024-03-01 09:19:12 -05:00
|
|
|
}
|
|
|
|
|
2023-03-04 00:15:26 -06:00
|
|
|
return segment;
|
2022-11-25 00:40:02 -06:00
|
|
|
}
|
|
|
|
catch (KeyNotFoundException)
|
|
|
|
{
|
|
|
|
return null;
|
|
|
|
}
|
2022-06-12 21:28:24 -05:00
|
|
|
}
|
|
|
|
|
2022-06-07 12:18:03 -05:00
|
|
|
/// <summary>
|
|
|
|
/// Erases all previously discovered introduction timestamps.
|
|
|
|
/// </summary>
|
2022-11-29 02:31:24 -06:00
|
|
|
/// <param name="mode">Mode.</param>
|
2024-06-07 17:09:48 +02:00
|
|
|
/// <param name="eraseCache">Erase cache.</param>
|
2022-06-07 12:18:03 -05:00
|
|
|
/// <response code="204">Operation successful.</response>
|
|
|
|
/// <returns>No content.</returns>
|
2024-05-13 23:50:51 +02:00
|
|
|
[Authorize(Policy = Policies.RequiresElevation)]
|
2022-06-07 12:18:03 -05:00
|
|
|
[HttpPost("Intros/EraseTimestamps")]
|
2024-06-07 17:09:48 +02:00
|
|
|
public ActionResult ResetIntroTimestamps([FromQuery] AnalysisMode mode, [FromQuery] bool eraseCache = false)
|
2022-06-07 12:18:03 -05:00
|
|
|
{
|
2022-11-29 02:31:24 -06:00
|
|
|
if (mode == AnalysisMode.Introduction)
|
|
|
|
{
|
|
|
|
Plugin.Instance!.Intros.Clear();
|
|
|
|
}
|
|
|
|
else if (mode == AnalysisMode.Credits)
|
|
|
|
{
|
|
|
|
Plugin.Instance!.Credits.Clear();
|
|
|
|
}
|
|
|
|
|
2024-06-07 17:09:48 +02:00
|
|
|
if (eraseCache)
|
|
|
|
{
|
|
|
|
FFmpegWrapper.DeleteCacheFiles(mode);
|
|
|
|
}
|
|
|
|
|
2024-06-15 13:16:47 +02:00
|
|
|
Plugin.Instance!.EpisodeStates.Clear();
|
2024-05-08 16:27:16 +02:00
|
|
|
Plugin.Instance!.SaveTimestamps(mode);
|
2022-06-07 12:18:03 -05:00
|
|
|
return NoContent();
|
|
|
|
}
|
2022-06-13 01:52:41 -05:00
|
|
|
|
|
|
|
/// <summary>
|
2022-11-24 00:43:23 -06:00
|
|
|
/// Get all introductions or credits. Only used by the end to end testing script.
|
2022-06-13 01:52:41 -05:00
|
|
|
/// </summary>
|
2022-11-24 00:43:23 -06:00
|
|
|
/// <param name="mode">Mode.</param>
|
|
|
|
/// <response code="200">All timestamps have been returned.</response>
|
2022-07-29 03:34:55 -05:00
|
|
|
/// <returns>List of IntroWithMetadata objects.</returns>
|
2024-05-13 23:50:51 +02:00
|
|
|
[Authorize(Policy = Policies.RequiresElevation)]
|
2022-06-13 01:52:41 -05:00
|
|
|
[HttpGet("Intros/All")]
|
2022-11-24 00:43:23 -06:00
|
|
|
public ActionResult<List<IntroWithMetadata>> GetAllTimestamps(
|
|
|
|
[FromQuery] AnalysisMode mode = AnalysisMode.Introduction)
|
2022-06-13 01:52:41 -05:00
|
|
|
{
|
2022-07-29 03:34:55 -05:00
|
|
|
List<IntroWithMetadata> intros = new();
|
|
|
|
|
2022-11-24 00:43:23 -06:00
|
|
|
var timestamps = mode == AnalysisMode.Introduction ?
|
|
|
|
Plugin.Instance!.Intros :
|
|
|
|
Plugin.Instance!.Credits;
|
|
|
|
|
2022-07-29 03:34:55 -05:00
|
|
|
// Get metadata for all intros
|
2022-11-24 00:43:23 -06:00
|
|
|
foreach (var intro in timestamps)
|
2022-07-29 03:34:55 -05:00
|
|
|
{
|
|
|
|
// Get the details of the item from Jellyfin
|
2024-04-20 12:21:07 +02:00
|
|
|
var rawItem = Plugin.Instance.GetItem(intro.Key);
|
2024-05-01 13:45:57 +02:00
|
|
|
if (rawItem == null || rawItem is not Episode episode)
|
2022-07-29 03:34:55 -05:00
|
|
|
{
|
|
|
|
throw new InvalidCastException("Unable to cast item id " + intro.Key + " to an Episode");
|
|
|
|
}
|
|
|
|
|
|
|
|
// Associate the metadata with the intro
|
|
|
|
intros.Add(
|
|
|
|
new IntroWithMetadata(
|
|
|
|
episode.SeriesName,
|
|
|
|
episode.AiredSeasonNumber ?? 0,
|
|
|
|
episode.Name,
|
|
|
|
intro.Value));
|
|
|
|
}
|
|
|
|
|
|
|
|
return intros;
|
2022-06-13 01:52:41 -05:00
|
|
|
}
|
2022-11-06 21:20:52 -06:00
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Gets the user interface configuration.
|
|
|
|
/// </summary>
|
|
|
|
/// <response code="200">UserInterfaceConfiguration returned.</response>
|
|
|
|
/// <returns>UserInterfaceConfiguration.</returns>
|
2024-03-10 19:47:38 +01:00
|
|
|
[HttpGet]
|
2022-11-06 21:20:52 -06:00
|
|
|
[Route("Intros/UserInterfaceConfiguration")]
|
|
|
|
public ActionResult<UserInterfaceConfiguration> GetUserInterfaceConfiguration()
|
|
|
|
{
|
|
|
|
var config = Plugin.Instance!.Configuration;
|
2023-03-04 00:15:26 -06:00
|
|
|
return new UserInterfaceConfiguration(
|
|
|
|
config.SkipButtonVisible,
|
|
|
|
config.SkipButtonIntroText,
|
|
|
|
config.SkipButtonEndCreditsText);
|
2022-11-06 21:20:52 -06:00
|
|
|
}
|
2022-05-01 00:33:22 -05:00
|
|
|
}
|