|
|
|
@ -1,337 +1,3 @@
|
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.Globalization;
|
|
|
|
|
using System.IO;
|
|
|
|
|
using System.Linq;
|
|
|
|
|
using System.Text.RegularExpressions;
|
|
|
|
|
using NLog;
|
|
|
|
|
using NzbDrone.Common.Cache;
|
|
|
|
|
using NzbDrone.Common.EnsureThat;
|
|
|
|
|
using NzbDrone.Common.Extensions;
|
|
|
|
|
using NzbDrone.Core.MediaFiles;
|
|
|
|
|
using NzbDrone.Core.Qualities;
|
|
|
|
|
using NzbDrone.Core.Tv;
|
|
|
|
|
|
|
|
|
|
namespace NzbDrone.Core.Organizer
|
|
|
|
|
{
|
|
|
|
|
public interface IBuildFileNames
|
|
|
|
|
{
|
|
|
|
|
string BuildFileName(List<Episode> episodes, Series series, EpisodeFile episodeFile, NamingConfig namingConfig = null);
|
|
|
|
|
string BuildFileName(Movie movie, MovieFile movieFile, NamingConfig namingConfig = null);
|
|
|
|
|
string BuildFilePath(Movie movie, string fileName, string extension);
|
|
|
|
|
string BuildFilePath(Series series, int seasonNumber, string fileName, string extension);
|
|
|
|
|
string BuildSeasonPath(Series series, int seasonNumber);
|
|
|
|
|
BasicNamingConfig GetBasicNamingConfig(NamingConfig nameSpec);
|
|
|
|
|
string GetSeriesFolder(Series series, NamingConfig namingConfig = null);
|
|
|
|
|
string GetSeasonFolder(Series series, int seasonNumber, NamingConfig namingConfig = null);
|
|
|
|
|
string GetMovieFolder(Movie movie, NamingConfig namingConfig = null);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public class FileNameBuilder : IBuildFileNames
|
|
|
|
|
{
|
|
|
|
|
private readonly INamingConfigService _namingConfigService;
|
|
|
|
|
private readonly IQualityDefinitionService _qualityDefinitionService;
|
|
|
|
|
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>[- ._)\]]*)\}",
|
|
|
|
|
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
|
|
|
|
|
|
|
|
|
private static readonly Regex EpisodeRegex = new Regex(@"(?<episode>\{episode(?:\:0+)?})",
|
|
|
|
|
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
|
|
|
|
|
|
|
|
|
private static readonly Regex SeasonRegex = new Regex(@"(?<season>\{season(?:\:0+)?})",
|
|
|
|
|
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
|
|
|
|
|
|
|
|
|
private static readonly Regex AbsoluteEpisodeRegex = new Regex(@"(?<absolute>\{absolute(?:\:0+)?})",
|
|
|
|
|
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
|
|
|
|
|
|
|
|
|
public static readonly Regex SeasonEpisodePatternRegex = new Regex(@"(?<separator>(?<=})[- ._]+?)?(?<seasonEpisode>s?{season(?:\:0+)?}(?<episodeSeparator>[- ._]?[ex])(?<episode>{episode(?:\:0+)?}))(?<separator>[- ._]+?(?={))?",
|
|
|
|
|
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
|
|
|
|
|
|
|
|
|
public static readonly Regex AbsoluteEpisodePatternRegex = new Regex(@"(?<separator>(?<=})[- ._]+?)?(?<absolute>{absolute(?:\:0+)?})(?<separator>[- ._]+?(?={))?",
|
|
|
|
|
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
|
|
|
|
|
|
|
|
|
public static readonly Regex AirDateRegex = new Regex(@"\{Air(\s|\W|_)Date\}", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
|
|
|
|
|
|
|
|
|
public static readonly Regex SeriesTitleRegex = new Regex(@"(?<token>\{(?:Series)(?<separator>[- ._])(Clean)?Title\})",
|
|
|
|
|
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
|
|
|
|
|
|
|
|
|
public static readonly Regex MovieTitleRegex = new Regex(@"(?<token>\{((?:(Movie|Original))(?<separator>[- ._])(Clean)?Title(The)?)\})",
|
|
|
|
|
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
|
|
|
|
|
|
|
|
|
private static readonly Regex FileNameCleanupRegex = new Regex(@"([- ._])(\1)+", RegexOptions.Compiled);
|
|
|
|
|
private static readonly Regex TrimSeparatorsRegex = new Regex(@"[- ._]$", RegexOptions.Compiled);
|
|
|
|
|
|
|
|
|
|
private static readonly Regex ScenifyRemoveChars = new Regex(@"(?<=\s)(,|<|>|\/|\\|;|:|'|""|\||`|~|!|\?|@|$|%|^|\*|-|_|=){1}(?=\s)|('|:|\?|,)(?=(?:(?:s|m)\s)|\s|$)|(\(|\)|\[|\]|\{|\})", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
|
|
|
|
private static readonly Regex ScenifyReplaceChars = new Regex(@"[\/]", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
|
|
|
|
|
|
|
|
|
//TODO: Support Written numbers (One, Two, etc) and Roman Numerals (I, II, III etc)
|
|
|
|
|
private static readonly Regex MultiPartCleanupRegex = new Regex(@"(?:\(\d+\)|(Part|Pt\.?)\s?\d+)$", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
|
|
|
|
|
|
|
|
|
private static readonly char[] EpisodeTitleTrimCharacters = new[] { ' ', '.', '?' };
|
|
|
|
|
|
|
|
|
|
public FileNameBuilder(INamingConfigService namingConfigService,
|
|
|
|
|
IQualityDefinitionService qualityDefinitionService,
|
|
|
|
|
ICacheManager cacheManager,
|
|
|
|
|
Logger logger)
|
|
|
|
|
{
|
|
|
|
|
_namingConfigService = namingConfigService;
|
|
|
|
|
_qualityDefinitionService = qualityDefinitionService;
|
|
|
|
|
//_movieFormatCache = cacheManager.GetCache<MovieFormat>(GetType(), "movieFormat");
|
|
|
|
|
_episodeFormatCache = cacheManager.GetCache<EpisodeFormat[]>(GetType(), "episodeFormat");
|
|
|
|
|
_absoluteEpisodeFormatCache = cacheManager.GetCache<AbsoluteEpisodeFormat[]>(GetType(), "absoluteEpisodeFormat");
|
|
|
|
|
_logger = logger;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public string BuildFileName(List<Episode> episodes, Series series, EpisodeFile episodeFile, NamingConfig namingConfig = null)
|
|
|
|
|
{
|
|
|
|
|
if (namingConfig == null)
|
|
|
|
|
{
|
|
|
|
|
namingConfig = _namingConfigService.GetConfig();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!namingConfig.RenameEpisodes)
|
|
|
|
|
{
|
|
|
|
|
return GetOriginalTitle(episodeFile);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (namingConfig.StandardEpisodeFormat.IsNullOrWhiteSpace() && series.SeriesType == SeriesTypes.Standard)
|
|
|
|
|
{
|
|
|
|
|
throw new NamingFormatException("Standard episode format cannot be empty");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (namingConfig.DailyEpisodeFormat.IsNullOrWhiteSpace() && series.SeriesType == SeriesTypes.Daily)
|
|
|
|
|
{
|
|
|
|
|
throw new NamingFormatException("Daily episode format cannot be empty");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (namingConfig.AnimeEpisodeFormat.IsNullOrWhiteSpace() && series.SeriesType == SeriesTypes.Anime)
|
|
|
|
|
{
|
|
|
|
|
throw new NamingFormatException("Anime episode format cannot be empty");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var pattern = namingConfig.StandardEpisodeFormat;
|
|
|
|
|
var tokenHandlers = new Dictionary<string, Func<TokenMatch, string>>(FileNameBuilderTokenEqualityComparer.Instance);
|
|
|
|
|
|
|
|
|
|
episodes = episodes.OrderBy(e => e.SeasonNumber).ThenBy(e => e.EpisodeNumber).ToList();
|
|
|
|
|
|
|
|
|
|
if (series.SeriesType == SeriesTypes.Daily)
|
|
|
|
|
{
|
|
|
|
|
pattern = namingConfig.DailyEpisodeFormat;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (series.SeriesType == SeriesTypes.Anime && episodes.All(e => e.AbsoluteEpisodeNumber.HasValue))
|
|
|
|
|
{
|
|
|
|
|
pattern = namingConfig.AnimeEpisodeFormat;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pattern = AddSeasonEpisodeNumberingTokens(pattern, tokenHandlers, episodes, namingConfig);
|
|
|
|
|
pattern = AddAbsoluteNumberingTokens(pattern, tokenHandlers, series, episodes, namingConfig);
|
|
|
|
|
|
|
|
|
|
AddSeriesTokens(tokenHandlers, series);
|
|
|
|
|
AddEpisodeTokens(tokenHandlers, episodes);
|
|
|
|
|
AddEpisodeFileTokens(tokenHandlers, episodeFile);
|
|
|
|
|
AddQualityTokens(tokenHandlers, series, episodeFile);
|
|
|
|
|
AddMediaInfoTokens(tokenHandlers, episodeFile);
|
|
|
|
|
|
|
|
|
|
var fileName = ReplaceTokens(pattern, tokenHandlers, namingConfig).Trim();
|
|
|
|
|
fileName = FileNameCleanupRegex.Replace(fileName, match => match.Captures[0].Value[0].ToString());
|
|
|
|
|
fileName = TrimSeparatorsRegex.Replace(fileName, string.Empty);
|
|
|
|
|
|
|
|
|
|
return fileName;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public string BuildFileName(Movie movie, MovieFile movieFile, NamingConfig namingConfig = null)
|
|
|
|
|
{
|
|
|
|
|
if (namingConfig == null)
|
|
|
|
|
{
|
|
|
|
|
namingConfig = _namingConfigService.GetConfig();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!namingConfig.RenameEpisodes)
|
|
|
|
|
{
|
|
|
|
|
return GetOriginalTitle(movieFile);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//TODO: Update namingConfig for Movies!
|
|
|
|
|
var pattern = namingConfig.StandardMovieFormat;
|
|
|
|
|
var tokenHandlers = new Dictionary<string, Func<TokenMatch, string>>(FileNameBuilderTokenEqualityComparer.Instance);
|
|
|
|
|
|
|
|
|
|
AddMovieTokens(tokenHandlers, movie);
|
|
|
|
|
AddReleaseDateTokens(tokenHandlers, movie.Year); //In case we want to separate the year
|
|
|
|
|
AddImdbIdTokens(tokenHandlers, movie.ImdbId);
|
|
|
|
|
AddQualityTokens(tokenHandlers, movie, movieFile);
|
|
|
|
|
AddMediaInfoTokens(tokenHandlers, movieFile);
|
|
|
|
|
AddMovieFileTokens(tokenHandlers, movieFile);
|
|
|
|
|
|
|
|
|
|
var fileName = ReplaceTokens(pattern, tokenHandlers, namingConfig).Trim();
|
|
|
|
|
fileName = FileNameCleanupRegex.Replace(fileName, match => match.Captures[0].Value[0].ToString());
|
|
|
|
|
fileName = TrimSeparatorsRegex.Replace(fileName, string.Empty);
|
|
|
|
|
|
|
|
|
|
return fileName;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public string BuildFilePath(Series series, int seasonNumber, string fileName, string extension)
|
|
|
|
|
{
|
|
|
|
|
Ensure.That(extension, () => extension).IsNotNullOrWhiteSpace();
|
|
|
|
|
|
|
|
|
|
var path = BuildSeasonPath(series, seasonNumber);
|
|
|
|
|
|
|
|
|
|
return Path.Combine(path, fileName + extension);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public string BuildFilePath(Movie movie, string fileName, string extension)
|
|
|
|
|
{
|
|
|
|
|
Ensure.That(extension, () => extension).IsNotNullOrWhiteSpace();
|
|
|
|
|
|
|
|
|
|
var path = movie.Path;
|
|
|
|
|
|
|
|
|
|
return Path.Combine(path, fileName + extension);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public string BuildSeasonPath(Series series, int seasonNumber)
|
|
|
|
|
{
|
|
|
|
|
var path = series.Path;
|
|
|
|
|
|
|
|
|
|
if (series.SeasonFolder)
|
|
|
|
|
{
|
|
|
|
|
if (seasonNumber == 0)
|
|
|
|
|
{
|
|
|
|
|
path = Path.Combine(path, "Specials");
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
var seasonFolder = GetSeasonFolder(series, seasonNumber);
|
|
|
|
|
|
|
|
|
|
seasonFolder = CleanFileName(seasonFolder);
|
|
|
|
|
|
|
|
|
|
path = Path.Combine(path, seasonFolder);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return path;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public BasicNamingConfig GetBasicNamingConfig(NamingConfig nameSpec)
|
|
|
|
|
{
|
|
|
|
|
return new BasicNamingConfig(); //For now let's be lazy
|
|
|
|
|
|
|
|
|
|
var episodeFormat = GetEpisodeFormat(nameSpec.StandardEpisodeFormat).LastOrDefault();
|
|
|
|
|
|
|
|
|
|
if (episodeFormat == null)
|
|
|
|
|
{
|
|
|
|
|
return new BasicNamingConfig();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var basicNamingConfig = new BasicNamingConfig
|
|
|
|
|
{
|
|
|
|
|
Separator = episodeFormat.Separator,
|
|
|
|
|
NumberStyle = episodeFormat.SeasonEpisodePattern
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var titleTokens = TitleRegex.Matches(nameSpec.StandardEpisodeFormat);
|
|
|
|
|
|
|
|
|
|
foreach (Match match in titleTokens)
|
|
|
|
|
{
|
|
|
|
|
var separator = match.Groups["separator"].Value;
|
|
|
|
|
var token = match.Groups["token"].Value;
|
|
|
|
|
|
|
|
|
|
if (!separator.Equals(" "))
|
|
|
|
|
{
|
|
|
|
|
basicNamingConfig.ReplaceSpaces = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (token.StartsWith("{Series", StringComparison.InvariantCultureIgnoreCase))
|
|
|
|
|
{
|
|
|
|
|
basicNamingConfig.IncludeSeriesTitle = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (token.StartsWith("{Episode", StringComparison.InvariantCultureIgnoreCase))
|
|
|
|
|
{
|
|
|
|
|
basicNamingConfig.IncludeEpisodeTitle = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (token.StartsWith("{Quality", StringComparison.InvariantCultureIgnoreCase))
|
|
|
|
|
{
|
|
|
|
|
basicNamingConfig.IncludeQuality = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return basicNamingConfig;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public string GetSeriesFolder(Series series, NamingConfig namingConfig = null)
|
|
|
|
|
{
|
|
|
|
|
if (namingConfig == null)
|
|
|
|
|
{
|
|
|
|
|
namingConfig = _namingConfigService.GetConfig();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var tokenHandlers = new Dictionary<string, Func<TokenMatch, string>>(FileNameBuilderTokenEqualityComparer.Instance);
|
|
|
|
|
|
|
|
|
|
AddSeriesTokens(tokenHandlers, series);
|
|
|
|
|
|
|
|
|
|
return CleanFolderName(ReplaceTokens(namingConfig.SeriesFolderFormat, tokenHandlers, namingConfig));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public string GetSeasonFolder(Series series, int seasonNumber, NamingConfig namingConfig = null)
|
|
|
|
|
{
|
|
|
|
|
if (namingConfig == null)
|
|
|
|
|
{
|
|
|
|
|
namingConfig = _namingConfigService.GetConfig();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var tokenHandlers = new Dictionary<string, Func<TokenMatch, string>>(FileNameBuilderTokenEqualityComparer.Instance);
|
|
|
|
|
|
|
|
|
|
AddSeriesTokens(tokenHandlers, series);
|
|
|
|
|
AddSeasonTokens(tokenHandlers, seasonNumber);
|
|
|
|
|
|
|
|
|
|
return CleanFolderName(ReplaceTokens(namingConfig.SeasonFolderFormat, tokenHandlers, namingConfig));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public string GetMovieFolder(Movie movie, NamingConfig namingConfig = null)
|
|
|
|
|
{
|
|
|
|
|
if(namingConfig == null)
|
|
|
|
|
{
|
|
|
|
|
namingConfig = _namingConfigService.GetConfig();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var tokenHandlers = new Dictionary<string, Func<TokenMatch, string>>(FileNameBuilderTokenEqualityComparer.Instance);
|
|
|
|
|
|
|
|
|
|
AddMovieTokens(tokenHandlers, movie);
|
|
|
|
|
AddReleaseDateTokens(tokenHandlers, movie.Year);
|
|
|
|
|
AddImdbIdTokens(tokenHandlers, movie.ImdbId);
|
|
|
|
|
|
|
|
|
|
return CleanFolderName(ReplaceTokens(namingConfig.MovieFolderFormat, tokenHandlers, namingConfig));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static string CleanTitle(string title)
|
|
|
|
|
{
|
|
|
|
|
title = title.Replace("&", "and");
|
|
|
|
|
title = ScenifyReplaceChars.Replace(title, " ");
|
|
|
|
|
title = ScenifyRemoveChars.Replace(title, string.Empty);
|
|
|
|
|
|
|
|
|
|
return title;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static string TitleThe(string title)
|
|
|
|
|
{
|
|
|
|
|
string[] prefixes = { "The ", "An ", "A " };
|
|
|
|
|
foreach (string prefix in prefixes)
|
|
|
|
|
{
|
|
|
|
|
int prefix_length = prefix.Length;
|
|
|
|
|
if (prefix.ToLower() == title.Substring(0, prefix_length).ToLower())
|
|
|
|
|
{
|
|
|
|
|
title = title.Substring(prefix_length) + ", " + prefix.Trim();
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return title.Trim();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.Globalization;
|
|
|
|
@ -472,7 +138,7 @@ namespace NzbDrone.Core.Organizer
|
|
|
|
|
AddEpisodeFileTokens(tokenHandlers, episodeFile);
|
|
|
|
|
AddQualityTokens(tokenHandlers, series, episodeFile);
|
|
|
|
|
AddMediaInfoTokens(tokenHandlers, episodeFile);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var fileName = ReplaceTokens(pattern, tokenHandlers, namingConfig).Trim();
|
|
|
|
|
fileName = FileNameCleanupRegex.Replace(fileName, match => match.Captures[0].Value[0].ToString());
|
|
|
|
|
fileName = TrimSeparatorsRegex.Replace(fileName, string.Empty);
|
|
|
|
@ -564,10 +230,10 @@ namespace NzbDrone.Core.Organizer
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var basicNamingConfig = new BasicNamingConfig
|
|
|
|
|
{
|
|
|
|
|
Separator = episodeFormat.Separator,
|
|
|
|
|
NumberStyle = episodeFormat.SeasonEpisodePattern
|
|
|
|
|
};
|
|
|
|
|
{
|
|
|
|
|
Separator = episodeFormat.Separator,
|
|
|
|
|
NumberStyle = episodeFormat.SeasonEpisodePattern
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var titleTokens = TitleRegex.Matches(nameSpec.StandardEpisodeFormat);
|
|
|
|
|
|
|
|
|
@ -631,7 +297,7 @@ namespace NzbDrone.Core.Organizer
|
|
|
|
|
|
|
|
|
|
public string GetMovieFolder(Movie movie, NamingConfig namingConfig = null)
|
|
|
|
|
{
|
|
|
|
|
if(namingConfig == null)
|
|
|
|
|
if (namingConfig == null)
|
|
|
|
|
{
|
|
|
|
|
namingConfig = _namingConfigService.GetConfig();
|
|
|
|
|
}
|
|
|
|
@ -654,6 +320,20 @@ namespace NzbDrone.Core.Organizer
|
|
|
|
|
return title;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static string TitleThe(string title)
|
|
|
|
|
{
|
|
|
|
|
string[] prefixes = { "The ", "An ", "A " };
|
|
|
|
|
foreach (string prefix in prefixes)
|
|
|
|
|
{
|
|
|
|
|
int prefix_length = prefix.Length;
|
|
|
|
|
if (prefix.ToLower() == title.Substring(0, prefix_length).ToLower())
|
|
|
|
|
{
|
|
|
|
|
title = title.Substring(prefix_length) + ", " + prefix.Trim();
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return title.Trim();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static string CleanFileName(string name, bool replace = true)
|
|
|
|
@ -763,7 +443,7 @@ namespace NzbDrone.Core.Organizer
|
|
|
|
|
var absoluteEpisodePattern = absoluteEpisodeFormat.AbsoluteEpisodePattern;
|
|
|
|
|
string formatPattern;
|
|
|
|
|
|
|
|
|
|
switch ((MultiEpisodeStyle) namingConfig.MultiEpisodeStyle)
|
|
|
|
|
switch ((MultiEpisodeStyle)namingConfig.MultiEpisodeStyle)
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
case MultiEpisodeStyle.Duplicate:
|
|
|
|
@ -786,14 +466,14 @@ namespace NzbDrone.Core.Organizer
|
|
|
|
|
case MultiEpisodeStyle.Range:
|
|
|
|
|
case MultiEpisodeStyle.PrefixedRange:
|
|
|
|
|
formatPattern = "-" + absoluteEpisodeFormat.AbsoluteEpisodePattern;
|
|
|
|
|
var eps = new List<Episode> {episodes.First()};
|
|
|
|
|
var eps = new List<Episode> { episodes.First() };
|
|
|
|
|
|
|
|
|
|
if (episodes.Count > 1) eps.Add(episodes.Last());
|
|
|
|
|
|
|
|
|
|
absoluteEpisodePattern = FormatAbsoluteNumberTokens(absoluteEpisodePattern, formatPattern, eps);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
//MultiEpisodeStyle.Extend
|
|
|
|
|
//MultiEpisodeStyle.Extend
|
|
|
|
|
default:
|
|
|
|
|
formatPattern = "-" + absoluteEpisodeFormat.AbsoluteEpisodePattern;
|
|
|
|
|
absoluteEpisodePattern = FormatAbsoluteNumberTokens(absoluteEpisodePattern, formatPattern, episodes);
|
|
|
|
@ -1241,7 +921,7 @@ namespace NzbDrone.Core.Organizer
|
|
|
|
|
|
|
|
|
|
private AbsoluteEpisodeFormat[] GetAbsoluteFormat(string pattern)
|
|
|
|
|
{
|
|
|
|
|
return _absoluteEpisodeFormatCache.Get(pattern, () => AbsoluteEpisodePatternRegex.Matches(pattern).OfType<Match>()
|
|
|
|
|
return _absoluteEpisodeFormatCache.Get(pattern, () => AbsoluteEpisodePatternRegex.Matches(pattern).OfType<Match>()
|
|
|
|
|
.Select(match => new AbsoluteEpisodeFormat
|
|
|
|
|
{
|
|
|
|
|
Separator = match.Groups["separator"].Value.IsNotNullOrWhiteSpace() ? match.Groups["separator"].Value : "-",
|
|
|
|
@ -1396,4 +1076,4 @@ namespace NzbDrone.Core.Organizer
|
|
|
|
|
Range = 4,
|
|
|
|
|
PrefixedRange = 5
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|