using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; using System.Linq; using System.Net; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using Jellyfin.Api.Constants; using Jellyfin.Api.Extensions; using Jellyfin.Api.Helpers; using Jellyfin.Api.Models.LibraryDtos; using Jellyfin.Data.Entities; using MediaBrowser.Common.Progress; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Activity; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Net; using MediaBrowser.Model.Querying; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.Extensions.Logging; using Book = MediaBrowser.Controller.Entities.Book; using Movie = Jellyfin.Data.Entities.Movie; using MusicAlbum = Jellyfin.Data.Entities.MusicAlbum; namespace Jellyfin.Api.Controllers { /// /// Library Controller. /// public class LibraryController : BaseJellyfinApiController { private readonly IProviderManager _providerManager; private readonly ILibraryManager _libraryManager; private readonly IUserManager _userManager; private readonly IDtoService _dtoService; private readonly IAuthorizationContext _authContext; private readonly IActivityManager _activityManager; private readonly ILocalizationManager _localization; private readonly ILibraryMonitor _libraryMonitor; private readonly ILogger _logger; private readonly IServerConfigurationManager _serverConfigurationManager; /// /// Initializes a new instance of the class. /// /// Instance of the interface. /// Instance of the interface. /// Instance of the interface. /// Instance of the interface. /// Instance of the interface. /// Instance of the interface. /// Instance of the interface. /// Instance of the interface. /// Instance of the interface. /// Instance of the interface. public LibraryController( IProviderManager providerManager, ILibraryManager libraryManager, IUserManager userManager, IDtoService dtoService, IAuthorizationContext authContext, IActivityManager activityManager, ILocalizationManager localization, ILibraryMonitor libraryMonitor, ILogger logger, IServerConfigurationManager serverConfigurationManager) { _providerManager = providerManager; _libraryManager = libraryManager; _userManager = userManager; _dtoService = dtoService; _authContext = authContext; _activityManager = activityManager; _localization = localization; _libraryMonitor = libraryMonitor; _logger = logger; _serverConfigurationManager = serverConfigurationManager; } /// /// Get the original file of an item. /// /// The item id. /// File stream returned. /// Item not found. /// A with the original file. [HttpGet("/Items/{itemId}/File")] [Authorize(Policy = Policies.DefaultAuthorization)] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] public ActionResult GetFile([FromRoute] Guid itemId) { var item = _libraryManager.GetItemById(itemId); if (item == null) { return NotFound(); } using var fileStream = new FileStream(item.Path, FileMode.Open, FileAccess.Read); return File(fileStream, MimeTypes.GetMimeType(item.Path)); } /// /// Gets critic review for an item. /// /// The item id. /// Optional. The record index to start at. All items with a lower index will be dropped from the results. /// Optional. The maximum number of records to return. /// Critic reviews returned. /// The list of critic reviews. [HttpGet("/Items/{itemId}/CriticReviews")] [Authorize(Policy = Policies.DefaultAuthorization)] [Obsolete("This endpoint is obsolete.")] [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "itemId", Justification = "Imported from ServiceStack")] [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "startIndex", Justification = "Imported from ServiceStack")] [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "limit", Justification = "Imported from ServiceStack")] [ProducesResponseType(StatusCodes.Status200OK)] public ActionResult> GetCriticReviews( [FromRoute] Guid itemId, [FromQuery] int? startIndex, [FromQuery] int? limit) { return new QueryResult(); } /// /// Get theme songs for an item. /// /// The item id. /// Optional. Filter by user id, and attach user data. /// Optional. Determines whether or not parent items should be searched for theme media. /// Theme songs returned. /// Item not found. /// The item theme songs. [HttpGet("/Items/{itemId}/ThemeSongs")] [Authorize(Policy = Policies.DefaultAuthorization)] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] public ActionResult GetThemeSongs( [FromRoute] Guid itemId, [FromQuery] Guid userId, [FromQuery] bool inheritFromParent) { var user = !userId.Equals(Guid.Empty) ? _userManager.GetUserById(userId) : null; var item = itemId.Equals(Guid.Empty) ? (!userId.Equals(Guid.Empty) ? _libraryManager.GetUserRootFolder() : _libraryManager.RootFolder) : _libraryManager.GetItemById(itemId); if (item == null) { return NotFound("Item not found."); } IEnumerable themeItems; while (true) { themeItems = item.GetThemeSongs(); if (themeItems.Any() || !inheritFromParent) { break; } var parent = item.GetParent(); if (parent == null) { break; } item = parent; } var dtoOptions = new DtoOptions().AddClientFields(Request); var items = themeItems .Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, item)) .ToArray(); return new ThemeMediaResult { Items = items, TotalRecordCount = items.Length, OwnerId = item.Id }; } /// /// Get theme videos for an item. /// /// The item id. /// Optional. Filter by user id, and attach user data. /// Optional. Determines whether or not parent items should be searched for theme media. /// Theme videos returned. /// Item not found. /// The item theme videos. [HttpGet("/Items/{itemId}/ThemeVideos")] [Authorize(Policy = Policies.DefaultAuthorization)] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] public ActionResult GetThemeVideos( [FromRoute] Guid itemId, [FromQuery] Guid userId, [FromQuery] bool inheritFromParent) { var user = !userId.Equals(Guid.Empty) ? _userManager.GetUserById(userId) : null; var item = itemId.Equals(Guid.Empty) ? (!userId.Equals(Guid.Empty) ? _libraryManager.GetUserRootFolder() : _libraryManager.RootFolder) : _libraryManager.GetItemById(itemId); if (item == null) { return NotFound("Item not found."); } IEnumerable themeItems; while (true) { themeItems = item.GetThemeVideos(); if (themeItems.Any() || !inheritFromParent) { break; } var parent = item.GetParent(); if (parent == null) { break; } item = parent; } var dtoOptions = new DtoOptions().AddClientFields(Request); var items = themeItems .Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, item)) .ToArray(); return new ThemeMediaResult { Items = items, TotalRecordCount = items.Length, OwnerId = item.Id }; } /// /// Get theme songs and videos for an item. /// /// The item id. /// Optional. Filter by user id, and attach user data. /// Optional. Determines whether or not parent items should be searched for theme media. /// Theme songs and videos returned. /// Item not found. /// The item theme videos. [ProducesResponseType(StatusCodes.Status200OK)] public ActionResult GetThemeMedia( [FromRoute] Guid itemId, [FromQuery] Guid userId, [FromQuery] bool inheritFromParent) { var themeSongs = GetThemeSongs( itemId, userId, inheritFromParent); var themeVideos = GetThemeVideos( itemId, userId, inheritFromParent); return new AllThemeMediaResult { ThemeSongsResult = themeSongs?.Value, ThemeVideosResult = themeVideos?.Value, SoundtrackSongsResult = new ThemeMediaResult() }; } /// /// Starts a library scan. /// /// Library scan started. /// A . [HttpGet("/Library/Refresh")] [Authorize(Policy = Policies.RequiresElevation)] [ProducesResponseType(StatusCodes.Status204NoContent)] public async Task RefreshLibrary() { try { await _libraryManager.ValidateMediaLibrary(new SimpleProgress(), CancellationToken.None).ConfigureAwait(false); } catch (Exception ex) { _logger.LogError(ex, "Error refreshing library"); } return NoContent(); } /// /// Deletes an item from the library and filesystem. /// /// The item id. /// Item deleted. /// Unauthorized access. /// A . [HttpDelete("/Items/{itemId}")] [Authorize(Policy = Policies.DefaultAuthorization)] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] public ActionResult DeleteItem(Guid itemId) { var item = _libraryManager.GetItemById(itemId); var auth = _authContext.GetAuthorizationInfo(Request); var user = auth.User; if (!item.CanDelete(user)) { return Unauthorized("Unauthorized access"); } _libraryManager.DeleteItem( item, new DeleteOptions { DeleteFileLocation = true }, true); return NoContent(); } /// /// Deletes items from the library and filesystem. /// /// The item ids. /// Items deleted. /// Unauthorized access. /// A . [HttpDelete("/Items")] [Authorize(Policy = Policies.DefaultAuthorization)] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] public ActionResult DeleteItems([FromQuery] string ids) { var itemIds = string.IsNullOrWhiteSpace(ids) ? Array.Empty() : RequestHelpers.Split(ids, ',', true); foreach (var i in itemIds) { var item = _libraryManager.GetItemById(i); var auth = _authContext.GetAuthorizationInfo(Request); var user = auth.User; if (!item.CanDelete(user)) { if (ids.Length > 1) { return Unauthorized("Unauthorized access"); } continue; } _libraryManager.DeleteItem( item, new DeleteOptions { DeleteFileLocation = true }, true); } return NoContent(); } /// /// Get item counts. /// /// Optional. Get counts from a specific user's library. /// Optional. Get counts of favorite items. /// Item counts returned. /// Item counts. [HttpGet("/Items/Counts")] [Authorize(Policy = Policies.DefaultAuthorization)] [ProducesResponseType(StatusCodes.Status200OK)] public ActionResult GetItemCounts( [FromQuery] Guid userId, [FromQuery] bool? isFavorite) { var user = userId.Equals(Guid.Empty) ? null : _userManager.GetUserById(userId); var counts = new ItemCounts { AlbumCount = GetCount(typeof(MusicAlbum), user, isFavorite), EpisodeCount = GetCount(typeof(Episode), user, isFavorite), MovieCount = GetCount(typeof(Movie), user, isFavorite), SeriesCount = GetCount(typeof(Series), user, isFavorite), SongCount = GetCount(typeof(Audio), user, isFavorite), MusicVideoCount = GetCount(typeof(MusicVideo), user, isFavorite), BoxSetCount = GetCount(typeof(BoxSet), user, isFavorite), BookCount = GetCount(typeof(Book), user, isFavorite) }; return counts; } /// /// Gets all parents of an item. /// /// The item id. /// Optional. Filter by user id, and attach user data. /// Item parents returned. /// Item not found. /// Item parents. [HttpGet("/Items/{itemId}/Ancestors")] [Authorize(Policy = Policies.DefaultAuthorization)] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] public ActionResult> GetAncestors([FromRoute] Guid itemId, [FromQuery] Guid userId) { var item = _libraryManager.GetItemById(itemId); if (item == null) { return NotFound("Item not found"); } var baseItemDtos = new List(); var user = !userId.Equals(Guid.Empty) ? _userManager.GetUserById(userId) : null; var dtoOptions = new DtoOptions().AddClientFields(Request); BaseItem parent = item.GetParent(); while (parent != null) { if (user != null) { parent = TranslateParentItem(parent, user); } baseItemDtos.Add(_dtoService.GetBaseItemDto(parent, dtoOptions, user)); parent = parent.GetParent(); } return baseItemDtos; } /// /// Gets a list of physical paths from virtual folders. /// /// Physical paths returned. /// List of physical paths. [HttpGet("/Library/PhysicalPaths")] [Authorize(Policy = Policies.RequiresElevation)] [ProducesResponseType(StatusCodes.Status200OK)] public ActionResult> GetPhysicalPaths() { return Ok(_libraryManager.RootFolder.Children .SelectMany(c => c.PhysicalLocations)); } /// /// Gets all user media folders. /// /// Optional. Filter by folders that are marked hidden, or not. /// Media folders returned. /// List of user media folders. [HttpGet("/Library/MediaFolders")] [Authorize(Policy = Policies.DefaultAuthorization)] [ProducesResponseType(StatusCodes.Status200OK)] public ActionResult> GetMediaFolders([FromQuery] bool? isHidden) { var items = _libraryManager.GetUserRootFolder().Children.Concat(_libraryManager.RootFolder.VirtualChildren).OrderBy(i => i.SortName).ToList(); if (isHidden.HasValue) { var val = isHidden.Value; items = items.Where(i => i.IsHidden == val).ToList(); } var dtoOptions = new DtoOptions().AddClientFields(Request); var result = new QueryResult { TotalRecordCount = items.Count, Items = items.Select(i => _dtoService.GetBaseItemDto(i, dtoOptions)).ToArray() }; return result; } /// /// Reports that new episodes of a series have been added by an external source. /// /// The tvdbId. /// Report success. /// A . [HttpPost("/Library/Series/Added")] [HttpPost("/Library/Series/Updated")] [Authorize(Policy = Policies.DefaultAuthorization)] [ProducesResponseType(StatusCodes.Status204NoContent)] public ActionResult PostUpdatedSeries([FromQuery] string tvdbId) { var series = _libraryManager.GetItemList(new InternalItemsQuery { IncludeItemTypes = new[] { nameof(Series) }, DtoOptions = new DtoOptions(false) { EnableImages = false } }).Where(i => string.Equals(tvdbId, i.GetProviderId(MediaBrowser.Model.Entities.MetadataProvider.Tvdb), StringComparison.OrdinalIgnoreCase)).ToArray(); foreach (var item in series) { _libraryMonitor.ReportFileSystemChanged(item.Path); } return NoContent(); } /// /// Reports that new movies have been added by an external source. /// /// The tmdbId. /// The imdbId. /// Report success. /// A . [HttpPost("/Library/Movies/Added")] [HttpPost("/Library/Movies/Updated")] [Authorize(Policy = Policies.DefaultAuthorization)] [ProducesResponseType(StatusCodes.Status204NoContent)] public ActionResult PostUpdatedMovies([FromRoute] string tmdbId, [FromRoute] string imdbId) { var movies = _libraryManager.GetItemList(new InternalItemsQuery { IncludeItemTypes = new[] { nameof(Movie) }, DtoOptions = new DtoOptions(false) { EnableImages = false } }); if (!string.IsNullOrWhiteSpace(imdbId)) { movies = movies.Where(i => string.Equals(imdbId, i.GetProviderId(MediaBrowser.Model.Entities.MetadataProvider.Imdb), StringComparison.OrdinalIgnoreCase)).ToList(); } else if (!string.IsNullOrWhiteSpace(tmdbId)) { movies = movies.Where(i => string.Equals(tmdbId, i.GetProviderId(MediaBrowser.Model.Entities.MetadataProvider.Tmdb), StringComparison.OrdinalIgnoreCase)).ToList(); } else { movies = new List(); } foreach (var item in movies) { _libraryMonitor.ReportFileSystemChanged(item.Path); } return NoContent(); } /// /// Reports that new movies have been added by an external source. /// /// A list of updated media paths. /// Report success. /// A . [HttpPost("/Library/Media/Updated")] [Authorize(Policy = Policies.DefaultAuthorization)] [ProducesResponseType(StatusCodes.Status204NoContent)] public ActionResult PostUpdatedMedia([FromBody, BindRequired] MediaUpdateInfoDto[] updates) { foreach (var item in updates) { _libraryMonitor.ReportFileSystemChanged(item.Path); } return NoContent(); } /// /// Downloads item media. /// /// The item id. /// Media downloaded. /// Item not found. /// A containing the media stream. /// User can't download or item can't be downloaded. [HttpGet("/Items/{itemId}/Download")] [Authorize(Policy = Policies.Download)] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] public ActionResult GetDownload([FromRoute] Guid itemId) { var item = _libraryManager.GetItemById(itemId); if (item == null) { return NotFound(); } var auth = _authContext.GetAuthorizationInfo(Request); var user = auth.User; if (user != null) { if (!item.CanDownload(user)) { throw new ArgumentException("Item does not support downloading"); } } else { if (!item.CanDownload()) { throw new ArgumentException("Item does not support downloading"); } } if (user != null) { LogDownload(item, user, auth); } var path = item.Path; // Quotes are valid in linux. They'll possibly cause issues here var filename = (Path.GetFileName(path) ?? string.Empty).Replace("\"", string.Empty, StringComparison.Ordinal); if (!string.IsNullOrWhiteSpace(filename)) { // Kestrel doesn't support non-ASCII characters in headers if (Regex.IsMatch(filename, @"[^\p{IsBasicLatin}]")) { // Manually encoding non-ASCII characters, following https://tools.ietf.org/html/rfc5987#section-3.2.2 filename = WebUtility.UrlEncode(filename); } } // TODO determine non-ASCII validity. using var fileStream = new FileStream(path, FileMode.Open, FileAccess.Read); return File(fileStream, MimeTypes.GetMimeType(path), filename); } /// /// Gets similar items. /// /// The item id. /// Exclude artist ids. /// (Unused) Optional. include image information in output. /// (Unused) Optional. include user data. /// (Unused) Optional. the max number of images to return, per image type. /// (Unused) Optional. The image types to include in the output. /// Optional. Filter by user id, and attach user data. /// Optional. The maximum number of records to return. /// Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines, TrailerUrls. /// Similar items returned. /// A containing the similar items. [HttpGet("/Artists/{itemId}/Similar")] [HttpGet("/Items/{itemId}/Similar")] [HttpGet("/Albums/{itemId}/Similar")] [HttpGet("/Shows/{itemId}/Similar")] [HttpGet("/Movies/{itemId}/Similar")] [HttpGet("/Trailers/{itemId}/Similar")] [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "enableImages", Justification = "Imported from ServiceStack")] [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "enableUserData", Justification = "Imported from ServiceStack")] [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "imageTypeLimit", Justification = "Imported from ServiceStack")] [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "enableImageTypes", Justification = "Imported from ServiceStack")] [ProducesResponseType(StatusCodes.Status200OK)] public ActionResult> GetSimilarItems( [FromRoute] Guid itemId, [FromQuery] string excludeArtistIds, [FromQuery] bool? enableImages, [FromQuery] bool? enableUserData, [FromQuery] int? imageTypeLimit, [FromQuery] string enableImageTypes, [FromQuery] Guid userId, [FromQuery] int? limit, [FromQuery] string fields) { var item = itemId.Equals(Guid.Empty) ? (!userId.Equals(Guid.Empty) ? _libraryManager.GetUserRootFolder() : _libraryManager.RootFolder) : _libraryManager.GetItemById(itemId); var program = item as IHasProgramAttributes; var isMovie = item is MediaBrowser.Controller.Entities.Movies.Movie || (program != null && program.IsMovie) || item is Trailer; if (program != null && program.IsSeries) { return GetSimilarItemsResult( item, excludeArtistIds, userId, limit, fields, new[] { nameof(Series) }, false); } if (item is MediaBrowser.Controller.Entities.TV.Episode || (item is IItemByName && !(item is MusicArtist))) { return new QueryResult(); } return GetSimilarItemsResult( item, excludeArtistIds, userId, limit, fields, new[] { item.GetType().Name }, isMovie); } /// /// Gets the library options info. /// /// Library content type. /// Whether this is a new library. /// Library options info returned. /// Library options info. [HttpGet("/Libraries/AvailableOptions")] [Authorize(Policy = Policies.FirstTimeSetupOrElevated)] [ProducesResponseType(StatusCodes.Status200OK)] public ActionResult GetLibraryOptionsInfo([FromQuery] string libraryContentType, [FromQuery] bool isNewLibrary) { var result = new LibraryOptionsResultDto(); var types = GetRepresentativeItemTypes(libraryContentType); var typesList = types.ToList(); var plugins = _providerManager.GetAllMetadataPlugins() .Where(i => types.Contains(i.ItemType, StringComparer.OrdinalIgnoreCase)) .OrderBy(i => typesList.IndexOf(i.ItemType)) .ToList(); result.MetadataSavers = plugins .SelectMany(i => i.Plugins.Where(p => p.Type == MetadataPluginType.MetadataSaver)) .Select(i => new LibraryOptionInfoDto { Name = i.Name, DefaultEnabled = IsSaverEnabledByDefault(i.Name, types, isNewLibrary) }) .GroupBy(i => i.Name, StringComparer.OrdinalIgnoreCase) .Select(x => x.First()) .ToArray(); result.MetadataReaders = plugins .SelectMany(i => i.Plugins.Where(p => p.Type == MetadataPluginType.LocalMetadataProvider)) .Select(i => new LibraryOptionInfoDto { Name = i.Name, DefaultEnabled = true }) .GroupBy(i => i.Name, StringComparer.OrdinalIgnoreCase) .Select(x => x.First()) .ToArray(); result.SubtitleFetchers = plugins .SelectMany(i => i.Plugins.Where(p => p.Type == MetadataPluginType.SubtitleFetcher)) .Select(i => new LibraryOptionInfoDto { Name = i.Name, DefaultEnabled = true }) .GroupBy(i => i.Name, StringComparer.OrdinalIgnoreCase) .Select(x => x.First()) .ToArray(); var typeOptions = new List(); foreach (var type in types) { TypeOptions.DefaultImageOptions.TryGetValue(type, out var defaultImageOptions); typeOptions.Add(new LibraryTypeOptionsDto { Type = type, MetadataFetchers = plugins .Where(i => string.Equals(i.ItemType, type, StringComparison.OrdinalIgnoreCase)) .SelectMany(i => i.Plugins.Where(p => p.Type == MetadataPluginType.MetadataFetcher)) .Select(i => new LibraryOptionInfoDto { Name = i.Name, DefaultEnabled = IsMetadataFetcherEnabledByDefault(i.Name, type, isNewLibrary) }) .GroupBy(i => i.Name, StringComparer.OrdinalIgnoreCase) .Select(x => x.First()) .ToArray(), ImageFetchers = plugins .Where(i => string.Equals(i.ItemType, type, StringComparison.OrdinalIgnoreCase)) .SelectMany(i => i.Plugins.Where(p => p.Type == MetadataPluginType.ImageFetcher)) .Select(i => new LibraryOptionInfoDto { Name = i.Name, DefaultEnabled = IsImageFetcherEnabledByDefault(i.Name, type, isNewLibrary) }) .GroupBy(i => i.Name, StringComparer.OrdinalIgnoreCase) .Select(x => x.First()) .ToArray(), SupportedImageTypes = plugins .Where(i => string.Equals(i.ItemType, type, StringComparison.OrdinalIgnoreCase)) .SelectMany(i => i.SupportedImageTypes ?? Array.Empty()) .Distinct() .ToArray(), DefaultImageOptions = defaultImageOptions ?? Array.Empty() }); } result.TypeOptions = typeOptions.ToArray(); return result; } private int GetCount(Type type, User? user, bool? isFavorite) { var query = new InternalItemsQuery(user) { IncludeItemTypes = new[] { type.Name }, Limit = 0, Recursive = true, IsVirtualItem = false, IsFavorite = isFavorite, DtoOptions = new DtoOptions(false) { EnableImages = false } }; return _libraryManager.GetItemsResult(query).TotalRecordCount; } private BaseItem TranslateParentItem(BaseItem item, User user) { return item.GetParent() is AggregateFolder ? _libraryManager.GetUserRootFolder().GetChildren(user, true) .FirstOrDefault(i => i.PhysicalLocations.Contains(item.Path)) : item; } private void LogDownload(BaseItem item, User user, AuthorizationInfo auth) { try { _activityManager.Create(new ActivityLog( string.Format(CultureInfo.InvariantCulture, _localization.GetLocalizedString("UserDownloadingItemWithValues"), user.Username, item.Name), "UserDownloadingContent", auth.UserId) { ShortOverview = string.Format(CultureInfo.InvariantCulture, _localization.GetLocalizedString("AppDeviceValues"), auth.Client, auth.Device), }); } catch { // Logged at lower levels } } private QueryResult GetSimilarItemsResult( BaseItem item, string excludeArtistIds, Guid userId, int? limit, string fields, string[] includeItemTypes, bool isMovie) { var user = !userId.Equals(Guid.Empty) ? _userManager.GetUserById(userId) : null; var dtoOptions = new DtoOptions() .AddItemFields(fields) .AddClientFields(Request); var query = new InternalItemsQuery(user) { Limit = limit, IncludeItemTypes = includeItemTypes, IsMovie = isMovie, SimilarTo = item, DtoOptions = dtoOptions, EnableTotalRecordCount = !isMovie, EnableGroupByMetadataKey = isMovie }; // ExcludeArtistIds if (!string.IsNullOrEmpty(excludeArtistIds)) { query.ExcludeArtistIds = RequestHelpers.GetGuids(excludeArtistIds); } List itemsResult; if (isMovie) { var itemTypes = new List { nameof(MediaBrowser.Controller.Entities.Movies.Movie) }; if (_serverConfigurationManager.Configuration.EnableExternalContentInSuggestions) { itemTypes.Add(nameof(Trailer)); itemTypes.Add(nameof(LiveTvProgram)); } query.IncludeItemTypes = itemTypes.ToArray(); itemsResult = _libraryManager.GetArtists(query).Items.Select(i => i.Item1).ToList(); } else if (item is MusicArtist) { query.IncludeItemTypes = Array.Empty(); itemsResult = _libraryManager.GetArtists(query).Items.Select(i => i.Item1).ToList(); } else { itemsResult = _libraryManager.GetItemList(query); } var returnList = _dtoService.GetBaseItemDtos(itemsResult, dtoOptions, user); var result = new QueryResult { Items = returnList, TotalRecordCount = itemsResult.Count }; return result; } private static string[] GetRepresentativeItemTypes(string contentType) { return contentType switch { CollectionType.BoxSets => new[] { "BoxSet" }, CollectionType.Playlists => new[] { "Playlist" }, CollectionType.Movies => new[] { "Movie" }, CollectionType.TvShows => new[] { "Series", "Season", "Episode" }, CollectionType.Books => new[] { "Book" }, CollectionType.Music => new[] { "MusicArtist", "MusicAlbum", "Audio", "MusicVideo" }, CollectionType.HomeVideos => new[] { "Video", "Photo" }, CollectionType.Photos => new[] { "Video", "Photo" }, CollectionType.MusicVideos => new[] { "MusicVideo" }, _ => new[] { "Series", "Season", "Episode", "Movie" } }; } private bool IsSaverEnabledByDefault(string name, string[] itemTypes, bool isNewLibrary) { if (isNewLibrary) { return false; } var metadataOptions = _serverConfigurationManager.Configuration.MetadataOptions .Where(i => itemTypes.Contains(i.ItemType ?? string.Empty, StringComparer.OrdinalIgnoreCase)) .ToArray(); return metadataOptions.Length == 0 || metadataOptions.Any(i => !i.DisabledMetadataSavers.Contains(name, StringComparer.OrdinalIgnoreCase)); } private bool IsMetadataFetcherEnabledByDefault(string name, string type, bool isNewLibrary) { if (isNewLibrary) { if (string.Equals(name, "TheMovieDb", StringComparison.OrdinalIgnoreCase)) { return !(string.Equals(type, "Season", StringComparison.OrdinalIgnoreCase) || string.Equals(type, "Episode", StringComparison.OrdinalIgnoreCase) || string.Equals(type, "MusicVideo", StringComparison.OrdinalIgnoreCase)); } return string.Equals(name, "TheTVDB", StringComparison.OrdinalIgnoreCase) || string.Equals(name, "TheAudioDB", StringComparison.OrdinalIgnoreCase) || string.Equals(name, "MusicBrainz", StringComparison.OrdinalIgnoreCase); } var metadataOptions = _serverConfigurationManager.Configuration.MetadataOptions .Where(i => string.Equals(i.ItemType, type, StringComparison.OrdinalIgnoreCase)) .ToArray(); return metadataOptions.Length == 0 || metadataOptions.Any(i => !i.DisabledMetadataFetchers.Contains(name, StringComparer.OrdinalIgnoreCase)); } private bool IsImageFetcherEnabledByDefault(string name, string type, bool isNewLibrary) { if (isNewLibrary) { if (string.Equals(name, "TheMovieDb", StringComparison.OrdinalIgnoreCase)) { return !string.Equals(type, "Series", StringComparison.OrdinalIgnoreCase) && !string.Equals(type, "Season", StringComparison.OrdinalIgnoreCase) && !string.Equals(type, "Episode", StringComparison.OrdinalIgnoreCase) && !string.Equals(type, "MusicVideo", StringComparison.OrdinalIgnoreCase); } return string.Equals(name, "TheTVDB", StringComparison.OrdinalIgnoreCase) || string.Equals(name, "Screen Grabber", StringComparison.OrdinalIgnoreCase) || string.Equals(name, "TheAudioDB", StringComparison.OrdinalIgnoreCase) || string.Equals(name, "Image Extractor", StringComparison.OrdinalIgnoreCase); } var metadataOptions = _serverConfigurationManager.Configuration.MetadataOptions .Where(i => string.Equals(i.ItemType, type, StringComparison.OrdinalIgnoreCase)) .ToArray(); if (metadataOptions.Length == 0) { return true; } return metadataOptions.Any(i => !i.DisabledImageFetchers.Contains(name, StringComparer.OrdinalIgnoreCase)); } } }