mirror of
https://github.com/Sonarr/Sonarr
synced 2024-12-26 01:37:07 +00:00
Merge pull request #125 from Sonarr/original-title
New: Ability to use Original Title only in naming settings
This commit is contained in:
commit
48ec113598
5 changed files with 97 additions and 41 deletions
|
@ -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>
|
||||
|
|
|
@ -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()
|
||||
{
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue