aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Emby.Server.Implementations/Collections/CollectionManager.cs51
-rw-r--r--Emby.Server.Implementations/Library/MediaSourceManager.cs12
-rw-r--r--Jellyfin.Api/Helpers/MediaInfoHelper.cs2
-rw-r--r--Jellyfin.Server/Migrations/Routines/AddPeopleQueryIndex.cs6
-rw-r--r--Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs10
-rw-r--r--Jellyfin.Server/Startup.cs14
-rw-r--r--MediaBrowser.Common/Net/DefaultHttpClientHandler.cs19
-rw-r--r--MediaBrowser.Controller/Entities/Folder.cs9
-rw-r--r--MediaBrowser.Model/Notifications/NotificationOptions.cs9
-rw-r--r--MediaBrowser.Providers/Manager/MetadataService.cs63
-rw-r--r--MediaBrowser.Providers/Manager/ProviderManager.cs15
-rw-r--r--MediaBrowser.Providers/MediaBrowser.Providers.csproj2
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeImageProvider.cs2
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProvider.cs2
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs70
-rw-r--r--tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj6
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj4
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj6
-rw-r--r--tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj6
19 files changed, 195 insertions, 113 deletions
diff --git a/Emby.Server.Implementations/Collections/CollectionManager.cs b/Emby.Server.Implementations/Collections/CollectionManager.cs
index e984afdba..1b85a9d4b 100644
--- a/Emby.Server.Implementations/Collections/CollectionManager.cs
+++ b/Emby.Server.Implementations/Collections/CollectionManager.cs
@@ -121,7 +121,7 @@ namespace Emby.Server.Implementations.Collections
private IEnumerable<BoxSet> GetCollections(User user)
{
- var folder = GetCollectionsFolder(false).Result;
+ var folder = GetCollectionsFolder(false).GetAwaiter().GetResult();
return folder == null
? Enumerable.Empty<BoxSet>()
@@ -316,11 +316,11 @@ namespace Emby.Server.Implementations.Collections
{
var results = new Dictionary<Guid, BaseItem>();
- var allBoxsets = GetCollections(user).ToList();
+ var allBoxSets = GetCollections(user).ToList();
foreach (var item in items)
{
- if (!(item is ISupportsBoxSetGrouping))
+ if (item is not ISupportsBoxSetGrouping)
{
results[item.Id] = item;
}
@@ -328,34 +328,45 @@ namespace Emby.Server.Implementations.Collections
{
var itemId = item.Id;
- var currentBoxSets = allBoxsets
- .Where(i => i.ContainsLinkedChildByItemId(itemId))
- .ToList();
-
- if (currentBoxSets.Count > 0)
+ var itemIsInBoxSet = false;
+ foreach (var boxSet in allBoxSets)
{
- foreach (var boxset in currentBoxSets)
+ if (!boxSet.ContainsLinkedChildByItemId(itemId))
{
- results[boxset.Id] = boxset;
+ continue;
}
+
+ itemIsInBoxSet = true;
+
+ results.TryAdd(boxSet.Id, boxSet);
+ }
+
+ // skip any item that is in a box set
+ if (itemIsInBoxSet)
+ {
+ continue;
}
- else
+
+ var alreadyInResults = false;
+ // this is kind of a performance hack because only Video has alternate versions that should be in a box set?
+ if (item is Video video)
{
- var alreadyInResults = false;
- foreach (var child in item.GetMediaSources(true))
+ foreach (var childId in video.GetLocalAlternateVersionIds())
{
- if (Guid.TryParse(child.Id, out var id) && results.ContainsKey(id))
+ if (!results.ContainsKey(childId))
{
- alreadyInResults = true;
- break;
+ continue;
}
- }
- if (!alreadyInResults)
- {
- results[item.Id] = item;
+ alreadyInResults = true;
+ break;
}
}
+
+ if (!alreadyInResults)
+ {
+ results[itemId] = item;
+ }
}
}
diff --git a/Emby.Server.Implementations/Library/MediaSourceManager.cs b/Emby.Server.Implementations/Library/MediaSourceManager.cs
index b2943020c..d0b85f07d 100644
--- a/Emby.Server.Implementations/Library/MediaSourceManager.cs
+++ b/Emby.Server.Implementations/Library/MediaSourceManager.cs
@@ -199,10 +199,15 @@ namespace Emby.Server.Implementations.Library
{
source.SupportsTranscoding = user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding);
}
+ else if (string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase))
+ {
+ source.SupportsTranscoding = user.HasPermission(PermissionKind.EnableVideoPlaybackTranscoding);
+ source.SupportsDirectStream = user.HasPermission(PermissionKind.EnablePlaybackRemuxing);
+ }
}
}
- return SortMediaSources(list).Where(i => i.Type != MediaSourceType.Placeholder).ToList();
+ return SortMediaSources(list);
}
public MediaProtocol GetPathProtocol(string path)
@@ -436,7 +441,7 @@ namespace Emby.Server.Implementations.Library
}
}
- private static IEnumerable<MediaSourceInfo> SortMediaSources(IEnumerable<MediaSourceInfo> sources)
+ private static List<MediaSourceInfo> SortMediaSources(IEnumerable<MediaSourceInfo> sources)
{
return sources.OrderBy(i =>
{
@@ -451,8 +456,9 @@ namespace Emby.Server.Implementations.Library
{
var stream = i.VideoStream;
- return stream == null || stream.Width == null ? 0 : stream.Width.Value;
+ return stream?.Width ?? 0;
})
+ .Where(i => i.Type != MediaSourceType.Placeholder)
.ToList();
}
diff --git a/Jellyfin.Api/Helpers/MediaInfoHelper.cs b/Jellyfin.Api/Helpers/MediaInfoHelper.cs
index f07271821..295cfaf08 100644
--- a/Jellyfin.Api/Helpers/MediaInfoHelper.cs
+++ b/Jellyfin.Api/Helpers/MediaInfoHelper.cs
@@ -309,7 +309,7 @@ namespace Jellyfin.Api.Helpers
{
if (!user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding)
&& !user.HasPermission(PermissionKind.EnableVideoPlaybackTranscoding)
- && !user.HasPermission(PermissionKind.EnablePlaybackRemuxing))
+ && user.HasPermission(PermissionKind.EnablePlaybackRemuxing))
{
options.ForceDirectStream = true;
}
diff --git a/Jellyfin.Server/Migrations/Routines/AddPeopleQueryIndex.cs b/Jellyfin.Server/Migrations/Routines/AddPeopleQueryIndex.cs
index 2521d9952..6343c422d 100644
--- a/Jellyfin.Server/Migrations/Routines/AddPeopleQueryIndex.cs
+++ b/Jellyfin.Server/Migrations/Routines/AddPeopleQueryIndex.cs
@@ -41,9 +41,9 @@ namespace Jellyfin.Server.Migrations.Routines
var databasePath = Path.Join(_serverApplicationPaths.DataPath, DbFilename);
using var connection = SQLite3.Open(databasePath, ConnectionFlags.ReadWrite, null);
_logger.LogInformation("Creating index idx_TypedBaseItemsUserDataKeyType");
- connection.Execute("CREATE INDEX idx_TypedBaseItemsUserDataKeyType ON TypedBaseItems(UserDataKey, Type);");
+ connection.Execute("CREATE INDEX IF NOT EXISTS idx_TypedBaseItemsUserDataKeyType ON TypedBaseItems(UserDataKey, Type);");
_logger.LogInformation("Creating index idx_PeopleNameListOrder");
- connection.Execute("CREATE INDEX idx_PeopleNameListOrder ON People(Name, ListOrder);");
+ connection.Execute("CREATE INDEX IF NOT EXISTS idx_PeopleNameListOrder ON People(Name, ListOrder);");
}
}
-} \ No newline at end of file
+}
diff --git a/Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs b/Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs
index 07829c696..e25d29122 100644
--- a/Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs
+++ b/Jellyfin.Server/Migrations/Routines/MigrateDisplayPreferencesDb.cs
@@ -126,13 +126,13 @@ namespace Jellyfin.Server.Migrations.Routines
ShowSidebar = dto.ShowSidebar,
ScrollDirection = dto.ScrollDirection,
ChromecastVersion = chromecastVersion,
- SkipForwardLength = dto.CustomPrefs.TryGetValue("skipForwardLength", out var length)
- ? int.Parse(length, CultureInfo.InvariantCulture)
+ SkipForwardLength = dto.CustomPrefs.TryGetValue("skipForwardLength", out var length) && int.TryParse(length, out var skipForwardLength)
+ ? skipForwardLength
: 30000,
- SkipBackwardLength = dto.CustomPrefs.TryGetValue("skipBackLength", out length)
- ? int.Parse(length, CultureInfo.InvariantCulture)
+ SkipBackwardLength = dto.CustomPrefs.TryGetValue("skipBackLength", out length) && !string.IsNullOrEmpty(length) && int.TryParse(length, out var skipBackwardLength)
+ ? skipBackwardLength
: 10000,
- EnableNextVideoInfoOverlay = dto.CustomPrefs.TryGetValue("enableNextVideoInfoOverlay", out var enabled)
+ EnableNextVideoInfoOverlay = dto.CustomPrefs.TryGetValue("enableNextVideoInfoOverlay", out var enabled) && !string.IsNullOrEmpty(enabled)
? bool.Parse(enabled)
: true,
DashboardTheme = dto.CustomPrefs.TryGetValue("dashboardtheme", out var theme) ? theme : string.Empty,
diff --git a/Jellyfin.Server/Startup.cs b/Jellyfin.Server/Startup.cs
index e56e61092..f75139884 100644
--- a/Jellyfin.Server/Startup.cs
+++ b/Jellyfin.Server/Startup.cs
@@ -1,5 +1,9 @@
+using System;
+using System.Net;
+using System.Net.Http;
using System.Net.Http.Headers;
using System.Net.Mime;
+using System.Text;
using Jellyfin.Networking.Configuration;
using Jellyfin.Server.Extensions;
using Jellyfin.Server.Implementations;
@@ -67,6 +71,12 @@ namespace Jellyfin.Server
var acceptJsonHeader = new MediaTypeWithQualityHeaderValue(MediaTypeNames.Application.Json, 1.0);
var acceptXmlHeader = new MediaTypeWithQualityHeaderValue(MediaTypeNames.Application.Xml, 0.9);
var acceptAnyHeader = new MediaTypeWithQualityHeaderValue("*/*", 0.8);
+ Func<IServiceProvider, HttpMessageHandler> defaultHttpClientHandlerDelegate = (_) => new SocketsHttpHandler()
+ {
+ AutomaticDecompression = DecompressionMethods.All,
+ RequestHeaderEncodingSelector = (_, _) => Encoding.UTF8
+ };
+
services
.AddHttpClient(NamedClient.Default, c =>
{
@@ -75,7 +85,7 @@ namespace Jellyfin.Server
c.DefaultRequestHeaders.Accept.Add(acceptXmlHeader);
c.DefaultRequestHeaders.Accept.Add(acceptAnyHeader);
})
- .ConfigurePrimaryHttpMessageHandler(x => new DefaultHttpClientHandler());
+ .ConfigurePrimaryHttpMessageHandler(defaultHttpClientHandlerDelegate);
services.AddHttpClient(NamedClient.MusicBrainz, c =>
{
@@ -84,7 +94,7 @@ namespace Jellyfin.Server
c.DefaultRequestHeaders.Accept.Add(acceptXmlHeader);
c.DefaultRequestHeaders.Accept.Add(acceptAnyHeader);
})
- .ConfigurePrimaryHttpMessageHandler(x => new DefaultHttpClientHandler());
+ .ConfigurePrimaryHttpMessageHandler(defaultHttpClientHandlerDelegate);
services.AddHealthChecks()
.AddDbContextCheck<JellyfinDb>();
diff --git a/MediaBrowser.Common/Net/DefaultHttpClientHandler.cs b/MediaBrowser.Common/Net/DefaultHttpClientHandler.cs
deleted file mode 100644
index f1c5f2477..000000000
--- a/MediaBrowser.Common/Net/DefaultHttpClientHandler.cs
+++ /dev/null
@@ -1,19 +0,0 @@
-using System.Net;
-using System.Net.Http;
-
-namespace MediaBrowser.Common.Net
-{
- /// <summary>
- /// Default http client handler.
- /// </summary>
- public class DefaultHttpClientHandler : HttpClientHandler
- {
- /// <summary>
- /// Initializes a new instance of the <see cref="DefaultHttpClientHandler"/> class.
- /// </summary>
- public DefaultHttpClientHandler()
- {
- AutomaticDecompression = DecompressionMethods.All;
- }
- }
-}
diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs
index bdca5c0ee..d45f8758c 100644
--- a/MediaBrowser.Controller/Entities/Folder.cs
+++ b/MediaBrowser.Controller/Entities/Folder.cs
@@ -1433,9 +1433,14 @@ namespace MediaBrowser.Controller.Entities
var linkedChildren = LinkedChildren;
foreach (var i in linkedChildren)
{
- if (i.ItemId.HasValue && i.ItemId.Value == itemId)
+ if (i.ItemId.HasValue)
{
- return true;
+ if (i.ItemId.Value == itemId)
+ {
+ return true;
+ }
+
+ continue;
}
var child = GetLinkedChild(i);
diff --git a/MediaBrowser.Model/Notifications/NotificationOptions.cs b/MediaBrowser.Model/Notifications/NotificationOptions.cs
index 12e093b21..09beb2ef7 100644
--- a/MediaBrowser.Model/Notifications/NotificationOptions.cs
+++ b/MediaBrowser.Model/Notifications/NotificationOptions.cs
@@ -93,16 +93,17 @@ namespace MediaBrowser.Model.Notifications
{
NotificationOption opt = GetOptions(notificationType);
- return opt == null ||
- !opt.DisabledServices.Contains(service, StringComparer.OrdinalIgnoreCase);
+ return opt == null
+ || !opt.DisabledServices.Contains(service, StringComparer.OrdinalIgnoreCase);
}
public bool IsEnabledToMonitorUser(string type, Guid userId)
{
NotificationOption opt = GetOptions(type);
- return opt != null && opt.Enabled &&
- !opt.DisabledMonitorUsers.Contains(userId.ToString(string.Empty), StringComparer.OrdinalIgnoreCase);
+ return opt != null
+ && opt.Enabled
+ && !opt.DisabledMonitorUsers.Contains(userId.ToString("N"), StringComparer.OrdinalIgnoreCase);
}
public bool IsEnabledToSendToUser(string type, string userId, User user)
diff --git a/MediaBrowser.Providers/Manager/MetadataService.cs b/MediaBrowser.Providers/Manager/MetadataService.cs
index 437b43eca..f12586665 100644
--- a/MediaBrowser.Providers/Manager/MetadataService.cs
+++ b/MediaBrowser.Providers/Manager/MetadataService.cs
@@ -223,13 +223,13 @@ namespace MediaBrowser.Providers.Manager
var baseItem = result.Item;
LibraryManager.UpdatePeople(baseItem, result.People);
- await SavePeopleMetadataAsync(result.People, libraryOptions, cancellationToken).ConfigureAwait(false);
+ await SavePeopleMetadataAsync(result.People, cancellationToken).ConfigureAwait(false);
}
await result.Item.UpdateToRepositoryAsync(reason, cancellationToken).ConfigureAwait(false);
}
- private async Task SavePeopleMetadataAsync(List<PersonInfo> people, LibraryOptions libraryOptions, CancellationToken cancellationToken)
+ private async Task SavePeopleMetadataAsync(IEnumerable<PersonInfo> people, CancellationToken cancellationToken)
{
var personsToSave = new List<BaseItem>();
@@ -237,39 +237,44 @@ namespace MediaBrowser.Providers.Manager
{
cancellationToken.ThrowIfCancellationRequested();
- if (person.ProviderIds.Count > 0 || !string.IsNullOrWhiteSpace(person.ImageUrl))
+ var itemUpdateType = ItemUpdateType.MetadataDownload;
+ var saveEntity = false;
+ var personEntity = LibraryManager.GetPerson(person.Name);
+
+ // if PresentationUniqueKey is empty it's likely a new item.
+ if (string.IsNullOrEmpty(personEntity.PresentationUniqueKey))
{
- var itemUpdateType = ItemUpdateType.MetadataDownload;
- var saveEntity = false;
- var personEntity = LibraryManager.GetPerson(person.Name);
- foreach (var id in person.ProviderIds)
- {
- if (!string.Equals(personEntity.GetProviderId(id.Key), id.Value, StringComparison.OrdinalIgnoreCase))
- {
- personEntity.SetProviderId(id.Key, id.Value);
- saveEntity = true;
- }
- }
+ personEntity.PresentationUniqueKey = personEntity.CreatePresentationUniqueKey();
+ saveEntity = true;
+ }
- if (!string.IsNullOrWhiteSpace(person.ImageUrl) && !personEntity.HasImage(ImageType.Primary))
+ foreach (var id in person.ProviderIds)
+ {
+ if (!string.Equals(personEntity.GetProviderId(id.Key), id.Value, StringComparison.OrdinalIgnoreCase))
{
- personEntity.SetImage(
- new ItemImageInfo
- {
- Path = person.ImageUrl,
- Type = ImageType.Primary
- },
- 0);
-
+ personEntity.SetProviderId(id.Key, id.Value);
saveEntity = true;
- itemUpdateType = ItemUpdateType.ImageUpdate;
}
+ }
- if (saveEntity)
- {
- personsToSave.Add(personEntity);
- await LibraryManager.RunMetadataSavers(personEntity, itemUpdateType).ConfigureAwait(false);
- }
+ if (!string.IsNullOrWhiteSpace(person.ImageUrl) && !personEntity.HasImage(ImageType.Primary))
+ {
+ personEntity.SetImage(
+ new ItemImageInfo
+ {
+ Path = person.ImageUrl,
+ Type = ImageType.Primary
+ },
+ 0);
+
+ saveEntity = true;
+ itemUpdateType = ItemUpdateType.ImageUpdate;
+ }
+
+ if (saveEntity)
+ {
+ personsToSave.Add(personEntity);
+ await LibraryManager.RunMetadataSavers(personEntity, itemUpdateType).ConfigureAwait(false);
}
}
diff --git a/MediaBrowser.Providers/Manager/ProviderManager.cs b/MediaBrowser.Providers/Manager/ProviderManager.cs
index 3bb2c6f0b..201f1c470 100644
--- a/MediaBrowser.Providers/Manager/ProviderManager.cs
+++ b/MediaBrowser.Providers/Manager/ProviderManager.cs
@@ -1073,17 +1073,16 @@ namespace MediaBrowser.Providers.Manager
try
{
var item = libraryManager.GetItemById(refreshItem.Item1);
- if (item != null)
+ if (item == null)
{
- // Try to throttle this a little bit.
- await Task.Delay(100, cancellationToken).ConfigureAwait(false);
+ continue;
+ }
- var task = item is MusicArtist artist
- ? RefreshArtist(artist, refreshItem.Item2, cancellationToken)
- : RefreshItem(item, refreshItem.Item2, cancellationToken);
+ var task = item is MusicArtist artist
+ ? RefreshArtist(artist, refreshItem.Item2, cancellationToken)
+ : RefreshItem(item, refreshItem.Item2, cancellationToken);
- await task.ConfigureAwait(false);
- }
+ await task.ConfigureAwait(false);
}
catch (OperationCanceledException)
{
diff --git a/MediaBrowser.Providers/MediaBrowser.Providers.csproj b/MediaBrowser.Providers/MediaBrowser.Providers.csproj
index de7860b2e..cdb07a15d 100644
--- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj
+++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj
@@ -19,7 +19,7 @@
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Http" Version="5.0.0" />
- <PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
+ <PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="OptimizedPriorityQueue" Version="5.0.0" />
<PackageReference Include="PlaylistsNET" Version="1.1.3" />
<PackageReference Include="TMDbLib" Version="1.8.1" />
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeImageProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeImageProvider.cs
index d92336624..ba18c542f 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeImageProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeImageProvider.cs
@@ -65,7 +65,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
// TODO use image languages if All Languages isn't toggled, but there's currently no way to get that value in here
var episodeResult = await _tmdbClientManager
- .GetEpisodeAsync(seriesTmdbId, seasonNumber.Value, episodeNumber.Value, null, null, cancellationToken)
+ .GetEpisodeAsync(seriesTmdbId, seasonNumber.Value, episodeNumber.Value, series.DisplayOrder, null, null, cancellationToken)
.ConfigureAwait(false);
var stills = episodeResult?.Images?.Stills;
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProvider.cs b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProvider.cs
index b455e5634..36e7fe91a 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProvider.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/TV/TmdbEpisodeProvider.cs
@@ -92,7 +92,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb.TV
}
var episodeResult = await _tmdbClientManager
- .GetEpisodeAsync(seriesTmdbId, seasonNumber.Value, episodeNumber.Value, info.MetadataLanguage, TmdbUtils.GetImageLanguagesParam(info.MetadataLanguage), cancellationToken)
+ .GetEpisodeAsync(seriesTmdbId, seasonNumber.Value, episodeNumber.Value, info.SeriesDisplayOrder, info.MetadataLanguage, TmdbUtils.GetImageLanguagesParam(info.MetadataLanguage), cancellationToken)
.ConfigureAwait(false);
if (episodeResult == null)
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs b/MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs
index bf0f027fc..05e5d3ced 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/TmdbClientManager.cs
@@ -125,7 +125,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
tmdbId,
language: TmdbUtils.NormalizeLanguage(language),
includeImageLanguage: imageLanguages,
- extraMethods: TvShowMethods.Credits | TvShowMethods.Images | TvShowMethods.Keywords | TvShowMethods.ExternalIds | TvShowMethods.Videos | TvShowMethods.ContentRatings,
+ extraMethods: TvShowMethods.Credits | TvShowMethods.Images | TvShowMethods.Keywords | TvShowMethods.ExternalIds | TvShowMethods.Videos | TvShowMethods.ContentRatings | TvShowMethods.EpisodeGroups,
cancellationToken: cancellationToken).ConfigureAwait(false);
if (series != null)
@@ -137,6 +137,56 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
}
/// <summary>
+ /// Gets a tv show episode group from the TMDb API based on the show id and the display order.
+ /// </summary>
+ /// <param name="tvShowId">The tv show's TMDb id.</param>
+ /// <param name="displayOrder">The display order.</param>
+ /// <param name="language">The tv show's language.</param>
+ /// <param name="imageLanguages">A comma-separated list of image languages.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns>The TMDb tv show episode group information or null if not found.</returns>
+ private async Task<TvGroupCollection> GetSeriesGroupAsync(int tvShowId, string displayOrder, string language, string imageLanguages, CancellationToken cancellationToken)
+ {
+ TvGroupType? groupType =
+ string.Equals(displayOrder, "absolute", StringComparison.Ordinal) ? TvGroupType.Absolute :
+ string.Equals(displayOrder, "dvd", StringComparison.Ordinal) ? TvGroupType.DVD :
+ null;
+
+ if (groupType == null)
+ {
+ return null;
+ }
+
+ var key = $"group-{tvShowId.ToString(CultureInfo.InvariantCulture)}-{displayOrder}-{language}";
+ if (_memoryCache.TryGetValue(key, out TvGroupCollection group))
+ {
+ return group;
+ }
+
+ await EnsureClientConfigAsync().ConfigureAwait(false);
+
+ var series = await GetSeriesAsync(tvShowId, language, imageLanguages, cancellationToken).ConfigureAwait(false);
+ var episodeGroupId = series?.EpisodeGroups.Results.Find(g => g.Type == groupType)?.Id;
+
+ if (episodeGroupId == null)
+ {
+ return null;
+ }
+
+ group = await _tmDbClient.GetTvEpisodeGroupsAsync(
+ episodeGroupId,
+ language: TmdbUtils.NormalizeLanguage(language),
+ cancellationToken: cancellationToken).ConfigureAwait(false);
+
+ if (group != null)
+ {
+ _memoryCache.Set(key, group, TimeSpan.FromHours(CacheDurationInHours));
+ }
+
+ return group;
+ }
+
+ /// <summary>
/// Gets a tv season from the TMDb API based on the tv show's TMDb id.
/// </summary>
/// <param name="tvShowId">The tv season's TMDb id.</param>
@@ -177,13 +227,14 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
/// <param name="tvShowId">The tv show's TMDb id.</param>
/// <param name="seasonNumber">The season number.</param>
/// <param name="episodeNumber">The episode number.</param>
+ /// <param name="displayOrder">The display order.</param>
/// <param name="language">The episode's language.</param>
/// <param name="imageLanguages">A comma-separated list of image languages.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The TMDb tv episode information or null if not found.</returns>
- public async Task<TvEpisode> GetEpisodeAsync(int tvShowId, int seasonNumber, int episodeNumber, string language, string imageLanguages, CancellationToken cancellationToken)
+ public async Task<TvEpisode> GetEpisodeAsync(int tvShowId, int seasonNumber, int episodeNumber, string displayOrder, string language, string imageLanguages, CancellationToken cancellationToken)
{
- var key = $"episode-{tvShowId.ToString(CultureInfo.InvariantCulture)}-s{seasonNumber.ToString(CultureInfo.InvariantCulture)}e{episodeNumber.ToString(CultureInfo.InvariantCulture)}-{language}";
+ var key = $"episode-{tvShowId.ToString(CultureInfo.InvariantCulture)}-s{seasonNumber.ToString(CultureInfo.InvariantCulture)}e{episodeNumber.ToString(CultureInfo.InvariantCulture)}-{displayOrder}-{language}";
if (_memoryCache.TryGetValue(key, out TvEpisode episode))
{
return episode;
@@ -191,6 +242,19 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
await EnsureClientConfigAsync().ConfigureAwait(false);
+ var group = await GetSeriesGroupAsync(tvShowId, displayOrder, language, imageLanguages, cancellationToken);
+ if (group != null)
+ {
+ var season = group.Groups.Find(s => s.Order == seasonNumber);
+ // Episode order starts at 0
+ var ep = season?.Episodes.Find(e => e.Order == episodeNumber - 1);
+ if (ep != null)
+ {
+ seasonNumber = ep.SeasonNumber;
+ episodeNumber = ep.EpisodeNumber;
+ }
+ }
+
episode = await _tmDbClient.GetTvEpisodeAsync(
tvShowId,
seasonNumber,
diff --git a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj
index f288561b7..050d4c040 100644
--- a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj
+++ b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj
@@ -15,9 +15,9 @@
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="AutoFixture" Version="4.15.0" />
- <PackageReference Include="AutoFixture.AutoMoq" Version="4.15.0" />
- <PackageReference Include="AutoFixture.Xunit2" Version="4.15.0" />
+ <PackageReference Include="AutoFixture" Version="4.16.0" />
+ <PackageReference Include="AutoFixture.AutoMoq" Version="4.16.0" />
+ <PackageReference Include="AutoFixture.Xunit2" Version="4.16.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="5.0.5" />
<PackageReference Include="Microsoft.Extensions.Options" Version="5.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.4" />
diff --git a/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj b/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj
index ee59dad5a..486899f4f 100644
--- a/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj
+++ b/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj
@@ -22,8 +22,8 @@
</ItemGroup>
<ItemGroup>
- <PackageReference Include="AutoFixture" Version="4.15.0" />
- <PackageReference Include="AutoFixture.AutoMoq" Version="4.15.0" />
+ <PackageReference Include="AutoFixture" Version="4.16.0" />
+ <PackageReference Include="AutoFixture.AutoMoq" Version="4.16.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.4" />
<PackageReference Include="Moq" Version="4.16.1" />
<PackageReference Include="xunit" Version="2.4.1" />
diff --git a/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj b/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj
index 8d4d9e3d2..0de92249a 100644
--- a/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj
+++ b/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj
@@ -9,9 +9,9 @@
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="AutoFixture" Version="4.15.0" />
- <PackageReference Include="AutoFixture.AutoMoq" Version="4.15.0" />
- <PackageReference Include="AutoFixture.Xunit2" Version="4.15.0" />
+ <PackageReference Include="AutoFixture" Version="4.16.0" />
+ <PackageReference Include="AutoFixture.AutoMoq" Version="4.16.0" />
+ <PackageReference Include="AutoFixture.Xunit2" Version="4.16.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="5.0.5" />
<PackageReference Include="Microsoft.Extensions.Options" Version="5.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.4" />
diff --git a/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj b/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj
index 4a5cf1fee..9e60dbcd9 100644
--- a/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj
+++ b/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj
@@ -10,9 +10,9 @@
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="AutoFixture" Version="4.15.0" />
- <PackageReference Include="AutoFixture.AutoMoq" Version="4.15.0" />
- <PackageReference Include="AutoFixture.Xunit2" Version="4.15.0" />
+ <PackageReference Include="AutoFixture" Version="4.16.0" />
+ <PackageReference Include="AutoFixture.AutoMoq" Version="4.16.0" />
+ <PackageReference Include="AutoFixture.Xunit2" Version="4.16.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="5.0.5" />
<PackageReference Include="Microsoft.Extensions.Options" Version="5.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.4" />