Merge pull request #125 from Sonarr/original-title

New: Ability to use Original Title only in naming settings
This commit is contained in:
Mark McDowall 2014-11-05 16:43:47 -08:00
commit 48ec113598
5 changed files with 97 additions and 41 deletions

View File

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

View File

@ -384,7 +384,7 @@ namespace NzbDrone.Core.Test.OrganizerTests
}
[Test]
public void should_be_able_to_use_orginal_title()
public void should_be_able_to_use_original_title()
{
_series.Title = "30 Rock";
_namingConfig.StandardEpisodeFormat = "{Series Title} - {Original Title}";
@ -618,15 +618,16 @@ namespace NzbDrone.Core.Test.OrganizerTests
}
[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.StandardEpisodeFormat = "{Original Title}";
_episodeFile.SceneName = null;
_episodeFile.RelativePath = "existing.file.mkv";
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
.Should().Be(String.Empty);
.Should().Be(Path.GetFileNameWithoutExtension(_episodeFile.RelativePath));
}
[Test]
@ -640,6 +641,19 @@ namespace NzbDrone.Core.Test.OrganizerTests
.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]
public void should_allow_period_between_season_and_episode()
{

View File

@ -53,6 +53,9 @@ namespace NzbDrone.Core.Organizer
public static readonly Regex SeriesTitleRegex = new Regex(@"(?<token>\{(?:Series)(?<separator>[- ._])(Clean)?Title\})",
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 char[] EpisodeTitleTrimCharacters = new[] { ' ', '.', '?' };
@ -78,27 +81,22 @@ namespace NzbDrone.Core.Organizer
if (!namingConfig.RenameEpisodes)
{
if (episodeFile.SceneName.IsNullOrWhiteSpace())
{
if (episodeFile.RelativePath.IsNullOrWhiteSpace())
{
return Path.GetFileNameWithoutExtension(episodeFile.Path);
}
return Path.GetFileNameWithoutExtension(episodeFile.RelativePath);
}
return episodeFile.SceneName;
return GetOriginalTitle(episodeFile);
}
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)
{
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;
@ -124,10 +122,10 @@ namespace NzbDrone.Core.Organizer
AddEpisodeFileTokens(tokenHandlers, series, episodeFile);
AddMediaInfoTokens(tokenHandlers, episodeFile);
var filename = ReplaceTokens(pattern, tokenHandlers).Trim();
filename = FileNameCleanupRegex.Replace(filename, match => match.Captures[0].Value[0].ToString());
var fileName = ReplaceTokens(pattern, tokenHandlers).Trim();
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)
@ -398,7 +396,7 @@ namespace NzbDrone.Core.Organizer
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["{Quality Title}"] = m => GetQualityTitle(series, episodeFile.Quality);
}
@ -556,17 +554,6 @@ namespace NzbDrone.Core.Organizer
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)
{
var episodeIndex = 0;
@ -665,6 +652,31 @@ namespace NzbDrone.Core.Organizer
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

View File

@ -10,10 +10,13 @@ namespace NzbDrone.Core.Organizer
private static readonly Regex SeasonFolderRegex = new Regex(@"(\{season(\:\d+)?\})",
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)
{
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)
@ -41,10 +44,10 @@ namespace NzbDrone.Core.Organizer
}
}
public class ValidDailyEpisodeFormatValidator : PropertyValidator
public class ValidStandardEpisodeFormatValidator : PropertyValidator
{
public ValidDailyEpisodeFormatValidator()
: base("Must contain Air Date or Season and Episode")
public ValidStandardEpisodeFormatValidator()
: base("Must contain season and episode numbers OR Original Title")
{
}
@ -54,7 +57,30 @@ namespace NzbDrone.Core.Organizer
var value = context.PropertyValue as String;
if (!FileNameBuilder.SeasonEpisodePatternRegex.IsMatch(value) &&
!FileNameBuilder.AirDateRegex.IsMatch(value))
!FileNameValidation.OriginalTitleRegex.IsMatch(value))
{
return false;
}
return true;
}
}
public class ValidDailyEpisodeFormatValidator : PropertyValidator
{
public ValidDailyEpisodeFormatValidator()
: base("Must contain Air Date OR Season and Episode OR Original Title")
{
}
protected override bool IsValid(PropertyValidatorContext context)
{
var value = context.PropertyValue as String;
if (!FileNameBuilder.SeasonEpisodePatternRegex.IsMatch(value) &&
!FileNameBuilder.AirDateRegex.IsMatch(value) &&
!FileNameValidation.OriginalTitleRegex.IsMatch(value))
{
return false;
}
@ -66,7 +92,7 @@ namespace NzbDrone.Core.Organizer
public class ValidAnimeEpisodeFormatValidator : PropertyValidator
{
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;
if (!FileNameBuilder.SeasonEpisodePatternRegex.IsMatch(value) &&
!FileNameBuilder.AbsoluteEpisodePatternRegex.IsMatch(value))
!FileNameBuilder.AbsoluteEpisodePatternRegex.IsMatch(value) &&
!FileNameValidation.OriginalTitleRegex.IsMatch(value))
{
return false;
}

View File

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