Prechádzať zdrojové kódy

Merge branch 'jellyfin:master' into master

Negulici-R. Barnabas 2 rokov pred
rodič
commit
1e41636e30

+ 2 - 0
CONTRIBUTORS.md

@@ -157,6 +157,7 @@
  - [jonas-resch](https://github.com/jonas-resch)
  - [vgambier](https://github.com/vgambier)
  - [MinecraftPlaye](https://github.com/MinecraftPlaye)
+ - [RealGreenDragon](https://github.com/RealGreenDragon)
 
 # Emby Contributors
 
@@ -225,3 +226,4 @@
  - [gnuyent](https://github.com/gnuyent)
  - [Matthew Jones](https://github.com/matthew-jones-uk)
  - [Jakob Kukla](https://github.com/jakobkukla)
+ - [Utku Özdemir](https://github.com/utkuozdemir)

+ 3 - 1
Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs

@@ -464,7 +464,9 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
             var result = ResolveVideos<T>(parent, fileSystemEntries, SupportsMultiVersion, collectionType, parseName) ??
                 new MultiItemResolverResult();
 
-            if (result.Items.Count == 1)
+            var isPhotosCollection = string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase)
+                                         || string.Equals(collectionType, CollectionType.Photos, StringComparison.OrdinalIgnoreCase);
+            if (!isPhotosCollection && result.Items.Count == 1)
             {
                 var videoPath = result.Items[0].Path;
                 var hasPhotos = photos.Any(i => !PhotoResolver.IsOwnedByResolvedMedia(videoPath, i.Name));

+ 4 - 1
Emby.Server.Implementations/Localization/Core/ko.json

@@ -120,5 +120,8 @@
     "Forced": "강제하기",
     "Default": "기본 설정",
     "TaskOptimizeDatabaseDescription": "데이터베이스를 압축하고 사용 가능한 공간을 늘립니다. 라이브러리를 검색한 후 이 작업을 실행하거나 데이터베이스 수정같은 비슷한 작업을 수행하면 성능이 향상될 수 있습니다.",
-    "TaskOptimizeDatabase": "데이터베이스 최적화"
+    "TaskOptimizeDatabase": "데이터베이스 최적화",
+    "TaskKeyframeExtractorDescription": "비디오 파일에서 키프레임을 추출하여 더 정확한 HLS 재생 목록을 만듭니다. 이 작업은 오랫동안 진행될 수 있습니다.",
+    "TaskKeyframeExtractor": "키프레임 추출",
+    "External": "외부"
 }

+ 5 - 5
Emby.Server.Implementations/TV/TVSeriesManager.cs

@@ -43,9 +43,9 @@ namespace Emby.Server.Implementations.TV
             }
 
             string presentationUniqueKey = null;
-            if (!string.IsNullOrEmpty(query.SeriesId))
+            if (query.SeriesId.HasValue && !query.SeriesId.Value.Equals(default))
             {
-                if (_libraryManager.GetItemById(query.SeriesId) is Series series)
+                if (_libraryManager.GetItemById(query.SeriesId.Value) is Series series)
                 {
                     presentationUniqueKey = GetUniqueSeriesKey(series);
                 }
@@ -93,9 +93,9 @@ namespace Emby.Server.Implementations.TV
 
             string presentationUniqueKey = null;
             int? limit = null;
-            if (!string.IsNullOrEmpty(request.SeriesId))
+            if (request.SeriesId.HasValue && !request.SeriesId.Value.Equals(default))
             {
-                if (_libraryManager.GetItemById(request.SeriesId) is Series series)
+                if (_libraryManager.GetItemById(request.SeriesId.Value) is Series series)
                 {
                     presentationUniqueKey = GetUniqueSeriesKey(series);
                     limit = 1;
@@ -153,7 +153,7 @@ namespace Emby.Server.Implementations.TV
 
             // If viewing all next up for all series, remove first episodes
             // But if that returns empty, keep those first episodes (avoid completely empty view)
-            var alwaysEnableFirstEpisode = !string.IsNullOrEmpty(request.SeriesId);
+            var alwaysEnableFirstEpisode = request.SeriesId.HasValue && !request.SeriesId.Value.Equals(default);
             var anyFound = false;
 
             return allNextUp

+ 42 - 15
Jellyfin.Api/Controllers/ItemsController.cs

@@ -1,6 +1,7 @@
 using System;
 using System.ComponentModel.DataAnnotations;
 using System.Linq;
+using System.Threading.Tasks;
 using Jellyfin.Api.Constants;
 using Jellyfin.Api.Extensions;
 using Jellyfin.Api.Helpers;
@@ -9,6 +10,7 @@ using Jellyfin.Data.Enums;
 using MediaBrowser.Controller.Dto;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Net;
 using MediaBrowser.Controller.Session;
 using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Entities;
@@ -32,6 +34,7 @@ namespace Jellyfin.Api.Controllers
         private readonly ILibraryManager _libraryManager;
         private readonly ILocalizationManager _localization;
         private readonly IDtoService _dtoService;
+        private readonly IAuthorizationContext _authContext;
         private readonly ILogger<ItemsController> _logger;
         private readonly ISessionManager _sessionManager;
 
@@ -42,6 +45,7 @@ namespace Jellyfin.Api.Controllers
         /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
         /// <param name="localization">Instance of the <see cref="ILocalizationManager"/> interface.</param>
         /// <param name="dtoService">Instance of the <see cref="IDtoService"/> interface.</param>
+        /// <param name="authContext">Instance of the <see cref="IAuthorizationContext"/> interface.</param>
         /// <param name="logger">Instance of the <see cref="ILogger"/> interface.</param>
         /// <param name="sessionManager">Instance of the <see cref="ISessionManager"/> interface.</param>
         public ItemsController(
@@ -49,6 +53,7 @@ namespace Jellyfin.Api.Controllers
             ILibraryManager libraryManager,
             ILocalizationManager localization,
             IDtoService dtoService,
+            IAuthorizationContext authContext,
             ILogger<ItemsController> logger,
             ISessionManager sessionManager)
         {
@@ -56,6 +61,7 @@ namespace Jellyfin.Api.Controllers
             _libraryManager = libraryManager;
             _localization = localization;
             _dtoService = dtoService;
+            _authContext = authContext;
             _logger = logger;
             _sessionManager = sessionManager;
         }
@@ -63,7 +69,7 @@ namespace Jellyfin.Api.Controllers
         /// <summary>
         /// Gets items based on a query.
         /// </summary>
-        /// <param name="userId">The user id supplied as query parameter.</param>
+        /// <param name="userId">The user id supplied as query parameter; this is required when not using an API key.</param>
         /// <param name="maxOfficialRating">Optional filter by maximum official rating (PG, PG-13, TV-MA, etc).</param>
         /// <param name="hasThemeSong">Optional filter by items with theme songs.</param>
         /// <param name="hasThemeVideo">Optional filter by items with theme videos.</param>
@@ -151,15 +157,15 @@ namespace Jellyfin.Api.Controllers
         /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the items.</returns>
         [HttpGet("Items")]
         [ProducesResponseType(StatusCodes.Status200OK)]
-        public ActionResult<QueryResult<BaseItemDto>> GetItems(
-            [FromQuery] Guid userId,
+        public async Task<ActionResult<QueryResult<BaseItemDto>>> GetItems(
+            [FromQuery] Guid? userId,
             [FromQuery] string? maxOfficialRating,
             [FromQuery] bool? hasThemeSong,
             [FromQuery] bool? hasThemeVideo,
             [FromQuery] bool? hasSubtitles,
             [FromQuery] bool? hasSpecialFeature,
             [FromQuery] bool? hasTrailer,
-            [FromQuery] string? adjacentTo,
+            [FromQuery] Guid? adjacentTo,
             [FromQuery] int? parentIndexNumber,
             [FromQuery] bool? hasParentalRating,
             [FromQuery] bool? isHd,
@@ -238,7 +244,19 @@ namespace Jellyfin.Api.Controllers
             [FromQuery] bool enableTotalRecordCount = true,
             [FromQuery] bool? enableImages = true)
         {
-            var user = userId.Equals(default) ? null : _userManager.GetUserById(userId);
+            var auth = await _authContext.GetAuthorizationInfo(Request).ConfigureAwait(false);
+
+            // if api key is used (auth.IsApiKey == true), then `user` will be null throughout this method
+            var user = !auth.IsApiKey && userId.HasValue && !userId.Value.Equals(default)
+                ? _userManager.GetUserById(userId.Value)
+                : null;
+
+            // beyond this point, we're either using an api key or we have a valid user
+            if (!auth.IsApiKey && user is null)
+            {
+                return BadRequest("userId is required");
+            }
+
             var dtoOptions = new DtoOptions { Fields = fields }
                 .AddClientFields(Request)
                 .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
@@ -270,30 +288,39 @@ namespace Jellyfin.Api.Controllers
                 includeItemTypes = new[] { BaseItemKind.Playlist };
             }
 
-            var enabledChannels = user!.GetPreferenceValues<Guid>(PreferenceKind.EnabledChannels);
+            var enabledChannels = auth.IsApiKey
+                ? Array.Empty<Guid>()
+                : user!.GetPreferenceValues<Guid>(PreferenceKind.EnabledChannels);
 
-            bool isInEnabledFolder = Array.IndexOf(user.GetPreferenceValues<Guid>(PreferenceKind.EnabledFolders), item.Id) != -1
+            // api keys are always enabled for all folders
+            bool isInEnabledFolder = auth.IsApiKey
+                                     || Array.IndexOf(user!.GetPreferenceValues<Guid>(PreferenceKind.EnabledFolders), item.Id) != -1
                                      // Assume all folders inside an EnabledChannel are enabled
                                      || Array.IndexOf(enabledChannels, item.Id) != -1
                                      // Assume all items inside an EnabledChannel are enabled
                                      || Array.IndexOf(enabledChannels, item.ChannelId) != -1;
 
-            var collectionFolders = _libraryManager.GetCollectionFolders(item);
-            foreach (var collectionFolder in collectionFolders)
+            if (!isInEnabledFolder)
             {
-                if (user.GetPreferenceValues<Guid>(PreferenceKind.EnabledFolders).Contains(collectionFolder.Id))
+                var collectionFolders = _libraryManager.GetCollectionFolders(item);
+                foreach (var collectionFolder in collectionFolders)
                 {
-                    isInEnabledFolder = true;
+                    // api keys never enter this block, so user is never null
+                    if (user!.GetPreferenceValues<Guid>(PreferenceKind.EnabledFolders).Contains(collectionFolder.Id))
+                    {
+                        isInEnabledFolder = true;
+                    }
                 }
             }
 
+            // api keys are always enabled for all folders, so user is never null
             if (item is not UserRootFolder
                 && !isInEnabledFolder
-                && !user.HasPermission(PermissionKind.EnableAllFolders)
+                && !user!.HasPermission(PermissionKind.EnableAllFolders)
                 && !user.HasPermission(PermissionKind.EnableAllChannels)
                 && !string.Equals(collectionType, CollectionType.Folders, StringComparison.OrdinalIgnoreCase))
             {
-                _logger.LogWarning("{UserName} is not permitted to access Library {ItemName}.", user.Username, item.Name);
+                _logger.LogWarning("{UserName} is not permitted to access Library {ItemName}", user.Username, item.Name);
                 return Unauthorized($"{user.Username} is not permitted to access Library {item.Name}.");
             }
 
@@ -606,7 +633,7 @@ namespace Jellyfin.Api.Controllers
         /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the items.</returns>
         [HttpGet("Users/{userId}/Items")]
         [ProducesResponseType(StatusCodes.Status200OK)]
-        public ActionResult<QueryResult<BaseItemDto>> GetItemsByUserId(
+        public Task<ActionResult<QueryResult<BaseItemDto>>> GetItemsByUserId(
             [FromRoute] Guid userId,
             [FromQuery] string? maxOfficialRating,
             [FromQuery] bool? hasThemeSong,
@@ -614,7 +641,7 @@ namespace Jellyfin.Api.Controllers
             [FromQuery] bool? hasSubtitles,
             [FromQuery] bool? hasSpecialFeature,
             [FromQuery] bool? hasTrailer,
-            [FromQuery] string? adjacentTo,
+            [FromQuery] Guid? adjacentTo,
             [FromQuery] int? parentIndexNumber,
             [FromQuery] bool? hasParentalRating,
             [FromQuery] bool? isHd,

+ 5 - 3
Jellyfin.Api/Controllers/SearchController.cs

@@ -79,7 +79,7 @@ namespace Jellyfin.Api.Controllers
         [HttpGet]
         [Description("Gets search hints based on a search term")]
         [ProducesResponseType(StatusCodes.Status200OK)]
-        public ActionResult<SearchHintResult> Get(
+        public ActionResult<SearchHintResult> GetSearchHints(
             [FromQuery] int? startIndex,
             [FromQuery] int? limit,
             [FromQuery] Guid? userId,
@@ -140,7 +140,7 @@ namespace Jellyfin.Api.Controllers
                 IndexNumber = item.IndexNumber,
                 ParentIndexNumber = item.ParentIndexNumber,
                 Id = item.Id,
-                Type = item.GetClientTypeName(),
+                Type = item.GetBaseItemKind(),
                 MediaType = item.MediaType,
                 MatchedTerm = hintInfo.MatchedTerm,
                 RunTimeTicks = item.RunTimeTicks,
@@ -149,8 +149,10 @@ namespace Jellyfin.Api.Controllers
                 EndDate = item.EndDate
             };
 
-            // legacy
+#pragma warning disable CS0618
+            // Kept for compatibility with older clients
             result.ItemId = result.Id;
+#pragma warning restore CS0618
 
             if (item.IsFolder)
             {

+ 5 - 4
Jellyfin.Api/Controllers/TrailersController.cs

@@ -1,4 +1,5 @@
 using System;
+using System.Threading.Tasks;
 using Jellyfin.Api.Constants;
 using Jellyfin.Api.ModelBinders;
 using Jellyfin.Data.Enums;
@@ -31,7 +32,7 @@ namespace Jellyfin.Api.Controllers
         /// <summary>
         /// Finds movies and trailers similar to a given trailer.
         /// </summary>
-        /// <param name="userId">The user id.</param>
+        /// <param name="userId">The user id supplied as query parameter; this is required when not using an API key.</param>
         /// <param name="maxOfficialRating">Optional filter by maximum official rating (PG, PG-13, TV-MA, etc).</param>
         /// <param name="hasThemeSong">Optional filter by items with theme songs.</param>
         /// <param name="hasThemeVideo">Optional filter by items with theme videos.</param>
@@ -118,15 +119,15 @@ namespace Jellyfin.Api.Controllers
         /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the trailers.</returns>
         [HttpGet]
         [ProducesResponseType(StatusCodes.Status200OK)]
-        public ActionResult<QueryResult<BaseItemDto>> GetTrailers(
-            [FromQuery] Guid userId,
+        public Task<ActionResult<QueryResult<BaseItemDto>>> GetTrailers(
+            [FromQuery] Guid? userId,
             [FromQuery] string? maxOfficialRating,
             [FromQuery] bool? hasThemeSong,
             [FromQuery] bool? hasThemeVideo,
             [FromQuery] bool? hasSubtitles,
             [FromQuery] bool? hasSpecialFeature,
             [FromQuery] bool? hasTrailer,
-            [FromQuery] string? adjacentTo,
+            [FromQuery] Guid? adjacentTo,
             [FromQuery] int? parentIndexNumber,
             [FromQuery] bool? hasParentalRating,
             [FromQuery] bool? isHd,

+ 5 - 5
Jellyfin.Api/Controllers/TvShowsController.cs

@@ -77,7 +77,7 @@ namespace Jellyfin.Api.Controllers
             [FromQuery] int? startIndex,
             [FromQuery] int? limit,
             [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
-            [FromQuery] string? seriesId,
+            [FromQuery] Guid? seriesId,
             [FromQuery] Guid? parentId,
             [FromQuery] bool? enableImages,
             [FromQuery] int? imageTypeLimit,
@@ -206,7 +206,7 @@ namespace Jellyfin.Api.Controllers
             [FromQuery] int? season,
             [FromQuery] Guid? seasonId,
             [FromQuery] bool? isMissing,
-            [FromQuery] string? adjacentTo,
+            [FromQuery] Guid? adjacentTo,
             [FromQuery] Guid? startItemId,
             [FromQuery] int? startIndex,
             [FromQuery] int? limit,
@@ -278,9 +278,9 @@ namespace Jellyfin.Api.Controllers
             }
 
             // This must be the last filter
-            if (!string.IsNullOrEmpty(adjacentTo))
+            if (adjacentTo.HasValue && !adjacentTo.Value.Equals(default))
             {
-                episodes = UserViewBuilder.FilterForAdjacency(episodes, adjacentTo).ToList();
+                episodes = UserViewBuilder.FilterForAdjacency(episodes, adjacentTo.Value).ToList();
             }
 
             if (string.Equals(sortBy, ItemSortBy.Random, StringComparison.OrdinalIgnoreCase))
@@ -326,7 +326,7 @@ namespace Jellyfin.Api.Controllers
             [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
             [FromQuery] bool? isSpecialSeason,
             [FromQuery] bool? isMissing,
-            [FromQuery] string? adjacentTo,
+            [FromQuery] Guid? adjacentTo,
             [FromQuery] bool? enableImages,
             [FromQuery] int? imageTypeLimit,
             [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes,

+ 3 - 3
MediaBrowser.Controller/Entities/Folder.cs

@@ -860,7 +860,7 @@ namespace MediaBrowser.Controller.Entities
                 return true;
             }
 
-            if (!string.IsNullOrEmpty(query.AdjacentTo))
+            if (query.AdjacentTo.HasValue && !query.AdjacentTo.Value.Equals(default))
             {
                 Logger.LogDebug("Query requires post-filtering due to AdjacentTo");
                 return true;
@@ -1029,9 +1029,9 @@ namespace MediaBrowser.Controller.Entities
             #pragma warning restore CA1309
 
             // This must be the last filter
-            if (!string.IsNullOrEmpty(query.AdjacentTo))
+            if (query.AdjacentTo.HasValue && !query.AdjacentTo.Value.Equals(default))
             {
-                items = UserViewBuilder.FilterForAdjacency(items.ToList(), query.AdjacentTo);
+                items = UserViewBuilder.FilterForAdjacency(items.ToList(), query.AdjacentTo.Value);
             }
 
             return UserViewBuilder.SortAndPage(items, null, query, LibraryManager, enableSorting);

+ 1 - 1
MediaBrowser.Controller/Entities/InternalItemsQuery.cs

@@ -129,7 +129,7 @@ namespace MediaBrowser.Controller.Entities
 
         public Guid[] ExcludeItemIds { get; set; }
 
-        public string? AdjacentTo { get; set; }
+        public Guid? AdjacentTo { get; set; }
 
         public string[] PersonTypes { get; set; }
 

+ 5 - 6
MediaBrowser.Controller/Entities/UserViewBuilder.cs

@@ -433,9 +433,9 @@ namespace MediaBrowser.Controller.Entities
             var user = query.User;
 
             // This must be the last filter
-            if (!string.IsNullOrEmpty(query.AdjacentTo))
+            if (query.AdjacentTo.HasValue && !query.AdjacentTo.Value.Equals(default))
             {
-                items = FilterForAdjacency(items.ToList(), query.AdjacentTo);
+                items = FilterForAdjacency(items.ToList(), query.AdjacentTo.Value);
             }
 
             return SortAndPage(items, totalRecordLimit, query, libraryManager, true);
@@ -985,10 +985,9 @@ namespace MediaBrowser.Controller.Entities
             return _userViewManager.GetUserSubView(parent.Id, type, localizationKey, sortName);
         }
 
-        public static IEnumerable<BaseItem> FilterForAdjacency(List<BaseItem> list, string adjacentToId)
+        public static IEnumerable<BaseItem> FilterForAdjacency(List<BaseItem> list, Guid adjacentTo)
         {
-            var adjacentToIdGuid = new Guid(adjacentToId);
-            var adjacentToItem = list.FirstOrDefault(i => i.Id.Equals(adjacentToIdGuid));
+            var adjacentToItem = list.FirstOrDefault(i => i.Id.Equals(adjacentTo));
 
             var index = list.IndexOf(adjacentToItem);
 
@@ -1005,7 +1004,7 @@ namespace MediaBrowser.Controller.Entities
                 nextId = list[index + 1].Id;
             }
 
-            return list.Where(i => i.Id.Equals(previousId) || i.Id.Equals(nextId) || i.Id.Equals(adjacentToIdGuid));
+            return list.Where(i => i.Id.Equals(previousId) || i.Id.Equals(nextId) || i.Id.Equals(adjacentTo));
         }
     }
 }

+ 1 - 1
MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs

@@ -574,7 +574,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
                     throw;
                 }
 
-                var ranToCompletion = await process.WaitForExitAsync(TimeSpan.FromMinutes(5)).ConfigureAwait(false);
+                var ranToCompletion = await process.WaitForExitAsync(TimeSpan.FromMinutes(30)).ConfigureAwait(false);
 
                 if (!ranToCompletion)
                 {

+ 1 - 1
MediaBrowser.Model/Querying/NextUpQuery.cs

@@ -33,7 +33,7 @@ namespace MediaBrowser.Model.Querying
         /// Gets or sets the series id.
         /// </summary>
         /// <value>The series id.</value>
-        public string SeriesId { get; set; }
+        public Guid? SeriesId { get; set; }
 
         /// <summary>
         /// Gets or sets the start index. Use for paging.

+ 50 - 16
MediaBrowser.Model/Search/SearchHint.cs

@@ -1,8 +1,6 @@
-#nullable disable
-#pragma warning disable CS1591
-
 using System;
 using System.Collections.Generic;
+using Jellyfin.Data.Enums;
 
 namespace MediaBrowser.Model.Search
 {
@@ -11,12 +9,28 @@ namespace MediaBrowser.Model.Search
     /// </summary>
     public class SearchHint
     {
+        /// <summary>
+        /// Initializes a new instance of the <see cref="SearchHint" /> class.
+        /// </summary>
+        public SearchHint()
+        {
+            Name = string.Empty;
+            MatchedTerm = string.Empty;
+            MediaType = string.Empty;
+            Artists = Array.Empty<string>();
+        }
+
         /// <summary>
         /// Gets or sets the item id.
         /// </summary>
         /// <value>The item id.</value>
+        [Obsolete("Use Id instead")]
         public Guid ItemId { get; set; }
 
+        /// <summary>
+        /// Gets or sets the item id.
+        /// </summary>
+        /// <value>The item id.</value>
         public Guid Id { get; set; }
 
         /// <summary>
@@ -53,38 +67,42 @@ namespace MediaBrowser.Model.Search
         /// Gets or sets the image tag.
         /// </summary>
         /// <value>The image tag.</value>
-        public string PrimaryImageTag { get; set; }
+        public string? PrimaryImageTag { get; set; }
 
         /// <summary>
         /// Gets or sets the thumb image tag.
         /// </summary>
         /// <value>The thumb image tag.</value>
-        public string ThumbImageTag { get; set; }
+        public string? ThumbImageTag { get; set; }
 
         /// <summary>
         /// Gets or sets the thumb image item identifier.
         /// </summary>
         /// <value>The thumb image item identifier.</value>
-        public string ThumbImageItemId { get; set; }
+        public string? ThumbImageItemId { get; set; }
 
         /// <summary>
         /// Gets or sets the backdrop image tag.
         /// </summary>
         /// <value>The backdrop image tag.</value>
-        public string BackdropImageTag { get; set; }
+        public string? BackdropImageTag { get; set; }
 
         /// <summary>
         /// Gets or sets the backdrop image item identifier.
         /// </summary>
         /// <value>The backdrop image item identifier.</value>
-        public string BackdropImageItemId { get; set; }
+        public string? BackdropImageItemId { get; set; }
 
         /// <summary>
         /// Gets or sets the type.
         /// </summary>
         /// <value>The type.</value>
-        public string Type { get; set; }
+        public BaseItemKind Type { get; set; }
 
+        /// <summary>
+        /// Gets a value indicating whether this instance is folder.
+        /// </summary>
+        /// <value><c>true</c> if this instance is folder; otherwise, <c>false</c>.</value>
         public bool? IsFolder { get; set; }
 
         /// <summary>
@@ -99,31 +117,47 @@ namespace MediaBrowser.Model.Search
         /// <value>The type of the media.</value>
         public string MediaType { get; set; }
 
+        /// <summary>
+        /// Gets or sets the start date.
+        /// </summary>
+        /// <value>The start date.</value>
         public DateTime? StartDate { get; set; }
 
+        /// <summary>
+        /// Gets or sets the end date.
+        /// </summary>
+        /// <value>The end date.</value>
         public DateTime? EndDate { get; set; }
 
         /// <summary>
         /// Gets or sets the series.
         /// </summary>
         /// <value>The series.</value>
-        public string Series { get; set; }
+        public string? Series { get; set; }
 
-        public string Status { get; set; }
+        /// <summary>
+        /// Gets or sets the status.
+        /// </summary>
+        /// <value>The status.</value>
+        public string? Status { get; set; }
 
         /// <summary>
         /// Gets or sets the album.
         /// </summary>
         /// <value>The album.</value>
-        public string Album { get; set; }
+        public string? Album { get; set; }
 
-        public Guid AlbumId { get; set; }
+        /// <summary>
+        /// Gets or sets the album id.
+        /// </summary>
+        /// <value>The album id.</value>
+        public Guid? AlbumId { get; set; }
 
         /// <summary>
         /// Gets or sets the album artist.
         /// </summary>
         /// <value>The album artist.</value>
-        public string AlbumArtist { get; set; }
+        public string? AlbumArtist { get; set; }
 
         /// <summary>
         /// Gets or sets the artists.
@@ -147,13 +181,13 @@ namespace MediaBrowser.Model.Search
         /// Gets or sets the channel identifier.
         /// </summary>
         /// <value>The channel identifier.</value>
-        public Guid ChannelId { get; set; }
+        public Guid? ChannelId { get; set; }
 
         /// <summary>
         /// Gets or sets the name of the channel.
         /// </summary>
         /// <value>The name of the channel.</value>
-        public string ChannelName { get; set; }
+        public string? ChannelName { get; set; }
 
         /// <summary>
         /// Gets or sets the primary image aspect ratio.