using System; using System.Globalization; using System.Linq; using System.Threading; using Jellyfin.Api.Constants; using Jellyfin.Api.Extensions; using Jellyfin.Api.Helpers; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Querying; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; namespace Jellyfin.Api.Controllers { /// /// The videos controller. /// [Route("Videos")] public class VideosController : BaseJellyfinApiController { private readonly ILibraryManager _libraryManager; private readonly IUserManager _userManager; private readonly IDtoService _dtoService; /// /// Initializes a new instance of the class. /// /// Instance of the interface. /// Instance of the interface. /// Instance of the interface. public VideosController( ILibraryManager libraryManager, IUserManager userManager, IDtoService dtoService) { _libraryManager = libraryManager; _userManager = userManager; _dtoService = dtoService; } /// /// Gets additional parts for a video. /// /// The item id. /// Optional. Filter by user id, and attach user data. /// Additional parts returned. /// A with the parts. [HttpGet("{itemId}/AdditionalParts")] [Authorize(Policy = Policies.DefaultAuthorization)] [ProducesResponseType(StatusCodes.Status200OK)] public ActionResult> GetAdditionalPart([FromRoute] Guid itemId, [FromQuery] Guid? userId) { var user = userId.HasValue && !userId.Equals(Guid.Empty) ? _userManager.GetUserById(userId.Value) : null; var item = itemId.Equals(Guid.Empty) ? (!userId.Equals(Guid.Empty) ? _libraryManager.GetUserRootFolder() : _libraryManager.RootFolder) : _libraryManager.GetItemById(itemId); var dtoOptions = new DtoOptions(); dtoOptions = dtoOptions.AddClientFields(Request); BaseItemDto[] items; if (item is Video video) { items = video.GetAdditionalParts() .Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, video)) .ToArray(); } else { items = Array.Empty(); } var result = new QueryResult { Items = items, TotalRecordCount = items.Length }; return result; } /// /// Removes alternate video sources. /// /// The item id. /// Alternate sources deleted. /// Video not found. /// A indicating success, or a if the video doesn't exist. [HttpDelete("{itemId}/AlternateSources")] [Authorize(Policy = Policies.RequiresElevation)] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] public ActionResult DeleteAlternateSources([FromRoute] Guid itemId) { var video = (Video)_libraryManager.GetItemById(itemId); if (video == null) { return NotFound("The video either does not exist or the id does not belong to a video."); } foreach (var link in video.GetLinkedAlternateVersions()) { link.SetPrimaryVersionId(null); link.LinkedAlternateVersions = Array.Empty(); link.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None); } video.LinkedAlternateVersions = Array.Empty(); video.SetPrimaryVersionId(null); video.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None); return NoContent(); } /// /// Merges videos into a single record. /// /// Item id list. This allows multiple, comma delimited. /// Videos merged. /// Supply at least 2 video ids. /// A indicating success, or a if less than two ids were supplied. [HttpPost("MergeVersions")] [Authorize(Policy = Policies.RequiresElevation)] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status400BadRequest)] public ActionResult MergeVersions([FromQuery] string? itemIds) { var items = RequestHelpers.Split(itemIds, ',', true) .Select(i => _libraryManager.GetItemById(i)) .OfType