1
0
Fork 0
mirror of https://github.com/Radarr/Radarr synced 2024-12-29 11:25:26 +00:00

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

This commit is contained in:
Taloth Saldono 2014-09-05 22:05:52 +02:00
parent ec4dc89142
commit 49e2f26ffc
2 changed files with 151 additions and 117 deletions

View file

@ -487,6 +487,26 @@ public void should_replace_absolute_numbering_when_series_is_anime()
.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]
public void should_use_dash_as_separator_when_multi_episode_style_is_extend_for_anime()
{

View file

@ -26,7 +26,8 @@ public class FileNameBuilder : IBuildFileNames
{
private readonly INamingConfigService _namingConfigService;
private readonly IQualityDefinitionService _qualityDefinitionService;
private readonly ICached<EpisodeFormat> _patternCache;
private readonly ICached<EpisodeFormat[]> _episodeFormatCache;
private readonly ICached<AbsoluteEpisodeFormat[]> _absoluteEpisodeFormatCache;
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>[- ._]*)\}",
@ -63,7 +64,8 @@ public FileNameBuilder(INamingConfigService namingConfigService,
{
_namingConfigService = namingConfigService;
_qualityDefinitionService = qualityDefinitionService;
_patternCache = cacheManager.GetCache<EpisodeFormat>(GetType());
_episodeFormatCache = cacheManager.GetCache<EpisodeFormat[]>(GetType(), "episodeFormat");
_absoluteEpisodeFormatCache = cacheManager.GetCache<AbsoluteEpisodeFormat[]>(GetType(), "absoluteEpisodeFormat");
_logger = logger;
}
@ -100,14 +102,6 @@ public string BuildFileName(List<Episode> episodes, Series series, EpisodeFile e
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)
{
pattern = namingConfig.DailyEpisodeFormat;
@ -118,84 +112,17 @@ public string BuildFileName(List<Episode> episodes, Series series, EpisodeFile e
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;
pattern = AddAbsoluteNumberingTokens(pattern, tokenHandlers, series, episodes, namingConfig);
foreach (var episode in episodes.Skip(1))
{
switch ((MultiEpisodeStyle)namingConfig.MultiEpisodeStyle)
{
case MultiEpisodeStyle.Duplicate:
seasonEpisodePattern += episodeFormat.Separator + episodeFormat.SeasonEpisodePattern;
break;
AddSeriesTokens(tokenHandlers, series);
case MultiEpisodeStyle.Repeat:
seasonEpisodePattern += episodeFormat.EpisodeSeparator + episodeFormat.EpisodePattern;
break;
AddEpisodeTokens(tokenHandlers, episodes);
case MultiEpisodeStyle.Scene:
seasonEpisodePattern += "-" + episodeFormat.EpisodeSeparator + episodeFormat.EpisodePattern;
break;
AddEpisodeFileTokens(tokenHandlers, episodeFile);
//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)
{
if (series.SeriesType != SeriesTypes.Anime)
{
pattern = pattern.Replace(absoluteEpisodeFormat.AbsoluteEpisodePattern, "");
}
else
{
pattern = pattern.Replace(absoluteEpisodeFormat.AbsoluteEpisodePattern, "{Absolute Pattern}");
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);
tokenHandlers["{Absolute Pattern}"] = m => absoluteEpisodePattern;
}
}
AddMediaInfoTokens(tokenHandlers, episodeFile);
var filename = ReplaceTokens(pattern, tokenHandlers).Trim();
filename = FileNameCleanupRegex.Replace(filename, match => match.Captures[0].Value[0].ToString());
@ -234,7 +161,7 @@ public string BuildFilePath(Series series, int seasonNumber, string fileName, st
public BasicNamingConfig GetBasicNamingConfig(NamingConfig nameSpec)
{
var episodeFormat = GetEpisodeFormat(nameSpec.StandardEpisodeFormat);
var episodeFormat = GetEpisodeFormat(nameSpec.StandardEpisodeFormat).LastOrDefault();
if (episodeFormat == null)
{
@ -347,9 +274,112 @@ private void AddSeriesTokens(Dictionary<String, Func<TokenMatch, String>> tokenH
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)
{
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)
@ -579,42 +609,26 @@ private string ReplaceNumberToken(string token, int value)
return value.ToString(split[1]);
}
private EpisodeFormat GetEpisodeFormat(string pattern)
private EpisodeFormat[] GetEpisodeFormat(string pattern)
{
return _patternCache.Get(pattern, () =>
{
var match = SeasonEpisodePatternRegex.Match(pattern);
if (match.Success)
return _episodeFormatCache.Get(pattern, () => SeasonEpisodePatternRegex.Matches(pattern).OfType<Match>()
.Select(match => new EpisodeFormat
{
return new EpisodeFormat
{
EpisodeSeparator = match.Groups["episodeSeparator"].Value,
Separator = match.Groups["separator"].Value,
EpisodePattern = match.Groups["episode"].Value,
SeasonEpisodePattern = match.Groups["seasonEpisode"].Value,
};
}
return null;
});
EpisodeSeparator = match.Groups["episodeSeparator"].Value,
Separator = match.Groups["separator"].Value,
EpisodePattern = match.Groups["episode"].Value,
SeasonEpisodePattern = match.Groups["seasonEpisode"].Value,
}).ToArray());
}
private AbsoluteEpisodeFormat GetAbsoluteFormat(string pattern)
private AbsoluteEpisodeFormat[] GetAbsoluteFormat(string pattern)
{
var match = AbsoluteEpisodePatternRegex.Match(pattern);
if (match.Success)
{
return new AbsoluteEpisodeFormat
{
Separator = match.Groups["separator"].Value,
AbsoluteEpisodePattern = match.Groups["absolute"].Value
};
}
return null;
return _absoluteEpisodeFormatCache.Get(pattern, () => AbsoluteEpisodePatternRegex.Matches(pattern).OfType<Match>()
.Select(match => new AbsoluteEpisodeFormat
{
Separator = match.Groups["separator"].Value,
AbsoluteEpisodePattern = match.Groups["absolute"].Value
}).ToArray());
}
private String GetEpisodeTitle(List<Episode> episodes)