New: Additional naming options for Quality in file name (Full, Title, Proper)

pull/2/head
Mark McDowall 10 years ago
parent f214e5cb25
commit f344a96444

@ -70,7 +70,7 @@ namespace NzbDrone.Core.Test.OrganizerTests
private void GivenProper() private void GivenProper()
{ {
_episodeFile.Quality.Revision.Version =2; _episodeFile.Quality.Revision.Version = 2;
} }
[Test] [Test]
@ -214,13 +214,13 @@ namespace NzbDrone.Core.Test.OrganizerTests
} }
[Test] [Test]
public void should_replace_quality_title_with_proper() public void should_replace_quality_proper_with_proper()
{ {
_namingConfig.StandardEpisodeFormat = "{Quality Title}"; _namingConfig.StandardEpisodeFormat = "{Quality Proper}";
GivenProper(); GivenProper();
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile) Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
.Should().Be("HDTV-720p Proper"); .Should().Be("Proper");
} }
[Test] [Test]
@ -564,10 +564,10 @@ namespace NzbDrone.Core.Test.OrganizerTests
[Test] [Test]
public void should_include_affixes_if_value_not_empty() public void should_include_affixes_if_value_not_empty()
{ {
_namingConfig.StandardEpisodeFormat = "{Series.Title}.S{season:00}E{episode:00}{_Episode.Title_}"; _namingConfig.StandardEpisodeFormat = "{Series.Title}.S{season:00}E{episode:00}{_Episode.Title_}{Quality.Title}";
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile) Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
.Should().Be("South.Park.S15E06_City.Sushi_"); .Should().Be("South.Park.S15E06_City.Sushi_HDTV-720p");
} }
[Test] [Test]
@ -691,6 +691,91 @@ namespace NzbDrone.Core.Test.OrganizerTests
.Should().Be("South Park - 15x06 - 15x07 - [100-101] - City Sushi - HDTV-720p"); .Should().Be("South Park - 15x06 - 15x07 - [100-101] - City Sushi - HDTV-720p");
} }
[Test]
public void should_replace_quality_proper_with_v2_for_anime_v2()
{
_series.SeriesType = SeriesTypes.Anime;
_namingConfig.AnimeEpisodeFormat = "{Quality Proper}";
GivenProper();
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
.Should().Be("v2");
}
[Test]
public void should_not_include_quality_proper_when_release_is_not_a_proper()
{
_namingConfig.StandardEpisodeFormat = "{Quality Title} {Quality Proper}";
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
.Should().Be("HDTV-720p");
}
[Test]
public void should_wrap_proper_in_square_brackets()
{
_namingConfig.StandardEpisodeFormat = "{Series Title} - S{season:00}E{episode:00} [{Quality Title}] {[Quality Proper]}";
GivenProper();
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
.Should().Be("South Park - S15E06 [HDTV-720p] [Proper]");
}
[Test]
public void should_not_wrap_proper_in_square_brackets_when_not_a_proper()
{
_namingConfig.StandardEpisodeFormat = "{Series Title} - S{season:00}E{episode:00} [{Quality Title}] {[Quality Proper]}";
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
.Should().Be("South Park - S15E06 [HDTV-720p]");
}
[Test]
public void should_replace_quality_full_with_quality_title_only_when_not_a_proper()
{
_namingConfig.StandardEpisodeFormat = "{Series Title} - S{season:00}E{episode:00} [{Quality Full}]";
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
.Should().Be("South Park - S15E06 [HDTV-720p]");
}
[Test]
public void should_replace_quality_full_with_quality_title_and_proper_only_when_a_proper()
{
_namingConfig.StandardEpisodeFormat = "{Series Title} - S{season:00}E{episode:00} [{Quality Full}]";
GivenProper();
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
.Should().Be("South Park - S15E06 [HDTV-720p Proper]");
}
[TestCase(' ')]
[TestCase('-')]
[TestCase('.')]
[TestCase('_')]
public void should_trim_extra_separators_from_end_when_quality_proper_is_not_included(char separator)
{
_namingConfig.StandardEpisodeFormat = String.Format("{{Quality{0}Title}}{0}{{Quality{0}Proper}}", separator);
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
.Should().Be("HDTV-720p");
}
[TestCase(' ')]
[TestCase('-')]
[TestCase('.')]
[TestCase('_')]
public void should_trim_extra_separators_from_middle_when_quality_proper_is_not_included(char separator)
{
_namingConfig.StandardEpisodeFormat = String.Format("{{Quality{0}Title}}{0}{{Quality{0}Proper}}{0}{{Episode{0}Title}}", separator);
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
.Should().Be(String.Format("HDTV-720p{0}City{0}Sushi", separator));
}
[Test] [Test]
public void should_format_range_multi_episode_properly() public void should_format_range_multi_episode_properly()
{ {

@ -0,0 +1,100 @@
using System;
using System.Data;
using System.Linq;
using System.Text.RegularExpressions;
using FluentMigrator;
using NzbDrone.Core.Datastore.Migration.Framework;
namespace NzbDrone.Core.Datastore.Migration
{
[Migration(69)]
public class quality_proper : NzbDroneMigrationBase
{
protected override void MainDbUpgrade()
{
Execute.WithConnection(ConvertQualityTitle);
}
private static readonly Regex QualityTitleRegex = new Regex(@"\{(?<prefix>[- ._\[(]*)(?<token>(?:quality)(?:(?<separator>[- ._]+)(?:title))?)(?<suffix>[- ._)\]]*)\}",
RegexOptions.Compiled | RegexOptions.IgnoreCase);
private void ConvertQualityTitle(IDbConnection conn, IDbTransaction tran)
{
using (IDbCommand namingConfigCmd = conn.CreateCommand())
{
namingConfigCmd.Transaction = tran;
namingConfigCmd.CommandText = @"SELECT StandardEpisodeFormat, DailyEpisodeFormat, AnimeEpisodeFormat FROM NamingConfig LIMIT 1";
using (IDataReader configReader = namingConfigCmd.ExecuteReader())
{
while (configReader.Read())
{
var currentStandard = configReader.GetString(0);
var currentDaily = configReader.GetString(1);
var currentAnime = configReader.GetString(1);
var newStandard = GetNewFormat(currentStandard);
var newDaily = GetNewFormat(currentDaily);
var newAnime = GetNewFormat(currentAnime);
using (IDbCommand updateCmd = conn.CreateCommand())
{
updateCmd.Transaction = tran;
updateCmd.CommandText = "UPDATE NamingConfig SET StandardEpisodeFormat = ?, DailyEpisodeFormat = ?, AnimeEpisodeFormat = ?";
updateCmd.AddParameter(newStandard);
updateCmd.AddParameter(newDaily);
updateCmd.AddParameter(newAnime);
updateCmd.ExecuteNonQuery();
}
}
}
}
}
private string GetNewFormat(string currentFormat)
{
var matches = QualityTitleRegex.Matches(currentFormat);
var result = currentFormat;
foreach (Match match in matches)
{
var tokenMatch = GetTokenMatch(match);
var qualityFullToken = String.Format("Quality{0}Full", tokenMatch.Separator); ;
if (tokenMatch.Token.All(t => !Char.IsLetter(t) || Char.IsLower(t)))
{
qualityFullToken = String.Format("quality{0}full", tokenMatch.Separator);
}
else if (tokenMatch.Token.All(t => !Char.IsLetter(t) || Char.IsUpper(t)))
{
qualityFullToken = String.Format("QUALITY{0}FULL", tokenMatch.Separator);
}
result = result.Replace(match.Groups["token"].Value, qualityFullToken);
}
return result;
}
private TokenMatch69 GetTokenMatch(Match match)
{
return new TokenMatch69
{
Prefix = match.Groups["prefix"].Value,
Token = match.Groups["token"].Value,
Separator = match.Groups["separator"].Value,
Suffix = match.Groups["suffix"].Value,
};
}
private class TokenMatch69
{
public string Prefix { get; set; }
public string Token { get; set; }
public string Separator { get; set; }
public string Suffix { get; set; }
}
}
}

@ -228,6 +228,7 @@
<Compile Include="Datastore\Migration\068_add_release_restrictions.cs" /> <Compile Include="Datastore\Migration\068_add_release_restrictions.cs" />
<Compile Include="Datastore\Migration\066_add_tags.cs" /> <Compile Include="Datastore\Migration\066_add_tags.cs" />
<Compile Include="Datastore\Migration\067_add_added_to_series.cs" /> <Compile Include="Datastore\Migration\067_add_added_to_series.cs" />
<Compile Include="Datastore\Migration\069_quality_proper.cs" />
<Compile Include="Datastore\Migration\Framework\MigrationContext.cs" /> <Compile Include="Datastore\Migration\Framework\MigrationContext.cs" />
<Compile Include="Datastore\Migration\Framework\MigrationController.cs" /> <Compile Include="Datastore\Migration\Framework\MigrationController.cs" />
<Compile Include="Datastore\Migration\Framework\MigrationDbFactory.cs" /> <Compile Include="Datastore\Migration\Framework\MigrationDbFactory.cs" />

@ -30,7 +30,7 @@ namespace NzbDrone.Core.Organizer
private readonly ICached<AbsoluteEpisodeFormat[]> _absoluteEpisodeFormatCache; private readonly ICached<AbsoluteEpisodeFormat[]> _absoluteEpisodeFormatCache;
private readonly Logger _logger; 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>[- ._]*)\}", 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); RegexOptions.Compiled | RegexOptions.IgnoreCase);
private static readonly Regex EpisodeRegex = new Regex(@"(?<episode>\{episode(?:\:0+)?})", private static readonly Regex EpisodeRegex = new Regex(@"(?<episode>\{episode(?:\:0+)?})",
@ -56,7 +56,8 @@ namespace NzbDrone.Core.Organizer
private static readonly Regex OriginalTitleRegex = new Regex(@"(\^{original[- ._]title\}$)", private static readonly Regex OriginalTitleRegex = new Regex(@"(\^{original[- ._]title\}$)",
RegexOptions.Compiled | RegexOptions.IgnoreCase); RegexOptions.Compiled | RegexOptions.IgnoreCase);
private static readonly Regex FileNameCleanupRegex = new Regex(@"\.{2,}", RegexOptions.Compiled); private static readonly Regex FileNameCleanupRegex = new Regex(@"([- ._])(\1)+", RegexOptions.Compiled);
private static readonly Regex TrimSeparatorsRegex = new Regex(@"[- ._]$", RegexOptions.Compiled);
private static readonly char[] EpisodeTitleTrimCharacters = new[] { ' ', '.', '?' }; private static readonly char[] EpisodeTitleTrimCharacters = new[] { ' ', '.', '?' };
@ -120,10 +121,12 @@ namespace NzbDrone.Core.Organizer
AddSeriesTokens(tokenHandlers, series); AddSeriesTokens(tokenHandlers, series);
AddEpisodeTokens(tokenHandlers, episodes); AddEpisodeTokens(tokenHandlers, episodes);
AddEpisodeFileTokens(tokenHandlers, series, episodeFile); AddEpisodeFileTokens(tokenHandlers, series, episodeFile);
AddQualityTokens(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());
fileName = TrimSeparatorsRegex.Replace(fileName, String.Empty);
return fileName; return fileName;
} }
@ -417,7 +420,16 @@ namespace NzbDrone.Core.Organizer
{ {
tokenHandlers["{Original Title}"] = m => GetOriginalTitle(episodeFile); 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); }
private void AddQualityTokens(Dictionary<String, Func<TokenMatch, String>> tokenHandlers, Series series, EpisodeFile episodeFile)
{
var qualityTitle = _qualityDefinitionService.Get(episodeFile.Quality.Quality).Title;
var qualityProper = GetQualityProper(series, episodeFile.Quality);
tokenHandlers["{Quality Full}"] = m => String.Format("{0} {1}", qualityTitle, qualityProper);
tokenHandlers["{Quality Title}"] = m => qualityTitle;
tokenHandlers["{Quality Proper}"] = m => qualityProper;
} }
private void AddMediaInfoTokens(Dictionary<String, Func<TokenMatch, String>> tokenHandlers, EpisodeFile episodeFile) private void AddMediaInfoTokens(Dictionary<String, Func<TokenMatch, String>> tokenHandlers, EpisodeFile episodeFile)
@ -651,24 +663,19 @@ namespace NzbDrone.Core.Organizer
return String.Join(" + ", titles); return String.Join(" + ", titles);
} }
private String GetQualityTitle(Series series, QualityModel quality) private String GetQualityProper(Series series, QualityModel quality)
{ {
var qualitySuffix = String.Empty;
if (quality.Revision.Version > 1) if (quality.Revision.Version > 1)
{ {
if (series.SeriesType == SeriesTypes.Anime) if (series.SeriesType == SeriesTypes.Anime)
{ {
qualitySuffix = " v" + quality.Revision.Version; return "v" + quality.Revision.Version;
} }
else return "Proper";
{
qualitySuffix = " Proper";
}
} }
return _qualityDefinitionService.Get(quality.Quality).Title + qualitySuffix; return String.Empty;
} }
private String GetOriginalTitle(EpisodeFile episodeFile) private String GetOriginalTitle(EpisodeFile episodeFile)

@ -12,9 +12,9 @@ namespace NzbDrone.Core.Organizer
{ {
RenameEpisodes = false, RenameEpisodes = false,
MultiEpisodeStyle = 0, MultiEpisodeStyle = 0,
StandardEpisodeFormat = "{Series Title} - S{season:00}E{episode:00} - {Episode Title} {Quality Title}", StandardEpisodeFormat = "{Series Title} - S{season:00}E{episode:00} - {Episode Title} {Quality Full}",
DailyEpisodeFormat = "{Series Title} - {Air-Date} - {Episode Title} {Quality Title}", DailyEpisodeFormat = "{Series Title} - {Air-Date} - {Episode Title} {Quality Full}",
AnimeEpisodeFormat = "{Series Title} - S{season:00}E{episode:00} - {Episode Title} {Quality Title}", AnimeEpisodeFormat = "{Series Title} - S{season:00}E{episode:00} - {Episode Title} {Quality Full}",
SeriesFolderFormat = "{Series Title}", SeriesFolderFormat = "{Series Title}",
SeasonFolderFormat = "Season {season}" SeasonFolderFormat = "Season {season}"
}; };

@ -47,7 +47,7 @@
{{> SeasonNamingPartial}} {{> SeasonNamingPartial}}
{{> EpisodeNamingPartial}} {{> EpisodeNamingPartial}}
{{> EpisodeTitleNamingPartial}} {{> EpisodeTitleNamingPartial}}
{{> QualityTitleNamingPartial}} {{> QualityNamingPartial}}
{{> MediaInfoNamingPartial}} {{> MediaInfoNamingPartial}}
{{> ReleaseGroupNamingPartial}} {{> ReleaseGroupNamingPartial}}
{{> OriginalTitleNamingPartial}} {{> OriginalTitleNamingPartial}}
@ -79,7 +79,7 @@
{{> SeasonNamingPartial}} {{> SeasonNamingPartial}}
{{> EpisodeNamingPartial}} {{> EpisodeNamingPartial}}
{{> EpisodeTitleNamingPartial}} {{> EpisodeTitleNamingPartial}}
{{> QualityTitleNamingPartial}} {{> QualityNamingPartial}}
{{> MediaInfoNamingPartial}} {{> MediaInfoNamingPartial}}
{{> ReleaseGroupNamingPartial}} {{> ReleaseGroupNamingPartial}}
{{> OriginalTitleNamingPartial}} {{> OriginalTitleNamingPartial}}
@ -111,7 +111,7 @@
{{> SeasonNamingPartial}} {{> SeasonNamingPartial}}
{{> EpisodeNamingPartial}} {{> EpisodeNamingPartial}}
{{> EpisodeTitleNamingPartial}} {{> EpisodeTitleNamingPartial}}
{{> QualityTitleNamingPartial}} {{> QualityNamingPartial}}
{{> MediaInfoNamingPartial}} {{> MediaInfoNamingPartial}}
{{> ReleaseGroupNamingPartial}} {{> ReleaseGroupNamingPartial}}
{{> OriginalTitleNamingPartial}} {{> OriginalTitleNamingPartial}}

@ -1,6 +1,9 @@
<li class="dropdown-submenu"> <li class="dropdown-submenu">
<a href="#" tabindex="-1" data-token="Quality Title">Quality Title</a> <a href="#" tabindex="-1" data-token="Quality Full">Quality</a>
<ul class="dropdown-menu"> <ul class="dropdown-menu">
<li><a href="#" data-token="Quality Full">Quality Full</a></li>
<li><a href="#" data-token="Quality.Full">Quality.Full</a></li>
<li><a href="#" data-token="Quality_Full">Quality_Full</a></li>
<li><a href="#" data-token="Quality Title">Quality Title</a></li> <li><a href="#" data-token="Quality Title">Quality Title</a></li>
<li><a href="#" data-token="Quality.Title">Quality.Title</a></li> <li><a href="#" data-token="Quality.Title">Quality.Title</a></li>
<li><a href="#" data-token="Quality_Title">Quality_Title</a></li> <li><a href="#" data-token="Quality_Title">Quality_Title</a></li>
Loading…
Cancel
Save