using System.ComponentModel.DataAnnotations; using ErsatzTV.Application.MediaItems; using ErsatzTV.Controllers.Api.Requests; using ErsatzTV.Core; using ErsatzTV.Core.Api.MediaItems; using ErsatzTV.Core.Errors; using ErsatzTV.Extensions; using MediatR; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; namespace ErsatzTV.Controllers.Api; [ApiController] public class MediaItemsController(IMediator mediator) : ControllerBase { [HttpDelete("/api/v1/media-items", Name = "DeleteMediaItems")] [Tags("Media Items")] [EndpointSummary("Delete media items from the database")] [EndpointGroupName("general")] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status422UnprocessableEntity)] public async Task Delete( [Required][FromBody] DeleteMediaItemsRequest request, CancellationToken cancellationToken) { if (request.Ids is null || request.Ids.Count == 0) { return BaseError.New("At least one media item id is required").ToErrorResult(); } Either result = await mediator.Send(request.ToCommand(), cancellationToken); return result.ToDeletedResult(); } [HttpGet("/api/v1/media-items/{id:int}/info", Name = "GetMediaItemInfo")] [Tags("Media Items")] [EndpointSummary("Get technical media info for a media item")] [EndpointGroupName("general")] [ProducesResponseType(typeof(MediaItemInfoResponseModel), StatusCodes.Status200OK)] [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)] [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status422UnprocessableEntity)] public async Task GetInfo(int id, CancellationToken cancellationToken) { Either result = await mediator.Send(new GetMediaItemInfo(id), cancellationToken); return result.Match( Left: error => error is UnableToLocateMediaItem ? ApiResults.NotFoundProblem(error.Value) : error.ToErrorResult(), Right: info => (IActionResult)new OkObjectResult(Project(info))); } private static MediaItemInfoResponseModel Project(MediaItemInfo info) => new( info.Id, info.Title, info.Kind, info.LibraryKind, string.IsNullOrWhiteSpace(info.ServerName) ? null : info.ServerName, info.LibraryName, info.State, info.Duration, string.IsNullOrWhiteSpace(info.SampleAspectRatio) ? null : info.SampleAspectRatio, string.IsNullOrWhiteSpace(info.DisplayAspectRatio) ? null : info.DisplayAspectRatio, string.IsNullOrWhiteSpace(info.RFrameRate) ? null : info.RFrameRate, info.VideoScanKind, info.InterlacedRatio, info.Width, info.Height, info.Streams.Map(Project).ToList(), info.Chapters.Map(Project).ToList()); private static MediaItemInfoStreamResponseModel Project(MediaItemInfoStream stream) => new( stream.Index, stream.Kind, stream.Title, stream.Codec, stream.Profile, stream.Language, stream.Channels, stream.Default, stream.Forced, stream.AttachedPic, stream.PixelFormat, stream.ColorRange, stream.ColorSpace, stream.ColorTransfer, stream.ColorPrimaries, stream.BitsPerRawSample, stream.MimeType, stream.FileName, stream.IsExtracted); private static MediaItemInfoChapterResponseModel Project(MediaItemInfoChapter chapter) => new(chapter.Title, chapter.StartTime, chapter.EndTime); }