mirror of
https://github.com/Radarr/Radarr
synced 2024-12-28 19:05:55 +00:00
Fixed: Multiple SeasonEpisode formats in the same pattern are now supported.
This commit is contained in:
parent
ec4dc89142
commit
49e2f26ffc
2 changed files with 151 additions and 117 deletions
|
@ -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()
|
||||
{
|
||||
|
|
|
@ -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,85 +112,18 @@ 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;
|
||||
|
||||
//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;
|
||||
}
|
||||
}
|
||||
AddEpisodeFileTokens(tokenHandlers, episodeFile);
|
||||
|
||||
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)
|
||||
|
|
Loading…
Reference in a new issue