Fixed: Multiple SeasonEpisode formats in the same pattern are now supported.

pull/2/head
Taloth Saldono 10 years ago
parent ec4dc89142
commit 49e2f26ffc

@ -487,6 +487,26 @@ namespace NzbDrone.Core.Test.OrganizerTests
.Should().Be("South.Park.100.City.Sushi"); .Should().Be("South.Park.100.City.Sushi");
} }
[Test]
public void should_replace_duplicate_numbering_individually()
{
_series.SeriesType = SeriesTypes.Anime;
_namingConfig.AnimeEpisodeFormat = "{Series.Title}.{season}x{episode:00}.{absolute:000}\\{Series.Title}.S{season:00}E{episode:00}.{absolute:00}.{Episode.Title}";
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
.Should().Be("South.Park.15x06.100\\South.Park.S15E06.100.City.Sushi");
}
[Test]
public void should_replace_individual_season_episode_tokens()
{
_series.SeriesType = SeriesTypes.Anime;
_namingConfig.AnimeEpisodeFormat = "{Series Title} Season {season:0000} Episode {episode:0000}\\{Series.Title}.S{season:00}E{episode:00}.{absolute:00}.{Episode.Title}";
Subject.BuildFileName(new List<Episode> { _episode1, _episode2 }, _series, _episodeFile)
.Should().Be("South Park Season 0015 Episode 0006-0007\\South.Park.S15E06-07.100-101.City.Sushi");
}
[Test] [Test]
public void should_use_dash_as_separator_when_multi_episode_style_is_extend_for_anime() public void should_use_dash_as_separator_when_multi_episode_style_is_extend_for_anime()
{ {

@ -26,7 +26,8 @@ namespace NzbDrone.Core.Organizer
{ {
private readonly INamingConfigService _namingConfigService; private readonly INamingConfigService _namingConfigService;
private readonly IQualityDefinitionService _qualityDefinitionService; private readonly IQualityDefinitionService _qualityDefinitionService;
private readonly ICached<EpisodeFormat> _patternCache; private readonly ICached<EpisodeFormat[]> _episodeFormatCache;
private readonly ICached<AbsoluteEpisodeFormat[]> _absoluteEpisodeFormatCache;
private readonly Logger _logger; private readonly Logger _logger;
private static readonly Regex TitleRegex = new Regex(@"\{(?<prefix>[- ._]*)(?<token>(?:[a-z0-9]+)(?:(?<separator>[- ._]+)(?:[a-z0-9]+))?)(?::(?<customFormat>[a-z0-9]+))?(?<suffix>[- ._]*)\}", private static readonly Regex TitleRegex = new Regex(@"\{(?<prefix>[- ._]*)(?<token>(?:[a-z0-9]+)(?:(?<separator>[- ._]+)(?:[a-z0-9]+))?)(?::(?<customFormat>[a-z0-9]+))?(?<suffix>[- ._]*)\}",
@ -63,7 +64,8 @@ namespace NzbDrone.Core.Organizer
{ {
_namingConfigService = namingConfigService; _namingConfigService = namingConfigService;
_qualityDefinitionService = qualityDefinitionService; _qualityDefinitionService = qualityDefinitionService;
_patternCache = cacheManager.GetCache<EpisodeFormat>(GetType()); _episodeFormatCache = cacheManager.GetCache<EpisodeFormat[]>(GetType(), "episodeFormat");
_absoluteEpisodeFormatCache = cacheManager.GetCache<AbsoluteEpisodeFormat[]>(GetType(), "absoluteEpisodeFormat");
_logger = logger; _logger = logger;
} }
@ -100,14 +102,6 @@ namespace NzbDrone.Core.Organizer
episodes = episodes.OrderBy(e => e.SeasonNumber).ThenBy(e => e.EpisodeNumber).ToList(); episodes = episodes.OrderBy(e => e.SeasonNumber).ThenBy(e => e.EpisodeNumber).ToList();
AddSeriesTokens(tokenHandlers, series);
AddEpisodeTokens(tokenHandlers, episodes);
AddEpisodeFileTokens(tokenHandlers, episodeFile);
AddMediaInfoTokens(tokenHandlers, episodeFile);
if (series.SeriesType == SeriesTypes.Daily) if (series.SeriesType == SeriesTypes.Daily)
{ {
pattern = namingConfig.DailyEpisodeFormat; pattern = namingConfig.DailyEpisodeFormat;
@ -118,85 +112,18 @@ namespace NzbDrone.Core.Organizer
pattern = namingConfig.AnimeEpisodeFormat; pattern = namingConfig.AnimeEpisodeFormat;
} }
var episodeFormat = GetEpisodeFormat(pattern); pattern = AddSeasonEpisodeNumberingTokens(pattern, tokenHandlers, episodes, namingConfig);
if (episodeFormat != null)
{
pattern = pattern.Replace(episodeFormat.SeasonEpisodePattern, "{Season Episode}");
var seasonEpisodePattern = episodeFormat.SeasonEpisodePattern;
foreach (var episode in episodes.Skip(1))
{
switch ((MultiEpisodeStyle)namingConfig.MultiEpisodeStyle)
{
case MultiEpisodeStyle.Duplicate:
seasonEpisodePattern += episodeFormat.Separator + episodeFormat.SeasonEpisodePattern;
break;
case MultiEpisodeStyle.Repeat:
seasonEpisodePattern += episodeFormat.EpisodeSeparator + episodeFormat.EpisodePattern;
break;
case MultiEpisodeStyle.Scene:
seasonEpisodePattern += "-" + episodeFormat.EpisodeSeparator + episodeFormat.EpisodePattern;
break;
//MultiEpisodeStyle.Extend
default:
seasonEpisodePattern += "-" + episodeFormat.EpisodePattern;
break;
}
}
seasonEpisodePattern = ReplaceNumberTokens(seasonEpisodePattern, episodes);
tokenHandlers["{Season Episode}"] = m => seasonEpisodePattern;
}
//TODO: Extract to another method
var absoluteEpisodeFormat = GetAbsoluteFormat(pattern);
if (absoluteEpisodeFormat != null) pattern = AddAbsoluteNumberingTokens(pattern, tokenHandlers, series, episodes, namingConfig);
{
if (series.SeriesType != SeriesTypes.Anime)
{
pattern = pattern.Replace(absoluteEpisodeFormat.AbsoluteEpisodePattern, "");
}
else AddSeriesTokens(tokenHandlers, series);
{
pattern = pattern.Replace(absoluteEpisodeFormat.AbsoluteEpisodePattern, "{Absolute Pattern}");
var absoluteEpisodePattern = absoluteEpisodeFormat.AbsoluteEpisodePattern;
foreach (var episode in episodes.Skip(1)) AddEpisodeTokens(tokenHandlers, episodes);
{
switch ((MultiEpisodeStyle)namingConfig.MultiEpisodeStyle)
{
case MultiEpisodeStyle.Duplicate:
absoluteEpisodePattern += absoluteEpisodeFormat.Separator +
absoluteEpisodeFormat.AbsoluteEpisodePattern;
break;
case MultiEpisodeStyle.Repeat:
absoluteEpisodePattern += absoluteEpisodeFormat.Separator +
absoluteEpisodeFormat.AbsoluteEpisodePattern;
break;
case MultiEpisodeStyle.Scene:
absoluteEpisodePattern += "-" + absoluteEpisodeFormat.AbsoluteEpisodePattern;
break;
//MultiEpisodeStyle.Extend
default:
absoluteEpisodePattern += "-" + absoluteEpisodeFormat.AbsoluteEpisodePattern;
break;
}
}
absoluteEpisodePattern = ReplaceAbsoluteNumberTokens(absoluteEpisodePattern, episodes); AddEpisodeFileTokens(tokenHandlers, episodeFile);
tokenHandlers["{Absolute Pattern}"] = m => absoluteEpisodePattern;
}
}
AddMediaInfoTokens(tokenHandlers, episodeFile);
var filename = ReplaceTokens(pattern, tokenHandlers).Trim(); var filename = ReplaceTokens(pattern, tokenHandlers).Trim();
filename = FileNameCleanupRegex.Replace(filename, match => match.Captures[0].Value[0].ToString()); filename = FileNameCleanupRegex.Replace(filename, match => match.Captures[0].Value[0].ToString());
@ -234,7 +161,7 @@ namespace NzbDrone.Core.Organizer
public BasicNamingConfig GetBasicNamingConfig(NamingConfig nameSpec) public BasicNamingConfig GetBasicNamingConfig(NamingConfig nameSpec)
{ {
var episodeFormat = GetEpisodeFormat(nameSpec.StandardEpisodeFormat); var episodeFormat = GetEpisodeFormat(nameSpec.StandardEpisodeFormat).LastOrDefault();
if (episodeFormat == null) if (episodeFormat == null)
{ {
@ -347,9 +274,112 @@ namespace NzbDrone.Core.Organizer
tokenHandlers["{Series CleanTitle}"] = m => CleanTitle(series.Title); tokenHandlers["{Series CleanTitle}"] = m => CleanTitle(series.Title);
} }
private String AddSeasonEpisodeNumberingTokens(String pattern, Dictionary<String, Func<TokenMatch, String>> tokenHandlers, List<Episode> episodes, NamingConfig namingConfig)
{
var episodeFormats = GetEpisodeFormat(pattern).DistinctBy(v => v.SeasonEpisodePattern).ToList();
int index = 1;
foreach (var episodeFormat in episodeFormats)
{
var seasonEpisodePattern = episodeFormat.SeasonEpisodePattern;
foreach (var episode in episodes.Skip(1))
{
switch ((MultiEpisodeStyle)namingConfig.MultiEpisodeStyle)
{
case MultiEpisodeStyle.Duplicate:
seasonEpisodePattern += episodeFormat.Separator + episodeFormat.SeasonEpisodePattern;
break;
case MultiEpisodeStyle.Repeat:
seasonEpisodePattern += episodeFormat.EpisodeSeparator + episodeFormat.EpisodePattern;
break;
case MultiEpisodeStyle.Scene:
seasonEpisodePattern += "-" + episodeFormat.EpisodeSeparator + episodeFormat.EpisodePattern;
break;
//MultiEpisodeStyle.Extend
default:
seasonEpisodePattern += "-" + episodeFormat.EpisodePattern;
break;
}
}
seasonEpisodePattern = ReplaceNumberTokens(seasonEpisodePattern, episodes);
var token = String.Format("{{Season Episode{0}}}", index++);
pattern = pattern.Replace(episodeFormat.SeasonEpisodePattern, token);
tokenHandlers[token] = m => seasonEpisodePattern;
}
AddSeasonTokens(tokenHandlers, episodes.First().SeasonNumber);
if (episodes.Count > 1)
{
tokenHandlers["{Episode}"] = m => episodes.First().EpisodeNumber.ToString(m.CustomFormat) + "-" + episodes.Last().EpisodeNumber.ToString(m.CustomFormat);
}
else
{
tokenHandlers["{Episode}"] = m => episodes.First().EpisodeNumber.ToString(m.CustomFormat);
}
return pattern;
}
private String AddAbsoluteNumberingTokens(String pattern, Dictionary<String, Func<TokenMatch, String>> tokenHandlers, Series series, List<Episode> episodes, NamingConfig namingConfig)
{
var absoluteEpisodeFormats = GetAbsoluteFormat(pattern).DistinctBy(v => v.AbsoluteEpisodePattern).ToList();
int index = 1;
foreach (var absoluteEpisodeFormat in absoluteEpisodeFormats)
{
if (series.SeriesType != SeriesTypes.Anime)
{
pattern = pattern.Replace(absoluteEpisodeFormat.AbsoluteEpisodePattern, "");
continue;
}
var absoluteEpisodePattern = absoluteEpisodeFormat.AbsoluteEpisodePattern;
foreach (var episode in episodes.Skip(1))
{
switch ((MultiEpisodeStyle)namingConfig.MultiEpisodeStyle)
{
case MultiEpisodeStyle.Duplicate:
absoluteEpisodePattern += absoluteEpisodeFormat.Separator +
absoluteEpisodeFormat.AbsoluteEpisodePattern;
break;
case MultiEpisodeStyle.Repeat:
absoluteEpisodePattern += absoluteEpisodeFormat.Separator +
absoluteEpisodeFormat.AbsoluteEpisodePattern;
break;
case MultiEpisodeStyle.Scene:
absoluteEpisodePattern += "-" + absoluteEpisodeFormat.AbsoluteEpisodePattern;
break;
//MultiEpisodeStyle.Extend
default:
absoluteEpisodePattern += "-" + absoluteEpisodeFormat.AbsoluteEpisodePattern;
break;
}
}
absoluteEpisodePattern = ReplaceAbsoluteNumberTokens(absoluteEpisodePattern, episodes);
var token = String.Format("{{Absolute Pattern{0}}}", index++);
pattern = pattern.Replace(absoluteEpisodeFormat.AbsoluteEpisodePattern, token);
tokenHandlers[token] = m => absoluteEpisodePattern;
}
return pattern;
}
private void AddSeasonTokens(Dictionary<String, Func<TokenMatch, String>> tokenHandlers, Int32 seasonNumber) private void AddSeasonTokens(Dictionary<String, Func<TokenMatch, String>> tokenHandlers, Int32 seasonNumber)
{ {
tokenHandlers["{Season}"] = m => seasonNumber.ToString(m.CustomFormat ?? "0"); tokenHandlers["{Season}"] = m => seasonNumber.ToString(m.CustomFormat);
} }
private void AddEpisodeTokens(Dictionary<String, Func<TokenMatch, String>> tokenHandlers, List<Episode> episodes) private void AddEpisodeTokens(Dictionary<String, Func<TokenMatch, String>> tokenHandlers, List<Episode> episodes)
@ -579,42 +609,26 @@ namespace NzbDrone.Core.Organizer
return value.ToString(split[1]); return value.ToString(split[1]);
} }
private EpisodeFormat GetEpisodeFormat(string pattern) private EpisodeFormat[] GetEpisodeFormat(string pattern)
{ {
return _patternCache.Get(pattern, () => return _episodeFormatCache.Get(pattern, () => SeasonEpisodePatternRegex.Matches(pattern).OfType<Match>()
{ .Select(match => new EpisodeFormat
var match = SeasonEpisodePatternRegex.Match(pattern);
if (match.Success)
{ {
return new EpisodeFormat EpisodeSeparator = match.Groups["episodeSeparator"].Value,
{ Separator = match.Groups["separator"].Value,
EpisodeSeparator = match.Groups["episodeSeparator"].Value, EpisodePattern = match.Groups["episode"].Value,
Separator = match.Groups["separator"].Value, SeasonEpisodePattern = match.Groups["seasonEpisode"].Value,
EpisodePattern = match.Groups["episode"].Value, }).ToArray());
SeasonEpisodePattern = match.Groups["seasonEpisode"].Value,
};
}
return null;
});
} }
private AbsoluteEpisodeFormat GetAbsoluteFormat(string pattern) private AbsoluteEpisodeFormat[] GetAbsoluteFormat(string pattern)
{ {
var match = AbsoluteEpisodePatternRegex.Match(pattern); return _absoluteEpisodeFormatCache.Get(pattern, () => AbsoluteEpisodePatternRegex.Matches(pattern).OfType<Match>()
.Select(match => new AbsoluteEpisodeFormat
if (match.Success) {
{ Separator = match.Groups["separator"].Value,
return new AbsoluteEpisodeFormat AbsoluteEpisodePattern = match.Groups["absolute"].Value
{ }).ToArray());
Separator = match.Groups["separator"].Value,
AbsoluteEpisodePattern = match.Groups["absolute"].Value
};
}
return null;
} }
private String GetEpisodeTitle(List<Episode> episodes) private String GetEpisodeTitle(List<Episode> episodes)

Loading…
Cancel
Save