diff --git a/Shokofin/API/Info/SeasonInfo.cs b/Shokofin/API/Info/SeasonInfo.cs index 1caa6c68..217e091b 100644 --- a/Shokofin/API/Info/SeasonInfo.cs +++ b/Shokofin/API/Info/SeasonInfo.cs @@ -106,7 +106,20 @@ public class SeasonInfo /// public readonly IReadOnlyDictionary RelationMap; - public SeasonInfo(Series series, SeriesType? customType, IEnumerable extraIds, DateTime? earliestImportedAt, DateTime? lastImportedAt, List episodes, List cast, List relations, string[] genres, string[] tags, string[] productionLocations, string? contentRating) + public SeasonInfo( + Series series, + SeriesType? customType, + IEnumerable extraIds, + DateTime? earliestImportedAt, + DateTime? lastImportedAt, + List episodes, + List cast, + List relations, + string[] genres, + string[] tags, + string[] productionLocations, + IReadOnlyDictionary> tagSeriesMap, + string? contentRating) { var seriesId = series.IDs.Shoko.ToString(); var studios = cast @@ -138,13 +151,19 @@ public SeasonInfo(Series series, SeriesType? customType, IEnumerable ext .ThenBy(episode => episode.AniDB.EpisodeNumber) .ToList(); + // Take note of any episodes which should be mapped as specials. + var mappedAsSpecials = episodes + .Where(episode => episode.AniDB.Type is EpisodeType.Normal or EpisodeType.Other && tagSeriesMap[episode.Shoko.IDs.ParentSeries.ToString()].ContainsKey("/custom user tags/shokofin/map as specials")) + .ToHashSet(); + // Iterate over the episodes once and store some values for later use. int index = 0; int lastNormalEpisode = -1; foreach (var episode in episodes) { if (episode.Shoko.IsHidden) continue; - switch (episode.AniDB.Type) { + var episodeType = mappedAsSpecials.Contains(episode) ? EpisodeType.Special : episode.AniDB.Type; + switch (episodeType) { case EpisodeType.Normal: episodesList.Add(episode); lastNormalEpisode = index; @@ -159,7 +178,7 @@ public SeasonInfo(Series series, SeriesType? customType, IEnumerable ext if (episode.ExtraType != null) { extrasList.Add(episode); } - else if (episode.AniDB.Type == EpisodeType.Special) { + else if (episodeType is EpisodeType.Special) { specialsList.Add(episode); if (lastNormalEpisode == -1) { specialsBeforeEpisodes.Add(episode.Id); @@ -167,7 +186,7 @@ public SeasonInfo(Series series, SeriesType? customType, IEnumerable ext else { var previousEpisode = episodes .GetRange(lastNormalEpisode, index - lastNormalEpisode) - .FirstOrDefault(e => e.AniDB.Type == EpisodeType.Normal); + .FirstOrDefault(e => e.AniDB.Type is EpisodeType.Normal && !mappedAsSpecials.Contains(e)); if (previousEpisode != null) specialsAnchorDictionary[episode] = previousEpisode; } diff --git a/Shokofin/API/ShokoAPIManager.cs b/Shokofin/API/ShokoAPIManager.cs index 1aff4611..5520b7df 100644 --- a/Shokofin/API/ShokoAPIManager.cs +++ b/Shokofin/API/ShokoAPIManager.cs @@ -756,6 +756,7 @@ private async Task CreateSeasonInfo(Series series) var genresTasks = detailsIds.Select(id => GetGenresForSeries(id)); var tagsTasks = detailsIds.Select(id => GetTagsForSeries(id)); var productionLocationsTasks = detailsIds.Select(id => GetProductionLocations(id)); + var namespacedTagsTasks = detailsIds.Select(id => GetNamespacedTagsForSeries(id)); // Await the tasks in order. var cast = (await Task.WhenAll(castTasks)) @@ -781,16 +782,22 @@ private async Task CreateSeasonInfo(Series series) .OrderBy(g => g) .Distinct() .ToArray(); + var namespacedTags = (await Task.WhenAll(namespacedTagsTasks)) + .Select((t, i) => (t, i)) + .ToDictionary(t => detailsIds[t.i], (t) => t.t); // Create the season info using the merged details. - seasonInfo = new SeasonInfo(series, customSeriesType, extraIds, earliestImportedAt, lastImportedAt, episodes, cast, relations, genres, tags, productionLocations, contentRating); + seasonInfo = new SeasonInfo(series, customSeriesType, extraIds, earliestImportedAt, lastImportedAt, episodes, cast, relations, genres, tags, productionLocations, namespacedTags, contentRating); } else { var cast = await APIClient.GetSeriesCast(seriesId).ConfigureAwait(false); var relations = await APIClient.GetSeriesRelations(seriesId).ConfigureAwait(false); var genres = await GetGenresForSeries(seriesId).ConfigureAwait(false); var tags = await GetTagsForSeries(seriesId).ConfigureAwait(false); var productionLocations = await GetProductionLocations(seriesId).ConfigureAwait(false); - seasonInfo = new SeasonInfo(series, customSeriesType, extraIds, earliestImportedAt, lastImportedAt, episodes, cast, relations, genres, tags, productionLocations, contentRating); + var namespacedTags = new Dictionary>() { + { seriesId, await GetNamespacedTagsForSeries(seriesId).ConfigureAwait(false) }, + }; + seasonInfo = new SeasonInfo(series, customSeriesType, extraIds, earliestImportedAt, lastImportedAt, episodes, cast, relations, genres, tags, productionLocations, namespacedTags, contentRating); } foreach (var episode in episodes)