mirror of https://github.com/lidarr/Lidarr
New: Additional naming options for Quality in file name (Full, Title, Proper)
This commit is contained in:
parent
f214e5cb25
commit
f344a96444
|
@ -70,7 +70,7 @@ namespace NzbDrone.Core.Test.OrganizerTests
|
|||
|
||||
private void GivenProper()
|
||||
{
|
||||
_episodeFile.Quality.Revision.Version =2;
|
||||
_episodeFile.Quality.Revision.Version = 2;
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
@ -214,13 +214,13 @@ namespace NzbDrone.Core.Test.OrganizerTests
|
|||
}
|
||||
|
||||
[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();
|
||||
|
||||
Subject.BuildFileName(new List<Episode> { _episode1 }, _series, _episodeFile)
|
||||
.Should().Be("HDTV-720p Proper");
|
||||
.Should().Be("Proper");
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
@ -564,10 +564,10 @@ namespace NzbDrone.Core.Test.OrganizerTests
|
|||
[Test]
|
||||
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)
|
||||
.Should().Be("South.Park.S15E06_City.Sushi_");
|
||||
.Should().Be("South.Park.S15E06_City.Sushi_HDTV-720p");
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
@ -691,6 +691,91 @@ namespace NzbDrone.Core.Test.OrganizerTests
|
|||
.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]
|
||||
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\066_add_tags.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\MigrationController.cs" />
|
||||
<Compile Include="Datastore\Migration\Framework\MigrationDbFactory.cs" />
|
||||
|
|
|
@ -30,7 +30,7 @@ namespace NzbDrone.Core.Organizer
|
|||
private readonly ICached<AbsoluteEpisodeFormat[]> _absoluteEpisodeFormatCache;
|
||||
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);
|
||||
|
||||
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\}$)",
|
||||
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[] { ' ', '.', '?' };
|
||||
|
||||
|
@ -120,10 +121,12 @@ namespace NzbDrone.Core.Organizer
|
|||
AddSeriesTokens(tokenHandlers, series);
|
||||
AddEpisodeTokens(tokenHandlers, episodes);
|
||||
AddEpisodeFileTokens(tokenHandlers, series, episodeFile);
|
||||
AddQualityTokens(tokenHandlers, series, episodeFile);
|
||||
AddMediaInfoTokens(tokenHandlers, episodeFile);
|
||||
|
||||
var fileName = ReplaceTokens(pattern, tokenHandlers).Trim();
|
||||
fileName = FileNameCleanupRegex.Replace(fileName, match => match.Captures[0].Value[0].ToString());
|
||||
fileName = TrimSeparatorsRegex.Replace(fileName, String.Empty);
|
||||
|
||||
return fileName;
|
||||
}
|
||||
|
@ -417,7 +420,16 @@ namespace NzbDrone.Core.Organizer
|
|||
{
|
||||
tokenHandlers["{Original Title}"] = m => GetOriginalTitle(episodeFile);
|
||||
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)
|
||||
|
@ -651,24 +663,19 @@ namespace NzbDrone.Core.Organizer
|
|||
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 (series.SeriesType == SeriesTypes.Anime)
|
||||
{
|
||||
qualitySuffix = " v" + quality.Revision.Version;
|
||||
return "v" + quality.Revision.Version;
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
qualitySuffix = " Proper";
|
||||
}
|
||||
return "Proper";
|
||||
}
|
||||
|
||||
return _qualityDefinitionService.Get(quality.Quality).Title + qualitySuffix;
|
||||
return String.Empty;
|
||||
}
|
||||
|
||||
private String GetOriginalTitle(EpisodeFile episodeFile)
|
||||
|
|
|
@ -12,9 +12,9 @@ namespace NzbDrone.Core.Organizer
|
|||
{
|
||||
RenameEpisodes = false,
|
||||
MultiEpisodeStyle = 0,
|
||||
StandardEpisodeFormat = "{Series Title} - S{season:00}E{episode:00} - {Episode Title} {Quality Title}",
|
||||
DailyEpisodeFormat = "{Series Title} - {Air-Date} - {Episode Title} {Quality Title}",
|
||||
AnimeEpisodeFormat = "{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 Full}",
|
||||
AnimeEpisodeFormat = "{Series Title} - S{season:00}E{episode:00} - {Episode Title} {Quality Full}",
|
||||
SeriesFolderFormat = "{Series Title}",
|
||||
SeasonFolderFormat = "Season {season}"
|
||||
};
|
||||
|
|
|
@ -47,7 +47,7 @@
|
|||
{{> SeasonNamingPartial}}
|
||||
{{> EpisodeNamingPartial}}
|
||||
{{> EpisodeTitleNamingPartial}}
|
||||
{{> QualityTitleNamingPartial}}
|
||||
{{> QualityNamingPartial}}
|
||||
{{> MediaInfoNamingPartial}}
|
||||
{{> ReleaseGroupNamingPartial}}
|
||||
{{> OriginalTitleNamingPartial}}
|
||||
|
@ -79,7 +79,7 @@
|
|||
{{> SeasonNamingPartial}}
|
||||
{{> EpisodeNamingPartial}}
|
||||
{{> EpisodeTitleNamingPartial}}
|
||||
{{> QualityTitleNamingPartial}}
|
||||
{{> QualityNamingPartial}}
|
||||
{{> MediaInfoNamingPartial}}
|
||||
{{> ReleaseGroupNamingPartial}}
|
||||
{{> OriginalTitleNamingPartial}}
|
||||
|
@ -111,7 +111,7 @@
|
|||
{{> SeasonNamingPartial}}
|
||||
{{> EpisodeNamingPartial}}
|
||||
{{> EpisodeTitleNamingPartial}}
|
||||
{{> QualityTitleNamingPartial}}
|
||||
{{> QualityNamingPartial}}
|
||||
{{> MediaInfoNamingPartial}}
|
||||
{{> ReleaseGroupNamingPartial}}
|
||||
{{> OriginalTitleNamingPartial}}
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
<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">
|
||||
<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>
|
Loading…
Reference in New Issue