mirror of
				https://github.com/jellyfin/jellyfin.git
				synced 2025-11-03 19:17:24 -05:00 
			
		
		
		
	Validate item access (#11171)
This commit is contained in:
		
							parent
							
								
									9a4db80085
								
							
						
					
					
						commit
						6fb6b5f176
					
				@ -46,6 +46,7 @@ using MediaBrowser.Model.Library;
 | 
				
			|||||||
using MediaBrowser.Model.Querying;
 | 
					using MediaBrowser.Model.Querying;
 | 
				
			||||||
using MediaBrowser.Model.Tasks;
 | 
					using MediaBrowser.Model.Tasks;
 | 
				
			||||||
using Microsoft.Extensions.Logging;
 | 
					using Microsoft.Extensions.Logging;
 | 
				
			||||||
 | 
					using TMDbLib.Objects.Authentication;
 | 
				
			||||||
using Episode = MediaBrowser.Controller.Entities.TV.Episode;
 | 
					using Episode = MediaBrowser.Controller.Entities.TV.Episode;
 | 
				
			||||||
using EpisodeInfo = Emby.Naming.TV.EpisodeInfo;
 | 
					using EpisodeInfo = Emby.Naming.TV.EpisodeInfo;
 | 
				
			||||||
using Genre = MediaBrowser.Controller.Entities.Genre;
 | 
					using Genre = MediaBrowser.Controller.Entities.Genre;
 | 
				
			||||||
@ -1222,12 +1223,7 @@ namespace Emby.Server.Implementations.Library
 | 
				
			|||||||
            return null;
 | 
					            return null;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        /// <summary>
 | 
					        /// <inheritdoc />
 | 
				
			||||||
        /// Gets the item by id.
 | 
					 | 
				
			||||||
        /// </summary>
 | 
					 | 
				
			||||||
        /// <param name="id">The id.</param>
 | 
					 | 
				
			||||||
        /// <returns>BaseItem.</returns>
 | 
					 | 
				
			||||||
        /// <exception cref="ArgumentNullException"><paramref name="id"/> is <c>null</c>.</exception>
 | 
					 | 
				
			||||||
        public BaseItem GetItemById(Guid id)
 | 
					        public BaseItem GetItemById(Guid id)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            if (id.IsEmpty())
 | 
					            if (id.IsEmpty())
 | 
				
			||||||
@ -1263,6 +1259,22 @@ namespace Emby.Server.Implementations.Library
 | 
				
			|||||||
            return null;
 | 
					            return null;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <inheritdoc />
 | 
				
			||||||
 | 
					        public T GetItemById<T>(Guid id, Guid userId)
 | 
				
			||||||
 | 
					            where T : BaseItem
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var user = userId.IsEmpty() ? null : _userManager.GetUserById(userId);
 | 
				
			||||||
 | 
					            return GetItemById<T>(id, user);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <inheritdoc />
 | 
				
			||||||
 | 
					        public T GetItemById<T>(Guid id, User user)
 | 
				
			||||||
 | 
					            where T : BaseItem
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            var item = GetItemById<T>(id);
 | 
				
			||||||
 | 
					            return ItemIsVisible(item, user) ? item : null;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public List<BaseItem> GetItemList(InternalItemsQuery query, bool allowExternalContent)
 | 
					        public List<BaseItem> GetItemList(InternalItemsQuery query, bool allowExternalContent)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            if (query.Recursive && !query.ParentId.IsEmpty())
 | 
					            if (query.Recursive && !query.ParentId.IsEmpty())
 | 
				
			||||||
@ -3191,5 +3203,20 @@ namespace Emby.Server.Implementations.Library
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            CollectionFolder.SaveLibraryOptions(virtualFolderPath, libraryOptions);
 | 
					            CollectionFolder.SaveLibraryOptions(virtualFolderPath, libraryOptions);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        private static bool ItemIsVisible(BaseItem item, User user)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            if (item is null)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                return false;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (user is null)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                return true;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return item is UserRootFolder || item.IsVisibleStandalone(user);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -194,7 +194,7 @@ public class DisplayPreferencesController : BaseJellyfinApiController
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        foreach (var key in displayPreferences.CustomPrefs.Keys.Where(key => key.StartsWith("landing-", StringComparison.OrdinalIgnoreCase)))
 | 
					        foreach (var key in displayPreferences.CustomPrefs.Keys.Where(key => key.StartsWith("landing-", StringComparison.OrdinalIgnoreCase)))
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            if (!Enum.TryParse<ViewType>(displayPreferences.CustomPrefs[key], true, out var type))
 | 
					            if (!Enum.TryParse<ViewType>(displayPreferences.CustomPrefs[key], true, out _))
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                _logger.LogError("Invalid ViewType: {LandingScreenOption}", displayPreferences.CustomPrefs[key]);
 | 
					                _logger.LogError("Invalid ViewType: {LandingScreenOption}", displayPreferences.CustomPrefs[key]);
 | 
				
			||||||
                displayPreferences.CustomPrefs.Remove(key);
 | 
					                displayPreferences.CustomPrefs.Remove(key);
 | 
				
			||||||
 | 
				
			|||||||
@ -162,7 +162,7 @@ public class FilterController : BaseJellyfinApiController
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
        else if (parentId.HasValue)
 | 
					        else if (parentId.HasValue)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            parentItem = _libraryManager.GetItemById(parentId.Value);
 | 
					            parentItem = _libraryManager.GetItemById<BaseItem>(parentId.Value);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        var filters = new QueryFilters();
 | 
					        var filters = new QueryFilters();
 | 
				
			||||||
 | 
				
			|||||||
@ -90,6 +90,7 @@ public class ImageController : BaseJellyfinApiController
 | 
				
			|||||||
    /// <param name="userId">User Id.</param>
 | 
					    /// <param name="userId">User Id.</param>
 | 
				
			||||||
    /// <response code="204">Image updated.</response>
 | 
					    /// <response code="204">Image updated.</response>
 | 
				
			||||||
    /// <response code="403">User does not have permission to delete the image.</response>
 | 
					    /// <response code="403">User does not have permission to delete the image.</response>
 | 
				
			||||||
 | 
					    /// <response code="404">Item not found.</response>
 | 
				
			||||||
    /// <returns>A <see cref="NoContentResult"/>.</returns>
 | 
					    /// <returns>A <see cref="NoContentResult"/>.</returns>
 | 
				
			||||||
    [HttpPost("UserImage")]
 | 
					    [HttpPost("UserImage")]
 | 
				
			||||||
    [Authorize]
 | 
					    [Authorize]
 | 
				
			||||||
@ -97,6 +98,7 @@ public class ImageController : BaseJellyfinApiController
 | 
				
			|||||||
    [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
					    [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
				
			||||||
    [ProducesResponseType(StatusCodes.Status400BadRequest)]
 | 
					    [ProducesResponseType(StatusCodes.Status400BadRequest)]
 | 
				
			||||||
    [ProducesResponseType(StatusCodes.Status403Forbidden)]
 | 
					    [ProducesResponseType(StatusCodes.Status403Forbidden)]
 | 
				
			||||||
 | 
					    [ProducesResponseType(StatusCodes.Status404NotFound)]
 | 
				
			||||||
    public async Task<ActionResult> PostUserImage(
 | 
					    public async Task<ActionResult> PostUserImage(
 | 
				
			||||||
        [FromQuery] Guid? userId)
 | 
					        [FromQuery] Guid? userId)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
@ -289,7 +291,7 @@ public class ImageController : BaseJellyfinApiController
 | 
				
			|||||||
        [FromRoute, Required] ImageType imageType,
 | 
					        [FromRoute, Required] ImageType imageType,
 | 
				
			||||||
        [FromQuery] int? imageIndex)
 | 
					        [FromQuery] int? imageIndex)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        var item = _libraryManager.GetItemById(itemId);
 | 
					        var item = _libraryManager.GetItemById<BaseItem>(itemId, User.GetUserId());
 | 
				
			||||||
        if (item is null)
 | 
					        if (item is null)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            return NotFound();
 | 
					            return NotFound();
 | 
				
			||||||
@ -317,7 +319,7 @@ public class ImageController : BaseJellyfinApiController
 | 
				
			|||||||
        [FromRoute, Required] ImageType imageType,
 | 
					        [FromRoute, Required] ImageType imageType,
 | 
				
			||||||
        [FromRoute] int imageIndex)
 | 
					        [FromRoute] int imageIndex)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        var item = _libraryManager.GetItemById(itemId);
 | 
					        var item = _libraryManager.GetItemById<BaseItem>(itemId, User.GetUserId());
 | 
				
			||||||
        if (item is null)
 | 
					        if (item is null)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            return NotFound();
 | 
					            return NotFound();
 | 
				
			||||||
@ -346,7 +348,7 @@ public class ImageController : BaseJellyfinApiController
 | 
				
			|||||||
        [FromRoute, Required] Guid itemId,
 | 
					        [FromRoute, Required] Guid itemId,
 | 
				
			||||||
        [FromRoute, Required] ImageType imageType)
 | 
					        [FromRoute, Required] ImageType imageType)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        var item = _libraryManager.GetItemById(itemId);
 | 
					        var item = _libraryManager.GetItemById<BaseItem>(itemId, User.GetUserId());
 | 
				
			||||||
        if (item is null)
 | 
					        if (item is null)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            return NotFound();
 | 
					            return NotFound();
 | 
				
			||||||
@ -390,7 +392,7 @@ public class ImageController : BaseJellyfinApiController
 | 
				
			|||||||
        [FromRoute, Required] ImageType imageType,
 | 
					        [FromRoute, Required] ImageType imageType,
 | 
				
			||||||
        [FromRoute] int imageIndex)
 | 
					        [FromRoute] int imageIndex)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        var item = _libraryManager.GetItemById(itemId);
 | 
					        var item = _libraryManager.GetItemById<BaseItem>(itemId, User.GetUserId());
 | 
				
			||||||
        if (item is null)
 | 
					        if (item is null)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            return NotFound();
 | 
					            return NotFound();
 | 
				
			||||||
@ -433,7 +435,7 @@ public class ImageController : BaseJellyfinApiController
 | 
				
			|||||||
        [FromRoute, Required] int imageIndex,
 | 
					        [FromRoute, Required] int imageIndex,
 | 
				
			||||||
        [FromQuery, Required] int newIndex)
 | 
					        [FromQuery, Required] int newIndex)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        var item = _libraryManager.GetItemById(itemId);
 | 
					        var item = _libraryManager.GetItemById<BaseItem>(itemId, User.GetUserId());
 | 
				
			||||||
        if (item is null)
 | 
					        if (item is null)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            return NotFound();
 | 
					            return NotFound();
 | 
				
			||||||
@ -456,7 +458,7 @@ public class ImageController : BaseJellyfinApiController
 | 
				
			|||||||
    [ProducesResponseType(StatusCodes.Status404NotFound)]
 | 
					    [ProducesResponseType(StatusCodes.Status404NotFound)]
 | 
				
			||||||
    public async Task<ActionResult<IEnumerable<ImageInfo>>> GetItemImageInfos([FromRoute, Required] Guid itemId)
 | 
					    public async Task<ActionResult<IEnumerable<ImageInfo>>> GetItemImageInfos([FromRoute, Required] Guid itemId)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        var item = _libraryManager.GetItemById(itemId);
 | 
					        var item = _libraryManager.GetItemById<BaseItem>(itemId, User.GetUserId());
 | 
				
			||||||
        if (item is null)
 | 
					        if (item is null)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            return NotFound();
 | 
					            return NotFound();
 | 
				
			||||||
@ -559,7 +561,7 @@ public class ImageController : BaseJellyfinApiController
 | 
				
			|||||||
        [FromQuery] string? foregroundLayer,
 | 
					        [FromQuery] string? foregroundLayer,
 | 
				
			||||||
        [FromQuery] int? imageIndex)
 | 
					        [FromQuery] int? imageIndex)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        var item = _libraryManager.GetItemById(itemId);
 | 
					        var item = _libraryManager.GetItemById<BaseItem>(itemId, User.GetUserId());
 | 
				
			||||||
        if (item is null)
 | 
					        if (item is null)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            return NotFound();
 | 
					            return NotFound();
 | 
				
			||||||
@ -637,7 +639,7 @@ public class ImageController : BaseJellyfinApiController
 | 
				
			|||||||
        [FromQuery] string? backgroundColor,
 | 
					        [FromQuery] string? backgroundColor,
 | 
				
			||||||
        [FromQuery] string? foregroundLayer)
 | 
					        [FromQuery] string? foregroundLayer)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        var item = _libraryManager.GetItemById(itemId);
 | 
					        var item = _libraryManager.GetItemById<BaseItem>(itemId, User.GetUserId());
 | 
				
			||||||
        if (item is null)
 | 
					        if (item is null)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            return NotFound();
 | 
					            return NotFound();
 | 
				
			||||||
@ -715,7 +717,7 @@ public class ImageController : BaseJellyfinApiController
 | 
				
			|||||||
        [FromQuery] string? foregroundLayer,
 | 
					        [FromQuery] string? foregroundLayer,
 | 
				
			||||||
        [FromRoute, Required] int imageIndex)
 | 
					        [FromRoute, Required] int imageIndex)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        var item = _libraryManager.GetItemById(itemId);
 | 
					        var item = _libraryManager.GetItemById<BaseItem>(itemId, User.GetUserId());
 | 
				
			||||||
        if (item is null)
 | 
					        if (item is null)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            return NotFound();
 | 
					            return NotFound();
 | 
				
			||||||
 | 
				
			|||||||
@ -62,9 +62,11 @@ public class InstantMixController : BaseJellyfinApiController
 | 
				
			|||||||
    /// <param name="imageTypeLimit">Optional. The max number of images to return, per image type.</param>
 | 
					    /// <param name="imageTypeLimit">Optional. The max number of images to return, per image type.</param>
 | 
				
			||||||
    /// <param name="enableImageTypes">Optional. The image types to include in the output.</param>
 | 
					    /// <param name="enableImageTypes">Optional. The image types to include in the output.</param>
 | 
				
			||||||
    /// <response code="200">Instant playlist returned.</response>
 | 
					    /// <response code="200">Instant playlist returned.</response>
 | 
				
			||||||
 | 
					    /// <response code="404">Item not found.</response>
 | 
				
			||||||
    /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the playlist items.</returns>
 | 
					    /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the playlist items.</returns>
 | 
				
			||||||
    [HttpGet("Songs/{itemId}/InstantMix")]
 | 
					    [HttpGet("Songs/{itemId}/InstantMix")]
 | 
				
			||||||
    [ProducesResponseType(StatusCodes.Status200OK)]
 | 
					    [ProducesResponseType(StatusCodes.Status200OK)]
 | 
				
			||||||
 | 
					    [ProducesResponseType(StatusCodes.Status404NotFound)]
 | 
				
			||||||
    public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromSong(
 | 
					    public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromSong(
 | 
				
			||||||
        [FromRoute, Required] Guid itemId,
 | 
					        [FromRoute, Required] Guid itemId,
 | 
				
			||||||
        [FromQuery] Guid? userId,
 | 
					        [FromQuery] Guid? userId,
 | 
				
			||||||
@ -75,11 +77,16 @@ public class InstantMixController : BaseJellyfinApiController
 | 
				
			|||||||
        [FromQuery] int? imageTypeLimit,
 | 
					        [FromQuery] int? imageTypeLimit,
 | 
				
			||||||
        [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes)
 | 
					        [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        var item = _libraryManager.GetItemById(itemId);
 | 
					 | 
				
			||||||
        userId = RequestHelpers.GetUserId(User, userId);
 | 
					        userId = RequestHelpers.GetUserId(User, userId);
 | 
				
			||||||
        var user = userId.IsNullOrEmpty()
 | 
					        var user = userId.IsNullOrEmpty()
 | 
				
			||||||
            ? null
 | 
					            ? null
 | 
				
			||||||
            : _userManager.GetUserById(userId.Value);
 | 
					            : _userManager.GetUserById(userId.Value);
 | 
				
			||||||
 | 
					        var item = _libraryManager.GetItemById<BaseItem>(itemId, user);
 | 
				
			||||||
 | 
					        if (item is null)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            return NotFound();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        var dtoOptions = new DtoOptions { Fields = fields }
 | 
					        var dtoOptions = new DtoOptions { Fields = fields }
 | 
				
			||||||
            .AddClientFields(User)
 | 
					            .AddClientFields(User)
 | 
				
			||||||
            .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
 | 
					            .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
 | 
				
			||||||
@ -99,9 +106,11 @@ public class InstantMixController : BaseJellyfinApiController
 | 
				
			|||||||
    /// <param name="imageTypeLimit">Optional. The max number of images to return, per image type.</param>
 | 
					    /// <param name="imageTypeLimit">Optional. The max number of images to return, per image type.</param>
 | 
				
			||||||
    /// <param name="enableImageTypes">Optional. The image types to include in the output.</param>
 | 
					    /// <param name="enableImageTypes">Optional. The image types to include in the output.</param>
 | 
				
			||||||
    /// <response code="200">Instant playlist returned.</response>
 | 
					    /// <response code="200">Instant playlist returned.</response>
 | 
				
			||||||
 | 
					    /// <response code="404">Item not found.</response>
 | 
				
			||||||
    /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the playlist items.</returns>
 | 
					    /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the playlist items.</returns>
 | 
				
			||||||
    [HttpGet("Albums/{itemId}/InstantMix")]
 | 
					    [HttpGet("Albums/{itemId}/InstantMix")]
 | 
				
			||||||
    [ProducesResponseType(StatusCodes.Status200OK)]
 | 
					    [ProducesResponseType(StatusCodes.Status200OK)]
 | 
				
			||||||
 | 
					    [ProducesResponseType(StatusCodes.Status404NotFound)]
 | 
				
			||||||
    public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromAlbum(
 | 
					    public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromAlbum(
 | 
				
			||||||
        [FromRoute, Required] Guid itemId,
 | 
					        [FromRoute, Required] Guid itemId,
 | 
				
			||||||
        [FromQuery] Guid? userId,
 | 
					        [FromQuery] Guid? userId,
 | 
				
			||||||
@ -112,15 +121,20 @@ public class InstantMixController : BaseJellyfinApiController
 | 
				
			|||||||
        [FromQuery] int? imageTypeLimit,
 | 
					        [FromQuery] int? imageTypeLimit,
 | 
				
			||||||
        [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes)
 | 
					        [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        var album = _libraryManager.GetItemById(itemId);
 | 
					 | 
				
			||||||
        userId = RequestHelpers.GetUserId(User, userId);
 | 
					        userId = RequestHelpers.GetUserId(User, userId);
 | 
				
			||||||
        var user = userId.IsNullOrEmpty()
 | 
					        var user = userId.IsNullOrEmpty()
 | 
				
			||||||
            ? null
 | 
					            ? null
 | 
				
			||||||
            : _userManager.GetUserById(userId.Value);
 | 
					            : _userManager.GetUserById(userId.Value);
 | 
				
			||||||
 | 
					        var item = _libraryManager.GetItemById<BaseItem>(itemId, user);
 | 
				
			||||||
 | 
					        if (item is null)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            return NotFound();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        var dtoOptions = new DtoOptions { Fields = fields }
 | 
					        var dtoOptions = new DtoOptions { Fields = fields }
 | 
				
			||||||
            .AddClientFields(User)
 | 
					            .AddClientFields(User)
 | 
				
			||||||
            .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
 | 
					            .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
 | 
				
			||||||
        var items = _musicManager.GetInstantMixFromItem(album, user, dtoOptions);
 | 
					        var items = _musicManager.GetInstantMixFromItem(item, user, dtoOptions);
 | 
				
			||||||
        return GetResult(items, user, limit, dtoOptions);
 | 
					        return GetResult(items, user, limit, dtoOptions);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -136,9 +150,11 @@ public class InstantMixController : BaseJellyfinApiController
 | 
				
			|||||||
    /// <param name="imageTypeLimit">Optional. The max number of images to return, per image type.</param>
 | 
					    /// <param name="imageTypeLimit">Optional. The max number of images to return, per image type.</param>
 | 
				
			||||||
    /// <param name="enableImageTypes">Optional. The image types to include in the output.</param>
 | 
					    /// <param name="enableImageTypes">Optional. The image types to include in the output.</param>
 | 
				
			||||||
    /// <response code="200">Instant playlist returned.</response>
 | 
					    /// <response code="200">Instant playlist returned.</response>
 | 
				
			||||||
 | 
					    /// <response code="404">Item not found.</response>
 | 
				
			||||||
    /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the playlist items.</returns>
 | 
					    /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the playlist items.</returns>
 | 
				
			||||||
    [HttpGet("Playlists/{itemId}/InstantMix")]
 | 
					    [HttpGet("Playlists/{itemId}/InstantMix")]
 | 
				
			||||||
    [ProducesResponseType(StatusCodes.Status200OK)]
 | 
					    [ProducesResponseType(StatusCodes.Status200OK)]
 | 
				
			||||||
 | 
					    [ProducesResponseType(StatusCodes.Status404NotFound)]
 | 
				
			||||||
    public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromPlaylist(
 | 
					    public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromPlaylist(
 | 
				
			||||||
        [FromRoute, Required] Guid itemId,
 | 
					        [FromRoute, Required] Guid itemId,
 | 
				
			||||||
        [FromQuery] Guid? userId,
 | 
					        [FromQuery] Guid? userId,
 | 
				
			||||||
@ -149,15 +165,20 @@ public class InstantMixController : BaseJellyfinApiController
 | 
				
			|||||||
        [FromQuery] int? imageTypeLimit,
 | 
					        [FromQuery] int? imageTypeLimit,
 | 
				
			||||||
        [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes)
 | 
					        [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        var playlist = (Playlist)_libraryManager.GetItemById(itemId);
 | 
					 | 
				
			||||||
        userId = RequestHelpers.GetUserId(User, userId);
 | 
					        userId = RequestHelpers.GetUserId(User, userId);
 | 
				
			||||||
        var user = userId.IsNullOrEmpty()
 | 
					        var user = userId.IsNullOrEmpty()
 | 
				
			||||||
            ? null
 | 
					            ? null
 | 
				
			||||||
            : _userManager.GetUserById(userId.Value);
 | 
					            : _userManager.GetUserById(userId.Value);
 | 
				
			||||||
 | 
					        var item = _libraryManager.GetItemById<Playlist>(itemId, user);
 | 
				
			||||||
 | 
					        if (item is null)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            return NotFound();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        var dtoOptions = new DtoOptions { Fields = fields }
 | 
					        var dtoOptions = new DtoOptions { Fields = fields }
 | 
				
			||||||
            .AddClientFields(User)
 | 
					            .AddClientFields(User)
 | 
				
			||||||
            .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
 | 
					            .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
 | 
				
			||||||
        var items = _musicManager.GetInstantMixFromItem(playlist, user, dtoOptions);
 | 
					        var items = _musicManager.GetInstantMixFromItem(item, user, dtoOptions);
 | 
				
			||||||
        return GetResult(items, user, limit, dtoOptions);
 | 
					        return GetResult(items, user, limit, dtoOptions);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -209,9 +230,11 @@ public class InstantMixController : BaseJellyfinApiController
 | 
				
			|||||||
    /// <param name="imageTypeLimit">Optional. The max number of images to return, per image type.</param>
 | 
					    /// <param name="imageTypeLimit">Optional. The max number of images to return, per image type.</param>
 | 
				
			||||||
    /// <param name="enableImageTypes">Optional. The image types to include in the output.</param>
 | 
					    /// <param name="enableImageTypes">Optional. The image types to include in the output.</param>
 | 
				
			||||||
    /// <response code="200">Instant playlist returned.</response>
 | 
					    /// <response code="200">Instant playlist returned.</response>
 | 
				
			||||||
 | 
					    /// <response code="404">Item not found.</response>
 | 
				
			||||||
    /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the playlist items.</returns>
 | 
					    /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the playlist items.</returns>
 | 
				
			||||||
    [HttpGet("Artists/{itemId}/InstantMix")]
 | 
					    [HttpGet("Artists/{itemId}/InstantMix")]
 | 
				
			||||||
    [ProducesResponseType(StatusCodes.Status200OK)]
 | 
					    [ProducesResponseType(StatusCodes.Status200OK)]
 | 
				
			||||||
 | 
					    [ProducesResponseType(StatusCodes.Status404NotFound)]
 | 
				
			||||||
    public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromArtists(
 | 
					    public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromArtists(
 | 
				
			||||||
        [FromRoute, Required] Guid itemId,
 | 
					        [FromRoute, Required] Guid itemId,
 | 
				
			||||||
        [FromQuery] Guid? userId,
 | 
					        [FromQuery] Guid? userId,
 | 
				
			||||||
@ -222,11 +245,16 @@ public class InstantMixController : BaseJellyfinApiController
 | 
				
			|||||||
        [FromQuery] int? imageTypeLimit,
 | 
					        [FromQuery] int? imageTypeLimit,
 | 
				
			||||||
        [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes)
 | 
					        [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        var item = _libraryManager.GetItemById(itemId);
 | 
					 | 
				
			||||||
        userId = RequestHelpers.GetUserId(User, userId);
 | 
					        userId = RequestHelpers.GetUserId(User, userId);
 | 
				
			||||||
        var user = userId.IsNullOrEmpty()
 | 
					        var user = userId.IsNullOrEmpty()
 | 
				
			||||||
            ? null
 | 
					            ? null
 | 
				
			||||||
            : _userManager.GetUserById(userId.Value);
 | 
					            : _userManager.GetUserById(userId.Value);
 | 
				
			||||||
 | 
					        var item = _libraryManager.GetItemById<BaseItem>(itemId, user);
 | 
				
			||||||
 | 
					        if (item is null)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            return NotFound();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        var dtoOptions = new DtoOptions { Fields = fields }
 | 
					        var dtoOptions = new DtoOptions { Fields = fields }
 | 
				
			||||||
            .AddClientFields(User)
 | 
					            .AddClientFields(User)
 | 
				
			||||||
            .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
 | 
					            .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
 | 
				
			||||||
@ -246,9 +274,11 @@ public class InstantMixController : BaseJellyfinApiController
 | 
				
			|||||||
    /// <param name="imageTypeLimit">Optional. The max number of images to return, per image type.</param>
 | 
					    /// <param name="imageTypeLimit">Optional. The max number of images to return, per image type.</param>
 | 
				
			||||||
    /// <param name="enableImageTypes">Optional. The image types to include in the output.</param>
 | 
					    /// <param name="enableImageTypes">Optional. The image types to include in the output.</param>
 | 
				
			||||||
    /// <response code="200">Instant playlist returned.</response>
 | 
					    /// <response code="200">Instant playlist returned.</response>
 | 
				
			||||||
 | 
					    /// <response code="404">Item not found.</response>
 | 
				
			||||||
    /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the playlist items.</returns>
 | 
					    /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the playlist items.</returns>
 | 
				
			||||||
    [HttpGet("Items/{itemId}/InstantMix")]
 | 
					    [HttpGet("Items/{itemId}/InstantMix")]
 | 
				
			||||||
    [ProducesResponseType(StatusCodes.Status200OK)]
 | 
					    [ProducesResponseType(StatusCodes.Status200OK)]
 | 
				
			||||||
 | 
					    [ProducesResponseType(StatusCodes.Status404NotFound)]
 | 
				
			||||||
    public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromItem(
 | 
					    public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromItem(
 | 
				
			||||||
        [FromRoute, Required] Guid itemId,
 | 
					        [FromRoute, Required] Guid itemId,
 | 
				
			||||||
        [FromQuery] Guid? userId,
 | 
					        [FromQuery] Guid? userId,
 | 
				
			||||||
@ -259,11 +289,16 @@ public class InstantMixController : BaseJellyfinApiController
 | 
				
			|||||||
        [FromQuery] int? imageTypeLimit,
 | 
					        [FromQuery] int? imageTypeLimit,
 | 
				
			||||||
        [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes)
 | 
					        [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        var item = _libraryManager.GetItemById(itemId);
 | 
					 | 
				
			||||||
        userId = RequestHelpers.GetUserId(User, userId);
 | 
					        userId = RequestHelpers.GetUserId(User, userId);
 | 
				
			||||||
        var user = userId.IsNullOrEmpty()
 | 
					        var user = userId.IsNullOrEmpty()
 | 
				
			||||||
            ? null
 | 
					            ? null
 | 
				
			||||||
            : _userManager.GetUserById(userId.Value);
 | 
					            : _userManager.GetUserById(userId.Value);
 | 
				
			||||||
 | 
					        var item = _libraryManager.GetItemById<BaseItem>(itemId, user);
 | 
				
			||||||
 | 
					        if (item is null)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            return NotFound();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        var dtoOptions = new DtoOptions { Fields = fields }
 | 
					        var dtoOptions = new DtoOptions { Fields = fields }
 | 
				
			||||||
            .AddClientFields(User)
 | 
					            .AddClientFields(User)
 | 
				
			||||||
            .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
 | 
					            .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
 | 
				
			||||||
@ -283,9 +318,11 @@ public class InstantMixController : BaseJellyfinApiController
 | 
				
			|||||||
    /// <param name="imageTypeLimit">Optional. The max number of images to return, per image type.</param>
 | 
					    /// <param name="imageTypeLimit">Optional. The max number of images to return, per image type.</param>
 | 
				
			||||||
    /// <param name="enableImageTypes">Optional. The image types to include in the output.</param>
 | 
					    /// <param name="enableImageTypes">Optional. The image types to include in the output.</param>
 | 
				
			||||||
    /// <response code="200">Instant playlist returned.</response>
 | 
					    /// <response code="200">Instant playlist returned.</response>
 | 
				
			||||||
 | 
					    /// <response code="404">Item not found.</response>
 | 
				
			||||||
    /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the playlist items.</returns>
 | 
					    /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the playlist items.</returns>
 | 
				
			||||||
    [HttpGet("Artists/InstantMix")]
 | 
					    [HttpGet("Artists/InstantMix")]
 | 
				
			||||||
    [ProducesResponseType(StatusCodes.Status200OK)]
 | 
					    [ProducesResponseType(StatusCodes.Status200OK)]
 | 
				
			||||||
 | 
					    [ProducesResponseType(StatusCodes.Status404NotFound)]
 | 
				
			||||||
    [Obsolete("Use GetInstantMixFromArtists")]
 | 
					    [Obsolete("Use GetInstantMixFromArtists")]
 | 
				
			||||||
    public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromArtists2(
 | 
					    public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromArtists2(
 | 
				
			||||||
        [FromQuery, Required] Guid id,
 | 
					        [FromQuery, Required] Guid id,
 | 
				
			||||||
@ -320,9 +357,11 @@ public class InstantMixController : BaseJellyfinApiController
 | 
				
			|||||||
    /// <param name="imageTypeLimit">Optional. The max number of images to return, per image type.</param>
 | 
					    /// <param name="imageTypeLimit">Optional. The max number of images to return, per image type.</param>
 | 
				
			||||||
    /// <param name="enableImageTypes">Optional. The image types to include in the output.</param>
 | 
					    /// <param name="enableImageTypes">Optional. The image types to include in the output.</param>
 | 
				
			||||||
    /// <response code="200">Instant playlist returned.</response>
 | 
					    /// <response code="200">Instant playlist returned.</response>
 | 
				
			||||||
 | 
					    /// <response code="404">Item not found.</response>
 | 
				
			||||||
    /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the playlist items.</returns>
 | 
					    /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the playlist items.</returns>
 | 
				
			||||||
    [HttpGet("MusicGenres/InstantMix")]
 | 
					    [HttpGet("MusicGenres/InstantMix")]
 | 
				
			||||||
    [ProducesResponseType(StatusCodes.Status200OK)]
 | 
					    [ProducesResponseType(StatusCodes.Status200OK)]
 | 
				
			||||||
 | 
					    [ProducesResponseType(StatusCodes.Status404NotFound)]
 | 
				
			||||||
    public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromMusicGenreById(
 | 
					    public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromMusicGenreById(
 | 
				
			||||||
        [FromQuery, Required] Guid id,
 | 
					        [FromQuery, Required] Guid id,
 | 
				
			||||||
        [FromQuery] Guid? userId,
 | 
					        [FromQuery] Guid? userId,
 | 
				
			||||||
@ -333,11 +372,16 @@ public class InstantMixController : BaseJellyfinApiController
 | 
				
			|||||||
        [FromQuery] int? imageTypeLimit,
 | 
					        [FromQuery] int? imageTypeLimit,
 | 
				
			||||||
        [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes)
 | 
					        [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        var item = _libraryManager.GetItemById(id);
 | 
					 | 
				
			||||||
        userId = RequestHelpers.GetUserId(User, userId);
 | 
					        userId = RequestHelpers.GetUserId(User, userId);
 | 
				
			||||||
        var user = userId.IsNullOrEmpty()
 | 
					        var user = userId.IsNullOrEmpty()
 | 
				
			||||||
            ? null
 | 
					            ? null
 | 
				
			||||||
            : _userManager.GetUserById(userId.Value);
 | 
					            : _userManager.GetUserById(userId.Value);
 | 
				
			||||||
 | 
					        var item = _libraryManager.GetItemById<BaseItem>(id, user);
 | 
				
			||||||
 | 
					        if (item is null)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            return NotFound();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        var dtoOptions = new DtoOptions { Fields = fields }
 | 
					        var dtoOptions = new DtoOptions { Fields = fields }
 | 
				
			||||||
            .AddClientFields(User)
 | 
					            .AddClientFields(User)
 | 
				
			||||||
            .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
 | 
					            .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
 | 
				
			||||||
 | 
				
			|||||||
@ -4,6 +4,8 @@ using System.ComponentModel.DataAnnotations;
 | 
				
			|||||||
using System.Threading;
 | 
					using System.Threading;
 | 
				
			||||||
using System.Threading.Tasks;
 | 
					using System.Threading.Tasks;
 | 
				
			||||||
using Jellyfin.Api.Constants;
 | 
					using Jellyfin.Api.Constants;
 | 
				
			||||||
 | 
					using Jellyfin.Api.Extensions;
 | 
				
			||||||
 | 
					using Jellyfin.Api.Helpers;
 | 
				
			||||||
using MediaBrowser.Common.Api;
 | 
					using MediaBrowser.Common.Api;
 | 
				
			||||||
using MediaBrowser.Controller.Entities;
 | 
					using MediaBrowser.Controller.Entities;
 | 
				
			||||||
using MediaBrowser.Controller.Entities.Audio;
 | 
					using MediaBrowser.Controller.Entities.Audio;
 | 
				
			||||||
@ -64,7 +66,7 @@ public class ItemLookupController : BaseJellyfinApiController
 | 
				
			|||||||
    [ProducesResponseType(StatusCodes.Status404NotFound)]
 | 
					    [ProducesResponseType(StatusCodes.Status404NotFound)]
 | 
				
			||||||
    public ActionResult<IEnumerable<ExternalIdInfo>> GetExternalIdInfos([FromRoute, Required] Guid itemId)
 | 
					    public ActionResult<IEnumerable<ExternalIdInfo>> GetExternalIdInfos([FromRoute, Required] Guid itemId)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        var item = _libraryManager.GetItemById(itemId);
 | 
					        var item = _libraryManager.GetItemById<BaseItem>(itemId, User.GetUserId());
 | 
				
			||||||
        if (item is null)
 | 
					        if (item is null)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            return NotFound();
 | 
					            return NotFound();
 | 
				
			||||||
@ -234,6 +236,7 @@ public class ItemLookupController : BaseJellyfinApiController
 | 
				
			|||||||
    /// <param name="searchResult">The remote search result.</param>
 | 
					    /// <param name="searchResult">The remote search result.</param>
 | 
				
			||||||
    /// <param name="replaceAllImages">Optional. Whether or not to replace all images. Default: True.</param>
 | 
					    /// <param name="replaceAllImages">Optional. Whether or not to replace all images. Default: True.</param>
 | 
				
			||||||
    /// <response code="204">Item metadata refreshed.</response>
 | 
					    /// <response code="204">Item metadata refreshed.</response>
 | 
				
			||||||
 | 
					    /// <response code="404">Item not found.</response>
 | 
				
			||||||
    /// <returns>
 | 
					    /// <returns>
 | 
				
			||||||
    /// A <see cref="Task" /> that represents the asynchronous operation to get the remote search results.
 | 
					    /// A <see cref="Task" /> that represents the asynchronous operation to get the remote search results.
 | 
				
			||||||
    /// The task result contains an <see cref="NoContentResult"/>.
 | 
					    /// The task result contains an <see cref="NoContentResult"/>.
 | 
				
			||||||
@ -241,12 +244,18 @@ public class ItemLookupController : BaseJellyfinApiController
 | 
				
			|||||||
    [HttpPost("Items/RemoteSearch/Apply/{itemId}")]
 | 
					    [HttpPost("Items/RemoteSearch/Apply/{itemId}")]
 | 
				
			||||||
    [Authorize(Policy = Policies.RequiresElevation)]
 | 
					    [Authorize(Policy = Policies.RequiresElevation)]
 | 
				
			||||||
    [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
					    [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
				
			||||||
 | 
					    [ProducesResponseType(StatusCodes.Status404NotFound)]
 | 
				
			||||||
    public async Task<ActionResult> ApplySearchCriteria(
 | 
					    public async Task<ActionResult> ApplySearchCriteria(
 | 
				
			||||||
        [FromRoute, Required] Guid itemId,
 | 
					        [FromRoute, Required] Guid itemId,
 | 
				
			||||||
        [FromBody, Required] RemoteSearchResult searchResult,
 | 
					        [FromBody, Required] RemoteSearchResult searchResult,
 | 
				
			||||||
        [FromQuery] bool replaceAllImages = true)
 | 
					        [FromQuery] bool replaceAllImages = true)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        var item = _libraryManager.GetItemById(itemId);
 | 
					        var item = _libraryManager.GetItemById<BaseItem>(itemId, User.GetUserId());
 | 
				
			||||||
 | 
					        if (item is null)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            return NotFound();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        _logger.LogInformation(
 | 
					        _logger.LogInformation(
 | 
				
			||||||
            "Setting provider id's to item {ItemId}-{ItemName}: {@ProviderIds}",
 | 
					            "Setting provider id's to item {ItemId}-{ItemName}: {@ProviderIds}",
 | 
				
			||||||
            item.Id,
 | 
					            item.Id,
 | 
				
			||||||
 | 
				
			|||||||
@ -2,7 +2,10 @@ using System;
 | 
				
			|||||||
using System.ComponentModel;
 | 
					using System.ComponentModel;
 | 
				
			||||||
using System.ComponentModel.DataAnnotations;
 | 
					using System.ComponentModel.DataAnnotations;
 | 
				
			||||||
using Jellyfin.Api.Constants;
 | 
					using Jellyfin.Api.Constants;
 | 
				
			||||||
 | 
					using Jellyfin.Api.Extensions;
 | 
				
			||||||
 | 
					using Jellyfin.Api.Helpers;
 | 
				
			||||||
using MediaBrowser.Common.Api;
 | 
					using MediaBrowser.Common.Api;
 | 
				
			||||||
 | 
					using MediaBrowser.Controller.Entities;
 | 
				
			||||||
using MediaBrowser.Controller.Library;
 | 
					using MediaBrowser.Controller.Library;
 | 
				
			||||||
using MediaBrowser.Controller.Providers;
 | 
					using MediaBrowser.Controller.Providers;
 | 
				
			||||||
using MediaBrowser.Model.IO;
 | 
					using MediaBrowser.Model.IO;
 | 
				
			||||||
@ -61,7 +64,7 @@ public class ItemRefreshController : BaseJellyfinApiController
 | 
				
			|||||||
        [FromQuery] bool replaceAllMetadata = false,
 | 
					        [FromQuery] bool replaceAllMetadata = false,
 | 
				
			||||||
        [FromQuery] bool replaceAllImages = false)
 | 
					        [FromQuery] bool replaceAllImages = false)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        var item = _libraryManager.GetItemById(itemId);
 | 
					        var item = _libraryManager.GetItemById<BaseItem>(itemId, User.GetUserId());
 | 
				
			||||||
        if (item is null)
 | 
					        if (item is null)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            return NotFound();
 | 
					            return NotFound();
 | 
				
			||||||
 | 
				
			|||||||
@ -5,6 +5,8 @@ using System.Linq;
 | 
				
			|||||||
using System.Threading;
 | 
					using System.Threading;
 | 
				
			||||||
using System.Threading.Tasks;
 | 
					using System.Threading.Tasks;
 | 
				
			||||||
using Jellyfin.Api.Constants;
 | 
					using Jellyfin.Api.Constants;
 | 
				
			||||||
 | 
					using Jellyfin.Api.Extensions;
 | 
				
			||||||
 | 
					using Jellyfin.Api.Helpers;
 | 
				
			||||||
using Jellyfin.Data.Enums;
 | 
					using Jellyfin.Data.Enums;
 | 
				
			||||||
using MediaBrowser.Common.Api;
 | 
					using MediaBrowser.Common.Api;
 | 
				
			||||||
using MediaBrowser.Controller.Configuration;
 | 
					using MediaBrowser.Controller.Configuration;
 | 
				
			||||||
@ -72,7 +74,7 @@ public class ItemUpdateController : BaseJellyfinApiController
 | 
				
			|||||||
    [ProducesResponseType(StatusCodes.Status404NotFound)]
 | 
					    [ProducesResponseType(StatusCodes.Status404NotFound)]
 | 
				
			||||||
    public async Task<ActionResult> UpdateItem([FromRoute, Required] Guid itemId, [FromBody, Required] BaseItemDto request)
 | 
					    public async Task<ActionResult> UpdateItem([FromRoute, Required] Guid itemId, [FromBody, Required] BaseItemDto request)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        var item = _libraryManager.GetItemById(itemId);
 | 
					        var item = _libraryManager.GetItemById<BaseItem>(itemId, User.GetUserId());
 | 
				
			||||||
        if (item is null)
 | 
					        if (item is null)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            return NotFound();
 | 
					            return NotFound();
 | 
				
			||||||
@ -145,7 +147,11 @@ public class ItemUpdateController : BaseJellyfinApiController
 | 
				
			|||||||
    [ProducesResponseType(StatusCodes.Status404NotFound)]
 | 
					    [ProducesResponseType(StatusCodes.Status404NotFound)]
 | 
				
			||||||
    public ActionResult<MetadataEditorInfo> GetMetadataEditorInfo([FromRoute, Required] Guid itemId)
 | 
					    public ActionResult<MetadataEditorInfo> GetMetadataEditorInfo([FromRoute, Required] Guid itemId)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        var item = _libraryManager.GetItemById(itemId);
 | 
					        var item = _libraryManager.GetItemById<BaseItem>(itemId, User.GetUserId());
 | 
				
			||||||
 | 
					        if (item is null)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            return NotFound();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        var info = new MetadataEditorInfo
 | 
					        var info = new MetadataEditorInfo
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
@ -197,7 +203,7 @@ public class ItemUpdateController : BaseJellyfinApiController
 | 
				
			|||||||
    [ProducesResponseType(StatusCodes.Status404NotFound)]
 | 
					    [ProducesResponseType(StatusCodes.Status404NotFound)]
 | 
				
			||||||
    public ActionResult UpdateItemContentType([FromRoute, Required] Guid itemId, [FromQuery] string? contentType)
 | 
					    public ActionResult UpdateItemContentType([FromRoute, Required] Guid itemId, [FromQuery] string? contentType)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        var item = _libraryManager.GetItemById(itemId);
 | 
					        var item = _libraryManager.GetItemById<BaseItem>(itemId, User.GetUserId());
 | 
				
			||||||
        if (item is null)
 | 
					        if (item is null)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            return NotFound();
 | 
					            return NotFound();
 | 
				
			||||||
 | 
				
			|||||||
@ -967,9 +967,13 @@ public class ItemsController : BaseJellyfinApiController
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        var user = _userManager.GetUserById(requestUserId) ?? throw new ResourceNotFoundException();
 | 
					        var user = _userManager.GetUserById(requestUserId) ?? throw new ResourceNotFoundException();
 | 
				
			||||||
        var item = _libraryManager.GetItemById(itemId);
 | 
					        var item = _libraryManager.GetItemById<BaseItem>(itemId, user);
 | 
				
			||||||
 | 
					        if (item is null)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            return NotFound();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return (item == null) ? NotFound() : _userDataRepository.GetUserDataDto(item, user);
 | 
					        return _userDataRepository.GetUserDataDto(item, user);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// <summary>
 | 
					    /// <summary>
 | 
				
			||||||
@ -1014,8 +1018,8 @@ public class ItemsController : BaseJellyfinApiController
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        var user = _userManager.GetUserById(requestUserId) ?? throw new ResourceNotFoundException();
 | 
					        var user = _userManager.GetUserById(requestUserId) ?? throw new ResourceNotFoundException();
 | 
				
			||||||
        var item = _libraryManager.GetItemById(itemId);
 | 
					        var item = _libraryManager.GetItemById<BaseItem>(itemId, user);
 | 
				
			||||||
        if (item == null)
 | 
					        if (item is null)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            return NotFound();
 | 
					            return NotFound();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
				
			|||||||
@ -102,7 +102,7 @@ public class LibraryController : BaseJellyfinApiController
 | 
				
			|||||||
    [ProducesFile("video/*", "audio/*")]
 | 
					    [ProducesFile("video/*", "audio/*")]
 | 
				
			||||||
    public ActionResult GetFile([FromRoute, Required] Guid itemId)
 | 
					    public ActionResult GetFile([FromRoute, Required] Guid itemId)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        var item = _libraryManager.GetItemById(itemId);
 | 
					        var item = _libraryManager.GetItemById<BaseItem>(itemId, User.GetUserId());
 | 
				
			||||||
        if (item is null)
 | 
					        if (item is null)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            return NotFound();
 | 
					            return NotFound();
 | 
				
			||||||
@ -152,11 +152,10 @@ public class LibraryController : BaseJellyfinApiController
 | 
				
			|||||||
            ? (userId.IsNullOrEmpty()
 | 
					            ? (userId.IsNullOrEmpty()
 | 
				
			||||||
                ? _libraryManager.RootFolder
 | 
					                ? _libraryManager.RootFolder
 | 
				
			||||||
                : _libraryManager.GetUserRootFolder())
 | 
					                : _libraryManager.GetUserRootFolder())
 | 
				
			||||||
            : _libraryManager.GetItemById(itemId);
 | 
					            : _libraryManager.GetItemById<BaseItem>(itemId, user);
 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (item is null)
 | 
					        if (item is null)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            return NotFound("Item not found.");
 | 
					            return NotFound();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        IEnumerable<BaseItem> themeItems;
 | 
					        IEnumerable<BaseItem> themeItems;
 | 
				
			||||||
@ -214,16 +213,14 @@ public class LibraryController : BaseJellyfinApiController
 | 
				
			|||||||
        var user = userId.IsNullOrEmpty()
 | 
					        var user = userId.IsNullOrEmpty()
 | 
				
			||||||
            ? null
 | 
					            ? null
 | 
				
			||||||
            : _userManager.GetUserById(userId.Value);
 | 
					            : _userManager.GetUserById(userId.Value);
 | 
				
			||||||
 | 
					 | 
				
			||||||
        var item = itemId.IsEmpty()
 | 
					        var item = itemId.IsEmpty()
 | 
				
			||||||
            ? (userId.IsNullOrEmpty()
 | 
					            ? (userId.IsNullOrEmpty()
 | 
				
			||||||
                ? _libraryManager.RootFolder
 | 
					                ? _libraryManager.RootFolder
 | 
				
			||||||
                : _libraryManager.GetUserRootFolder())
 | 
					                : _libraryManager.GetUserRootFolder())
 | 
				
			||||||
            : _libraryManager.GetItemById(itemId);
 | 
					            : _libraryManager.GetItemById<BaseItem>(itemId, user);
 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (item is null)
 | 
					        if (item is null)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            return NotFound("Item not found.");
 | 
					            return NotFound();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        IEnumerable<BaseItem> themeItems;
 | 
					        IEnumerable<BaseItem> themeItems;
 | 
				
			||||||
@ -286,7 +283,8 @@ public class LibraryController : BaseJellyfinApiController
 | 
				
			|||||||
            userId,
 | 
					            userId,
 | 
				
			||||||
            inheritFromParent);
 | 
					            inheritFromParent);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (themeSongs.Result is NotFoundObjectResult || themeVideos.Result is NotFoundObjectResult)
 | 
					        if (themeSongs.Result is StatusCodeResult { StatusCode: StatusCodes.Status404NotFound }
 | 
				
			||||||
 | 
					            || themeVideos.Result is StatusCodeResult { StatusCode: StatusCodes.Status404NotFound })
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            return NotFound();
 | 
					            return NotFound();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@ -327,6 +325,7 @@ public class LibraryController : BaseJellyfinApiController
 | 
				
			|||||||
    /// <param name="itemId">The item id.</param>
 | 
					    /// <param name="itemId">The item id.</param>
 | 
				
			||||||
    /// <response code="204">Item deleted.</response>
 | 
					    /// <response code="204">Item deleted.</response>
 | 
				
			||||||
    /// <response code="401">Unauthorized access.</response>
 | 
					    /// <response code="401">Unauthorized access.</response>
 | 
				
			||||||
 | 
					    /// <response code="404">Item not found.</response>
 | 
				
			||||||
    /// <returns>A <see cref="NoContentResult"/>.</returns>
 | 
					    /// <returns>A <see cref="NoContentResult"/>.</returns>
 | 
				
			||||||
    [HttpDelete("Items/{itemId}")]
 | 
					    [HttpDelete("Items/{itemId}")]
 | 
				
			||||||
    [Authorize]
 | 
					    [Authorize]
 | 
				
			||||||
@ -335,17 +334,18 @@ public class LibraryController : BaseJellyfinApiController
 | 
				
			|||||||
    [ProducesResponseType(StatusCodes.Status404NotFound)]
 | 
					    [ProducesResponseType(StatusCodes.Status404NotFound)]
 | 
				
			||||||
    public ActionResult DeleteItem(Guid itemId)
 | 
					    public ActionResult DeleteItem(Guid itemId)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        var isApiKey = User.GetIsApiKey();
 | 
					 | 
				
			||||||
        var userId = User.GetUserId();
 | 
					        var userId = User.GetUserId();
 | 
				
			||||||
        var user = !isApiKey && !userId.IsEmpty()
 | 
					        var isApiKey = User.GetIsApiKey();
 | 
				
			||||||
            ? _userManager.GetUserById(userId) ?? throw new ResourceNotFoundException()
 | 
					        var user = userId.IsEmpty() && isApiKey
 | 
				
			||||||
            : null;
 | 
					            ? null
 | 
				
			||||||
        if (!isApiKey && user is null)
 | 
					            : _userManager.GetUserById(userId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (user is null && !isApiKey)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            return Unauthorized("Unauthorized access");
 | 
					            return NotFound();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        var item = _libraryManager.GetItemById(itemId);
 | 
					        var item = _libraryManager.GetItemById<BaseItem>(itemId, user);
 | 
				
			||||||
        if (item is null)
 | 
					        if (item is null)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            return NotFound();
 | 
					            return NotFound();
 | 
				
			||||||
@ -391,7 +391,7 @@ public class LibraryController : BaseJellyfinApiController
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        foreach (var i in ids)
 | 
					        foreach (var i in ids)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            var item = _libraryManager.GetItemById(i);
 | 
					            var item = _libraryManager.GetItemById<BaseItem>(i, user);
 | 
				
			||||||
            if (item is null)
 | 
					            if (item is null)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                return NotFound();
 | 
					                return NotFound();
 | 
				
			||||||
@ -459,19 +459,17 @@ public class LibraryController : BaseJellyfinApiController
 | 
				
			|||||||
    [ProducesResponseType(StatusCodes.Status404NotFound)]
 | 
					    [ProducesResponseType(StatusCodes.Status404NotFound)]
 | 
				
			||||||
    public ActionResult<IEnumerable<BaseItemDto>> GetAncestors([FromRoute, Required] Guid itemId, [FromQuery] Guid? userId)
 | 
					    public ActionResult<IEnumerable<BaseItemDto>> GetAncestors([FromRoute, Required] Guid itemId, [FromQuery] Guid? userId)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        var item = _libraryManager.GetItemById(itemId);
 | 
					 | 
				
			||||||
        userId = RequestHelpers.GetUserId(User, userId);
 | 
					        userId = RequestHelpers.GetUserId(User, userId);
 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (item is null)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            return NotFound("Item not found");
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        var baseItemDtos = new List<BaseItemDto>();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        var user = userId.IsNullOrEmpty()
 | 
					        var user = userId.IsNullOrEmpty()
 | 
				
			||||||
            ? null
 | 
					            ? null
 | 
				
			||||||
            : _userManager.GetUserById(userId.Value);
 | 
					            : _userManager.GetUserById(userId.Value);
 | 
				
			||||||
 | 
					        var item = _libraryManager.GetItemById<BaseItem>(itemId, user);
 | 
				
			||||||
 | 
					        if (item is null)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            return NotFound();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        var baseItemDtos = new List<BaseItemDto>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        var dtoOptions = new DtoOptions().AddClientFields(User);
 | 
					        var dtoOptions = new DtoOptions().AddClientFields(User);
 | 
				
			||||||
        BaseItem? parent = item.GetParent();
 | 
					        BaseItem? parent = item.GetParent();
 | 
				
			||||||
@ -644,14 +642,16 @@ public class LibraryController : BaseJellyfinApiController
 | 
				
			|||||||
    [ProducesFile("video/*", "audio/*")]
 | 
					    [ProducesFile("video/*", "audio/*")]
 | 
				
			||||||
    public async Task<ActionResult> GetDownload([FromRoute, Required] Guid itemId)
 | 
					    public async Task<ActionResult> GetDownload([FromRoute, Required] Guid itemId)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        var item = _libraryManager.GetItemById(itemId);
 | 
					        var userId = User.GetUserId();
 | 
				
			||||||
 | 
					        var user = userId.IsEmpty()
 | 
				
			||||||
 | 
					            ? null
 | 
				
			||||||
 | 
					            : _userManager.GetUserById(userId);
 | 
				
			||||||
 | 
					        var item = _libraryManager.GetItemById<BaseItem>(itemId, user);
 | 
				
			||||||
        if (item is null)
 | 
					        if (item is null)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            return NotFound();
 | 
					            return NotFound();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        var user = _userManager.GetUserById(User.GetUserId());
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (user is not null)
 | 
					        if (user is not null)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            if (!item.CanDownload(user))
 | 
					            if (!item.CanDownload(user))
 | 
				
			||||||
@ -704,12 +704,14 @@ public class LibraryController : BaseJellyfinApiController
 | 
				
			|||||||
        [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields)
 | 
					        [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        userId = RequestHelpers.GetUserId(User, userId);
 | 
					        userId = RequestHelpers.GetUserId(User, userId);
 | 
				
			||||||
 | 
					        var user = userId.IsNullOrEmpty()
 | 
				
			||||||
 | 
					            ? null
 | 
				
			||||||
 | 
					            : _userManager.GetUserById(userId.Value);
 | 
				
			||||||
        var item = itemId.IsEmpty()
 | 
					        var item = itemId.IsEmpty()
 | 
				
			||||||
            ? (userId.IsNullOrEmpty()
 | 
					            ? (user is null
 | 
				
			||||||
                ? _libraryManager.RootFolder
 | 
					                ? _libraryManager.RootFolder
 | 
				
			||||||
                : _libraryManager.GetUserRootFolder())
 | 
					                : _libraryManager.GetUserRootFolder())
 | 
				
			||||||
            : _libraryManager.GetItemById(itemId);
 | 
					            : _libraryManager.GetItemById<BaseItem>(itemId, user);
 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (item is null)
 | 
					        if (item is null)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            return NotFound();
 | 
					            return NotFound();
 | 
				
			||||||
@ -720,9 +722,6 @@ public class LibraryController : BaseJellyfinApiController
 | 
				
			|||||||
            return new QueryResult<BaseItemDto>();
 | 
					            return new QueryResult<BaseItemDto>();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        var user = userId.IsNullOrEmpty()
 | 
					 | 
				
			||||||
            ? null
 | 
					 | 
				
			||||||
            : _userManager.GetUserById(userId.Value);
 | 
					 | 
				
			||||||
        var dtoOptions = new DtoOptions { Fields = fields }
 | 
					        var dtoOptions = new DtoOptions { Fields = fields }
 | 
				
			||||||
            .AddClientFields(User);
 | 
					            .AddClientFields(User);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -6,6 +6,8 @@ using System.IO;
 | 
				
			|||||||
using System.Linq;
 | 
					using System.Linq;
 | 
				
			||||||
using System.Threading;
 | 
					using System.Threading;
 | 
				
			||||||
using System.Threading.Tasks;
 | 
					using System.Threading.Tasks;
 | 
				
			||||||
 | 
					using Jellyfin.Api.Extensions;
 | 
				
			||||||
 | 
					using Jellyfin.Api.Helpers;
 | 
				
			||||||
using Jellyfin.Api.ModelBinders;
 | 
					using Jellyfin.Api.ModelBinders;
 | 
				
			||||||
using Jellyfin.Api.Models.LibraryStructureDto;
 | 
					using Jellyfin.Api.Models.LibraryStructureDto;
 | 
				
			||||||
using MediaBrowser.Common.Api;
 | 
					using MediaBrowser.Common.Api;
 | 
				
			||||||
@ -311,15 +313,21 @@ public class LibraryStructureController : BaseJellyfinApiController
 | 
				
			|||||||
    /// </summary>
 | 
					    /// </summary>
 | 
				
			||||||
    /// <param name="request">The library name and options.</param>
 | 
					    /// <param name="request">The library name and options.</param>
 | 
				
			||||||
    /// <response code="204">Library updated.</response>
 | 
					    /// <response code="204">Library updated.</response>
 | 
				
			||||||
 | 
					    /// <response code="404">Item not found.</response>
 | 
				
			||||||
    /// <returns>A <see cref="NoContentResult"/>.</returns>
 | 
					    /// <returns>A <see cref="NoContentResult"/>.</returns>
 | 
				
			||||||
    [HttpPost("LibraryOptions")]
 | 
					    [HttpPost("LibraryOptions")]
 | 
				
			||||||
    [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
					    [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
				
			||||||
 | 
					    [ProducesResponseType(StatusCodes.Status404NotFound)]
 | 
				
			||||||
    public ActionResult UpdateLibraryOptions(
 | 
					    public ActionResult UpdateLibraryOptions(
 | 
				
			||||||
        [FromBody] UpdateLibraryOptionsDto request)
 | 
					        [FromBody] UpdateLibraryOptionsDto request)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        var collectionFolder = (CollectionFolder)_libraryManager.GetItemById(request.Id);
 | 
					        var item = _libraryManager.GetItemById<CollectionFolder>(request.Id, User.GetUserId());
 | 
				
			||||||
 | 
					        if (item is null)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            return NotFound();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        collectionFolder.UpdateLibraryOptions(request.LibraryOptions);
 | 
					        item.UpdateLibraryOptions(request.LibraryOptions);
 | 
				
			||||||
        return NoContent();
 | 
					        return NoContent();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -220,9 +220,11 @@ public class LiveTvController : BaseJellyfinApiController
 | 
				
			|||||||
    /// <param name="channelId">Channel id.</param>
 | 
					    /// <param name="channelId">Channel id.</param>
 | 
				
			||||||
    /// <param name="userId">Optional. Attach user data.</param>
 | 
					    /// <param name="userId">Optional. Attach user data.</param>
 | 
				
			||||||
    /// <response code="200">Live tv channel returned.</response>
 | 
					    /// <response code="200">Live tv channel returned.</response>
 | 
				
			||||||
 | 
					    /// <response code="404">Item not found.</response>
 | 
				
			||||||
    /// <returns>An <see cref="OkResult"/> containing the live tv channel.</returns>
 | 
					    /// <returns>An <see cref="OkResult"/> containing the live tv channel.</returns>
 | 
				
			||||||
    [HttpGet("Channels/{channelId}")]
 | 
					    [HttpGet("Channels/{channelId}")]
 | 
				
			||||||
    [ProducesResponseType(StatusCodes.Status200OK)]
 | 
					    [ProducesResponseType(StatusCodes.Status200OK)]
 | 
				
			||||||
 | 
					    [ProducesResponseType(StatusCodes.Status404NotFound)]
 | 
				
			||||||
    [Authorize(Policy = Policies.LiveTvAccess)]
 | 
					    [Authorize(Policy = Policies.LiveTvAccess)]
 | 
				
			||||||
    public ActionResult<BaseItemDto> GetChannel([FromRoute, Required] Guid channelId, [FromQuery] Guid? userId)
 | 
					    public ActionResult<BaseItemDto> GetChannel([FromRoute, Required] Guid channelId, [FromQuery] Guid? userId)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
@ -232,7 +234,12 @@ public class LiveTvController : BaseJellyfinApiController
 | 
				
			|||||||
            : _userManager.GetUserById(userId.Value);
 | 
					            : _userManager.GetUserById(userId.Value);
 | 
				
			||||||
        var item = channelId.IsEmpty()
 | 
					        var item = channelId.IsEmpty()
 | 
				
			||||||
            ? _libraryManager.GetUserRootFolder()
 | 
					            ? _libraryManager.GetUserRootFolder()
 | 
				
			||||||
            : _libraryManager.GetItemById(channelId);
 | 
					            : _libraryManager.GetItemById<BaseItem>(channelId, user);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (item is null)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            return NotFound();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        var dtoOptions = new DtoOptions()
 | 
					        var dtoOptions = new DtoOptions()
 | 
				
			||||||
            .AddClientFields(User);
 | 
					            .AddClientFields(User);
 | 
				
			||||||
@ -416,9 +423,11 @@ public class LiveTvController : BaseJellyfinApiController
 | 
				
			|||||||
    /// <param name="recordingId">Recording id.</param>
 | 
					    /// <param name="recordingId">Recording id.</param>
 | 
				
			||||||
    /// <param name="userId">Optional. Attach user data.</param>
 | 
					    /// <param name="userId">Optional. Attach user data.</param>
 | 
				
			||||||
    /// <response code="200">Recording returned.</response>
 | 
					    /// <response code="200">Recording returned.</response>
 | 
				
			||||||
 | 
					    /// <response code="404">Item not found.</response>
 | 
				
			||||||
    /// <returns>An <see cref="OkResult"/> containing the live tv recording.</returns>
 | 
					    /// <returns>An <see cref="OkResult"/> containing the live tv recording.</returns>
 | 
				
			||||||
    [HttpGet("Recordings/{recordingId}")]
 | 
					    [HttpGet("Recordings/{recordingId}")]
 | 
				
			||||||
    [ProducesResponseType(StatusCodes.Status200OK)]
 | 
					    [ProducesResponseType(StatusCodes.Status200OK)]
 | 
				
			||||||
 | 
					    [ProducesResponseType(StatusCodes.Status404NotFound)]
 | 
				
			||||||
    [Authorize(Policy = Policies.LiveTvAccess)]
 | 
					    [Authorize(Policy = Policies.LiveTvAccess)]
 | 
				
			||||||
    public ActionResult<BaseItemDto> GetRecording([FromRoute, Required] Guid recordingId, [FromQuery] Guid? userId)
 | 
					    public ActionResult<BaseItemDto> GetRecording([FromRoute, Required] Guid recordingId, [FromQuery] Guid? userId)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
@ -426,7 +435,13 @@ public class LiveTvController : BaseJellyfinApiController
 | 
				
			|||||||
        var user = userId.IsNullOrEmpty()
 | 
					        var user = userId.IsNullOrEmpty()
 | 
				
			||||||
            ? null
 | 
					            ? null
 | 
				
			||||||
            : _userManager.GetUserById(userId.Value);
 | 
					            : _userManager.GetUserById(userId.Value);
 | 
				
			||||||
        var item = recordingId.IsEmpty() ? _libraryManager.GetUserRootFolder() : _libraryManager.GetItemById(recordingId);
 | 
					        var item = recordingId.IsEmpty()
 | 
				
			||||||
 | 
					            ? _libraryManager.GetUserRootFolder()
 | 
				
			||||||
 | 
					            : _libraryManager.GetItemById<BaseItem>(recordingId, user);
 | 
				
			||||||
 | 
					        if (item is null)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            return NotFound();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        var dtoOptions = new DtoOptions()
 | 
					        var dtoOptions = new DtoOptions()
 | 
				
			||||||
            .AddClientFields(User);
 | 
					            .AddClientFields(User);
 | 
				
			||||||
@ -611,7 +626,8 @@ public class LiveTvController : BaseJellyfinApiController
 | 
				
			|||||||
        {
 | 
					        {
 | 
				
			||||||
            query.IsSeries = true;
 | 
					            query.IsSeries = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (_libraryManager.GetItemById(librarySeriesId.Value) is Series series)
 | 
					            var series = _libraryManager.GetItemById<Series>(librarySeriesId.Value);
 | 
				
			||||||
 | 
					            if (series is not null)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                query.Name = series.Name;
 | 
					                query.Name = series.Name;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
@ -665,7 +681,8 @@ public class LiveTvController : BaseJellyfinApiController
 | 
				
			|||||||
        {
 | 
					        {
 | 
				
			||||||
            query.IsSeries = true;
 | 
					            query.IsSeries = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (_libraryManager.GetItemById(body.LibrarySeriesId) is Series series)
 | 
					            var series = _libraryManager.GetItemById<Series>(body.LibrarySeriesId);
 | 
				
			||||||
 | 
					            if (series is not null)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                query.Name = series.Name;
 | 
					                query.Name = series.Name;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
@ -779,7 +796,7 @@ public class LiveTvController : BaseJellyfinApiController
 | 
				
			|||||||
    [ProducesResponseType(StatusCodes.Status404NotFound)]
 | 
					    [ProducesResponseType(StatusCodes.Status404NotFound)]
 | 
				
			||||||
    public ActionResult DeleteRecording([FromRoute, Required] Guid recordingId)
 | 
					    public ActionResult DeleteRecording([FromRoute, Required] Guid recordingId)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        var item = _libraryManager.GetItemById(recordingId);
 | 
					        var item = _libraryManager.GetItemById<BaseItem>(recordingId, User.GetUserId());
 | 
				
			||||||
        if (item is null)
 | 
					        if (item is null)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            return NotFound();
 | 
					            return NotFound();
 | 
				
			||||||
 | 
				
			|||||||
@ -7,6 +7,7 @@ using System.Threading;
 | 
				
			|||||||
using System.Threading.Tasks;
 | 
					using System.Threading.Tasks;
 | 
				
			||||||
using Jellyfin.Api.Attributes;
 | 
					using Jellyfin.Api.Attributes;
 | 
				
			||||||
using Jellyfin.Api.Extensions;
 | 
					using Jellyfin.Api.Extensions;
 | 
				
			||||||
 | 
					using Jellyfin.Api.Helpers;
 | 
				
			||||||
using Jellyfin.Extensions;
 | 
					using Jellyfin.Extensions;
 | 
				
			||||||
using MediaBrowser.Common.Api;
 | 
					using MediaBrowser.Common.Api;
 | 
				
			||||||
using MediaBrowser.Controller.Entities.Audio;
 | 
					using MediaBrowser.Controller.Entities.Audio;
 | 
				
			||||||
@ -66,37 +67,16 @@ public class LyricsController : BaseJellyfinApiController
 | 
				
			|||||||
    [HttpGet("Audio/{itemId}/Lyrics")]
 | 
					    [HttpGet("Audio/{itemId}/Lyrics")]
 | 
				
			||||||
    [Authorize]
 | 
					    [Authorize]
 | 
				
			||||||
    [ProducesResponseType(StatusCodes.Status200OK)]
 | 
					    [ProducesResponseType(StatusCodes.Status200OK)]
 | 
				
			||||||
 | 
					    [ProducesResponseType(StatusCodes.Status404NotFound)]
 | 
				
			||||||
    public async Task<ActionResult<LyricDto>> GetLyrics([FromRoute, Required] Guid itemId)
 | 
					    public async Task<ActionResult<LyricDto>> GetLyrics([FromRoute, Required] Guid itemId)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        var isApiKey = User.GetIsApiKey();
 | 
					        var item = _libraryManager.GetItemById<Audio>(itemId, User.GetUserId());
 | 
				
			||||||
        var userId = User.GetUserId();
 | 
					        if (item is null)
 | 
				
			||||||
        if (!isApiKey && userId.IsEmpty())
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            return BadRequest();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        var audio = _libraryManager.GetItemById<Audio>(itemId);
 | 
					 | 
				
			||||||
        if (audio is null)
 | 
					 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            return NotFound();
 | 
					            return NotFound();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (!isApiKey)
 | 
					        var result = await _lyricManager.GetLyricsAsync(item, CancellationToken.None).ConfigureAwait(false);
 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            var user = _userManager.GetUserById(userId);
 | 
					 | 
				
			||||||
            if (user is null)
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                return NotFound();
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            // Check the item is visible for the user
 | 
					 | 
				
			||||||
            if (!audio.IsVisible(user))
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                return Unauthorized($"{user.Username} is not permitted to access item {audio.Name}.");
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        var result = await _lyricManager.GetLyricsAsync(audio, CancellationToken.None).ConfigureAwait(false);
 | 
					 | 
				
			||||||
        if (result is not null)
 | 
					        if (result is not null)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            return Ok(result);
 | 
					            return Ok(result);
 | 
				
			||||||
@ -124,8 +104,8 @@ public class LyricsController : BaseJellyfinApiController
 | 
				
			|||||||
        [FromRoute, Required] Guid itemId,
 | 
					        [FromRoute, Required] Guid itemId,
 | 
				
			||||||
        [FromQuery, Required] string fileName)
 | 
					        [FromQuery, Required] string fileName)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        var audio = _libraryManager.GetItemById<Audio>(itemId);
 | 
					        var item = _libraryManager.GetItemById<Audio>(itemId, User.GetUserId());
 | 
				
			||||||
        if (audio is null)
 | 
					        if (item is null)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            return NotFound();
 | 
					            return NotFound();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@ -147,7 +127,7 @@ public class LyricsController : BaseJellyfinApiController
 | 
				
			|||||||
        {
 | 
					        {
 | 
				
			||||||
            await Request.Body.CopyToAsync(stream).ConfigureAwait(false);
 | 
					            await Request.Body.CopyToAsync(stream).ConfigureAwait(false);
 | 
				
			||||||
            var uploadedLyric = await _lyricManager.SaveLyricAsync(
 | 
					            var uploadedLyric = await _lyricManager.SaveLyricAsync(
 | 
				
			||||||
                    audio,
 | 
					                    item,
 | 
				
			||||||
                    format,
 | 
					                    format,
 | 
				
			||||||
                    stream)
 | 
					                    stream)
 | 
				
			||||||
                .ConfigureAwait(false);
 | 
					                .ConfigureAwait(false);
 | 
				
			||||||
@ -157,7 +137,7 @@ public class LyricsController : BaseJellyfinApiController
 | 
				
			|||||||
                return BadRequest();
 | 
					                return BadRequest();
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            _providerManager.QueueRefresh(audio.Id, new MetadataRefreshOptions(new DirectoryService(_fileSystem)), RefreshPriority.High);
 | 
					            _providerManager.QueueRefresh(item.Id, new MetadataRefreshOptions(new DirectoryService(_fileSystem)), RefreshPriority.High);
 | 
				
			||||||
            return Ok(uploadedLyric);
 | 
					            return Ok(uploadedLyric);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -176,13 +156,13 @@ public class LyricsController : BaseJellyfinApiController
 | 
				
			|||||||
    public async Task<ActionResult> DeleteLyrics(
 | 
					    public async Task<ActionResult> DeleteLyrics(
 | 
				
			||||||
        [FromRoute, Required] Guid itemId)
 | 
					        [FromRoute, Required] Guid itemId)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        var audio = _libraryManager.GetItemById<Audio>(itemId);
 | 
					        var item = _libraryManager.GetItemById<Audio>(itemId, User.GetUserId());
 | 
				
			||||||
        if (audio is null)
 | 
					        if (item is null)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            return NotFound();
 | 
					            return NotFound();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        await _lyricManager.DeleteLyricsAsync(audio).ConfigureAwait(false);
 | 
					        await _lyricManager.DeleteLyricsAsync(item).ConfigureAwait(false);
 | 
				
			||||||
        return NoContent();
 | 
					        return NoContent();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -199,13 +179,13 @@ public class LyricsController : BaseJellyfinApiController
 | 
				
			|||||||
    [ProducesResponseType(StatusCodes.Status404NotFound)]
 | 
					    [ProducesResponseType(StatusCodes.Status404NotFound)]
 | 
				
			||||||
    public async Task<ActionResult<IReadOnlyList<RemoteLyricInfoDto>>> SearchRemoteLyrics([FromRoute, Required] Guid itemId)
 | 
					    public async Task<ActionResult<IReadOnlyList<RemoteLyricInfoDto>>> SearchRemoteLyrics([FromRoute, Required] Guid itemId)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        var audio = _libraryManager.GetItemById<Audio>(itemId);
 | 
					        var item = _libraryManager.GetItemById<Audio>(itemId, User.GetUserId());
 | 
				
			||||||
        if (audio is null)
 | 
					        if (item is null)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            return NotFound();
 | 
					            return NotFound();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        var results = await _lyricManager.SearchLyricsAsync(audio, false, CancellationToken.None).ConfigureAwait(false);
 | 
					        var results = await _lyricManager.SearchLyricsAsync(item, false, CancellationToken.None).ConfigureAwait(false);
 | 
				
			||||||
        return Ok(results);
 | 
					        return Ok(results);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -225,19 +205,19 @@ public class LyricsController : BaseJellyfinApiController
 | 
				
			|||||||
        [FromRoute, Required] Guid itemId,
 | 
					        [FromRoute, Required] Guid itemId,
 | 
				
			||||||
        [FromRoute, Required] string lyricId)
 | 
					        [FromRoute, Required] string lyricId)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        var audio = _libraryManager.GetItemById<Audio>(itemId);
 | 
					        var item = _libraryManager.GetItemById<Audio>(itemId, User.GetUserId());
 | 
				
			||||||
        if (audio is null)
 | 
					        if (item is null)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            return NotFound();
 | 
					            return NotFound();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        var downloadedLyrics = await _lyricManager.DownloadLyricsAsync(audio, lyricId, CancellationToken.None).ConfigureAwait(false);
 | 
					        var downloadedLyrics = await _lyricManager.DownloadLyricsAsync(item, lyricId, CancellationToken.None).ConfigureAwait(false);
 | 
				
			||||||
        if (downloadedLyrics is null)
 | 
					        if (downloadedLyrics is null)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            return NotFound();
 | 
					            return NotFound();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        _providerManager.QueueRefresh(audio.Id, new MetadataRefreshOptions(new DirectoryService(_fileSystem)), RefreshPriority.High);
 | 
					        _providerManager.QueueRefresh(item.Id, new MetadataRefreshOptions(new DirectoryService(_fileSystem)), RefreshPriority.High);
 | 
				
			||||||
        return Ok(downloadedLyrics);
 | 
					        return Ok(downloadedLyrics);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -8,8 +8,10 @@ using Jellyfin.Api.Attributes;
 | 
				
			|||||||
using Jellyfin.Api.Extensions;
 | 
					using Jellyfin.Api.Extensions;
 | 
				
			||||||
using Jellyfin.Api.Helpers;
 | 
					using Jellyfin.Api.Helpers;
 | 
				
			||||||
using Jellyfin.Api.Models.MediaInfoDtos;
 | 
					using Jellyfin.Api.Models.MediaInfoDtos;
 | 
				
			||||||
 | 
					using Jellyfin.Extensions;
 | 
				
			||||||
using MediaBrowser.Common.Extensions;
 | 
					using MediaBrowser.Common.Extensions;
 | 
				
			||||||
using MediaBrowser.Controller.Devices;
 | 
					using MediaBrowser.Controller.Devices;
 | 
				
			||||||
 | 
					using MediaBrowser.Controller.Entities;
 | 
				
			||||||
using MediaBrowser.Controller.Library;
 | 
					using MediaBrowser.Controller.Library;
 | 
				
			||||||
using MediaBrowser.Model.MediaInfo;
 | 
					using MediaBrowser.Model.MediaInfo;
 | 
				
			||||||
using Microsoft.AspNetCore.Authorization;
 | 
					using Microsoft.AspNetCore.Authorization;
 | 
				
			||||||
@ -32,6 +34,7 @@ public class MediaInfoController : BaseJellyfinApiController
 | 
				
			|||||||
    private readonly ILibraryManager _libraryManager;
 | 
					    private readonly ILibraryManager _libraryManager;
 | 
				
			||||||
    private readonly ILogger<MediaInfoController> _logger;
 | 
					    private readonly ILogger<MediaInfoController> _logger;
 | 
				
			||||||
    private readonly MediaInfoHelper _mediaInfoHelper;
 | 
					    private readonly MediaInfoHelper _mediaInfoHelper;
 | 
				
			||||||
 | 
					    private readonly IUserManager _userManager;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// <summary>
 | 
					    /// <summary>
 | 
				
			||||||
    /// Initializes a new instance of the <see cref="MediaInfoController"/> class.
 | 
					    /// Initializes a new instance of the <see cref="MediaInfoController"/> class.
 | 
				
			||||||
@ -41,18 +44,21 @@ public class MediaInfoController : BaseJellyfinApiController
 | 
				
			|||||||
    /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
 | 
					    /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
 | 
				
			||||||
    /// <param name="logger">Instance of the <see cref="ILogger{MediaInfoController}"/> interface.</param>
 | 
					    /// <param name="logger">Instance of the <see cref="ILogger{MediaInfoController}"/> interface.</param>
 | 
				
			||||||
    /// <param name="mediaInfoHelper">Instance of the <see cref="MediaInfoHelper"/>.</param>
 | 
					    /// <param name="mediaInfoHelper">Instance of the <see cref="MediaInfoHelper"/>.</param>
 | 
				
			||||||
 | 
					    /// <param name="userManager">Instance of the <see cref="IUserManager"/> interface..</param>
 | 
				
			||||||
    public MediaInfoController(
 | 
					    public MediaInfoController(
 | 
				
			||||||
        IMediaSourceManager mediaSourceManager,
 | 
					        IMediaSourceManager mediaSourceManager,
 | 
				
			||||||
        IDeviceManager deviceManager,
 | 
					        IDeviceManager deviceManager,
 | 
				
			||||||
        ILibraryManager libraryManager,
 | 
					        ILibraryManager libraryManager,
 | 
				
			||||||
        ILogger<MediaInfoController> logger,
 | 
					        ILogger<MediaInfoController> logger,
 | 
				
			||||||
        MediaInfoHelper mediaInfoHelper)
 | 
					        MediaInfoHelper mediaInfoHelper,
 | 
				
			||||||
 | 
					        IUserManager userManager)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        _mediaSourceManager = mediaSourceManager;
 | 
					        _mediaSourceManager = mediaSourceManager;
 | 
				
			||||||
        _deviceManager = deviceManager;
 | 
					        _deviceManager = deviceManager;
 | 
				
			||||||
        _libraryManager = libraryManager;
 | 
					        _libraryManager = libraryManager;
 | 
				
			||||||
        _logger = logger;
 | 
					        _logger = logger;
 | 
				
			||||||
        _mediaInfoHelper = mediaInfoHelper;
 | 
					        _mediaInfoHelper = mediaInfoHelper;
 | 
				
			||||||
 | 
					        _userManager = userManager;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// <summary>
 | 
					    /// <summary>
 | 
				
			||||||
@ -61,16 +67,24 @@ public class MediaInfoController : BaseJellyfinApiController
 | 
				
			|||||||
    /// <param name="itemId">The item id.</param>
 | 
					    /// <param name="itemId">The item id.</param>
 | 
				
			||||||
    /// <param name="userId">The user id.</param>
 | 
					    /// <param name="userId">The user id.</param>
 | 
				
			||||||
    /// <response code="200">Playback info returned.</response>
 | 
					    /// <response code="200">Playback info returned.</response>
 | 
				
			||||||
 | 
					    /// <response code="404">Item not found.</response>
 | 
				
			||||||
    /// <returns>A <see cref="Task"/> containing a <see cref="PlaybackInfoResponse"/> with the playback information.</returns>
 | 
					    /// <returns>A <see cref="Task"/> containing a <see cref="PlaybackInfoResponse"/> with the playback information.</returns>
 | 
				
			||||||
    [HttpGet("Items/{itemId}/PlaybackInfo")]
 | 
					    [HttpGet("Items/{itemId}/PlaybackInfo")]
 | 
				
			||||||
    [ProducesResponseType(StatusCodes.Status200OK)]
 | 
					    [ProducesResponseType(StatusCodes.Status200OK)]
 | 
				
			||||||
 | 
					    [ProducesResponseType(StatusCodes.Status404NotFound)]
 | 
				
			||||||
    public async Task<ActionResult<PlaybackInfoResponse>> GetPlaybackInfo([FromRoute, Required] Guid itemId, [FromQuery] Guid? userId)
 | 
					    public async Task<ActionResult<PlaybackInfoResponse>> GetPlaybackInfo([FromRoute, Required] Guid itemId, [FromQuery] Guid? userId)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        userId = RequestHelpers.GetUserId(User, userId);
 | 
					        userId = RequestHelpers.GetUserId(User, userId);
 | 
				
			||||||
        return await _mediaInfoHelper.GetPlaybackInfo(
 | 
					        var user = userId.IsNullOrEmpty()
 | 
				
			||||||
                itemId,
 | 
					            ? null
 | 
				
			||||||
                userId)
 | 
					            : _userManager.GetUserById(userId.Value);
 | 
				
			||||||
            .ConfigureAwait(false);
 | 
					        var item = _libraryManager.GetItemById<BaseItem>(itemId, user);
 | 
				
			||||||
 | 
					        if (item is null)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            return NotFound();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return await _mediaInfoHelper.GetPlaybackInfo(item, user).ConfigureAwait(false);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// <summary>
 | 
					    /// <summary>
 | 
				
			||||||
@ -97,9 +111,11 @@ public class MediaInfoController : BaseJellyfinApiController
 | 
				
			|||||||
    /// <param name="allowAudioStreamCopy">Whether to allow to copy the audio stream. Default: true.</param>
 | 
					    /// <param name="allowAudioStreamCopy">Whether to allow to copy the audio stream. Default: true.</param>
 | 
				
			||||||
    /// <param name="playbackInfoDto">The playback info.</param>
 | 
					    /// <param name="playbackInfoDto">The playback info.</param>
 | 
				
			||||||
    /// <response code="200">Playback info returned.</response>
 | 
					    /// <response code="200">Playback info returned.</response>
 | 
				
			||||||
 | 
					    /// <response code="404">Item not found.</response>
 | 
				
			||||||
    /// <returns>A <see cref="Task"/> containing a <see cref="PlaybackInfoResponse"/> with the playback info.</returns>
 | 
					    /// <returns>A <see cref="Task"/> containing a <see cref="PlaybackInfoResponse"/> with the playback info.</returns>
 | 
				
			||||||
    [HttpPost("Items/{itemId}/PlaybackInfo")]
 | 
					    [HttpPost("Items/{itemId}/PlaybackInfo")]
 | 
				
			||||||
    [ProducesResponseType(StatusCodes.Status200OK)]
 | 
					    [ProducesResponseType(StatusCodes.Status200OK)]
 | 
				
			||||||
 | 
					    [ProducesResponseType(StatusCodes.Status404NotFound)]
 | 
				
			||||||
    public async Task<ActionResult<PlaybackInfoResponse>> GetPostedPlaybackInfo(
 | 
					    public async Task<ActionResult<PlaybackInfoResponse>> GetPostedPlaybackInfo(
 | 
				
			||||||
        [FromRoute, Required] Guid itemId,
 | 
					        [FromRoute, Required] Guid itemId,
 | 
				
			||||||
        [FromQuery, ParameterObsolete] Guid? userId,
 | 
					        [FromQuery, ParameterObsolete] Guid? userId,
 | 
				
			||||||
@ -148,9 +164,19 @@ public class MediaInfoController : BaseJellyfinApiController
 | 
				
			|||||||
        allowVideoStreamCopy ??= playbackInfoDto?.AllowVideoStreamCopy ?? true;
 | 
					        allowVideoStreamCopy ??= playbackInfoDto?.AllowVideoStreamCopy ?? true;
 | 
				
			||||||
        allowAudioStreamCopy ??= playbackInfoDto?.AllowAudioStreamCopy ?? true;
 | 
					        allowAudioStreamCopy ??= playbackInfoDto?.AllowAudioStreamCopy ?? true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        userId = RequestHelpers.GetUserId(User, userId);
 | 
				
			||||||
 | 
					        var user = userId.IsNullOrEmpty()
 | 
				
			||||||
 | 
					            ? null
 | 
				
			||||||
 | 
					            : _userManager.GetUserById(userId.Value);
 | 
				
			||||||
 | 
					        var item = _libraryManager.GetItemById<BaseItem>(itemId, user);
 | 
				
			||||||
 | 
					        if (item is null)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            return NotFound();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        var info = await _mediaInfoHelper.GetPlaybackInfo(
 | 
					        var info = await _mediaInfoHelper.GetPlaybackInfo(
 | 
				
			||||||
                itemId,
 | 
					                item,
 | 
				
			||||||
                userId,
 | 
					                user,
 | 
				
			||||||
                mediaSourceId,
 | 
					                mediaSourceId,
 | 
				
			||||||
                liveStreamId)
 | 
					                liveStreamId)
 | 
				
			||||||
            .ConfigureAwait(false);
 | 
					            .ConfigureAwait(false);
 | 
				
			||||||
@ -163,8 +189,6 @@ public class MediaInfoController : BaseJellyfinApiController
 | 
				
			|||||||
        if (profile is not null)
 | 
					        if (profile is not null)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            // set device specific data
 | 
					            // set device specific data
 | 
				
			||||||
            var item = _libraryManager.GetItemById(itemId);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            foreach (var mediaSource in info.MediaSources)
 | 
					            foreach (var mediaSource in info.MediaSources)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                _mediaInfoHelper.SetDeviceSpecificData(
 | 
					                _mediaInfoHelper.SetDeviceSpecificData(
 | 
				
			||||||
 | 
				
			|||||||
@ -482,8 +482,13 @@ public class PlaylistsController : BaseJellyfinApiController
 | 
				
			|||||||
        var user = userId.IsNullOrEmpty()
 | 
					        var user = userId.IsNullOrEmpty()
 | 
				
			||||||
            ? null
 | 
					            ? null
 | 
				
			||||||
            : _userManager.GetUserById(userId.Value);
 | 
					            : _userManager.GetUserById(userId.Value);
 | 
				
			||||||
 | 
					        var item = _libraryManager.GetItemById<Playlist>(playlistId, user);
 | 
				
			||||||
 | 
					        if (item is null)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            return NotFound();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        var items = playlist.GetManageableItems().ToArray();
 | 
					        var items = item.GetManageableItems().ToArray();
 | 
				
			||||||
        var count = items.Length;
 | 
					        var count = items.Length;
 | 
				
			||||||
        if (startIndex.HasValue)
 | 
					        if (startIndex.HasValue)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
 | 
				
			|||||||
@ -6,6 +6,7 @@ using Jellyfin.Api.Extensions;
 | 
				
			|||||||
using Jellyfin.Api.Helpers;
 | 
					using Jellyfin.Api.Helpers;
 | 
				
			||||||
using Jellyfin.Api.ModelBinders;
 | 
					using Jellyfin.Api.ModelBinders;
 | 
				
			||||||
using Jellyfin.Data.Entities;
 | 
					using Jellyfin.Data.Entities;
 | 
				
			||||||
 | 
					using Jellyfin.Extensions;
 | 
				
			||||||
using MediaBrowser.Controller.Entities;
 | 
					using MediaBrowser.Controller.Entities;
 | 
				
			||||||
using MediaBrowser.Controller.Library;
 | 
					using MediaBrowser.Controller.Library;
 | 
				
			||||||
using MediaBrowser.Controller.MediaEncoding;
 | 
					using MediaBrowser.Controller.MediaEncoding;
 | 
				
			||||||
@ -76,21 +77,21 @@ public class PlaystateController : BaseJellyfinApiController
 | 
				
			|||||||
        [FromRoute, Required] Guid itemId,
 | 
					        [FromRoute, Required] Guid itemId,
 | 
				
			||||||
        [FromQuery, ModelBinder(typeof(LegacyDateTimeModelBinder))] DateTime? datePlayed)
 | 
					        [FromQuery, ModelBinder(typeof(LegacyDateTimeModelBinder))] DateTime? datePlayed)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        var requestUserId = RequestHelpers.GetUserId(User, userId);
 | 
					        userId = RequestHelpers.GetUserId(User, userId);
 | 
				
			||||||
        var user = _userManager.GetUserById(requestUserId);
 | 
					        var user = _userManager.GetUserById(userId.Value);
 | 
				
			||||||
        if (user is null)
 | 
					        if (user is null)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            return NotFound();
 | 
					            return NotFound();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        var session = await RequestHelpers.GetSession(_sessionManager, _userManager, HttpContext).ConfigureAwait(false);
 | 
					        var item = _libraryManager.GetItemById<BaseItem>(itemId, user);
 | 
				
			||||||
 | 
					 | 
				
			||||||
        var item = _libraryManager.GetItemById(itemId);
 | 
					 | 
				
			||||||
        if (item is null)
 | 
					        if (item is null)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            return NotFound();
 | 
					            return NotFound();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        var session = await RequestHelpers.GetSession(_sessionManager, _userManager, HttpContext).ConfigureAwait(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        var dto = UpdatePlayedStatus(user, item, true, datePlayed);
 | 
					        var dto = UpdatePlayedStatus(user, item, true, datePlayed);
 | 
				
			||||||
        foreach (var additionalUserInfo in session.AdditionalUsers)
 | 
					        foreach (var additionalUserInfo in session.AdditionalUsers)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
@ -141,21 +142,21 @@ public class PlaystateController : BaseJellyfinApiController
 | 
				
			|||||||
        [FromQuery] Guid? userId,
 | 
					        [FromQuery] Guid? userId,
 | 
				
			||||||
        [FromRoute, Required] Guid itemId)
 | 
					        [FromRoute, Required] Guid itemId)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        var requestUserId = RequestHelpers.GetUserId(User, userId);
 | 
					        userId = RequestHelpers.GetUserId(User, userId);
 | 
				
			||||||
        var user = _userManager.GetUserById(requestUserId);
 | 
					        var user = _userManager.GetUserById(userId.Value);
 | 
				
			||||||
        if (user is null)
 | 
					        if (user is null)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            return NotFound();
 | 
					            return NotFound();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        var session = await RequestHelpers.GetSession(_sessionManager, _userManager, HttpContext).ConfigureAwait(false);
 | 
					        var item = _libraryManager.GetItemById<BaseItem>(itemId, user);
 | 
				
			||||||
        var item = _libraryManager.GetItemById(itemId);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (item is null)
 | 
					        if (item is null)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            return NotFound();
 | 
					            return NotFound();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        var session = await RequestHelpers.GetSession(_sessionManager, _userManager, HttpContext).ConfigureAwait(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        var dto = UpdatePlayedStatus(user, item, false, null);
 | 
					        var dto = UpdatePlayedStatus(user, item, false, null);
 | 
				
			||||||
        foreach (var additionalUserInfo in session.AdditionalUsers)
 | 
					        foreach (var additionalUserInfo in session.AdditionalUsers)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
 | 
				
			|||||||
@ -6,8 +6,11 @@ using System.Linq;
 | 
				
			|||||||
using System.Threading;
 | 
					using System.Threading;
 | 
				
			||||||
using System.Threading.Tasks;
 | 
					using System.Threading.Tasks;
 | 
				
			||||||
using Jellyfin.Api.Constants;
 | 
					using Jellyfin.Api.Constants;
 | 
				
			||||||
 | 
					using Jellyfin.Api.Extensions;
 | 
				
			||||||
 | 
					using Jellyfin.Api.Helpers;
 | 
				
			||||||
using MediaBrowser.Common.Api;
 | 
					using MediaBrowser.Common.Api;
 | 
				
			||||||
using MediaBrowser.Controller;
 | 
					using MediaBrowser.Controller;
 | 
				
			||||||
 | 
					using MediaBrowser.Controller.Entities;
 | 
				
			||||||
using MediaBrowser.Controller.Library;
 | 
					using MediaBrowser.Controller.Library;
 | 
				
			||||||
using MediaBrowser.Controller.Providers;
 | 
					using MediaBrowser.Controller.Providers;
 | 
				
			||||||
using MediaBrowser.Model.Entities;
 | 
					using MediaBrowser.Model.Entities;
 | 
				
			||||||
@ -68,7 +71,7 @@ public class RemoteImageController : BaseJellyfinApiController
 | 
				
			|||||||
        [FromQuery] string? providerName,
 | 
					        [FromQuery] string? providerName,
 | 
				
			||||||
        [FromQuery] bool includeAllLanguages = false)
 | 
					        [FromQuery] bool includeAllLanguages = false)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        var item = _libraryManager.GetItemById(itemId);
 | 
					        var item = _libraryManager.GetItemById<BaseItem>(itemId, User.GetUserId());
 | 
				
			||||||
        if (item is null)
 | 
					        if (item is null)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            return NotFound();
 | 
					            return NotFound();
 | 
				
			||||||
@ -127,7 +130,7 @@ public class RemoteImageController : BaseJellyfinApiController
 | 
				
			|||||||
    [ProducesResponseType(StatusCodes.Status404NotFound)]
 | 
					    [ProducesResponseType(StatusCodes.Status404NotFound)]
 | 
				
			||||||
    public ActionResult<IEnumerable<ImageProviderInfo>> GetRemoteImageProviders([FromRoute, Required] Guid itemId)
 | 
					    public ActionResult<IEnumerable<ImageProviderInfo>> GetRemoteImageProviders([FromRoute, Required] Guid itemId)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        var item = _libraryManager.GetItemById(itemId);
 | 
					        var item = _libraryManager.GetItemById<BaseItem>(itemId, User.GetUserId());
 | 
				
			||||||
        if (item is null)
 | 
					        if (item is null)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            return NotFound();
 | 
					            return NotFound();
 | 
				
			||||||
@ -154,7 +157,7 @@ public class RemoteImageController : BaseJellyfinApiController
 | 
				
			|||||||
        [FromQuery, Required] ImageType type,
 | 
					        [FromQuery, Required] ImageType type,
 | 
				
			||||||
        [FromQuery] string? imageUrl)
 | 
					        [FromQuery] string? imageUrl)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        var item = _libraryManager.GetItemById(itemId);
 | 
					        var item = _libraryManager.GetItemById<BaseItem>(itemId, User.GetUserId());
 | 
				
			||||||
        if (item is null)
 | 
					        if (item is null)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            return NotFound();
 | 
					            return NotFound();
 | 
				
			||||||
 | 
				
			|||||||
@ -211,7 +211,7 @@ public class SearchController : BaseJellyfinApiController
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        if (!item.ChannelId.IsEmpty())
 | 
					        if (!item.ChannelId.IsEmpty())
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            var channel = _libraryManager.GetItemById(item.ChannelId);
 | 
					            var channel = _libraryManager.GetItemById<BaseItem>(item.ChannelId);
 | 
				
			||||||
            result.ChannelName = channel?.Name;
 | 
					            result.ChannelName = channel?.Name;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -12,6 +12,7 @@ using System.Threading;
 | 
				
			|||||||
using System.Threading.Tasks;
 | 
					using System.Threading.Tasks;
 | 
				
			||||||
using Jellyfin.Api.Attributes;
 | 
					using Jellyfin.Api.Attributes;
 | 
				
			||||||
using Jellyfin.Api.Extensions;
 | 
					using Jellyfin.Api.Extensions;
 | 
				
			||||||
 | 
					using Jellyfin.Api.Helpers;
 | 
				
			||||||
using Jellyfin.Api.Models.SubtitleDtos;
 | 
					using Jellyfin.Api.Models.SubtitleDtos;
 | 
				
			||||||
using MediaBrowser.Common.Api;
 | 
					using MediaBrowser.Common.Api;
 | 
				
			||||||
using MediaBrowser.Common.Configuration;
 | 
					using MediaBrowser.Common.Configuration;
 | 
				
			||||||
@ -95,8 +96,7 @@ public class SubtitleController : BaseJellyfinApiController
 | 
				
			|||||||
        [FromRoute, Required] Guid itemId,
 | 
					        [FromRoute, Required] Guid itemId,
 | 
				
			||||||
        [FromRoute, Required] int index)
 | 
					        [FromRoute, Required] int index)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        var item = _libraryManager.GetItemById(itemId);
 | 
					        var item = _libraryManager.GetItemById<BaseItem>(itemId, User.GetUserId());
 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (item is null)
 | 
					        if (item is null)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            return NotFound();
 | 
					            return NotFound();
 | 
				
			||||||
@ -113,18 +113,24 @@ public class SubtitleController : BaseJellyfinApiController
 | 
				
			|||||||
    /// <param name="language">The language of the subtitles.</param>
 | 
					    /// <param name="language">The language of the subtitles.</param>
 | 
				
			||||||
    /// <param name="isPerfectMatch">Optional. Only show subtitles which are a perfect match.</param>
 | 
					    /// <param name="isPerfectMatch">Optional. Only show subtitles which are a perfect match.</param>
 | 
				
			||||||
    /// <response code="200">Subtitles retrieved.</response>
 | 
					    /// <response code="200">Subtitles retrieved.</response>
 | 
				
			||||||
 | 
					    /// <response code="404">Item not found.</response>
 | 
				
			||||||
    /// <returns>An array of <see cref="RemoteSubtitleInfo"/>.</returns>
 | 
					    /// <returns>An array of <see cref="RemoteSubtitleInfo"/>.</returns>
 | 
				
			||||||
    [HttpGet("Items/{itemId}/RemoteSearch/Subtitles/{language}")]
 | 
					    [HttpGet("Items/{itemId}/RemoteSearch/Subtitles/{language}")]
 | 
				
			||||||
    [Authorize(Policy = Policies.SubtitleManagement)]
 | 
					    [Authorize(Policy = Policies.SubtitleManagement)]
 | 
				
			||||||
    [ProducesResponseType(StatusCodes.Status200OK)]
 | 
					    [ProducesResponseType(StatusCodes.Status200OK)]
 | 
				
			||||||
 | 
					    [ProducesResponseType(StatusCodes.Status404NotFound)]
 | 
				
			||||||
    public async Task<ActionResult<IEnumerable<RemoteSubtitleInfo>>> SearchRemoteSubtitles(
 | 
					    public async Task<ActionResult<IEnumerable<RemoteSubtitleInfo>>> SearchRemoteSubtitles(
 | 
				
			||||||
        [FromRoute, Required] Guid itemId,
 | 
					        [FromRoute, Required] Guid itemId,
 | 
				
			||||||
        [FromRoute, Required] string language,
 | 
					        [FromRoute, Required] string language,
 | 
				
			||||||
        [FromQuery] bool? isPerfectMatch)
 | 
					        [FromQuery] bool? isPerfectMatch)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        var video = (Video)_libraryManager.GetItemById(itemId);
 | 
					        var item = _libraryManager.GetItemById<Video>(itemId, User.GetUserId());
 | 
				
			||||||
 | 
					        if (item is null)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            return NotFound();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return await _subtitleManager.SearchSubtitles(video, language, isPerfectMatch, false, CancellationToken.None).ConfigureAwait(false);
 | 
					        return await _subtitleManager.SearchSubtitles(item, language, isPerfectMatch, false, CancellationToken.None).ConfigureAwait(false);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// <summary>
 | 
					    /// <summary>
 | 
				
			||||||
@ -133,22 +139,28 @@ public class SubtitleController : BaseJellyfinApiController
 | 
				
			|||||||
    /// <param name="itemId">The item id.</param>
 | 
					    /// <param name="itemId">The item id.</param>
 | 
				
			||||||
    /// <param name="subtitleId">The subtitle id.</param>
 | 
					    /// <param name="subtitleId">The subtitle id.</param>
 | 
				
			||||||
    /// <response code="204">Subtitle downloaded.</response>
 | 
					    /// <response code="204">Subtitle downloaded.</response>
 | 
				
			||||||
 | 
					    /// <response code="404">Item not found.</response>
 | 
				
			||||||
    /// <returns>A <see cref="NoContentResult"/>.</returns>
 | 
					    /// <returns>A <see cref="NoContentResult"/>.</returns>
 | 
				
			||||||
    [HttpPost("Items/{itemId}/RemoteSearch/Subtitles/{subtitleId}")]
 | 
					    [HttpPost("Items/{itemId}/RemoteSearch/Subtitles/{subtitleId}")]
 | 
				
			||||||
    [Authorize(Policy = Policies.SubtitleManagement)]
 | 
					    [Authorize(Policy = Policies.SubtitleManagement)]
 | 
				
			||||||
    [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
					    [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
				
			||||||
 | 
					    [ProducesResponseType(StatusCodes.Status404NotFound)]
 | 
				
			||||||
    public async Task<ActionResult> DownloadRemoteSubtitles(
 | 
					    public async Task<ActionResult> DownloadRemoteSubtitles(
 | 
				
			||||||
        [FromRoute, Required] Guid itemId,
 | 
					        [FromRoute, Required] Guid itemId,
 | 
				
			||||||
        [FromRoute, Required] string subtitleId)
 | 
					        [FromRoute, Required] string subtitleId)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        var video = (Video)_libraryManager.GetItemById(itemId);
 | 
					        var item = _libraryManager.GetItemById<Video>(itemId, User.GetUserId());
 | 
				
			||||||
 | 
					        if (item is null)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            return NotFound();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        try
 | 
					        try
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            await _subtitleManager.DownloadSubtitles(video, subtitleId, CancellationToken.None)
 | 
					            await _subtitleManager.DownloadSubtitles(item, subtitleId, CancellationToken.None)
 | 
				
			||||||
                .ConfigureAwait(false);
 | 
					                .ConfigureAwait(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            _providerManager.QueueRefresh(video.Id, new MetadataRefreshOptions(new DirectoryService(_fileSystem)), RefreshPriority.High);
 | 
					            _providerManager.QueueRefresh(item.Id, new MetadataRefreshOptions(new DirectoryService(_fileSystem)), RefreshPriority.High);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        catch (Exception ex)
 | 
					        catch (Exception ex)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
@ -223,7 +235,7 @@ public class SubtitleController : BaseJellyfinApiController
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        if (string.IsNullOrEmpty(format))
 | 
					        if (string.IsNullOrEmpty(format))
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            var item = (Video)_libraryManager.GetItemById(itemId.Value);
 | 
					            var item = _libraryManager.GetItemById<Video>(itemId.Value);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            var idString = itemId.Value.ToString("N", CultureInfo.InvariantCulture);
 | 
					            var idString = itemId.Value.ToString("N", CultureInfo.InvariantCulture);
 | 
				
			||||||
            var mediaSource = _mediaSourceManager.GetStaticMediaSources(item, false)
 | 
					            var mediaSource = _mediaSourceManager.GetStaticMediaSources(item, false)
 | 
				
			||||||
@ -321,10 +333,12 @@ public class SubtitleController : BaseJellyfinApiController
 | 
				
			|||||||
    /// <param name="mediaSourceId">The media source id.</param>
 | 
					    /// <param name="mediaSourceId">The media source id.</param>
 | 
				
			||||||
    /// <param name="segmentLength">The subtitle segment length.</param>
 | 
					    /// <param name="segmentLength">The subtitle segment length.</param>
 | 
				
			||||||
    /// <response code="200">Subtitle playlist retrieved.</response>
 | 
					    /// <response code="200">Subtitle playlist retrieved.</response>
 | 
				
			||||||
 | 
					    /// <response code="404">Item not found.</response>
 | 
				
			||||||
    /// <returns>A <see cref="FileContentResult"/> with the HLS subtitle playlist.</returns>
 | 
					    /// <returns>A <see cref="FileContentResult"/> with the HLS subtitle playlist.</returns>
 | 
				
			||||||
    [HttpGet("Videos/{itemId}/{mediaSourceId}/Subtitles/{index}/subtitles.m3u8")]
 | 
					    [HttpGet("Videos/{itemId}/{mediaSourceId}/Subtitles/{index}/subtitles.m3u8")]
 | 
				
			||||||
    [Authorize]
 | 
					    [Authorize]
 | 
				
			||||||
    [ProducesResponseType(StatusCodes.Status200OK)]
 | 
					    [ProducesResponseType(StatusCodes.Status200OK)]
 | 
				
			||||||
 | 
					    [ProducesResponseType(StatusCodes.Status404NotFound)]
 | 
				
			||||||
    [ProducesPlaylistFile]
 | 
					    [ProducesPlaylistFile]
 | 
				
			||||||
    [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "index", Justification = "Imported from ServiceStack")]
 | 
					    [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "index", Justification = "Imported from ServiceStack")]
 | 
				
			||||||
    public async Task<ActionResult> GetSubtitlePlaylist(
 | 
					    public async Task<ActionResult> GetSubtitlePlaylist(
 | 
				
			||||||
@ -333,7 +347,11 @@ public class SubtitleController : BaseJellyfinApiController
 | 
				
			|||||||
        [FromRoute, Required] string mediaSourceId,
 | 
					        [FromRoute, Required] string mediaSourceId,
 | 
				
			||||||
        [FromQuery, Required] int segmentLength)
 | 
					        [FromQuery, Required] int segmentLength)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        var item = (Video)_libraryManager.GetItemById(itemId);
 | 
					        var item = _libraryManager.GetItemById<Video>(itemId, User.GetUserId());
 | 
				
			||||||
 | 
					        if (item is null)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            return NotFound();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        var mediaSource = await _mediaSourceManager.GetMediaSource(item, mediaSourceId, null, false, CancellationToken.None).ConfigureAwait(false);
 | 
					        var mediaSource = await _mediaSourceManager.GetMediaSource(item, mediaSourceId, null, false, CancellationToken.None).ConfigureAwait(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -397,15 +415,21 @@ public class SubtitleController : BaseJellyfinApiController
 | 
				
			|||||||
    /// <param name="itemId">The item the subtitle belongs to.</param>
 | 
					    /// <param name="itemId">The item the subtitle belongs to.</param>
 | 
				
			||||||
    /// <param name="body">The request body.</param>
 | 
					    /// <param name="body">The request body.</param>
 | 
				
			||||||
    /// <response code="204">Subtitle uploaded.</response>
 | 
					    /// <response code="204">Subtitle uploaded.</response>
 | 
				
			||||||
 | 
					    /// <response code="404">Item not found.</response>
 | 
				
			||||||
    /// <returns>A <see cref="NoContentResult"/>.</returns>
 | 
					    /// <returns>A <see cref="NoContentResult"/>.</returns>
 | 
				
			||||||
    [HttpPost("Videos/{itemId}/Subtitles")]
 | 
					    [HttpPost("Videos/{itemId}/Subtitles")]
 | 
				
			||||||
    [Authorize(Policy = Policies.SubtitleManagement)]
 | 
					    [Authorize(Policy = Policies.SubtitleManagement)]
 | 
				
			||||||
    [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
					    [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
				
			||||||
 | 
					    [ProducesResponseType(StatusCodes.Status404NotFound)]
 | 
				
			||||||
    public async Task<ActionResult> UploadSubtitle(
 | 
					    public async Task<ActionResult> UploadSubtitle(
 | 
				
			||||||
        [FromRoute, Required] Guid itemId,
 | 
					        [FromRoute, Required] Guid itemId,
 | 
				
			||||||
        [FromBody, Required] UploadSubtitleDto body)
 | 
					        [FromBody, Required] UploadSubtitleDto body)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        var video = (Video)_libraryManager.GetItemById(itemId);
 | 
					        var item = _libraryManager.GetItemById<Video>(itemId, User.GetUserId());
 | 
				
			||||||
 | 
					        if (item is null)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            return NotFound();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        var bytes = Encoding.UTF8.GetBytes(body.Data);
 | 
					        var bytes = Encoding.UTF8.GetBytes(body.Data);
 | 
				
			||||||
        var memoryStream = new MemoryStream(bytes, 0, bytes.Length, false, true);
 | 
					        var memoryStream = new MemoryStream(bytes, 0, bytes.Length, false, true);
 | 
				
			||||||
@ -416,7 +440,7 @@ public class SubtitleController : BaseJellyfinApiController
 | 
				
			|||||||
            await using (stream.ConfigureAwait(false))
 | 
					            await using (stream.ConfigureAwait(false))
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                await _subtitleManager.UploadSubtitle(
 | 
					                await _subtitleManager.UploadSubtitle(
 | 
				
			||||||
                    video,
 | 
					                    item,
 | 
				
			||||||
                    new SubtitleResponse
 | 
					                    new SubtitleResponse
 | 
				
			||||||
                    {
 | 
					                    {
 | 
				
			||||||
                        Format = body.Format,
 | 
					                        Format = body.Format,
 | 
				
			||||||
@ -425,7 +449,7 @@ public class SubtitleController : BaseJellyfinApiController
 | 
				
			|||||||
                        IsHearingImpaired = body.IsHearingImpaired,
 | 
					                        IsHearingImpaired = body.IsHearingImpaired,
 | 
				
			||||||
                        Stream = stream
 | 
					                        Stream = stream
 | 
				
			||||||
                    }).ConfigureAwait(false);
 | 
					                    }).ConfigureAwait(false);
 | 
				
			||||||
                _providerManager.QueueRefresh(video.Id, new MetadataRefreshOptions(new DirectoryService(_fileSystem)), RefreshPriority.High);
 | 
					                _providerManager.QueueRefresh(item.Id, new MetadataRefreshOptions(new DirectoryService(_fileSystem)), RefreshPriority.High);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                return NoContent();
 | 
					                return NoContent();
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
@ -452,7 +476,7 @@ public class SubtitleController : BaseJellyfinApiController
 | 
				
			|||||||
        long? endPositionTicks,
 | 
					        long? endPositionTicks,
 | 
				
			||||||
        bool copyTimestamps)
 | 
					        bool copyTimestamps)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        var item = _libraryManager.GetItemById(id);
 | 
					        var item = _libraryManager.GetItemById<BaseItem>(id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return _subtitleEncoder.GetSubtitles(
 | 
					        return _subtitleEncoder.GetSubtitles(
 | 
				
			||||||
            item,
 | 
					            item,
 | 
				
			||||||
 | 
				
			|||||||
@ -5,6 +5,8 @@ using System.Text;
 | 
				
			|||||||
using System.Threading.Tasks;
 | 
					using System.Threading.Tasks;
 | 
				
			||||||
using Jellyfin.Api.Attributes;
 | 
					using Jellyfin.Api.Attributes;
 | 
				
			||||||
using Jellyfin.Api.Extensions;
 | 
					using Jellyfin.Api.Extensions;
 | 
				
			||||||
 | 
					using Jellyfin.Api.Helpers;
 | 
				
			||||||
 | 
					using MediaBrowser.Controller.Entities;
 | 
				
			||||||
using MediaBrowser.Controller.Library;
 | 
					using MediaBrowser.Controller.Library;
 | 
				
			||||||
using MediaBrowser.Controller.Trickplay;
 | 
					using MediaBrowser.Controller.Trickplay;
 | 
				
			||||||
using MediaBrowser.Model;
 | 
					using MediaBrowser.Model;
 | 
				
			||||||
@ -84,7 +86,7 @@ public class TrickplayController : BaseJellyfinApiController
 | 
				
			|||||||
        [FromRoute, Required] int index,
 | 
					        [FromRoute, Required] int index,
 | 
				
			||||||
        [FromQuery] Guid? mediaSourceId)
 | 
					        [FromQuery] Guid? mediaSourceId)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        var item = _libraryManager.GetItemById(mediaSourceId ?? itemId);
 | 
					        var item = _libraryManager.GetItemById<BaseItem>(itemId, User.GetUserId());
 | 
				
			||||||
        if (item is null)
 | 
					        if (item is null)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            return NotFound();
 | 
					            return NotFound();
 | 
				
			||||||
 | 
				
			|||||||
@ -234,7 +234,7 @@ public class TvShowsController : BaseJellyfinApiController
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        if (seasonId.HasValue) // Season id was supplied. Get episodes by season id.
 | 
					        if (seasonId.HasValue) // Season id was supplied. Get episodes by season id.
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            var item = _libraryManager.GetItemById(seasonId.Value);
 | 
					            var item = _libraryManager.GetItemById<BaseItem>(seasonId.Value);
 | 
				
			||||||
            if (item is not Season seasonItem)
 | 
					            if (item is not Season seasonItem)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                return NotFound("No season exists with Id " + seasonId);
 | 
					                return NotFound("No season exists with Id " + seasonId);
 | 
				
			||||||
@ -244,7 +244,8 @@ public class TvShowsController : BaseJellyfinApiController
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
        else if (season.HasValue) // Season number was supplied. Get episodes by season number
 | 
					        else if (season.HasValue) // Season number was supplied. Get episodes by season number
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            if (_libraryManager.GetItemById(seriesId) is not Series series)
 | 
					            var series = _libraryManager.GetItemById<Series>(seriesId);
 | 
				
			||||||
 | 
					            if (series is null)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                return NotFound("Series not found");
 | 
					                return NotFound("Series not found");
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
@ -259,7 +260,7 @@ public class TvShowsController : BaseJellyfinApiController
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
        else // No season number or season id was supplied. Returning all episodes.
 | 
					        else // No season number or season id was supplied. Returning all episodes.
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            if (_libraryManager.GetItemById(seriesId) is not Series series)
 | 
					            if (_libraryManager.GetItemById<BaseItem>(seriesId) is not Series series)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                return NotFound("Series not found");
 | 
					                return NotFound("Series not found");
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
@ -342,13 +343,13 @@ public class TvShowsController : BaseJellyfinApiController
 | 
				
			|||||||
        var user = userId.IsNullOrEmpty()
 | 
					        var user = userId.IsNullOrEmpty()
 | 
				
			||||||
            ? null
 | 
					            ? null
 | 
				
			||||||
            : _userManager.GetUserById(userId.Value);
 | 
					            : _userManager.GetUserById(userId.Value);
 | 
				
			||||||
 | 
					        var item = _libraryManager.GetItemById<Series>(seriesId, user);
 | 
				
			||||||
        if (_libraryManager.GetItemById(seriesId) is not Series series)
 | 
					        if (item is null)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            return NotFound("Series not found");
 | 
					            return NotFound();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        var seasons = series.GetItemList(new InternalItemsQuery(user)
 | 
					        var seasons = item.GetItemList(new InternalItemsQuery(user)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            IsMissing = isMissing,
 | 
					            IsMissing = isMissing,
 | 
				
			||||||
            IsSpecialSeason = isSpecialSeason,
 | 
					            IsSpecialSeason = isSpecialSeason,
 | 
				
			||||||
 | 
				
			|||||||
@ -9,7 +9,9 @@ using Jellyfin.Api.Helpers;
 | 
				
			|||||||
using Jellyfin.Api.ModelBinders;
 | 
					using Jellyfin.Api.ModelBinders;
 | 
				
			||||||
using Jellyfin.Api.Models.StreamingDtos;
 | 
					using Jellyfin.Api.Models.StreamingDtos;
 | 
				
			||||||
using Jellyfin.Data.Enums;
 | 
					using Jellyfin.Data.Enums;
 | 
				
			||||||
 | 
					using Jellyfin.Extensions;
 | 
				
			||||||
using MediaBrowser.Common.Extensions;
 | 
					using MediaBrowser.Common.Extensions;
 | 
				
			||||||
 | 
					using MediaBrowser.Controller.Entities;
 | 
				
			||||||
using MediaBrowser.Controller.Library;
 | 
					using MediaBrowser.Controller.Library;
 | 
				
			||||||
using MediaBrowser.Controller.MediaEncoding;
 | 
					using MediaBrowser.Controller.MediaEncoding;
 | 
				
			||||||
using MediaBrowser.Controller.Streaming;
 | 
					using MediaBrowser.Controller.Streaming;
 | 
				
			||||||
@ -33,6 +35,7 @@ public class UniversalAudioController : BaseJellyfinApiController
 | 
				
			|||||||
    private readonly MediaInfoHelper _mediaInfoHelper;
 | 
					    private readonly MediaInfoHelper _mediaInfoHelper;
 | 
				
			||||||
    private readonly AudioHelper _audioHelper;
 | 
					    private readonly AudioHelper _audioHelper;
 | 
				
			||||||
    private readonly DynamicHlsHelper _dynamicHlsHelper;
 | 
					    private readonly DynamicHlsHelper _dynamicHlsHelper;
 | 
				
			||||||
 | 
					    private readonly IUserManager _userManager;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// <summary>
 | 
					    /// <summary>
 | 
				
			||||||
    /// Initializes a new instance of the <see cref="UniversalAudioController"/> class.
 | 
					    /// Initializes a new instance of the <see cref="UniversalAudioController"/> class.
 | 
				
			||||||
@ -42,18 +45,21 @@ public class UniversalAudioController : BaseJellyfinApiController
 | 
				
			|||||||
    /// <param name="mediaInfoHelper">Instance of <see cref="MediaInfoHelper"/>.</param>
 | 
					    /// <param name="mediaInfoHelper">Instance of <see cref="MediaInfoHelper"/>.</param>
 | 
				
			||||||
    /// <param name="audioHelper">Instance of <see cref="AudioHelper"/>.</param>
 | 
					    /// <param name="audioHelper">Instance of <see cref="AudioHelper"/>.</param>
 | 
				
			||||||
    /// <param name="dynamicHlsHelper">Instance of <see cref="DynamicHlsHelper"/>.</param>
 | 
					    /// <param name="dynamicHlsHelper">Instance of <see cref="DynamicHlsHelper"/>.</param>
 | 
				
			||||||
 | 
					    /// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
 | 
				
			||||||
    public UniversalAudioController(
 | 
					    public UniversalAudioController(
 | 
				
			||||||
        ILibraryManager libraryManager,
 | 
					        ILibraryManager libraryManager,
 | 
				
			||||||
        ILogger<UniversalAudioController> logger,
 | 
					        ILogger<UniversalAudioController> logger,
 | 
				
			||||||
        MediaInfoHelper mediaInfoHelper,
 | 
					        MediaInfoHelper mediaInfoHelper,
 | 
				
			||||||
        AudioHelper audioHelper,
 | 
					        AudioHelper audioHelper,
 | 
				
			||||||
        DynamicHlsHelper dynamicHlsHelper)
 | 
					        DynamicHlsHelper dynamicHlsHelper,
 | 
				
			||||||
 | 
					        IUserManager userManager)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        _libraryManager = libraryManager;
 | 
					        _libraryManager = libraryManager;
 | 
				
			||||||
        _logger = logger;
 | 
					        _logger = logger;
 | 
				
			||||||
        _mediaInfoHelper = mediaInfoHelper;
 | 
					        _mediaInfoHelper = mediaInfoHelper;
 | 
				
			||||||
        _audioHelper = audioHelper;
 | 
					        _audioHelper = audioHelper;
 | 
				
			||||||
        _dynamicHlsHelper = dynamicHlsHelper;
 | 
					        _dynamicHlsHelper = dynamicHlsHelper;
 | 
				
			||||||
 | 
					        _userManager = userManager;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// <summary>
 | 
					    /// <summary>
 | 
				
			||||||
@ -79,12 +85,14 @@ public class UniversalAudioController : BaseJellyfinApiController
 | 
				
			|||||||
    /// <param name="enableRedirection">Whether to enable redirection. Defaults to true.</param>
 | 
					    /// <param name="enableRedirection">Whether to enable redirection. Defaults to true.</param>
 | 
				
			||||||
    /// <response code="200">Audio stream returned.</response>
 | 
					    /// <response code="200">Audio stream returned.</response>
 | 
				
			||||||
    /// <response code="302">Redirected to remote audio stream.</response>
 | 
					    /// <response code="302">Redirected to remote audio stream.</response>
 | 
				
			||||||
 | 
					    /// <response code="404">Item not found.</response>
 | 
				
			||||||
    /// <returns>A <see cref="Task"/> containing the audio file.</returns>
 | 
					    /// <returns>A <see cref="Task"/> containing the audio file.</returns>
 | 
				
			||||||
    [HttpGet("Audio/{itemId}/universal")]
 | 
					    [HttpGet("Audio/{itemId}/universal")]
 | 
				
			||||||
    [HttpHead("Audio/{itemId}/universal", Name = "HeadUniversalAudioStream")]
 | 
					    [HttpHead("Audio/{itemId}/universal", Name = "HeadUniversalAudioStream")]
 | 
				
			||||||
    [Authorize]
 | 
					    [Authorize]
 | 
				
			||||||
    [ProducesResponseType(StatusCodes.Status200OK)]
 | 
					    [ProducesResponseType(StatusCodes.Status200OK)]
 | 
				
			||||||
    [ProducesResponseType(StatusCodes.Status302Found)]
 | 
					    [ProducesResponseType(StatusCodes.Status302Found)]
 | 
				
			||||||
 | 
					    [ProducesResponseType(StatusCodes.Status404NotFound)]
 | 
				
			||||||
    [ProducesAudioFile]
 | 
					    [ProducesAudioFile]
 | 
				
			||||||
    public async Task<ActionResult> GetUniversalAudioStream(
 | 
					    public async Task<ActionResult> GetUniversalAudioStream(
 | 
				
			||||||
        [FromRoute, Required] Guid itemId,
 | 
					        [FromRoute, Required] Guid itemId,
 | 
				
			||||||
@ -106,20 +114,27 @@ public class UniversalAudioController : BaseJellyfinApiController
 | 
				
			|||||||
        [FromQuery] bool breakOnNonKeyFrames = false,
 | 
					        [FromQuery] bool breakOnNonKeyFrames = false,
 | 
				
			||||||
        [FromQuery] bool enableRedirection = true)
 | 
					        [FromQuery] bool enableRedirection = true)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        var deviceProfile = GetDeviceProfile(container, transcodingContainer, audioCodec, transcodingProtocol, breakOnNonKeyFrames, transcodingAudioChannels, maxAudioSampleRate, maxAudioBitDepth, maxAudioChannels);
 | 
					 | 
				
			||||||
        userId = RequestHelpers.GetUserId(User, userId);
 | 
					        userId = RequestHelpers.GetUserId(User, userId);
 | 
				
			||||||
 | 
					        var user = userId.IsNullOrEmpty()
 | 
				
			||||||
 | 
					            ? null
 | 
				
			||||||
 | 
					            : _userManager.GetUserById(userId.Value);
 | 
				
			||||||
 | 
					        var item = _libraryManager.GetItemById<BaseItem>(itemId, user);
 | 
				
			||||||
 | 
					        if (item is null)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            return NotFound();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        var deviceProfile = GetDeviceProfile(container, transcodingContainer, audioCodec, transcodingProtocol, breakOnNonKeyFrames, transcodingAudioChannels, maxAudioSampleRate, maxAudioBitDepth, maxAudioChannels);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        _logger.LogInformation("GetPostedPlaybackInfo profile: {@Profile}", deviceProfile);
 | 
					        _logger.LogInformation("GetPostedPlaybackInfo profile: {@Profile}", deviceProfile);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        var info = await _mediaInfoHelper.GetPlaybackInfo(
 | 
					        var info = await _mediaInfoHelper.GetPlaybackInfo(
 | 
				
			||||||
                itemId,
 | 
					                item,
 | 
				
			||||||
                userId,
 | 
					                user,
 | 
				
			||||||
                mediaSourceId)
 | 
					                mediaSourceId)
 | 
				
			||||||
            .ConfigureAwait(false);
 | 
					            .ConfigureAwait(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // set device specific data
 | 
					        // set device specific data
 | 
				
			||||||
        var item = _libraryManager.GetItemById(itemId);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        foreach (var sourceInfo in info.MediaSources)
 | 
					        foreach (var sourceInfo in info.MediaSources)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            _mediaInfoHelper.SetDeviceSpecificData(
 | 
					            _mediaInfoHelper.SetDeviceSpecificData(
 | 
				
			||||||
 | 
				
			|||||||
@ -77,8 +77,8 @@ public class UserLibraryController : BaseJellyfinApiController
 | 
				
			|||||||
        [FromQuery] Guid? userId,
 | 
					        [FromQuery] Guid? userId,
 | 
				
			||||||
        [FromRoute, Required] Guid itemId)
 | 
					        [FromRoute, Required] Guid itemId)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        var requestUserId = RequestHelpers.GetUserId(User, userId);
 | 
					        userId = RequestHelpers.GetUserId(User, userId);
 | 
				
			||||||
        var user = _userManager.GetUserById(requestUserId);
 | 
					        var user = _userManager.GetUserById(userId.Value);
 | 
				
			||||||
        if (user is null)
 | 
					        if (user is null)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            return NotFound();
 | 
					            return NotFound();
 | 
				
			||||||
@ -86,20 +86,12 @@ public class UserLibraryController : BaseJellyfinApiController
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        var item = itemId.IsEmpty()
 | 
					        var item = itemId.IsEmpty()
 | 
				
			||||||
            ? _libraryManager.GetUserRootFolder()
 | 
					            ? _libraryManager.GetUserRootFolder()
 | 
				
			||||||
            : _libraryManager.GetItemById(itemId);
 | 
					            : _libraryManager.GetItemById<BaseItem>(itemId, user);
 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (item is null)
 | 
					        if (item is null)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            return NotFound();
 | 
					            return NotFound();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (item is not UserRootFolder
 | 
					 | 
				
			||||||
            // Check the item is visible for the user
 | 
					 | 
				
			||||||
            && !item.IsVisible(user))
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            return Unauthorized($"{user.Username} is not permitted to access item {item.Name}.");
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        await RefreshItemOnDemandIfNeeded(item).ConfigureAwait(false);
 | 
					        await RefreshItemOnDemandIfNeeded(item).ConfigureAwait(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        var dtoOptions = new DtoOptions().AddClientFields(User);
 | 
					        var dtoOptions = new DtoOptions().AddClientFields(User);
 | 
				
			||||||
@ -133,8 +125,8 @@ public class UserLibraryController : BaseJellyfinApiController
 | 
				
			|||||||
    [ProducesResponseType(StatusCodes.Status200OK)]
 | 
					    [ProducesResponseType(StatusCodes.Status200OK)]
 | 
				
			||||||
    public ActionResult<BaseItemDto> GetRootFolder([FromQuery] Guid? userId)
 | 
					    public ActionResult<BaseItemDto> GetRootFolder([FromQuery] Guid? userId)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        var requestUserId = RequestHelpers.GetUserId(User, userId);
 | 
					        userId = RequestHelpers.GetUserId(User, userId);
 | 
				
			||||||
        var user = _userManager.GetUserById(requestUserId);
 | 
					        var user = _userManager.GetUserById(userId.Value);
 | 
				
			||||||
        if (user is null)
 | 
					        if (user is null)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            return NotFound();
 | 
					            return NotFound();
 | 
				
			||||||
@ -172,8 +164,8 @@ public class UserLibraryController : BaseJellyfinApiController
 | 
				
			|||||||
        [FromQuery] Guid? userId,
 | 
					        [FromQuery] Guid? userId,
 | 
				
			||||||
        [FromRoute, Required] Guid itemId)
 | 
					        [FromRoute, Required] Guid itemId)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        var requestUserId = RequestHelpers.GetUserId(User, userId);
 | 
					        userId = RequestHelpers.GetUserId(User, userId);
 | 
				
			||||||
        var user = _userManager.GetUserById(requestUserId);
 | 
					        var user = _userManager.GetUserById(userId.Value);
 | 
				
			||||||
        if (user is null)
 | 
					        if (user is null)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            return NotFound();
 | 
					            return NotFound();
 | 
				
			||||||
@ -181,20 +173,12 @@ public class UserLibraryController : BaseJellyfinApiController
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        var item = itemId.IsEmpty()
 | 
					        var item = itemId.IsEmpty()
 | 
				
			||||||
            ? _libraryManager.GetUserRootFolder()
 | 
					            ? _libraryManager.GetUserRootFolder()
 | 
				
			||||||
            : _libraryManager.GetItemById(itemId);
 | 
					            : _libraryManager.GetItemById<BaseItem>(itemId, user);
 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (item is null)
 | 
					        if (item is null)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            return NotFound();
 | 
					            return NotFound();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (item is not UserRootFolder
 | 
					 | 
				
			||||||
            // Check the item is visible for the user
 | 
					 | 
				
			||||||
            && !item.IsVisible(user))
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            return Unauthorized($"{user.Username} is not permitted to access item {item.Name}.");
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        var items = await _libraryManager.GetIntros(item, user).ConfigureAwait(false);
 | 
					        var items = await _libraryManager.GetIntros(item, user).ConfigureAwait(false);
 | 
				
			||||||
        var dtoOptions = new DtoOptions().AddClientFields(User);
 | 
					        var dtoOptions = new DtoOptions().AddClientFields(User);
 | 
				
			||||||
        var dtos = items.Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user)).ToArray();
 | 
					        var dtos = items.Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user)).ToArray();
 | 
				
			||||||
@ -231,8 +215,8 @@ public class UserLibraryController : BaseJellyfinApiController
 | 
				
			|||||||
        [FromQuery] Guid? userId,
 | 
					        [FromQuery] Guid? userId,
 | 
				
			||||||
        [FromRoute, Required] Guid itemId)
 | 
					        [FromRoute, Required] Guid itemId)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        var requestUserId = RequestHelpers.GetUserId(User, userId);
 | 
					        userId = RequestHelpers.GetUserId(User, userId);
 | 
				
			||||||
        var user = _userManager.GetUserById(requestUserId);
 | 
					        var user = _userManager.GetUserById(userId.Value);
 | 
				
			||||||
        if (user is null)
 | 
					        if (user is null)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            return NotFound();
 | 
					            return NotFound();
 | 
				
			||||||
@ -240,20 +224,12 @@ public class UserLibraryController : BaseJellyfinApiController
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        var item = itemId.IsEmpty()
 | 
					        var item = itemId.IsEmpty()
 | 
				
			||||||
            ? _libraryManager.GetUserRootFolder()
 | 
					            ? _libraryManager.GetUserRootFolder()
 | 
				
			||||||
            : _libraryManager.GetItemById(itemId);
 | 
					            : _libraryManager.GetItemById<BaseItem>(itemId, user);
 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (item is null)
 | 
					        if (item is null)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            return NotFound();
 | 
					            return NotFound();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (item is not UserRootFolder
 | 
					 | 
				
			||||||
            // Check the item is visible for the user
 | 
					 | 
				
			||||||
            && !item.IsVisible(user))
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            return Unauthorized($"{user.Username} is not permitted to access item {item.Name}.");
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return MarkFavorite(user, item, true);
 | 
					        return MarkFavorite(user, item, true);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -286,8 +262,8 @@ public class UserLibraryController : BaseJellyfinApiController
 | 
				
			|||||||
        [FromQuery] Guid? userId,
 | 
					        [FromQuery] Guid? userId,
 | 
				
			||||||
        [FromRoute, Required] Guid itemId)
 | 
					        [FromRoute, Required] Guid itemId)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        var requestUserId = RequestHelpers.GetUserId(User, userId);
 | 
					        userId = RequestHelpers.GetUserId(User, userId);
 | 
				
			||||||
        var user = _userManager.GetUserById(requestUserId);
 | 
					        var user = _userManager.GetUserById(userId.Value);
 | 
				
			||||||
        if (user is null)
 | 
					        if (user is null)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            return NotFound();
 | 
					            return NotFound();
 | 
				
			||||||
@ -295,20 +271,12 @@ public class UserLibraryController : BaseJellyfinApiController
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        var item = itemId.IsEmpty()
 | 
					        var item = itemId.IsEmpty()
 | 
				
			||||||
            ? _libraryManager.GetUserRootFolder()
 | 
					            ? _libraryManager.GetUserRootFolder()
 | 
				
			||||||
            : _libraryManager.GetItemById(itemId);
 | 
					            : _libraryManager.GetItemById<BaseItem>(itemId, user);
 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (item is null)
 | 
					        if (item is null)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            return NotFound();
 | 
					            return NotFound();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (item is not UserRootFolder
 | 
					 | 
				
			||||||
            // Check the item is visible for the user
 | 
					 | 
				
			||||||
            && !item.IsVisible(user))
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            return Unauthorized($"{user.Username} is not permitted to access item {item.Name}.");
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return MarkFavorite(user, item, false);
 | 
					        return MarkFavorite(user, item, false);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -341,8 +309,8 @@ public class UserLibraryController : BaseJellyfinApiController
 | 
				
			|||||||
        [FromQuery] Guid? userId,
 | 
					        [FromQuery] Guid? userId,
 | 
				
			||||||
        [FromRoute, Required] Guid itemId)
 | 
					        [FromRoute, Required] Guid itemId)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        var requestUserId = RequestHelpers.GetUserId(User, userId);
 | 
					        userId = RequestHelpers.GetUserId(User, userId);
 | 
				
			||||||
        var user = _userManager.GetUserById(requestUserId);
 | 
					        var user = _userManager.GetUserById(userId.Value);
 | 
				
			||||||
        if (user is null)
 | 
					        if (user is null)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            return NotFound();
 | 
					            return NotFound();
 | 
				
			||||||
@ -350,20 +318,12 @@ public class UserLibraryController : BaseJellyfinApiController
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        var item = itemId.IsEmpty()
 | 
					        var item = itemId.IsEmpty()
 | 
				
			||||||
            ? _libraryManager.GetUserRootFolder()
 | 
					            ? _libraryManager.GetUserRootFolder()
 | 
				
			||||||
            : _libraryManager.GetItemById(itemId);
 | 
					            : _libraryManager.GetItemById<BaseItem>(itemId, user);
 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (item is null)
 | 
					        if (item is null)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            return NotFound();
 | 
					            return NotFound();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (item is not UserRootFolder
 | 
					 | 
				
			||||||
            // Check the item is visible for the user
 | 
					 | 
				
			||||||
            && !item.IsVisible(user))
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            return Unauthorized($"{user.Username} is not permitted to access item {item.Name}.");
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return UpdateUserItemRatingInternal(user, item, null);
 | 
					        return UpdateUserItemRatingInternal(user, item, null);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -398,8 +358,8 @@ public class UserLibraryController : BaseJellyfinApiController
 | 
				
			|||||||
        [FromRoute, Required] Guid itemId,
 | 
					        [FromRoute, Required] Guid itemId,
 | 
				
			||||||
        [FromQuery] bool? likes)
 | 
					        [FromQuery] bool? likes)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        var requestUserId = RequestHelpers.GetUserId(User, userId);
 | 
					        userId = RequestHelpers.GetUserId(User, userId);
 | 
				
			||||||
        var user = _userManager.GetUserById(requestUserId);
 | 
					        var user = _userManager.GetUserById(userId.Value);
 | 
				
			||||||
        if (user is null)
 | 
					        if (user is null)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            return NotFound();
 | 
					            return NotFound();
 | 
				
			||||||
@ -407,20 +367,12 @@ public class UserLibraryController : BaseJellyfinApiController
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        var item = itemId.IsEmpty()
 | 
					        var item = itemId.IsEmpty()
 | 
				
			||||||
            ? _libraryManager.GetUserRootFolder()
 | 
					            ? _libraryManager.GetUserRootFolder()
 | 
				
			||||||
            : _libraryManager.GetItemById(itemId);
 | 
					            : _libraryManager.GetItemById<BaseItem>(itemId, user);
 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (item is null)
 | 
					        if (item is null)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            return NotFound();
 | 
					            return NotFound();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (item is not UserRootFolder
 | 
					 | 
				
			||||||
            // Check the item is visible for the user
 | 
					 | 
				
			||||||
            && !item.IsVisible(user))
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            return Unauthorized($"{user.Username} is not permitted to access item {item.Name}.");
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return UpdateUserItemRatingInternal(user, item, likes);
 | 
					        return UpdateUserItemRatingInternal(user, item, likes);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -455,8 +407,8 @@ public class UserLibraryController : BaseJellyfinApiController
 | 
				
			|||||||
        [FromQuery] Guid? userId,
 | 
					        [FromQuery] Guid? userId,
 | 
				
			||||||
        [FromRoute, Required] Guid itemId)
 | 
					        [FromRoute, Required] Guid itemId)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        var requestUserId = RequestHelpers.GetUserId(User, userId);
 | 
					        userId = RequestHelpers.GetUserId(User, userId);
 | 
				
			||||||
        var user = _userManager.GetUserById(requestUserId);
 | 
					        var user = _userManager.GetUserById(userId.Value);
 | 
				
			||||||
        if (user is null)
 | 
					        if (user is null)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            return NotFound();
 | 
					            return NotFound();
 | 
				
			||||||
@ -464,20 +416,12 @@ public class UserLibraryController : BaseJellyfinApiController
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        var item = itemId.IsEmpty()
 | 
					        var item = itemId.IsEmpty()
 | 
				
			||||||
            ? _libraryManager.GetUserRootFolder()
 | 
					            ? _libraryManager.GetUserRootFolder()
 | 
				
			||||||
            : _libraryManager.GetItemById(itemId);
 | 
					            : _libraryManager.GetItemById<BaseItem>(itemId, user);
 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (item is null)
 | 
					        if (item is null)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            return NotFound();
 | 
					            return NotFound();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (item is not UserRootFolder
 | 
					 | 
				
			||||||
            // Check the item is visible for the user
 | 
					 | 
				
			||||||
            && !item.IsVisible(user))
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            return Unauthorized($"{user.Username} is not permitted to access item {item.Name}.");
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        var dtoOptions = new DtoOptions().AddClientFields(User);
 | 
					        var dtoOptions = new DtoOptions().AddClientFields(User);
 | 
				
			||||||
        if (item is IHasTrailers hasTrailers)
 | 
					        if (item is IHasTrailers hasTrailers)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
@ -519,8 +463,8 @@ public class UserLibraryController : BaseJellyfinApiController
 | 
				
			|||||||
        [FromQuery] Guid? userId,
 | 
					        [FromQuery] Guid? userId,
 | 
				
			||||||
        [FromRoute, Required] Guid itemId)
 | 
					        [FromRoute, Required] Guid itemId)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        var requestUserId = RequestHelpers.GetUserId(User, userId);
 | 
					        userId = RequestHelpers.GetUserId(User, userId);
 | 
				
			||||||
        var user = _userManager.GetUserById(requestUserId);
 | 
					        var user = _userManager.GetUserById(userId.Value);
 | 
				
			||||||
        if (user is null)
 | 
					        if (user is null)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            return NotFound();
 | 
					            return NotFound();
 | 
				
			||||||
@ -528,20 +472,12 @@ public class UserLibraryController : BaseJellyfinApiController
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        var item = itemId.IsEmpty()
 | 
					        var item = itemId.IsEmpty()
 | 
				
			||||||
            ? _libraryManager.GetUserRootFolder()
 | 
					            ? _libraryManager.GetUserRootFolder()
 | 
				
			||||||
            : _libraryManager.GetItemById(itemId);
 | 
					            : _libraryManager.GetItemById<BaseItem>(itemId, user);
 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (item is null)
 | 
					        if (item is null)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            return NotFound();
 | 
					            return NotFound();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (item is not UserRootFolder
 | 
					 | 
				
			||||||
            // Check the item is visible for the user
 | 
					 | 
				
			||||||
            && !item.IsVisible(user))
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            return Unauthorized($"{user.Username} is not permitted to access item {item.Name}.");
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        var dtoOptions = new DtoOptions().AddClientFields(User);
 | 
					        var dtoOptions = new DtoOptions().AddClientFields(User);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return Ok(item
 | 
					        return Ok(item
 | 
				
			||||||
 | 
				
			|||||||
@ -4,7 +4,10 @@ using System.Net.Mime;
 | 
				
			|||||||
using System.Threading;
 | 
					using System.Threading;
 | 
				
			||||||
using System.Threading.Tasks;
 | 
					using System.Threading.Tasks;
 | 
				
			||||||
using Jellyfin.Api.Attributes;
 | 
					using Jellyfin.Api.Attributes;
 | 
				
			||||||
 | 
					using Jellyfin.Api.Extensions;
 | 
				
			||||||
 | 
					using Jellyfin.Api.Helpers;
 | 
				
			||||||
using MediaBrowser.Common.Extensions;
 | 
					using MediaBrowser.Common.Extensions;
 | 
				
			||||||
 | 
					using MediaBrowser.Controller.Entities;
 | 
				
			||||||
using MediaBrowser.Controller.Library;
 | 
					using MediaBrowser.Controller.Library;
 | 
				
			||||||
using MediaBrowser.Controller.MediaEncoding;
 | 
					using MediaBrowser.Controller.MediaEncoding;
 | 
				
			||||||
using Microsoft.AspNetCore.Http;
 | 
					using Microsoft.AspNetCore.Http;
 | 
				
			||||||
@ -54,7 +57,7 @@ public class VideoAttachmentsController : BaseJellyfinApiController
 | 
				
			|||||||
    {
 | 
					    {
 | 
				
			||||||
        try
 | 
					        try
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            var item = _libraryManager.GetItemById(videoId);
 | 
					            var item = _libraryManager.GetItemById<BaseItem>(videoId, User.GetUserId());
 | 
				
			||||||
            if (item is null)
 | 
					            if (item is null)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                return NotFound();
 | 
					                return NotFound();
 | 
				
			||||||
 | 
				
			|||||||
@ -7,7 +7,6 @@ using System.Net.Http;
 | 
				
			|||||||
using System.Threading;
 | 
					using System.Threading;
 | 
				
			||||||
using System.Threading.Tasks;
 | 
					using System.Threading.Tasks;
 | 
				
			||||||
using Jellyfin.Api.Attributes;
 | 
					using Jellyfin.Api.Attributes;
 | 
				
			||||||
using Jellyfin.Api.Constants;
 | 
					 | 
				
			||||||
using Jellyfin.Api.Extensions;
 | 
					using Jellyfin.Api.Extensions;
 | 
				
			||||||
using Jellyfin.Api.Helpers;
 | 
					using Jellyfin.Api.Helpers;
 | 
				
			||||||
using Jellyfin.Api.ModelBinders;
 | 
					using Jellyfin.Api.ModelBinders;
 | 
				
			||||||
@ -105,7 +104,11 @@ public class VideosController : BaseJellyfinApiController
 | 
				
			|||||||
            ? (userId.IsNullOrEmpty()
 | 
					            ? (userId.IsNullOrEmpty()
 | 
				
			||||||
                ? _libraryManager.RootFolder
 | 
					                ? _libraryManager.RootFolder
 | 
				
			||||||
                : _libraryManager.GetUserRootFolder())
 | 
					                : _libraryManager.GetUserRootFolder())
 | 
				
			||||||
            : _libraryManager.GetItemById(itemId);
 | 
					            : _libraryManager.GetItemById<BaseItem>(itemId, user);
 | 
				
			||||||
 | 
					        if (item is null)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            return NotFound();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        var dtoOptions = new DtoOptions();
 | 
					        var dtoOptions = new DtoOptions();
 | 
				
			||||||
        dtoOptions = dtoOptions.AddClientFields(User);
 | 
					        dtoOptions = dtoOptions.AddClientFields(User);
 | 
				
			||||||
@ -139,24 +142,23 @@ public class VideosController : BaseJellyfinApiController
 | 
				
			|||||||
    [ProducesResponseType(StatusCodes.Status404NotFound)]
 | 
					    [ProducesResponseType(StatusCodes.Status404NotFound)]
 | 
				
			||||||
    public async Task<ActionResult> DeleteAlternateSources([FromRoute, Required] Guid itemId)
 | 
					    public async Task<ActionResult> DeleteAlternateSources([FromRoute, Required] Guid itemId)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        var video = (Video)_libraryManager.GetItemById(itemId);
 | 
					        var item = _libraryManager.GetItemById<Video>(itemId, User.GetUserId());
 | 
				
			||||||
 | 
					        if (item is null)
 | 
				
			||||||
        if (video is 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);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (video is null)
 | 
					 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            return NotFound();
 | 
					            return NotFound();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        foreach (var link in video.GetLinkedAlternateVersions())
 | 
					        if (item.LinkedAlternateVersions.Length == 0)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            item = _libraryManager.GetItemById<Video>(Guid.Parse(item.PrimaryVersionId));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (item is null)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            return NotFound();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        foreach (var link in item.GetLinkedAlternateVersions())
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            link.SetPrimaryVersionId(null);
 | 
					            link.SetPrimaryVersionId(null);
 | 
				
			||||||
            link.LinkedAlternateVersions = Array.Empty<LinkedChild>();
 | 
					            link.LinkedAlternateVersions = Array.Empty<LinkedChild>();
 | 
				
			||||||
@ -164,9 +166,9 @@ public class VideosController : BaseJellyfinApiController
 | 
				
			|||||||
            await link.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
 | 
					            await link.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        video.LinkedAlternateVersions = Array.Empty<LinkedChild>();
 | 
					        item.LinkedAlternateVersions = Array.Empty<LinkedChild>();
 | 
				
			||||||
        video.SetPrimaryVersionId(null);
 | 
					        item.SetPrimaryVersionId(null);
 | 
				
			||||||
        await video.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
 | 
					        await item.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return NoContent();
 | 
					        return NoContent();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -184,8 +186,9 @@ public class VideosController : BaseJellyfinApiController
 | 
				
			|||||||
    [ProducesResponseType(StatusCodes.Status400BadRequest)]
 | 
					    [ProducesResponseType(StatusCodes.Status400BadRequest)]
 | 
				
			||||||
    public async Task<ActionResult> MergeVersions([FromQuery, Required, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] ids)
 | 
					    public async Task<ActionResult> MergeVersions([FromQuery, Required, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] ids)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
 | 
					        var userId = User.GetUserId();
 | 
				
			||||||
        var items = ids
 | 
					        var items = ids
 | 
				
			||||||
            .Select(i => _libraryManager.GetItemById(i))
 | 
					            .Select(i => _libraryManager.GetItemById<BaseItem>(i, userId))
 | 
				
			||||||
            .OfType<Video>()
 | 
					            .OfType<Video>()
 | 
				
			||||||
            .OrderBy(i => i.Id)
 | 
					            .OrderBy(i => i.Id)
 | 
				
			||||||
            .ToList();
 | 
					            .ToList();
 | 
				
			||||||
 | 
				
			|||||||
@ -76,21 +76,17 @@ public class MediaInfoHelper
 | 
				
			|||||||
    /// <summary>
 | 
					    /// <summary>
 | 
				
			||||||
    /// Get playback info.
 | 
					    /// Get playback info.
 | 
				
			||||||
    /// </summary>
 | 
					    /// </summary>
 | 
				
			||||||
    /// <param name="id">Item id.</param>
 | 
					    /// <param name="item">The item.</param>
 | 
				
			||||||
    /// <param name="userId">User Id.</param>
 | 
					    /// <param name="user">The user.</param>
 | 
				
			||||||
    /// <param name="mediaSourceId">Media source id.</param>
 | 
					    /// <param name="mediaSourceId">Media source id.</param>
 | 
				
			||||||
    /// <param name="liveStreamId">Live stream id.</param>
 | 
					    /// <param name="liveStreamId">Live stream id.</param>
 | 
				
			||||||
    /// <returns>A <see cref="Task"/> containing the <see cref="PlaybackInfoResponse"/>.</returns>
 | 
					    /// <returns>A <see cref="Task"/> containing the <see cref="PlaybackInfoResponse"/>.</returns>
 | 
				
			||||||
    public async Task<PlaybackInfoResponse> GetPlaybackInfo(
 | 
					    public async Task<PlaybackInfoResponse> GetPlaybackInfo(
 | 
				
			||||||
        Guid id,
 | 
					        BaseItem item,
 | 
				
			||||||
        Guid? userId,
 | 
					        User? user,
 | 
				
			||||||
        string? mediaSourceId = null,
 | 
					        string? mediaSourceId = null,
 | 
				
			||||||
        string? liveStreamId = null)
 | 
					        string? liveStreamId = null)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        var user = userId.IsNullOrEmpty()
 | 
					 | 
				
			||||||
            ? null
 | 
					 | 
				
			||||||
            : _userManager.GetUserById(userId.Value);
 | 
					 | 
				
			||||||
        var item = _libraryManager.GetItemById(id);
 | 
					 | 
				
			||||||
        var result = new PlaybackInfoResponse();
 | 
					        var result = new PlaybackInfoResponse();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        MediaSourceInfo[] mediaSources;
 | 
					        MediaSourceInfo[] mediaSources;
 | 
				
			||||||
@ -402,7 +398,7 @@ public class MediaInfoHelper
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        if (profile is not null)
 | 
					        if (profile is not null)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            var item = _libraryManager.GetItemById(request.ItemId);
 | 
					            var item = _libraryManager.GetItemById<BaseItem>(request.ItemId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            SetDeviceSpecificData(
 | 
					            SetDeviceSpecificData(
 | 
				
			||||||
                item,
 | 
					                item,
 | 
				
			||||||
 | 
				
			|||||||
@ -11,6 +11,7 @@ using Jellyfin.Extensions;
 | 
				
			|||||||
using MediaBrowser.Common.Configuration;
 | 
					using MediaBrowser.Common.Configuration;
 | 
				
			||||||
using MediaBrowser.Common.Extensions;
 | 
					using MediaBrowser.Common.Extensions;
 | 
				
			||||||
using MediaBrowser.Controller.Configuration;
 | 
					using MediaBrowser.Controller.Configuration;
 | 
				
			||||||
 | 
					using MediaBrowser.Controller.Entities;
 | 
				
			||||||
using MediaBrowser.Controller.Library;
 | 
					using MediaBrowser.Controller.Library;
 | 
				
			||||||
using MediaBrowser.Controller.MediaEncoding;
 | 
					using MediaBrowser.Controller.MediaEncoding;
 | 
				
			||||||
using MediaBrowser.Controller.Streaming;
 | 
					using MediaBrowser.Controller.Streaming;
 | 
				
			||||||
@ -107,7 +108,7 @@ public static class StreamingHelpers
 | 
				
			|||||||
                                          ?? state.SupportedSubtitleCodecs.FirstOrDefault();
 | 
					                                          ?? state.SupportedSubtitleCodecs.FirstOrDefault();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        var item = libraryManager.GetItemById(streamingRequest.Id);
 | 
					        var item = libraryManager.GetItemById<BaseItem>(streamingRequest.Id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        state.IsInputVideo = item.MediaType == MediaType.Video;
 | 
					        state.IsInputVideo = item.MediaType == MediaType.Video;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -125,7 +126,7 @@ public static class StreamingHelpers
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            if (mediaSource is null)
 | 
					            if (mediaSource is null)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                var mediaSources = await mediaSourceManager.GetPlaybackMediaSources(libraryManager.GetItemById(streamingRequest.Id), null, false, false, cancellationToken).ConfigureAwait(false);
 | 
					                var mediaSources = await mediaSourceManager.GetPlaybackMediaSources(libraryManager.GetItemById<BaseItem>(streamingRequest.Id), null, false, false, cancellationToken).ConfigureAwait(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                mediaSource = string.IsNullOrEmpty(streamingRequest.MediaSourceId)
 | 
					                mediaSource = string.IsNullOrEmpty(streamingRequest.MediaSourceId)
 | 
				
			||||||
                    ? mediaSources[0]
 | 
					                    ? mediaSources[0]
 | 
				
			||||||
 | 
				
			|||||||
@ -177,6 +177,26 @@ namespace MediaBrowser.Controller.Library
 | 
				
			|||||||
        T GetItemById<T>(Guid id)
 | 
					        T GetItemById<T>(Guid id)
 | 
				
			||||||
            where T : BaseItem;
 | 
					            where T : BaseItem;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Gets the item by id, as T, and validates user access.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="id">The item id.</param>
 | 
				
			||||||
 | 
					        /// <param name="userId">The user id to validate against.</param>
 | 
				
			||||||
 | 
					        /// <typeparam name="T">The type of item.</typeparam>
 | 
				
			||||||
 | 
					        /// <returns>The item if found.</returns>
 | 
				
			||||||
 | 
					        public T GetItemById<T>(Guid id, Guid userId)
 | 
				
			||||||
 | 
					            where T : BaseItem;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /// <summary>
 | 
				
			||||||
 | 
					        /// Gets the item by id, as T, and validates user access.
 | 
				
			||||||
 | 
					        /// </summary>
 | 
				
			||||||
 | 
					        /// <param name="id">The item id.</param>
 | 
				
			||||||
 | 
					        /// <param name="user">The user to validate against.</param>
 | 
				
			||||||
 | 
					        /// <typeparam name="T">The type of item.</typeparam>
 | 
				
			||||||
 | 
					        /// <returns>The item if found.</returns>
 | 
				
			||||||
 | 
					        public T GetItemById<T>(Guid id, User user)
 | 
				
			||||||
 | 
					            where T : BaseItem;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        /// <summary>
 | 
					        /// <summary>
 | 
				
			||||||
        /// Gets the intros.
 | 
					        /// Gets the intros.
 | 
				
			||||||
        /// </summary>
 | 
					        /// </summary>
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user