using System;
using System.Collections.Generic;
using System.Globalization;
using System.Net.Mime;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
namespace ConfusedPolarBear.Plugin.IntroSkipper.Controllers;
///
/// Audio fingerprint visualization controller. Allows browsing fingerprints on a per episode basis.
///
[Authorize(Policy = "RequiresElevation")]
[ApiController]
[Produces(MediaTypeNames.Application.Json)]
[Route("Intros")]
public class VisualizationController : ControllerBase
{
private readonly ILogger _logger;
///
/// Initializes a new instance of the class.
///
/// Logger.
public VisualizationController(ILogger logger)
{
_logger = logger;
}
///
/// Returns all show names and seasons.
///
/// Dictionary of show names to a list of season names.
[HttpGet("Shows")]
public ActionResult>> GetShowSeasons()
{
_logger.LogDebug("Returning season names by series");
var showSeasons = new Dictionary>();
// Loop through all seasons in the analysis queue
foreach (var kvp in Plugin.Instance!.AnalysisQueue)
{
// Check that this season contains at least one episode.
var episodes = kvp.Value;
if (episodes is null || episodes.Count == 0)
{
_logger.LogDebug("Skipping season {Id} (null or empty)", kvp.Key);
continue;
}
// Peek at the top episode from this season and store the series name and season number.
var first = episodes[0];
var series = first.SeriesName;
var season = GetSeasonName(first);
// Validate the series and season before attempting to store it.
if (string.IsNullOrWhiteSpace(series) || string.IsNullOrWhiteSpace(season))
{
_logger.LogDebug("Skipping season {Id} (no name or number)", kvp.Key);
continue;
}
// TryAdd is used when adding the HashSet since it is a no-op if one was already created for this series.
showSeasons.TryAdd(series, new HashSet());
showSeasons[series].Add(season);
}
return showSeasons;
}
///
/// Returns the names and unique identifiers of all episodes in the provided season.
///
/// Show name.
/// Season name.
/// List of episode titles.
[HttpGet("Show/{Series}/{Season}")]
public ActionResult> GetSeasonEpisodes(
[FromRoute] string series,
[FromRoute] string season)
{
var visualEpisodes = new List();
if (!LookupSeasonByName(series, season, out var episodes))
{
return NotFound();
}
foreach (var e in episodes)
{
visualEpisodes.Add(new EpisodeVisualization(e.EpisodeId, e.Name));
}
return visualEpisodes;
}
///
/// Fingerprint the provided episode and returns the uncompressed fingerprint data points.
///
/// Episode id.
/// Read only collection of fingerprint points.
[HttpGet("Episode/{Id}/Chromaprint")]
public ActionResult GetEpisodeFingerprint([FromRoute] Guid id)
{
var queue = Plugin.Instance!.AnalysisQueue;
// Search through all queued episodes to find the requested id
foreach (var season in queue)
{
foreach (var needle in season.Value)
{
if (needle.EpisodeId == id)
{
return FFmpegWrapper.Fingerprint(needle);
}
}
}
return NotFound();
}
///
/// Erases all timestamps for the provided season.
///
/// Show name.
/// Season name.
/// Season timestamps erased.
/// Unable to find season in provided series.
/// No content.
[HttpDelete("Show/{Series}/{Season}")]
public ActionResult EraseSeason([FromRoute] string series, [FromRoute] string season)
{
if (!LookupSeasonByName(series, season, out var episodes))
{
return NotFound();
}
_logger.LogInformation("Erasing timestamps for {Series} {Season} at user request", series, season);
foreach (var e in episodes)
{
Plugin.Instance!.Intros.Remove(e.EpisodeId);
}
Plugin.Instance!.SaveTimestamps();
return NoContent();
}
///
/// Updates the timestamps for the provided episode.
///
/// Episode ID to update timestamps for.
/// New introduction start and end times.
/// New introduction timestamps saved.
/// No content.
[HttpPost("Episode/{Id}/UpdateIntroTimestamps")]
public ActionResult UpdateTimestamps([FromRoute] Guid id, [FromBody] Intro timestamps)
{
var tr = new TimeRange(timestamps.IntroStart, timestamps.IntroEnd);
Plugin.Instance!.Intros[id] = new Intro(id, tr);
Plugin.Instance!.SaveTimestamps();
return NoContent();
}
private string GetSeasonName(QueuedEpisode episode)
{
return "Season " + episode.SeasonNumber.ToString(CultureInfo.InvariantCulture);
}
///
/// Lookup a named season of a series and return all queued episodes.
///
/// Series name.
/// Season name.
/// Episodes.
/// Boolean indicating if the requested season was found.
private bool LookupSeasonByName(string series, string season, out List episodes)
{
foreach (var queuedEpisodes in Plugin.Instance!.AnalysisQueue)
{
var first = queuedEpisodes.Value[0];
var firstSeasonName = GetSeasonName(first);
// Assert that the queued episode series and season are equal to what was requested
if (
!string.Equals(first.SeriesName, series, StringComparison.OrdinalIgnoreCase) ||
!string.Equals(firstSeasonName, season, StringComparison.OrdinalIgnoreCase))
{
continue;
}
episodes = queuedEpisodes.Value;
return true;
}
episodes = new List();
return false;
}
}