diff --git a/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj b/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj
index 32bbf520f..8948394c8 100644
--- a/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj
+++ b/src/NzbDrone.Core.Test/NzbDrone.Core.Test.csproj
@@ -327,7 +327,6 @@
Always
-
App.config
diff --git a/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderFixture.cs b/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderFixture.cs
index 1d25ca1a5..bd1809135 100644
--- a/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderFixture.cs
+++ b/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderFixture.cs
@@ -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 { _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 { _episode1 }, _series, _episodeFile)
+ .Should().Be("30.Rock.S01E01.xvid-LOL");
+ }
+
[Test]
public void should_allow_period_between_season_and_episode()
{
diff --git a/src/NzbDrone.Core/Organizer/FileNameBuilder.cs b/src/NzbDrone.Core/Organizer/FileNameBuilder.cs
index 147f0187a..f40aa28bf 100644
--- a/src/NzbDrone.Core/Organizer/FileNameBuilder.cs
+++ b/src/NzbDrone.Core/Organizer/FileNameBuilder.cs
@@ -53,6 +53,9 @@ namespace NzbDrone.Core.Organizer
public static readonly Regex SeriesTitleRegex = new Regex(@"(?\{(?:Series)(?[- ._])(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> 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 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
diff --git a/src/NzbDrone.Core/Organizer/FileNameValidation.cs b/src/NzbDrone.Core/Organizer/FileNameValidation.cs
index 6546ac29d..52bc92eb6 100644
--- a/src/NzbDrone.Core/Organizer/FileNameValidation.cs
+++ b/src/NzbDrone.Core/Organizer/FileNameValidation.cs
@@ -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 ValidEpisodeFormat(this IRuleBuilder 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 ValidDailyEpisodeFormat(this IRuleBuilder 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;
}
diff --git a/src/NzbDrone.Core/Organizer/FilenameSampleService.cs b/src/NzbDrone.Core/Organizer/FilenameSampleService.cs
index e0a495bca..af4efac85 100644
--- a/src/NzbDrone.Core/Organizer/FilenameSampleService.cs
+++ b/src/NzbDrone.Core/Organizer/FilenameSampleService.cs
@@ -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
};