diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index 9c096916f..14723c0a7 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -291,6 +291,7 @@ namespace Emby.Server.Implementations.Data AddColumn(db, "TypedBaseItems", "Artists", "Text", existingColumnNames); AddColumn(db, "TypedBaseItems", "AlbumArtists", "Text", existingColumnNames); AddColumn(db, "TypedBaseItems", "ExternalId", "Text", existingColumnNames); + AddColumn(db, "TypedBaseItems", "SeriesPresentationUniqueKey", "Text", existingColumnNames); existingColumnNames = GetColumnNames(db, "ItemValues"); AddColumn(db, "ItemValues", "CleanValue", "Text", existingColumnNames); @@ -341,6 +342,7 @@ namespace Emby.Server.Implementations.Data "drop index if exists Idx_ProviderIds1", "drop table if exists Images", "drop index if exists idx_Images", + "drop index if exists idx_TypeSeriesPresentationUniqueKey", "create index if not exists idx_PathTypedBaseItems on TypedBaseItems(Path)", "create index if not exists idx_ParentIdTypedBaseItems on TypedBaseItems(ParentId)", @@ -353,6 +355,9 @@ namespace Emby.Server.Implementations.Data // covering index "create index if not exists idx_TopParentIdGuid on TypedBaseItems(TopParentId,Guid)", + // series + "create index if not exists idx_TypeSeriesPresentationUniqueKey1 on TypedBaseItems(Type,SeriesPresentationUniqueKey,PresentationUniqueKey,SortName)", + // live tv programs "create index if not exists idx_TypeTopParentIdStartDate on TypedBaseItems(Type,TopParentId,StartDate)", @@ -488,7 +493,8 @@ namespace Emby.Server.Implementations.Data "ExtraType", "Artists", "AlbumArtists", - "ExternalId" + "ExternalId", + "SeriesPresentationUniqueKey" }; private readonly string[] _mediaStreamSaveColumns = @@ -619,7 +625,8 @@ namespace Emby.Server.Implementations.Data "ExtraType", "Artists", "AlbumArtists", - "ExternalId" + "ExternalId", + "SeriesPresentationUniqueKey" }; var saveItemCommandCommandText = "replace into TypedBaseItems (" + string.Join(",", saveColumns.ToArray()) + ") values ("; @@ -1024,11 +1031,13 @@ namespace Emby.Server.Implementations.Data { saveItemStatement.TryBind("@SeriesId", hasSeries.SeriesId); saveItemStatement.TryBind("@SeriesSortName", hasSeries.SeriesSortName); + saveItemStatement.TryBind("@SeriesPresentationUniqueKey", hasSeries.SeriesPresentationUniqueKey); } else { saveItemStatement.TryBindNull("@SeriesId"); saveItemStatement.TryBindNull("@SeriesSortName"); + saveItemStatement.TryBindNull("@SeriesPresentationUniqueKey"); } saveItemStatement.TryBind("@ExternalSeriesId", item.ExternalSeriesId); @@ -1983,6 +1992,15 @@ namespace Emby.Server.Implementations.Data } index++; + if (hasSeries != null) + { + if (!reader.IsDBNull(index)) + { + hasSeries.SeriesPresentationUniqueKey = reader.GetString(index); + } + } + index++; + if (string.IsNullOrWhiteSpace(item.Tagline)) { var movie = item as Movie; @@ -4292,6 +4310,16 @@ namespace Emby.Server.Implementations.Data } } + if (!string.IsNullOrWhiteSpace(query.SeriesPresentationUniqueKey)) + { + whereClauses.Add("SeriesPresentationUniqueKey=@SeriesPresentationUniqueKey"); + + if (statement != null) + { + statement.TryBind("@SeriesPresentationUniqueKey", query.SeriesPresentationUniqueKey); + } + } + if (query.BlockUnratedItems.Length == 1) { whereClauses.Add("(InheritedParentalRatingValue > 0 or UnratedType <> @UnratedType)"); diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs index 8fb28fb59..d96756ce1 100644 --- a/Emby.Server.Implementations/Library/LibraryManager.cs +++ b/Emby.Server.Implementations/Library/LibraryManager.cs @@ -1057,6 +1057,12 @@ namespace Emby.Server.Implementations.Library try { await PerformLibraryValidation(progress, cancellationToken).ConfigureAwait(false); + + if (!ConfigurationManager.Configuration.EnableSeriesPresentationUniqueKey) + { + ConfigurationManager.Configuration.EnableSeriesPresentationUniqueKey = true; + ConfigurationManager.SaveConfiguration(); + } } finally { @@ -1478,8 +1484,9 @@ namespace Emby.Server.Implementations.Library !query.ParentId.HasValue && query.ChannelIds.Length == 0 && query.TopParentIds.Length == 0 && - string.IsNullOrWhiteSpace(query.AncestorWithPresentationUniqueKey) - && query.ItemIds.Length == 0) + string.IsNullOrWhiteSpace(query.AncestorWithPresentationUniqueKey) && + string.IsNullOrWhiteSpace(query.SeriesPresentationUniqueKey) && + query.ItemIds.Length == 0) { var userViews = _userviewManager().GetUserViews(new UserViewQuery { diff --git a/Emby.Server.Implementations/TV/TVSeriesManager.cs b/Emby.Server.Implementations/TV/TVSeriesManager.cs index 56e729995..4f876f6a3 100644 --- a/Emby.Server.Implementations/TV/TVSeriesManager.cs +++ b/Emby.Server.Implementations/TV/TVSeriesManager.cs @@ -158,9 +158,13 @@ namespace Emby.Server.Implementations.TV /// Task{Episode}. private Tuple> GetNextUp(Series series, User user) { + var enableSeriesPresentationKey = _config.Configuration.EnableSeriesPresentationUniqueKey; + var seriesKey = GetUniqueSeriesKey(series); + var lastWatchedEpisode = _libraryManager.GetItemList(new InternalItemsQuery(user) { - AncestorWithPresentationUniqueKey = GetUniqueSeriesKey(series), + AncestorWithPresentationUniqueKey = enableSeriesPresentationKey ? null : seriesKey, + SeriesPresentationUniqueKey = enableSeriesPresentationKey ? seriesKey : null, IncludeItemTypes = new[] { typeof(Episode).Name }, SortBy = new[] { ItemSortBy.SortName }, SortOrder = SortOrder.Descending, @@ -174,7 +178,8 @@ namespace Emby.Server.Implementations.TV { return _libraryManager.GetItemList(new InternalItemsQuery(user) { - AncestorWithPresentationUniqueKey = GetUniqueSeriesKey(series), + AncestorWithPresentationUniqueKey = enableSeriesPresentationKey ? null : seriesKey, + SeriesPresentationUniqueKey = enableSeriesPresentationKey ? seriesKey : null, IncludeItemTypes = new[] { typeof(Episode).Name }, SortBy = new[] { ItemSortBy.SortName }, SortOrder = SortOrder.Ascending, diff --git a/MediaBrowser.Api/StartupWizardService.cs b/MediaBrowser.Api/StartupWizardService.cs index 4e5047f78..25a460935 100644 --- a/MediaBrowser.Api/StartupWizardService.cs +++ b/MediaBrowser.Api/StartupWizardService.cs @@ -119,6 +119,7 @@ namespace MediaBrowser.Api config.SkipDeserializationForBasicTypes = true; config.SkipDeserializationForPrograms = true; config.SkipDeserializationForAudio = true; + config.EnableSeriesPresentationUniqueKey = true; } public void Post(UpdateStartupConfiguration request) diff --git a/MediaBrowser.Controller/Entities/Book.cs b/MediaBrowser.Controller/Entities/Book.cs index 9ccc7125a..a6da389f0 100644 --- a/MediaBrowser.Controller/Entities/Book.cs +++ b/MediaBrowser.Controller/Entities/Book.cs @@ -18,6 +18,8 @@ namespace MediaBrowser.Controller.Entities } } + [IgnoreDataMember] + public string SeriesPresentationUniqueKey { get; set; } [IgnoreDataMember] public string SeriesName { get; set; } [IgnoreDataMember] @@ -33,6 +35,10 @@ namespace MediaBrowser.Controller.Entities { return SeriesName; } + public string FindSeriesPresentationUniqueKey() + { + return SeriesPresentationUniqueKey; + } [IgnoreDataMember] public override bool EnableRefreshOnDateModifiedChange diff --git a/MediaBrowser.Controller/Entities/IHasSeries.cs b/MediaBrowser.Controller/Entities/IHasSeries.cs index 531f58788..203be93e8 100644 --- a/MediaBrowser.Controller/Entities/IHasSeries.cs +++ b/MediaBrowser.Controller/Entities/IHasSeries.cs @@ -15,5 +15,7 @@ namespace MediaBrowser.Controller.Entities string FindSeriesSortName(); Guid? SeriesId { get; set; } Guid? FindSeriesId(); + string SeriesPresentationUniqueKey { get; set; } + string FindSeriesPresentationUniqueKey(); } } diff --git a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs index 17ef81db9..a2d278a71 100644 --- a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs +++ b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs @@ -147,6 +147,7 @@ namespace MediaBrowser.Controller.Entities public string[] ArtistNames { get; set; } public string[] ExcludeArtistIds { get; set; } public string AncestorWithPresentationUniqueKey { get; set; } + public string SeriesPresentationUniqueKey { get; set; } public bool GroupByPresentationUniqueKey { get; set; } public bool EnableTotalRecordCount { get; set; } diff --git a/MediaBrowser.Controller/Entities/TV/Episode.cs b/MediaBrowser.Controller/Entities/TV/Episode.cs index 29a63f317..2bcccf5e8 100644 --- a/MediaBrowser.Controller/Entities/TV/Episode.cs +++ b/MediaBrowser.Controller/Entities/TV/Episode.cs @@ -165,13 +165,22 @@ namespace MediaBrowser.Controller.Entities.TV { return FindParent() != null; } - } + } + + [IgnoreDataMember] + public string SeriesPresentationUniqueKey { get; set; } [IgnoreDataMember] public string SeriesName { get; set; } [IgnoreDataMember] - public string SeasonName { get; set; } + public string SeasonName { get; set; } + + public string FindSeriesPresentationUniqueKey() + { + var series = Series; + return series == null ? null : series.PresentationUniqueKey; + } public string FindSeasonName() { diff --git a/MediaBrowser.Controller/Entities/TV/Season.cs b/MediaBrowser.Controller/Entities/TV/Season.cs index 2663a9dd5..e0cc496a1 100644 --- a/MediaBrowser.Controller/Entities/TV/Season.cs +++ b/MediaBrowser.Controller/Entities/TV/Season.cs @@ -207,12 +207,21 @@ namespace MediaBrowser.Controller.Entities.TV return UnratedItem.Series; } + [IgnoreDataMember] + public string SeriesPresentationUniqueKey { get; set; } + [IgnoreDataMember] public string SeriesName { get; set; } [IgnoreDataMember] public Guid? SeriesId { get; set; } + public string FindSeriesPresentationUniqueKey() + { + var series = Series; + return series == null ? null : series.PresentationUniqueKey; + } + public string FindSeriesName() { var series = Series; diff --git a/MediaBrowser.Controller/Entities/TV/Series.cs b/MediaBrowser.Controller/Entities/TV/Series.cs index ef25faf91..92cd20769 100644 --- a/MediaBrowser.Controller/Entities/TV/Series.cs +++ b/MediaBrowser.Controller/Entities/TV/Series.cs @@ -95,12 +95,16 @@ namespace MediaBrowser.Controller.Entities.TV public override string CreatePresentationUniqueKey() { - var userdatakeys = GetUserDataKeys(); - - if (userdatakeys.Count > 1) + if (LibraryManager.GetLibraryOptions(this).EnableAutomaticSeriesGrouping) { - return AddLibrariesToPresentationUniqueKey(userdatakeys[0]); + var userdatakeys = GetUserDataKeys(); + + if (userdatakeys.Count > 1) + { + return AddLibrariesToPresentationUniqueKey(userdatakeys[0]); + } } + return base.CreatePresentationUniqueKey(); } @@ -131,9 +135,13 @@ namespace MediaBrowser.Controller.Entities.TV public override int GetChildCount(User user) { + var enableSeriesPresentationKey = ConfigurationManager.Configuration.EnableSeriesPresentationUniqueKey; + var seriesKey = GetUniqueSeriesKey(this); + var result = LibraryManager.GetItemsResult(new InternalItemsQuery(user) { - AncestorWithPresentationUniqueKey = GetUniqueSeriesKey(this), + AncestorWithPresentationUniqueKey = enableSeriesPresentationKey ? null : seriesKey, + SeriesPresentationUniqueKey = enableSeriesPresentationKey ? seriesKey : null, IncludeItemTypes = new[] { typeof(Season).Name }, IsVirtualItem = false, Limit = 0 @@ -144,9 +152,15 @@ namespace MediaBrowser.Controller.Entities.TV public override int GetRecursiveChildCount(User user) { - var query = new InternalItemsQuery(user); + var enableSeriesPresentationKey = ConfigurationManager.Configuration.EnableSeriesPresentationUniqueKey; + var seriesKey = GetUniqueSeriesKey(this); + + var query = new InternalItemsQuery(user) + { + AncestorWithPresentationUniqueKey = enableSeriesPresentationKey ? null : seriesKey, + SeriesPresentationUniqueKey = enableSeriesPresentationKey ? seriesKey : null, + }; - query.AncestorWithPresentationUniqueKey = GetUniqueSeriesKey(this); if (query.SortBy.Length == 0) { query.SortBy = new[] { ItemSortBy.SortName }; @@ -223,11 +237,13 @@ namespace MediaBrowser.Controller.Entities.TV { var config = user.Configuration; + var enableSeriesPresentationKey = ConfigurationManager.Configuration.EnableSeriesPresentationUniqueKey; var seriesKey = GetUniqueSeriesKey(this); var query = new InternalItemsQuery(user) { - AncestorWithPresentationUniqueKey = seriesKey, + AncestorWithPresentationUniqueKey = enableSeriesPresentationKey ? null : seriesKey, + SeriesPresentationUniqueKey = enableSeriesPresentationKey ? seriesKey : null, IncludeItemTypes = new[] { typeof(Season).Name }, SortBy = new[] { ItemSortBy.SortName } }; @@ -259,7 +275,11 @@ namespace MediaBrowser.Controller.Entities.TV if (query.Recursive) { - query.AncestorWithPresentationUniqueKey = GetUniqueSeriesKey(this); + var enableSeriesPresentationKey = ConfigurationManager.Configuration.EnableSeriesPresentationUniqueKey; + var seriesKey = GetUniqueSeriesKey(this); + + query.AncestorWithPresentationUniqueKey = enableSeriesPresentationKey ? null : seriesKey; + query.SeriesPresentationUniqueKey = enableSeriesPresentationKey ? seriesKey : null; if (query.SortBy.Length == 0) { query.SortBy = new[] { ItemSortBy.SortName }; @@ -281,11 +301,13 @@ namespace MediaBrowser.Controller.Entities.TV public IEnumerable GetEpisodes(User user) { + var enableSeriesPresentationKey = ConfigurationManager.Configuration.EnableSeriesPresentationUniqueKey; var seriesKey = GetUniqueSeriesKey(this); var query = new InternalItemsQuery(user) { - AncestorWithPresentationUniqueKey = seriesKey, + AncestorWithPresentationUniqueKey = enableSeriesPresentationKey ? null : seriesKey, + SeriesPresentationUniqueKey = enableSeriesPresentationKey ? seriesKey : null, IncludeItemTypes = new[] { typeof(Episode).Name, typeof(Season).Name }, SortBy = new[] { ItemSortBy.SortName } }; @@ -387,14 +409,19 @@ namespace MediaBrowser.Controller.Entities.TV public IEnumerable GetSeasonEpisodes(Season parentSeason, User user) { + var enableSeriesPresentationKey = ConfigurationManager.Configuration.EnableSeriesPresentationUniqueKey; + + var queryFromSeries = ConfigurationManager.Configuration.DisplaySpecialsWithinSeasons; + // add optimization when this setting is not enabled - var seriesKey = ConfigurationManager.Configuration.DisplaySpecialsWithinSeasons ? + var seriesKey = queryFromSeries ? GetUniqueSeriesKey(this) : GetUniqueSeriesKey(parentSeason); var query = new InternalItemsQuery(user) { - AncestorWithPresentationUniqueKey = seriesKey, + AncestorWithPresentationUniqueKey = queryFromSeries && enableSeriesPresentationKey ? null : seriesKey, + SeriesPresentationUniqueKey = queryFromSeries && enableSeriesPresentationKey ? seriesKey : null, IncludeItemTypes = new[] { typeof(Episode).Name }, SortBy = new[] { ItemSortBy.SortName } }; diff --git a/MediaBrowser.Model/Configuration/LibraryOptions.cs b/MediaBrowser.Model/Configuration/LibraryOptions.cs index ffcb79cfb..e79253d19 100644 --- a/MediaBrowser.Model/Configuration/LibraryOptions.cs +++ b/MediaBrowser.Model/Configuration/LibraryOptions.cs @@ -14,6 +14,7 @@ public bool SaveLocalMetadata { get; set; } public bool EnableInternetProviders { get; set; } public bool ImportMissingEpisodes { get; set; } + public bool EnableAutomaticSeriesGrouping { get; set; } public LibraryOptions() { @@ -21,6 +22,7 @@ EnableRealtimeMonitor = true; PathInfos = new MediaPathInfo[] { }; EnableInternetProviders = true; + EnableAutomaticSeriesGrouping = true; } } diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index 520cc9701..35677813d 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -46,6 +46,7 @@ namespace MediaBrowser.Model.Configuration /// /// true if [use HTTPS]; otherwise, false. public bool EnableHttps { get; set; } + public bool EnableSeriesPresentationUniqueKey { get; set; } /// /// Gets or sets the value pointing to the file system where the ssl certiifcate is located.. diff --git a/MediaBrowser.Providers/TV/EpisodeMetadataService.cs b/MediaBrowser.Providers/TV/EpisodeMetadataService.cs index 2e4271c0f..538d96c17 100644 --- a/MediaBrowser.Providers/TV/EpisodeMetadataService.cs +++ b/MediaBrowser.Providers/TV/EpisodeMetadataService.cs @@ -55,6 +55,13 @@ namespace MediaBrowser.Providers.TV updateType |= ItemUpdateType.MetadataImport; } + var seriesPresentationUniqueKey = item.FindSeriesPresentationUniqueKey(); + if (!string.Equals(item.SeriesPresentationUniqueKey, seriesPresentationUniqueKey, StringComparison.Ordinal)) + { + item.SeriesPresentationUniqueKey = seriesPresentationUniqueKey; + updateType |= ItemUpdateType.MetadataImport; + } + return updateType; } diff --git a/MediaBrowser.Providers/TV/SeasonMetadataService.cs b/MediaBrowser.Providers/TV/SeasonMetadataService.cs index 81a0c0b4a..af7dea59e 100644 --- a/MediaBrowser.Providers/TV/SeasonMetadataService.cs +++ b/MediaBrowser.Providers/TV/SeasonMetadataService.cs @@ -51,6 +51,13 @@ namespace MediaBrowser.Providers.TV updateType |= ItemUpdateType.MetadataImport; } + var seriesPresentationUniqueKey = item.FindSeriesPresentationUniqueKey(); + if (!string.Equals(item.SeriesPresentationUniqueKey, seriesPresentationUniqueKey, StringComparison.Ordinal)) + { + item.SeriesPresentationUniqueKey = seriesPresentationUniqueKey; + updateType |= ItemUpdateType.MetadataImport; + } + var seriesId = item.FindSeriesId(); if (item.SeriesId != seriesId) { diff --git a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj index 3564cffa1..3cf7e54c0 100644 --- a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj +++ b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj @@ -693,9 +693,6 @@ PreserveNewest - - PreserveNewest - PreserveNewest @@ -876,9 +873,6 @@ PreserveNewest - - PreserveNewest - PreserveNewest diff --git a/MediaBrowser.XbmcMetadata/Savers/MovieNfoSaver.cs b/MediaBrowser.XbmcMetadata/Savers/MovieNfoSaver.cs index dc827b861..3bd3a1555 100644 --- a/MediaBrowser.XbmcMetadata/Savers/MovieNfoSaver.cs +++ b/MediaBrowser.XbmcMetadata/Savers/MovieNfoSaver.cs @@ -35,7 +35,7 @@ namespace MediaBrowser.XbmcMetadata.Savers list.Add(Path.Combine(path, "VIDEO_TS", "VIDEO_TS.nfo")); } - if (item.VideoType == VideoType.Dvd || item.VideoType == VideoType.BluRay || item.VideoType == VideoType.HdDvd) + if (!item.IsPlaceHolder && (item.VideoType == VideoType.Dvd || item.VideoType == VideoType.BluRay || item.VideoType == VideoType.HdDvd)) { var path = item.ContainingFolderPath;