New: Ability to use Original Title only in naming settings

pull/3113/head
Mark McDowall 10 years ago
parent 4026a0f096
commit 8711c41649

@ -327,7 +327,6 @@
<Content Include="Files\RSS\fanzub.xml"> <Content Include="Files\RSS\fanzub.xml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory> <CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content> </Content>
<Content Include="License.txt" />
<None Include="..\NzbDrone.Test.Common\App.config"> <None Include="..\NzbDrone.Test.Common\App.config">
<Link>App.config</Link> <Link>App.config</Link>
</None> </None>

@ -384,7 +384,7 @@ namespace NzbDrone.Core.Test.OrganizerTests
} }
[Test] [Test]
public void should_be_able_to_use_orginal_title() public void should_be_able_to_use_original_title()
{ {
_series.Title = "30 Rock"; _series.Title = "30 Rock";
_namingConfig.StandardEpisodeFormat = "{Series Title} - {Original Title}"; _namingConfig.StandardEpisodeFormat = "{Series Title} - {Original Title}";
@ -618,15 +618,16 @@ namespace NzbDrone.Core.Test.OrganizerTests
} }
[Test] [Test]
public void should_use_empty_string_instead_of_null_when_scene_name_is_not_available() public void should_use_existing_filename_when_scene_name_is_not_available()
{ {
_namingConfig.RenameEpisodes = true; _namingConfig.RenameEpisodes = true;
_namingConfig.StandardEpisodeFormat = "{Original Title}"; _namingConfig.StandardEpisodeFormat = "{Original Title}";
_episodeFile.SceneName = null; _episodeFile.SceneName = null;
_episodeFile.RelativePath = "existing.file.mkv";
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile) Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
.Should().Be(String.Empty); .Should().Be(Path.GetFileNameWithoutExtension(_episodeFile.RelativePath));
} }
[Test] [Test]
@ -640,6 +641,19 @@ namespace NzbDrone.Core.Test.OrganizerTests
.Should().Be("South Park - S15E06 - S15E07 - (HDTV-720p, , DRONE) - City Sushi"); .Should().Be("South Park - S15E06 - S15E07 - (HDTV-720p, , DRONE) - City Sushi");
} }
[Test]
public void should_be_able_to_use_only_original_title()
{
_series.Title = "30 Rock";
_namingConfig.StandardEpisodeFormat = "{Original Title}";
_episodeFile.SceneName = "30.Rock.S01E01.xvid-LOL";
_episodeFile.RelativePath = "30 Rock - S01E01 - Test";
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
.Should().Be("30.Rock.S01E01.xvid-LOL");
}
[Test] [Test]
public void should_allow_period_between_season_and_episode() public void should_allow_period_between_season_and_episode()
{ {

@ -53,6 +53,9 @@ namespace NzbDrone.Core.Organizer
public static readonly Regex SeriesTitleRegex = new Regex(@"(?<token>\{(?:Series)(?<separator>[- ._])(Clean)?Title\})", public static readonly Regex SeriesTitleRegex = new Regex(@"(?<token>\{(?:Series)(?<separator>[- ._])(Clean)?Title\})",
RegexOptions.Compiled | RegexOptions.IgnoreCase); RegexOptions.Compiled | RegexOptions.IgnoreCase);
private static readonly Regex OriginalTitleRegex = new Regex(@"(\^{original[- ._]title\}$)",
RegexOptions.Compiled | RegexOptions.IgnoreCase);
private static readonly Regex FileNameCleanupRegex = new Regex(@"\.{2,}", RegexOptions.Compiled); private static readonly Regex FileNameCleanupRegex = new Regex(@"\.{2,}", RegexOptions.Compiled);
private static readonly char[] EpisodeTitleTrimCharacters = new[] { ' ', '.', '?' }; private static readonly char[] EpisodeTitleTrimCharacters = new[] { ' ', '.', '?' };
@ -78,27 +81,22 @@ namespace NzbDrone.Core.Organizer
if (!namingConfig.RenameEpisodes) if (!namingConfig.RenameEpisodes)
{ {
if (episodeFile.SceneName.IsNullOrWhiteSpace()) return GetOriginalTitle(episodeFile);
{
if (episodeFile.RelativePath.IsNullOrWhiteSpace())
{
return Path.GetFileNameWithoutExtension(episodeFile.Path);
}
return Path.GetFileNameWithoutExtension(episodeFile.RelativePath);
}
return episodeFile.SceneName;
} }
if (namingConfig.StandardEpisodeFormat.IsNullOrWhiteSpace() && series.SeriesType == SeriesTypes.Standard) if (namingConfig.StandardEpisodeFormat.IsNullOrWhiteSpace() && series.SeriesType == SeriesTypes.Standard)
{ {
throw new NamingFormatException("Standard episode format cannot be null"); throw new NamingFormatException("Standard episode format cannot be empty");
} }
if (namingConfig.DailyEpisodeFormat.IsNullOrWhiteSpace() && series.SeriesType == SeriesTypes.Daily) if (namingConfig.DailyEpisodeFormat.IsNullOrWhiteSpace() && series.SeriesType == SeriesTypes.Daily)
{ {
throw new NamingFormatException("Daily episode format cannot be null"); 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 pattern = namingConfig.StandardEpisodeFormat;
@ -124,10 +122,10 @@ namespace NzbDrone.Core.Organizer
AddEpisodeFileTokens(tokenHandlers, series, episodeFile); AddEpisodeFileTokens(tokenHandlers, series, episodeFile);
AddMediaInfoTokens(tokenHandlers, episodeFile); 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());
return filename; return fileName;
} }
public string BuildFilePath(Series series, int seasonNumber, string fileName, string extension) public string BuildFilePath(Series series, int seasonNumber, string fileName, string extension)
@ -398,7 +396,7 @@ namespace NzbDrone.Core.Organizer
private void AddEpisodeFileTokens(Dictionary<String, Func<TokenMatch, String>> tokenHandlers, Series series, EpisodeFile episodeFile) private void AddEpisodeFileTokens(Dictionary<String, Func<TokenMatch, String>> tokenHandlers, Series series, EpisodeFile episodeFile)
{ {
tokenHandlers["{Original Title}"] = m => episodeFile.SceneName ?? String.Empty; tokenHandlers["{Original Title}"] = m => GetOriginalTitle(episodeFile);
tokenHandlers["{Release Group}"] = m => episodeFile.ReleaseGroup ?? "DRONE"; tokenHandlers["{Release Group}"] = m => episodeFile.ReleaseGroup ?? "DRONE";
tokenHandlers["{Quality Title}"] = m => GetQualityTitle(series, episodeFile.Quality); tokenHandlers["{Quality Title}"] = m => GetQualityTitle(series, episodeFile.Quality);
} }
@ -556,17 +554,6 @@ namespace NzbDrone.Core.Organizer
return replacementText; return replacementText;
} }
private sealed class TokenMatch
{
public Match RegexMatch { get; set; }
public String Prefix { get; set; }
public String Separator { get; set; }
public String Suffix { get; set; }
public String Token { get; set; }
public String CustomFormat { get; set; }
}
private string ReplaceNumberTokens(string pattern, List<Episode> episodes) private string ReplaceNumberTokens(string pattern, List<Episode> episodes)
{ {
var episodeIndex = 0; var episodeIndex = 0;
@ -665,6 +652,31 @@ namespace NzbDrone.Core.Organizer
return _qualityDefinitionService.Get(quality.Quality).Title + qualitySuffix; return _qualityDefinitionService.Get(quality.Quality).Title + qualitySuffix;
} }
private String GetOriginalTitle(EpisodeFile episodeFile)
{
if (episodeFile.SceneName.IsNullOrWhiteSpace())
{
if (episodeFile.RelativePath.IsNullOrWhiteSpace())
{
return Path.GetFileNameWithoutExtension(episodeFile.Path);
}
return Path.GetFileNameWithoutExtension(episodeFile.RelativePath);
}
return episodeFile.SceneName;
}
}
internal sealed class TokenMatch
{
public Match RegexMatch { get; set; }
public String Prefix { get; set; }
public String Separator { get; set; }
public String Suffix { get; set; }
public String Token { get; set; }
public String CustomFormat { get; set; }
} }
public enum MultiEpisodeStyle public enum MultiEpisodeStyle

@ -10,10 +10,13 @@ namespace NzbDrone.Core.Organizer
private static readonly Regex SeasonFolderRegex = new Regex(@"(\{season(\:\d+)?\})", private static readonly Regex SeasonFolderRegex = new Regex(@"(\{season(\:\d+)?\})",
RegexOptions.Compiled | RegexOptions.IgnoreCase); RegexOptions.Compiled | RegexOptions.IgnoreCase);
internal static readonly Regex OriginalTitleRegex = new Regex(@"(\{original[- ._]title\})",
RegexOptions.Compiled | RegexOptions.IgnoreCase);
public static IRuleBuilderOptions<T, string> ValidEpisodeFormat<T>(this IRuleBuilder<T, string> ruleBuilder) public static IRuleBuilderOptions<T, string> ValidEpisodeFormat<T>(this IRuleBuilder<T, string> ruleBuilder)
{ {
ruleBuilder.SetValidator(new NotEmptyValidator(null)); ruleBuilder.SetValidator(new NotEmptyValidator(null));
return ruleBuilder.SetValidator(new RegularExpressionValidator(FileNameBuilder.SeasonEpisodePatternRegex)).WithMessage("Must contain season and episode numbers"); return ruleBuilder.SetValidator(new ValidStandardEpisodeFormatValidator());
} }
public static IRuleBuilderOptions<T, string> ValidDailyEpisodeFormat<T>(this IRuleBuilder<T, string> ruleBuilder) public static IRuleBuilderOptions<T, string> ValidDailyEpisodeFormat<T>(this IRuleBuilder<T, string> ruleBuilder)
@ -41,10 +44,32 @@ namespace NzbDrone.Core.Organizer
} }
} }
public class ValidStandardEpisodeFormatValidator : PropertyValidator
{
public ValidStandardEpisodeFormatValidator()
: base("Must contain season and episode numbers OR Original Title")
{
}
protected override bool IsValid(PropertyValidatorContext context)
{
var value = context.PropertyValue as String;
if (!FileNameBuilder.SeasonEpisodePatternRegex.IsMatch(value) &&
!FileNameValidation.OriginalTitleRegex.IsMatch(value))
{
return false;
}
return true;
}
}
public class ValidDailyEpisodeFormatValidator : PropertyValidator public class ValidDailyEpisodeFormatValidator : PropertyValidator
{ {
public ValidDailyEpisodeFormatValidator() public ValidDailyEpisodeFormatValidator()
: base("Must contain Air Date or Season and Episode") : base("Must contain Air Date OR Season and Episode OR Original Title")
{ {
} }
@ -54,7 +79,8 @@ namespace NzbDrone.Core.Organizer
var value = context.PropertyValue as String; var value = context.PropertyValue as String;
if (!FileNameBuilder.SeasonEpisodePatternRegex.IsMatch(value) && if (!FileNameBuilder.SeasonEpisodePatternRegex.IsMatch(value) &&
!FileNameBuilder.AirDateRegex.IsMatch(value)) !FileNameBuilder.AirDateRegex.IsMatch(value) &&
!FileNameValidation.OriginalTitleRegex.IsMatch(value))
{ {
return false; return false;
} }
@ -66,7 +92,7 @@ namespace NzbDrone.Core.Organizer
public class ValidAnimeEpisodeFormatValidator : PropertyValidator public class ValidAnimeEpisodeFormatValidator : PropertyValidator
{ {
public ValidAnimeEpisodeFormatValidator() public ValidAnimeEpisodeFormatValidator()
: base("Must contain Absolute Episode number or Season and Episode") : base("Must contain Absolute Episode number OR Season and Episode OR Original Title")
{ {
} }
@ -76,7 +102,8 @@ namespace NzbDrone.Core.Organizer
var value = context.PropertyValue as String; var value = context.PropertyValue as String;
if (!FileNameBuilder.SeasonEpisodePatternRegex.IsMatch(value) && if (!FileNameBuilder.SeasonEpisodePatternRegex.IsMatch(value) &&
!FileNameBuilder.AbsoluteEpisodePatternRegex.IsMatch(value)) !FileNameBuilder.AbsoluteEpisodePatternRegex.IsMatch(value) &&
!FileNameValidation.OriginalTitleRegex.IsMatch(value))
{ {
return false; return false;
} }

@ -60,7 +60,7 @@ namespace NzbDrone.Core.Organizer
EpisodeNumber = 1, EpisodeNumber = 1,
Title = "Episode Title (1)", Title = "Episode Title (1)",
AirDate = "2013-10-30", AirDate = "2013-10-30",
AbsoluteEpisodeNumber = 1 AbsoluteEpisodeNumber = 1,
}; };
_episode2 = new Episode _episode2 = new Episode
@ -94,6 +94,7 @@ namespace NzbDrone.Core.Organizer
{ {
Quality = new QualityModel(Quality.HDTV720p), Quality = new QualityModel(Quality.HDTV720p),
RelativePath = "Series.Title.S01E01.720p.HDTV.x264-EVOLVE.mkv", RelativePath = "Series.Title.S01E01.720p.HDTV.x264-EVOLVE.mkv",
SceneName = "Series.Title.S01E01.720p.HDTV.x264-EVOLVE",
ReleaseGroup = "RlsGrp", ReleaseGroup = "RlsGrp",
MediaInfo = mediaInfo MediaInfo = mediaInfo
}; };
@ -102,14 +103,16 @@ namespace NzbDrone.Core.Organizer
{ {
Quality = new QualityModel(Quality.HDTV720p), Quality = new QualityModel(Quality.HDTV720p),
RelativePath = "Series.Title.S01E01-E02.720p.HDTV.x264-EVOLVE.mkv", RelativePath = "Series.Title.S01E01-E02.720p.HDTV.x264-EVOLVE.mkv",
SceneName = "Series.Title.S01E01-E02.720p.HDTV.x264-EVOLVE",
ReleaseGroup = "RlsGrp", ReleaseGroup = "RlsGrp",
MediaInfo = mediaInfo MediaInfo = mediaInfo,
}; };
_dailyEpisodeFile = new EpisodeFile _dailyEpisodeFile = new EpisodeFile
{ {
Quality = new QualityModel(Quality.HDTV720p), Quality = new QualityModel(Quality.HDTV720p),
RelativePath = "Series.Title.2013.10.30.HDTV.x264-EVOLVE.mkv", RelativePath = "Series.Title.2013.10.30.HDTV.x264-EVOLVE.mkv",
SceneName = "Series.Title.2013.10.30.HDTV.x264-EVOLVE",
ReleaseGroup = "RlsGrp", ReleaseGroup = "RlsGrp",
MediaInfo = mediaInfo MediaInfo = mediaInfo
}; };
@ -118,6 +121,7 @@ namespace NzbDrone.Core.Organizer
{ {
Quality = new QualityModel(Quality.HDTV720p), Quality = new QualityModel(Quality.HDTV720p),
RelativePath = "Series.Title.001.HDTV.x264-EVOLVE.mkv", RelativePath = "Series.Title.001.HDTV.x264-EVOLVE.mkv",
SceneName = "Series.Title.001.HDTV.x264-EVOLVE",
ReleaseGroup = "RlsGrp", ReleaseGroup = "RlsGrp",
MediaInfo = mediaInfoAnime MediaInfo = mediaInfoAnime
}; };

Loading…
Cancel
Save