diff --git a/frontend/src/Episode/SceneInfo.js b/frontend/src/Episode/SceneInfo.js index 317d51a0a..5be1f5704 100644 --- a/frontend/src/Episode/SceneInfo.js +++ b/frontend/src/Episode/SceneInfo.js @@ -1,3 +1,4 @@ +import _ from 'lodash'; import PropTypes from 'prop-types'; import React from 'react'; import DescriptionList from 'Components/DescriptionList/DescriptionList'; @@ -16,6 +17,38 @@ function SceneInfo(props) { seriesType } = props; + const reducedAlternateTitles = alternateTitles.map((alternateTitle) => { + let suffix = ''; + + const altSceneSeasonNumber = sceneSeasonNumber === undefined ? seasonNumber : sceneSeasonNumber; + const altSceneEpisodeNumber = sceneEpisodeNumber === undefined ? episodeNumber : sceneEpisodeNumber; + + const mappingSeasonNumber = alternateTitle.sceneOrigin === 'tvdb' ? seasonNumber : altSceneSeasonNumber; + const altSeasonNumber = (alternateTitle.sceneSeasonNumber !== -1 && alternateTitle.sceneSeasonNumber !== undefined) ? alternateTitle.sceneSeasonNumber : mappingSeasonNumber; + const altEpisodeNumber = alternateTitle.sceneOrigin === 'tvdb' ? episodeNumber : altSceneEpisodeNumber; + + if (altEpisodeNumber !== altSceneEpisodeNumber) { + suffix = `S${padNumber(altSeasonNumber, 2)}E${padNumber(altEpisodeNumber, 2)}`; + } else if (altSeasonNumber !== altSceneSeasonNumber) { + suffix = `S${padNumber(altSeasonNumber, 2)}`; + } + + return { + alternateTitle, + title: alternateTitle.title, + suffix, + comment: alternateTitle.comment + }; + }); + + const groupedAlternateTitles = _.map(_.groupBy(reducedAlternateTitles, (item) => `${item.title} ${item.suffix}`), (group) => { + return { + title: group[0].title, + suffix: group[0].suffix, + comment: _.uniq(group.map((item) => item.comment)).join('/') + }; + }); + return ( { @@ -53,38 +86,23 @@ function SceneInfo(props) { { - alternateTitles.map((alternateTitle) => { - let suffix = ''; - - const altSceneSeasonNumber = sceneSeasonNumber === undefined ? seasonNumber : sceneSeasonNumber; - const altSceneEpisodeNumber = sceneEpisodeNumber === undefined ? episodeNumber : sceneEpisodeNumber; - - const mappingSeasonNumber = alternateTitle.sceneOrigin === 'tvdb' ? seasonNumber : altSceneSeasonNumber; - const altSeasonNumber = (alternateTitle.sceneSeasonNumber !== -1 && alternateTitle.sceneSeasonNumber !== undefined) ? alternateTitle.sceneSeasonNumber : mappingSeasonNumber; - const altEpisodeNumber = alternateTitle.sceneOrigin === 'tvdb' ? episodeNumber : altSceneEpisodeNumber; - - if (altEpisodeNumber !== altSceneEpisodeNumber) { - suffix = `S${padNumber(altSeasonNumber, 2)}E${padNumber(altEpisodeNumber, 2)}`; - } else if (altSeasonNumber !== altSceneSeasonNumber) { - suffix = `S${padNumber(altSeasonNumber, 2)}`; - } - + groupedAlternateTitles.map(({ title, suffix, comment }) => { return (
- {alternateTitle.title} + {title} { suffix && ({suffix}) } { - alternateTitle.comment && - {alternateTitle.comment} + comment && + {comment} }
); diff --git a/frontend/src/Utilities/Series/filterAlternateTitles.js b/frontend/src/Utilities/Series/filterAlternateTitles.js index b89ccce2a..52a6723c1 100644 --- a/frontend/src/Utilities/Series/filterAlternateTitles.js +++ b/frontend/src/Utilities/Series/filterAlternateTitles.js @@ -17,6 +17,7 @@ function filterAlternateTitles(alternateTitles, seriesTitle, useSceneNumbering, const hasAltSeasonNumber = (alternateTitle.seasonNumber !== -1 && alternateTitle.seasonNumber !== undefined); const hasAltSceneSeasonNumber = (alternateTitle.sceneSeasonNumber !== -1 && alternateTitle.sceneSeasonNumber !== undefined); + // Global alias that should be displayed global if (!hasAltSeasonNumber && !hasAltSceneSeasonNumber && (alternateTitle.title !== seriesTitle) && (!alternateTitle.sceneOrigin || !useSceneNumbering)) { @@ -24,9 +25,18 @@ function filterAlternateTitles(alternateTitles, seriesTitle, useSceneNumbering, return; } - if ((sceneSeasonNumber !== undefined && sceneSeasonNumber === alternateTitle.sceneSeasonNumber) || - (seasonNumber !== undefined && seasonNumber === alternateTitle.seasonNumber) || - (!hasAltSeasonNumber && !hasAltSceneSeasonNumber && alternateTitle.sceneOrigin && useSceneNumbering)) { + // Global alias that should be displayed per episode + if (!hasAltSeasonNumber && !hasAltSceneSeasonNumber && alternateTitle.sceneOrigin && useSceneNumbering) { + seasonTitles.push(alternateTitle); + return; + } + + // Apply the alternative mapping (release to scene) + const mappedAltSeasonNumber = hasAltSeasonNumber ? alternateTitle.seasonNumber : alternateTitle.sceneSeasonNumber; + // Select scene or tvdb on the episode + const mappedSeasonNumber = alternateTitle.sceneOrigin === 'tvdb' ? seasonNumber : sceneSeasonNumber; + + if (mappedSeasonNumber !== undefined && mappedSeasonNumber === mappedAltSeasonNumber) { seasonTitles.push(alternateTitle); return; } diff --git a/src/NzbDrone.Common/Extensions/NullableExtensions.cs b/src/NzbDrone.Common/Extensions/NullableExtensions.cs new file mode 100644 index 000000000..61f0f6592 --- /dev/null +++ b/src/NzbDrone.Common/Extensions/NullableExtensions.cs @@ -0,0 +1,15 @@ +namespace NzbDrone.Common.Extensions +{ + public static class NullableExtensions + { + public static int? NonNegative(this int? source) + { + if (source.HasValue && source.Value != -1) + { + return source; + } + + return null; + } + } +} diff --git a/src/NzbDrone.Core/IndexerSearch/NzbSearchService.cs b/src/NzbDrone.Core/IndexerSearch/NzbSearchService.cs index b6ce7eff4..d0f7f6c15 100644 --- a/src/NzbDrone.Core/IndexerSearch/NzbSearchService.cs +++ b/src/NzbDrone.Core/IndexerSearch/NzbSearchService.cs @@ -14,6 +14,7 @@ using System.Linq; using NzbDrone.Common.TPL; using NzbDrone.Common.Extensions; using NzbDrone.Core.Exceptions; +using NzbDrone.Core.Parser; namespace NzbDrone.Core.IndexerSearch { @@ -233,9 +234,36 @@ namespace NzbDrone.Core.IndexerSearch foreach (var sceneMapping in sceneMappings) { - if ((sceneMapping.SeasonNumber ?? -1) != -1 && sceneMapping.SeasonNumber != episode.SeasonNumber) + // There are two kinds of mappings: + // - Mapped on Release Season Number with sceneMapping.SceneSeasonNumber specified and optionally sceneMapping.SeasonNumber. This translates via episode.SceneSeasonNumber/SeasonNumber to specific episodes. + // - Mapped on Episode Season Number with optionally sceneMapping.SeasonNumber. This translates from episode.SceneSeasonNumber/SeasonNumber to specific releases. (Filter by episode.SeasonNumber or globally) + + var ignoreSceneNumbering = (sceneMapping.SceneOrigin == "tvdb" || sceneMapping.SceneOrigin == "unknown:tvdb"); + var mappingSceneSeasonNumber = sceneMapping.SceneSeasonNumber.NonNegative(); + var mappingSeasonNumber = sceneMapping.SeasonNumber.NonNegative(); + + // Select scene or tvdb on the episode + var mappedSeasonNumber = ignoreSceneNumbering ? episode.SeasonNumber : (episode.SceneSeasonNumber ?? episode.SeasonNumber); + var releaseSeasonNumber = sceneMapping.SceneSeasonNumber.NonNegative() ?? mappedSeasonNumber; + + if (mappingSceneSeasonNumber.HasValue) { - continue; + // Apply the alternative mapping (release to scene/tvdb) + var mappedAltSeasonNumber = sceneMapping.SeasonNumber.NonNegative() ?? sceneMapping.SceneSeasonNumber.NonNegative() ?? mappedSeasonNumber; + + // Check if the mapping applies to the current season + if (mappedAltSeasonNumber != mappedSeasonNumber) + { + continue; + } + } + else + { + // Check if the mapping applies to the current season + if (mappingSeasonNumber.HasValue && mappingSeasonNumber.Value != episode.SeasonNumber) + { + continue; + } } if (sceneMapping.ParseTerm == series.CleanTitle && sceneMapping.FilterRegex.IsNotNullOrWhiteSpace()) @@ -245,16 +273,16 @@ namespace NzbDrone.Core.IndexerSearch } // By default we do a alt title search in case indexers don't have the release properly indexed. Services can override this behavior. - var searchMode = sceneMapping.SearchMode ?? ((sceneMapping.SceneSeasonNumber ?? -1) != -1 ? SearchMode.SearchTitle : SearchMode.Default); + var searchMode = sceneMapping.SearchMode ?? ((mappingSceneSeasonNumber.HasValue && series.CleanTitle != sceneMapping.SearchTerm.CleanSeriesTitle()) ? SearchMode.SearchTitle : SearchMode.Default); - if (sceneMapping.SceneOrigin == "tvdb" || sceneMapping.SceneOrigin == "unknown:tvdb") + if (ignoreSceneNumbering) { yield return new SceneEpisodeMapping { Episode = episode, SearchMode = searchMode, SceneTitles = new List { sceneMapping.SearchTerm }, - SeasonNumber = (sceneMapping.SceneSeasonNumber ?? -1) == -1 ? episode.SeasonNumber : sceneMapping.SceneSeasonNumber.Value, + SeasonNumber = releaseSeasonNumber, EpisodeNumber = episode.EpisodeNumber, AbsoluteEpisodeNumber = episode.AbsoluteEpisodeNumber }; @@ -266,7 +294,7 @@ namespace NzbDrone.Core.IndexerSearch Episode = episode, SearchMode = searchMode, SceneTitles = new List { sceneMapping.SearchTerm }, - SeasonNumber = (sceneMapping.SceneSeasonNumber ?? -1) == -1 ? (episode.SceneSeasonNumber ?? episode.SeasonNumber) : sceneMapping.SceneSeasonNumber.Value, + SeasonNumber = releaseSeasonNumber, EpisodeNumber = episode.SceneEpisodeNumber ?? episode.EpisodeNumber, AbsoluteEpisodeNumber = episode.SceneAbsoluteEpisodeNumber ?? episode.AbsoluteEpisodeNumber };