using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Globalization; using System.Linq; using System.Net.Http; using System.Threading; using System.Threading.Tasks; using Jellyfin.Api.Attributes; using Jellyfin.Api.Constants; using Jellyfin.Api.Extensions; using Jellyfin.Api.Helpers; using Jellyfin.Api.ModelBinders; using Jellyfin.Api.Models.StreamingDtos; using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.Net; using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.Net; using MediaBrowser.Model.Querying; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; namespace Jellyfin.Api.Controllers { /// /// The videos controller. /// public class VideosController : BaseJellyfinApiController { private readonly ILibraryManager _libraryManager; private readonly IUserManager _userManager; private readonly IDtoService _dtoService; private readonly IDlnaManager _dlnaManager; private readonly IAuthorizationContext _authContext; private readonly IMediaSourceManager _mediaSourceManager; private readonly IServerConfigurationManager _serverConfigurationManager; private readonly IMediaEncoder _mediaEncoder; private readonly IDeviceManager _deviceManager; private readonly TranscodingJobHelper _transcodingJobHelper; private readonly IHttpClientFactory _httpClientFactory; private readonly EncodingHelper _encodingHelper; private readonly TranscodingJobType _transcodingJobType = TranscodingJobType.Progressive; /// /// Initializes a new instance of the class. /// /// Instance of the interface. /// Instance of the interface. /// Instance of the interface. /// Instance of the interface. /// Instance of the interface. /// Instance of the interface. /// Instance of the interface. /// Instance of the interface. /// Instance of the interface. /// Instance of the class. /// Instance of the interface. /// Instance of . public VideosController( ILibraryManager libraryManager, IUserManager userManager, IDtoService dtoService, IDlnaManager dlnaManager, IAuthorizationContext authContext, IMediaSourceManager mediaSourceManager, IServerConfigurationManager serverConfigurationManager, IMediaEncoder mediaEncoder, IDeviceManager deviceManager, TranscodingJobHelper transcodingJobHelper, IHttpClientFactory httpClientFactory, EncodingHelper encodingHelper) { _libraryManager = libraryManager; _userManager = userManager; _dtoService = dtoService; _dlnaManager = dlnaManager; _authContext = authContext; _mediaSourceManager = mediaSourceManager; _serverConfigurationManager = serverConfigurationManager; _mediaEncoder = mediaEncoder; _deviceManager = deviceManager; _transcodingJobHelper = transcodingJobHelper; _httpClientFactory = httpClientFactory; _encodingHelper = encodingHelper; } /// /// 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, Required] Guid itemId, [FromQuery] Guid? userId) { var user = userId is null || userId.Value.Equals(default) ? null : _userManager.GetUserById(userId.Value); var item = itemId.Equals(default) ? (userId is null || userId.Value.Equals(default) ? _libraryManager.RootFolder : _libraryManager.GetUserRootFolder()) : _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); 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.Status204NoContent)] [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task DeleteAlternateSources([FromRoute, Required] 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."); } if (video.LinkedAlternateVersions.Length == 0) { video = (Video)_libraryManager.GetItemById(video.PrimaryVersionId); } foreach (var link in video.GetLinkedAlternateVersions()) { link.SetPrimaryVersionId(null); link.LinkedAlternateVersions = Array.Empty(); await link.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); } video.LinkedAlternateVersions = Array.Empty(); video.SetPrimaryVersionId(null); await video.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); 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 async Task MergeVersions([FromQuery, Required, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] ids) { var items = ids .Select(i => _libraryManager.GetItemById(i)) .OfType