mirror of
https://github.com/lidarr/lidarr.git
synced 2025-08-19 13:10:13 -07:00
NamingConfig Refactor
Adds track NamingConfig, Gets naming section in settings working. Adds Release Year token and track number token
This commit is contained in:
parent
a8ac1f3adc
commit
fe58f54ad4
17 changed files with 392 additions and 194 deletions
|
@ -35,10 +35,13 @@ namespace NzbDrone.Api.Config
|
||||||
|
|
||||||
SharedValidator.RuleFor(c => c.MultiEpisodeStyle).InclusiveBetween(0, 5);
|
SharedValidator.RuleFor(c => c.MultiEpisodeStyle).InclusiveBetween(0, 5);
|
||||||
SharedValidator.RuleFor(c => c.StandardEpisodeFormat).ValidEpisodeFormat();
|
SharedValidator.RuleFor(c => c.StandardEpisodeFormat).ValidEpisodeFormat();
|
||||||
|
SharedValidator.RuleFor(c => c.StandardTrackFormat).ValidTrackFormat();
|
||||||
SharedValidator.RuleFor(c => c.DailyEpisodeFormat).ValidDailyEpisodeFormat();
|
SharedValidator.RuleFor(c => c.DailyEpisodeFormat).ValidDailyEpisodeFormat();
|
||||||
SharedValidator.RuleFor(c => c.AnimeEpisodeFormat).ValidAnimeEpisodeFormat();
|
SharedValidator.RuleFor(c => c.AnimeEpisodeFormat).ValidAnimeEpisodeFormat();
|
||||||
SharedValidator.RuleFor(c => c.SeriesFolderFormat).ValidSeriesFolderFormat();
|
SharedValidator.RuleFor(c => c.SeriesFolderFormat).ValidSeriesFolderFormat();
|
||||||
SharedValidator.RuleFor(c => c.SeasonFolderFormat).ValidSeasonFolderFormat();
|
SharedValidator.RuleFor(c => c.SeasonFolderFormat).ValidSeasonFolderFormat();
|
||||||
|
SharedValidator.RuleFor(c => c.ArtistFolderFormat).ValidArtistFolderFormat();
|
||||||
|
SharedValidator.RuleFor(c => c.AlbumFolderFormat).ValidAlbumFolderFormat();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateNamingConfig(NamingConfigResource resource)
|
private void UpdateNamingConfig(NamingConfigResource resource)
|
||||||
|
@ -74,6 +77,7 @@ namespace NzbDrone.Api.Config
|
||||||
var sampleResource = new NamingSampleResource();
|
var sampleResource = new NamingSampleResource();
|
||||||
|
|
||||||
var singleEpisodeSampleResult = _filenameSampleService.GetStandardSample(nameSpec);
|
var singleEpisodeSampleResult = _filenameSampleService.GetStandardSample(nameSpec);
|
||||||
|
var singleTrackSampleResult = _filenameSampleService.GetStandardTrackSample(nameSpec);
|
||||||
var multiEpisodeSampleResult = _filenameSampleService.GetMultiEpisodeSample(nameSpec);
|
var multiEpisodeSampleResult = _filenameSampleService.GetMultiEpisodeSample(nameSpec);
|
||||||
var dailyEpisodeSampleResult = _filenameSampleService.GetDailySample(nameSpec);
|
var dailyEpisodeSampleResult = _filenameSampleService.GetDailySample(nameSpec);
|
||||||
var animeEpisodeSampleResult = _filenameSampleService.GetAnimeSample(nameSpec);
|
var animeEpisodeSampleResult = _filenameSampleService.GetAnimeSample(nameSpec);
|
||||||
|
@ -83,6 +87,10 @@ namespace NzbDrone.Api.Config
|
||||||
? "Invalid format"
|
? "Invalid format"
|
||||||
: singleEpisodeSampleResult.FileName;
|
: singleEpisodeSampleResult.FileName;
|
||||||
|
|
||||||
|
sampleResource.SingleTrackExample = _filenameValidationService.ValidateTrackFilename(singleTrackSampleResult) != null
|
||||||
|
? "Invalid format"
|
||||||
|
: singleTrackSampleResult.FileName;
|
||||||
|
|
||||||
sampleResource.MultiEpisodeExample = _filenameValidationService.ValidateStandardFilename(multiEpisodeSampleResult) != null
|
sampleResource.MultiEpisodeExample = _filenameValidationService.ValidateStandardFilename(multiEpisodeSampleResult) != null
|
||||||
? "Invalid format"
|
? "Invalid format"
|
||||||
: multiEpisodeSampleResult.FileName;
|
: multiEpisodeSampleResult.FileName;
|
||||||
|
@ -107,18 +115,28 @@ namespace NzbDrone.Api.Config
|
||||||
? "Invalid format"
|
? "Invalid format"
|
||||||
: _filenameSampleService.GetSeasonFolderSample(nameSpec);
|
: _filenameSampleService.GetSeasonFolderSample(nameSpec);
|
||||||
|
|
||||||
|
sampleResource.ArtistFolderExample = nameSpec.ArtistFolderFormat.IsNullOrWhiteSpace()
|
||||||
|
? "Invalid format"
|
||||||
|
: _filenameSampleService.GetArtistFolderSample(nameSpec);
|
||||||
|
|
||||||
|
sampleResource.AlbumFolderExample = nameSpec.AlbumFolderFormat.IsNullOrWhiteSpace()
|
||||||
|
? "Invalid format"
|
||||||
|
: _filenameSampleService.GetAlbumFolderSample(nameSpec);
|
||||||
|
|
||||||
return sampleResource.AsResponse();
|
return sampleResource.AsResponse();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ValidateFormatResult(NamingConfig nameSpec)
|
private void ValidateFormatResult(NamingConfig nameSpec)
|
||||||
{
|
{
|
||||||
var singleEpisodeSampleResult = _filenameSampleService.GetStandardSample(nameSpec);
|
var singleEpisodeSampleResult = _filenameSampleService.GetStandardSample(nameSpec);
|
||||||
|
var singleTrackSampleResult = _filenameSampleService.GetStandardTrackSample(nameSpec);
|
||||||
var multiEpisodeSampleResult = _filenameSampleService.GetMultiEpisodeSample(nameSpec);
|
var multiEpisodeSampleResult = _filenameSampleService.GetMultiEpisodeSample(nameSpec);
|
||||||
var dailyEpisodeSampleResult = _filenameSampleService.GetDailySample(nameSpec);
|
var dailyEpisodeSampleResult = _filenameSampleService.GetDailySample(nameSpec);
|
||||||
var animeEpisodeSampleResult = _filenameSampleService.GetAnimeSample(nameSpec);
|
var animeEpisodeSampleResult = _filenameSampleService.GetAnimeSample(nameSpec);
|
||||||
var animeMultiEpisodeSampleResult = _filenameSampleService.GetAnimeMultiEpisodeSample(nameSpec);
|
var animeMultiEpisodeSampleResult = _filenameSampleService.GetAnimeMultiEpisodeSample(nameSpec);
|
||||||
|
|
||||||
var singleEpisodeValidationResult = _filenameValidationService.ValidateStandardFilename(singleEpisodeSampleResult);
|
var singleEpisodeValidationResult = _filenameValidationService.ValidateStandardFilename(singleEpisodeSampleResult);
|
||||||
|
var singleTrackValidationResult = _filenameValidationService.ValidateTrackFilename(singleTrackSampleResult);
|
||||||
var multiEpisodeValidationResult = _filenameValidationService.ValidateStandardFilename(multiEpisodeSampleResult);
|
var multiEpisodeValidationResult = _filenameValidationService.ValidateStandardFilename(multiEpisodeSampleResult);
|
||||||
var dailyEpisodeValidationResult = _filenameValidationService.ValidateDailyFilename(dailyEpisodeSampleResult);
|
var dailyEpisodeValidationResult = _filenameValidationService.ValidateDailyFilename(dailyEpisodeSampleResult);
|
||||||
var animeEpisodeValidationResult = _filenameValidationService.ValidateAnimeFilename(animeEpisodeSampleResult);
|
var animeEpisodeValidationResult = _filenameValidationService.ValidateAnimeFilename(animeEpisodeSampleResult);
|
||||||
|
@ -127,6 +145,7 @@ namespace NzbDrone.Api.Config
|
||||||
var validationFailures = new List<ValidationFailure>();
|
var validationFailures = new List<ValidationFailure>();
|
||||||
|
|
||||||
validationFailures.AddIfNotNull(singleEpisodeValidationResult);
|
validationFailures.AddIfNotNull(singleEpisodeValidationResult);
|
||||||
|
validationFailures.AddIfNotNull(singleTrackValidationResult);
|
||||||
validationFailures.AddIfNotNull(multiEpisodeValidationResult);
|
validationFailures.AddIfNotNull(multiEpisodeValidationResult);
|
||||||
validationFailures.AddIfNotNull(dailyEpisodeValidationResult);
|
validationFailures.AddIfNotNull(dailyEpisodeValidationResult);
|
||||||
validationFailures.AddIfNotNull(animeEpisodeValidationResult);
|
validationFailures.AddIfNotNull(animeEpisodeValidationResult);
|
||||||
|
|
|
@ -6,13 +6,17 @@ namespace NzbDrone.Api.Config
|
||||||
public class NamingConfigResource : RestResource
|
public class NamingConfigResource : RestResource
|
||||||
{
|
{
|
||||||
public bool RenameEpisodes { get; set; }
|
public bool RenameEpisodes { get; set; }
|
||||||
|
public bool RenameTracks { get; set; }
|
||||||
public bool ReplaceIllegalCharacters { get; set; }
|
public bool ReplaceIllegalCharacters { get; set; }
|
||||||
public int MultiEpisodeStyle { get; set; }
|
public int MultiEpisodeStyle { get; set; }
|
||||||
public string StandardEpisodeFormat { get; set; }
|
public string StandardEpisodeFormat { get; set; }
|
||||||
|
public string StandardTrackFormat { get; set; }
|
||||||
public string DailyEpisodeFormat { get; set; }
|
public string DailyEpisodeFormat { get; set; }
|
||||||
public string AnimeEpisodeFormat { get; set; }
|
public string AnimeEpisodeFormat { get; set; }
|
||||||
public string SeriesFolderFormat { get; set; }
|
public string SeriesFolderFormat { get; set; }
|
||||||
public string SeasonFolderFormat { get; set; }
|
public string SeasonFolderFormat { get; set; }
|
||||||
|
public string ArtistFolderFormat { get; set; }
|
||||||
|
public string AlbumFolderFormat { get; set; }
|
||||||
public bool IncludeSeriesTitle { get; set; }
|
public bool IncludeSeriesTitle { get; set; }
|
||||||
public bool IncludeEpisodeTitle { get; set; }
|
public bool IncludeEpisodeTitle { get; set; }
|
||||||
public bool IncludeQuality { get; set; }
|
public bool IncludeQuality { get; set; }
|
||||||
|
@ -30,13 +34,17 @@ namespace NzbDrone.Api.Config
|
||||||
Id = model.Id,
|
Id = model.Id,
|
||||||
|
|
||||||
RenameEpisodes = model.RenameEpisodes,
|
RenameEpisodes = model.RenameEpisodes,
|
||||||
|
RenameTracks = model.RenameTracks,
|
||||||
ReplaceIllegalCharacters = model.ReplaceIllegalCharacters,
|
ReplaceIllegalCharacters = model.ReplaceIllegalCharacters,
|
||||||
MultiEpisodeStyle = model.MultiEpisodeStyle,
|
MultiEpisodeStyle = model.MultiEpisodeStyle,
|
||||||
StandardEpisodeFormat = model.StandardEpisodeFormat,
|
StandardEpisodeFormat = model.StandardEpisodeFormat,
|
||||||
|
StandardTrackFormat = model.StandardTrackFormat,
|
||||||
DailyEpisodeFormat = model.DailyEpisodeFormat,
|
DailyEpisodeFormat = model.DailyEpisodeFormat,
|
||||||
AnimeEpisodeFormat = model.AnimeEpisodeFormat,
|
AnimeEpisodeFormat = model.AnimeEpisodeFormat,
|
||||||
SeriesFolderFormat = model.SeriesFolderFormat,
|
SeriesFolderFormat = model.SeriesFolderFormat,
|
||||||
SeasonFolderFormat = model.SeasonFolderFormat
|
SeasonFolderFormat = model.SeasonFolderFormat,
|
||||||
|
ArtistFolderFormat = model.ArtistFolderFormat,
|
||||||
|
AlbumFolderFormat = model.AlbumFolderFormat
|
||||||
//IncludeSeriesTitle
|
//IncludeSeriesTitle
|
||||||
//IncludeEpisodeTitle
|
//IncludeEpisodeTitle
|
||||||
//IncludeQuality
|
//IncludeQuality
|
||||||
|
@ -63,13 +71,17 @@ namespace NzbDrone.Api.Config
|
||||||
Id = resource.Id,
|
Id = resource.Id,
|
||||||
|
|
||||||
RenameEpisodes = resource.RenameEpisodes,
|
RenameEpisodes = resource.RenameEpisodes,
|
||||||
|
RenameTracks = resource.RenameTracks,
|
||||||
ReplaceIllegalCharacters = resource.ReplaceIllegalCharacters,
|
ReplaceIllegalCharacters = resource.ReplaceIllegalCharacters,
|
||||||
MultiEpisodeStyle = resource.MultiEpisodeStyle,
|
MultiEpisodeStyle = resource.MultiEpisodeStyle,
|
||||||
StandardEpisodeFormat = resource.StandardEpisodeFormat,
|
StandardEpisodeFormat = resource.StandardEpisodeFormat,
|
||||||
|
StandardTrackFormat = resource.StandardTrackFormat,
|
||||||
DailyEpisodeFormat = resource.DailyEpisodeFormat,
|
DailyEpisodeFormat = resource.DailyEpisodeFormat,
|
||||||
AnimeEpisodeFormat = resource.AnimeEpisodeFormat,
|
AnimeEpisodeFormat = resource.AnimeEpisodeFormat,
|
||||||
SeriesFolderFormat = resource.SeriesFolderFormat,
|
SeriesFolderFormat = resource.SeriesFolderFormat,
|
||||||
SeasonFolderFormat = resource.SeasonFolderFormat
|
SeasonFolderFormat = resource.SeasonFolderFormat,
|
||||||
|
ArtistFolderFormat = resource.ArtistFolderFormat,
|
||||||
|
AlbumFolderFormat = resource.AlbumFolderFormat
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,11 +3,14 @@
|
||||||
public class NamingSampleResource
|
public class NamingSampleResource
|
||||||
{
|
{
|
||||||
public string SingleEpisodeExample { get; set; }
|
public string SingleEpisodeExample { get; set; }
|
||||||
|
public string SingleTrackExample { get; set; }
|
||||||
public string MultiEpisodeExample { get; set; }
|
public string MultiEpisodeExample { get; set; }
|
||||||
public string DailyEpisodeExample { get; set; }
|
public string DailyEpisodeExample { get; set; }
|
||||||
public string AnimeEpisodeExample { get; set; }
|
public string AnimeEpisodeExample { get; set; }
|
||||||
public string AnimeMultiEpisodeExample { get; set; }
|
public string AnimeMultiEpisodeExample { get; set; }
|
||||||
public string SeriesFolderExample { get; set; }
|
public string SeriesFolderExample { get; set; }
|
||||||
public string SeasonFolderExample { get; set; }
|
public string SeasonFolderExample { get; set; }
|
||||||
|
public string ArtistFolderExample { get; set; }
|
||||||
|
public string AlbumFolderExample { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -97,6 +97,8 @@ namespace NzbDrone.Core.Datastore.Migration
|
||||||
|
|
||||||
Alter.Table("NamingConfig")
|
Alter.Table("NamingConfig")
|
||||||
.AddColumn("ArtistFolderFormat").AsString().Nullable()
|
.AddColumn("ArtistFolderFormat").AsString().Nullable()
|
||||||
|
.AddColumn("RenameTracks").AsBoolean().Nullable()
|
||||||
|
.AddColumn("StandardTrackFormat").AsString().Nullable()
|
||||||
.AddColumn("AlbumFolderFormat").AsString().Nullable();
|
.AddColumn("AlbumFolderFormat").AsString().Nullable();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,12 +18,13 @@ namespace NzbDrone.Core.Organizer
|
||||||
public interface IBuildFileNames
|
public interface IBuildFileNames
|
||||||
{
|
{
|
||||||
string BuildFileName(List<Episode> episodes, Series series, EpisodeFile episodeFile, NamingConfig namingConfig = null);
|
string BuildFileName(List<Episode> episodes, Series series, EpisodeFile episodeFile, NamingConfig namingConfig = null);
|
||||||
|
string BuildTrackFileName(List<Track> tracks, Artist artist, Album album, TrackFile trackFile, NamingConfig namingConfig = null);
|
||||||
string BuildFilePath(Series series, int seasonNumber, string fileName, string extension);
|
string BuildFilePath(Series series, int seasonNumber, string fileName, string extension);
|
||||||
string BuildSeasonPath(Series series, int seasonNumber);
|
string BuildSeasonPath(Series series, int seasonNumber);
|
||||||
BasicNamingConfig GetBasicNamingConfig(NamingConfig nameSpec);
|
BasicNamingConfig GetBasicNamingConfig(NamingConfig nameSpec);
|
||||||
string GetSeriesFolder(Series series, NamingConfig namingConfig = null);
|
string GetSeriesFolder(Series series, NamingConfig namingConfig = null);
|
||||||
string GetArtistFolder(Artist artist, NamingConfig namingConfig = null);
|
string GetArtistFolder(Artist artist, NamingConfig namingConfig = null);
|
||||||
string GetAlbumFolder(Album album, NamingConfig namingConfig = null);
|
string GetAlbumFolder(Artist artist, Album album, NamingConfig namingConfig = null);
|
||||||
string GetSeasonFolder(Series series, int seasonNumber, NamingConfig namingConfig = null);
|
string GetSeasonFolder(Series series, int seasonNumber, NamingConfig namingConfig = null);
|
||||||
|
|
||||||
// TODO: Implement Music functions
|
// TODO: Implement Music functions
|
||||||
|
@ -44,6 +45,9 @@ namespace NzbDrone.Core.Organizer
|
||||||
private static readonly Regex EpisodeRegex = new Regex(@"(?<episode>\{episode(?:\:0+)?})",
|
private static readonly Regex EpisodeRegex = new Regex(@"(?<episode>\{episode(?:\:0+)?})",
|
||||||
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||||
|
|
||||||
|
private static readonly Regex TrackRegex = new Regex(@"(?<track>\{track(?:\:0+)?})",
|
||||||
|
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||||
|
|
||||||
private static readonly Regex SeasonRegex = new Regex(@"(?<season>\{season(?:\:0+)?})",
|
private static readonly Regex SeasonRegex = new Regex(@"(?<season>\{season(?:\:0+)?})",
|
||||||
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||||
|
|
||||||
|
@ -61,6 +65,12 @@ namespace NzbDrone.Core.Organizer
|
||||||
public static readonly Regex SeriesTitleRegex = new Regex(@"(?<token>\{(?:Series)(?<separator>[- ._])(Clean)?Title\})",
|
public static readonly Regex SeriesTitleRegex = new Regex(@"(?<token>\{(?:Series)(?<separator>[- ._])(Clean)?Title\})",
|
||||||
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||||
|
|
||||||
|
public static readonly Regex ArtistNameRegex = new Regex(@"(?<token>\{(?:Artist)(?<separator>[- ._])(Clean)?Name\})",
|
||||||
|
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||||
|
|
||||||
|
public static readonly Regex AlbumTitleRegex = new Regex(@"(?<token>\{(?:Album)(?<separator>[- ._])(Clean)?Title\})",
|
||||||
|
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||||
|
|
||||||
private static readonly Regex FileNameCleanupRegex = new Regex(@"([- ._])(\1)+", RegexOptions.Compiled);
|
private static readonly Regex FileNameCleanupRegex = new Regex(@"([- ._])(\1)+", RegexOptions.Compiled);
|
||||||
private static readonly Regex TrimSeparatorsRegex = new Regex(@"[- ._]$", RegexOptions.Compiled);
|
private static readonly Regex TrimSeparatorsRegex = new Regex(@"[- ._]$", RegexOptions.Compiled);
|
||||||
|
|
||||||
|
@ -142,6 +152,47 @@ namespace NzbDrone.Core.Organizer
|
||||||
return fileName;
|
return fileName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string BuildTrackFileName(List<Track> tracks, Artist artist, Album album, TrackFile trackFile, NamingConfig namingConfig = null)
|
||||||
|
{
|
||||||
|
if (namingConfig == null)
|
||||||
|
{
|
||||||
|
namingConfig = _namingConfigService.GetConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!namingConfig.RenameTracks)
|
||||||
|
{
|
||||||
|
return GetOriginalTitle(trackFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (namingConfig.StandardTrackFormat.IsNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
throw new NamingFormatException("Standard track format cannot be empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
var pattern = namingConfig.StandardTrackFormat;
|
||||||
|
var tokenHandlers = new Dictionary<string, Func<TokenMatch, string>>(FileNameBuilderTokenEqualityComparer.Instance);
|
||||||
|
|
||||||
|
tracks = tracks.OrderBy(e => e.AlbumId).ThenBy(e => e.TrackNumber).ToList();
|
||||||
|
|
||||||
|
//pattern = AddSeasonEpisodeNumberingTokens(pattern, tokenHandlers, episodes, namingConfig);
|
||||||
|
|
||||||
|
pattern = FormatTrackNumberTokens(pattern, "", tracks);
|
||||||
|
//pattern = AddAbsoluteNumberingTokens(pattern, tokenHandlers, series, episodes, namingConfig);
|
||||||
|
|
||||||
|
AddArtistTokens(tokenHandlers, artist);
|
||||||
|
AddAlbumTokens(tokenHandlers, album);
|
||||||
|
AddTrackTokens(tokenHandlers, tracks);
|
||||||
|
AddTrackFileTokens(tokenHandlers, trackFile);
|
||||||
|
AddQualityTokens(tokenHandlers, artist, trackFile);
|
||||||
|
//AddMediaInfoTokens(tokenHandlers, trackFile); TODO ReWork MediaInfo for Tracks
|
||||||
|
|
||||||
|
var fileName = ReplaceTokens(pattern, tokenHandlers, namingConfig).Trim();
|
||||||
|
fileName = FileNameCleanupRegex.Replace(fileName, match => match.Captures[0].Value[0].ToString());
|
||||||
|
fileName = TrimSeparatorsRegex.Replace(fileName, string.Empty);
|
||||||
|
|
||||||
|
return fileName;
|
||||||
|
}
|
||||||
|
|
||||||
public string BuildFilePath(Series series, int seasonNumber, string fileName, string extension)
|
public string BuildFilePath(Series series, int seasonNumber, string fileName, string extension)
|
||||||
{
|
{
|
||||||
Ensure.That(extension, () => extension).IsNotNullOrWhiteSpace();
|
Ensure.That(extension, () => extension).IsNotNullOrWhiteSpace();
|
||||||
|
@ -263,7 +314,7 @@ namespace NzbDrone.Core.Organizer
|
||||||
return CleanFolderName(ReplaceTokens(namingConfig.SeasonFolderFormat, tokenHandlers, namingConfig));
|
return CleanFolderName(ReplaceTokens(namingConfig.SeasonFolderFormat, tokenHandlers, namingConfig));
|
||||||
}
|
}
|
||||||
|
|
||||||
public string GetAlbumFolder(Album album, NamingConfig namingConfig = null)
|
public string GetAlbumFolder(Artist artist, Album album, NamingConfig namingConfig = null)
|
||||||
{
|
{
|
||||||
if (namingConfig == null)
|
if (namingConfig == null)
|
||||||
{
|
{
|
||||||
|
@ -273,6 +324,7 @@ namespace NzbDrone.Core.Organizer
|
||||||
var tokenHandlers = new Dictionary<string, Func<TokenMatch, string>>(FileNameBuilderTokenEqualityComparer.Instance);
|
var tokenHandlers = new Dictionary<string, Func<TokenMatch, string>>(FileNameBuilderTokenEqualityComparer.Instance);
|
||||||
|
|
||||||
AddAlbumTokens(tokenHandlers, album);
|
AddAlbumTokens(tokenHandlers, album);
|
||||||
|
AddArtistTokens(tokenHandlers, artist);
|
||||||
|
|
||||||
return CleanFolderName(ReplaceTokens(namingConfig.AlbumFolderFormat, tokenHandlers, namingConfig));
|
return CleanFolderName(ReplaceTokens(namingConfig.AlbumFolderFormat, tokenHandlers, namingConfig));
|
||||||
}
|
}
|
||||||
|
@ -322,7 +374,7 @@ namespace NzbDrone.Core.Organizer
|
||||||
{
|
{
|
||||||
tokenHandlers["{Album Title}"] = m => album.Title;
|
tokenHandlers["{Album Title}"] = m => album.Title;
|
||||||
tokenHandlers["{Album CleanTitle}"] = m => CleanTitle(album.Title);
|
tokenHandlers["{Album CleanTitle}"] = m => CleanTitle(album.Title);
|
||||||
tokenHandlers["{Album Year}"] = m => album.ReleaseDate.Year.ToString();
|
tokenHandlers["{Release Year}"] = m => album.ReleaseDate.Year.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
private string AddSeasonEpisodeNumberingTokens(string pattern, Dictionary<string, Func<TokenMatch, string>> tokenHandlers, List<Episode> episodes, NamingConfig namingConfig)
|
private string AddSeasonEpisodeNumberingTokens(string pattern, Dictionary<string, Func<TokenMatch, string>> tokenHandlers, List<Episode> episodes, NamingConfig namingConfig)
|
||||||
|
@ -469,6 +521,12 @@ namespace NzbDrone.Core.Organizer
|
||||||
tokenHandlers["{Episode CleanTitle}"] = m => CleanTitle(GetEpisodeTitle(episodes, "and"));
|
tokenHandlers["{Episode CleanTitle}"] = m => CleanTitle(GetEpisodeTitle(episodes, "and"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void AddTrackTokens(Dictionary<string, Func<TokenMatch, string>> tokenHandlers, List<Track> tracks)
|
||||||
|
{
|
||||||
|
tokenHandlers["{Track Title}"] = m => GetTrackTitle(tracks, "+");
|
||||||
|
tokenHandlers["{Track CleanTitle}"] = m => CleanTitle(GetTrackTitle(tracks, "and"));
|
||||||
|
}
|
||||||
|
|
||||||
private void AddEpisodeFileTokens(Dictionary<string, Func<TokenMatch, string>> tokenHandlers, EpisodeFile episodeFile)
|
private void AddEpisodeFileTokens(Dictionary<string, Func<TokenMatch, string>> tokenHandlers, EpisodeFile episodeFile)
|
||||||
{
|
{
|
||||||
tokenHandlers["{Original Title}"] = m => GetOriginalTitle(episodeFile);
|
tokenHandlers["{Original Title}"] = m => GetOriginalTitle(episodeFile);
|
||||||
|
@ -476,6 +534,13 @@ namespace NzbDrone.Core.Organizer
|
||||||
tokenHandlers["{Release Group}"] = m => episodeFile.ReleaseGroup ?? m.DefaultValue("Lidarr");
|
tokenHandlers["{Release Group}"] = m => episodeFile.ReleaseGroup ?? m.DefaultValue("Lidarr");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void AddTrackFileTokens(Dictionary<string, Func<TokenMatch, string>> tokenHandlers, TrackFile trackFile)
|
||||||
|
{
|
||||||
|
tokenHandlers["{Original Title}"] = m => GetOriginalTitle(trackFile);
|
||||||
|
tokenHandlers["{Original Filename}"] = m => GetOriginalFileName(trackFile);
|
||||||
|
tokenHandlers["{Release Group}"] = m => trackFile.ReleaseGroup ?? m.DefaultValue("Lidarr");
|
||||||
|
}
|
||||||
|
|
||||||
private void AddQualityTokens(Dictionary<string, Func<TokenMatch, string>> tokenHandlers, Series series, EpisodeFile episodeFile)
|
private void AddQualityTokens(Dictionary<string, Func<TokenMatch, string>> tokenHandlers, Series series, EpisodeFile episodeFile)
|
||||||
{
|
{
|
||||||
var qualityTitle = _qualityDefinitionService.Get(episodeFile.Quality.Quality).Title;
|
var qualityTitle = _qualityDefinitionService.Get(episodeFile.Quality.Quality).Title;
|
||||||
|
@ -488,6 +553,18 @@ namespace NzbDrone.Core.Organizer
|
||||||
tokenHandlers["{Quality Real}"] = m => qualityReal;
|
tokenHandlers["{Quality Real}"] = m => qualityReal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void AddQualityTokens(Dictionary<string, Func<TokenMatch, string>> tokenHandlers, Artist artist, TrackFile trackFile)
|
||||||
|
{
|
||||||
|
var qualityTitle = _qualityDefinitionService.Get(trackFile.Quality.Quality).Title;
|
||||||
|
//var qualityProper = GetQualityProper(artist, trackFile.Quality);
|
||||||
|
//var qualityReal = GetQualityReal(artist, trackFile.Quality);
|
||||||
|
|
||||||
|
tokenHandlers["{Quality Full}"] = m => String.Format("{0}", qualityTitle);
|
||||||
|
tokenHandlers["{Quality Title}"] = m => qualityTitle;
|
||||||
|
//tokenHandlers["{Quality Proper}"] = m => qualityProper;
|
||||||
|
//tokenHandlers["{Quality Real}"] = m => qualityReal;
|
||||||
|
}
|
||||||
|
|
||||||
private void AddMediaInfoTokens(Dictionary<string, Func<TokenMatch, string>> tokenHandlers, EpisodeFile episodeFile)
|
private void AddMediaInfoTokens(Dictionary<string, Func<TokenMatch, string>> tokenHandlers, EpisodeFile episodeFile)
|
||||||
{
|
{
|
||||||
if (episodeFile.MediaInfo == null) return;
|
if (episodeFile.MediaInfo == null) return;
|
||||||
|
@ -683,6 +760,20 @@ namespace NzbDrone.Core.Organizer
|
||||||
return ReplaceSeasonTokens(pattern, episodes.First().SeasonNumber);
|
return ReplaceSeasonTokens(pattern, episodes.First().SeasonNumber);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string FormatTrackNumberTokens(string basePattern, string formatPattern, List<Track> tracks)
|
||||||
|
{
|
||||||
|
var pattern = string.Empty;
|
||||||
|
|
||||||
|
for (int i = 0; i < tracks.Count; i++)
|
||||||
|
{
|
||||||
|
var patternToReplace = i == 0 ? basePattern : formatPattern;
|
||||||
|
|
||||||
|
pattern += TrackRegex.Replace(patternToReplace, match => ReplaceNumberToken(match.Groups["track"].Value, tracks[i].TrackNumber));
|
||||||
|
}
|
||||||
|
|
||||||
|
return pattern;
|
||||||
|
}
|
||||||
|
|
||||||
private string FormatAbsoluteNumberTokens(string basePattern, string formatPattern, List<Episode> episodes)
|
private string FormatAbsoluteNumberTokens(string basePattern, string formatPattern, List<Episode> episodes)
|
||||||
{
|
{
|
||||||
var pattern = string.Empty;
|
var pattern = string.Empty;
|
||||||
|
@ -765,6 +856,30 @@ namespace NzbDrone.Core.Organizer
|
||||||
return string.Join(separator, titles);
|
return string.Join(separator, titles);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string GetTrackTitle(List<Track> tracks, string separator)
|
||||||
|
{
|
||||||
|
separator = string.Format(" {0} ", separator.Trim());
|
||||||
|
|
||||||
|
if (tracks.Count == 1)
|
||||||
|
{
|
||||||
|
return tracks.First().Title.TrimEnd(EpisodeTitleTrimCharacters);
|
||||||
|
}
|
||||||
|
|
||||||
|
var titles = tracks.Select(c => c.Title.TrimEnd(EpisodeTitleTrimCharacters))
|
||||||
|
.Select(CleanupEpisodeTitle)
|
||||||
|
.Distinct()
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
if (titles.All(t => t.IsNullOrWhiteSpace()))
|
||||||
|
{
|
||||||
|
titles = tracks.Select(c => c.Title.TrimEnd(EpisodeTitleTrimCharacters))
|
||||||
|
.Distinct()
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
return string.Join(separator, titles);
|
||||||
|
}
|
||||||
|
|
||||||
private string CleanupEpisodeTitle(string title)
|
private string CleanupEpisodeTitle(string title)
|
||||||
{
|
{
|
||||||
//this will remove (1),(2) from the end of multi part episodes.
|
//this will remove (1),(2) from the end of multi part episodes.
|
||||||
|
@ -806,6 +921,16 @@ namespace NzbDrone.Core.Organizer
|
||||||
return episodeFile.SceneName;
|
return episodeFile.SceneName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string GetOriginalTitle(TrackFile trackFile)
|
||||||
|
{
|
||||||
|
if (trackFile.SceneName.IsNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
return GetOriginalFileName(trackFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
return trackFile.SceneName;
|
||||||
|
}
|
||||||
|
|
||||||
private string GetOriginalFileName(EpisodeFile episodeFile)
|
private string GetOriginalFileName(EpisodeFile episodeFile)
|
||||||
{
|
{
|
||||||
if (episodeFile.RelativePath.IsNullOrWhiteSpace())
|
if (episodeFile.RelativePath.IsNullOrWhiteSpace())
|
||||||
|
@ -816,35 +941,16 @@ namespace NzbDrone.Core.Organizer
|
||||||
return Path.GetFileNameWithoutExtension(episodeFile.RelativePath);
|
return Path.GetFileNameWithoutExtension(episodeFile.RelativePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
//public string GetArtistFolder(Artist artist, NamingConfig namingConfig = null)
|
private string GetOriginalFileName(TrackFile trackFile)
|
||||||
//{
|
{
|
||||||
// if (namingConfig == null)
|
if (trackFile.RelativePath.IsNullOrWhiteSpace())
|
||||||
// {
|
{
|
||||||
// namingConfig = _namingConfigService.GetConfig();
|
return Path.GetFileNameWithoutExtension(trackFile.Path);
|
||||||
// }
|
}
|
||||||
|
|
||||||
// var tokenHandlers = new Dictionary<string, Func<TokenMatch, string>>(FileNameBuilderTokenEqualityComparer.Instance);
|
return Path.GetFileNameWithoutExtension(trackFile.RelativePath);
|
||||||
|
}
|
||||||
|
|
||||||
// AddArtistTokens(tokenHandlers, artist);
|
|
||||||
|
|
||||||
// return CleanFolderName(ReplaceTokens("{Artist Name}", tokenHandlers, namingConfig)); //namingConfig.ArtistFolderFormat,
|
|
||||||
//}
|
|
||||||
|
|
||||||
//public string GetAlbumFolder(Artist artist, string albumName, NamingConfig namingConfig = null)
|
|
||||||
//{
|
|
||||||
// throw new NotImplementedException();
|
|
||||||
// //if (namingConfig == null)
|
|
||||||
// //{
|
|
||||||
// // namingConfig = _namingConfigService.GetConfig();
|
|
||||||
// //}
|
|
||||||
|
|
||||||
// //var tokenHandlers = new Dictionary<string, Func<TokenMatch, string>>(FileNameBuilderTokenEqualityComparer.Instance);
|
|
||||||
|
|
||||||
// //AddSeriesTokens(tokenHandlers, artist);
|
|
||||||
// //AddSeasonTokens(tokenHandlers, seasonNumber);
|
|
||||||
|
|
||||||
// //return CleanFolderName(ReplaceTokens(namingConfig.SeasonFolderFormat, tokenHandlers, namingConfig));
|
|
||||||
//}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed class TokenMatch
|
internal sealed class TokenMatch
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
using NzbDrone.Core.MediaFiles;
|
using NzbDrone.Core.MediaFiles;
|
||||||
using NzbDrone.Core.Qualities;
|
using NzbDrone.Core.Qualities;
|
||||||
using NzbDrone.Core.Tv;
|
using NzbDrone.Core.Tv;
|
||||||
|
using NzbDrone.Core.Music;
|
||||||
using NzbDrone.Core.MediaFiles.MediaInfo;
|
using NzbDrone.Core.MediaFiles.MediaInfo;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Organizer
|
namespace NzbDrone.Core.Organizer
|
||||||
|
@ -9,26 +10,34 @@ namespace NzbDrone.Core.Organizer
|
||||||
public interface IFilenameSampleService
|
public interface IFilenameSampleService
|
||||||
{
|
{
|
||||||
SampleResult GetStandardSample(NamingConfig nameSpec);
|
SampleResult GetStandardSample(NamingConfig nameSpec);
|
||||||
|
SampleResult GetStandardTrackSample(NamingConfig nameSpec);
|
||||||
SampleResult GetMultiEpisodeSample(NamingConfig nameSpec);
|
SampleResult GetMultiEpisodeSample(NamingConfig nameSpec);
|
||||||
SampleResult GetDailySample(NamingConfig nameSpec);
|
SampleResult GetDailySample(NamingConfig nameSpec);
|
||||||
SampleResult GetAnimeSample(NamingConfig nameSpec);
|
SampleResult GetAnimeSample(NamingConfig nameSpec);
|
||||||
SampleResult GetAnimeMultiEpisodeSample(NamingConfig nameSpec);
|
SampleResult GetAnimeMultiEpisodeSample(NamingConfig nameSpec);
|
||||||
string GetSeriesFolderSample(NamingConfig nameSpec);
|
string GetSeriesFolderSample(NamingConfig nameSpec);
|
||||||
string GetSeasonFolderSample(NamingConfig nameSpec);
|
string GetSeasonFolderSample(NamingConfig nameSpec);
|
||||||
|
string GetArtistFolderSample(NamingConfig nameSpec);
|
||||||
|
string GetAlbumFolderSample(NamingConfig nameSpec);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class FileNameSampleService : IFilenameSampleService
|
public class FileNameSampleService : IFilenameSampleService
|
||||||
{
|
{
|
||||||
private readonly IBuildFileNames _buildFileNames;
|
private readonly IBuildFileNames _buildFileNames;
|
||||||
private static Series _standardSeries;
|
private static Series _standardSeries;
|
||||||
|
private static Artist _standardArtist;
|
||||||
|
private static Album _standardAlbum;
|
||||||
|
private static Track _track1;
|
||||||
private static Series _dailySeries;
|
private static Series _dailySeries;
|
||||||
private static Series _animeSeries;
|
private static Series _animeSeries;
|
||||||
private static Episode _episode1;
|
private static Episode _episode1;
|
||||||
private static Episode _episode2;
|
private static Episode _episode2;
|
||||||
private static Episode _episode3;
|
private static Episode _episode3;
|
||||||
private static List<Episode> _singleEpisode;
|
private static List<Episode> _singleEpisode;
|
||||||
|
private static List<Track> _singleTrack;
|
||||||
private static List<Episode> _multiEpisodes;
|
private static List<Episode> _multiEpisodes;
|
||||||
private static EpisodeFile _singleEpisodeFile;
|
private static EpisodeFile _singleEpisodeFile;
|
||||||
|
private static TrackFile _singleTrackFile;
|
||||||
private static EpisodeFile _multiEpisodeFile;
|
private static EpisodeFile _multiEpisodeFile;
|
||||||
private static EpisodeFile _dailyEpisodeFile;
|
private static EpisodeFile _dailyEpisodeFile;
|
||||||
private static EpisodeFile _animeEpisodeFile;
|
private static EpisodeFile _animeEpisodeFile;
|
||||||
|
@ -44,6 +53,17 @@ namespace NzbDrone.Core.Organizer
|
||||||
Title = "Series Title (2010)"
|
Title = "Series Title (2010)"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
_standardArtist = new Artist
|
||||||
|
{
|
||||||
|
Name = "Artist Name"
|
||||||
|
};
|
||||||
|
|
||||||
|
_standardAlbum = new Album
|
||||||
|
{
|
||||||
|
Title = "Album Title",
|
||||||
|
ReleaseDate = System.DateTime.Today
|
||||||
|
};
|
||||||
|
|
||||||
_dailySeries = new Series
|
_dailySeries = new Series
|
||||||
{
|
{
|
||||||
SeriesType = SeriesTypes.Daily,
|
SeriesType = SeriesTypes.Daily,
|
||||||
|
@ -56,6 +76,14 @@ namespace NzbDrone.Core.Organizer
|
||||||
Title = "Series Title (2010)"
|
Title = "Series Title (2010)"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
_track1 = new Track
|
||||||
|
{
|
||||||
|
TrackNumber = 3,
|
||||||
|
|
||||||
|
Title = "Track Title (1)",
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
_episode1 = new Episode
|
_episode1 = new Episode
|
||||||
{
|
{
|
||||||
SeasonNumber = 1,
|
SeasonNumber = 1,
|
||||||
|
@ -82,6 +110,7 @@ namespace NzbDrone.Core.Organizer
|
||||||
};
|
};
|
||||||
|
|
||||||
_singleEpisode = new List<Episode> { _episode1 };
|
_singleEpisode = new List<Episode> { _episode1 };
|
||||||
|
_singleTrack = new List<Track> { _track1 };
|
||||||
_multiEpisodes = new List<Episode> { _episode1, _episode2, _episode3 };
|
_multiEpisodes = new List<Episode> { _episode1, _episode2, _episode3 };
|
||||||
|
|
||||||
var mediaInfo = new MediaInfoModel()
|
var mediaInfo = new MediaInfoModel()
|
||||||
|
@ -115,6 +144,15 @@ namespace NzbDrone.Core.Organizer
|
||||||
MediaInfo = mediaInfo
|
MediaInfo = mediaInfo
|
||||||
};
|
};
|
||||||
|
|
||||||
|
_singleTrackFile = new TrackFile
|
||||||
|
{
|
||||||
|
Quality = new QualityModel(Quality.MP3256, new Revision(2)),
|
||||||
|
RelativePath = "Artist.Name.Album.Name.TrackNum.Track.Title.MP3256.mp3",
|
||||||
|
SceneName = "Artist.Name.Album.Name.TrackNum.Track.Title.MP3256",
|
||||||
|
ReleaseGroup = "RlsGrp",
|
||||||
|
MediaInfo = mediaInfo
|
||||||
|
};
|
||||||
|
|
||||||
_multiEpisodeFile = new EpisodeFile
|
_multiEpisodeFile = new EpisodeFile
|
||||||
{
|
{
|
||||||
Quality = new QualityModel(Quality.MP3256, new Revision(2)),
|
Quality = new QualityModel(Quality.MP3256, new Revision(2)),
|
||||||
|
@ -165,6 +203,20 @@ namespace NzbDrone.Core.Organizer
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public SampleResult GetStandardTrackSample(NamingConfig nameSpec)
|
||||||
|
{
|
||||||
|
var result = new SampleResult
|
||||||
|
{
|
||||||
|
FileName = BuildTrackSample(_singleTrack, _standardArtist, _standardAlbum, _singleTrackFile, nameSpec),
|
||||||
|
Artist = _standardArtist,
|
||||||
|
Album = _standardAlbum,
|
||||||
|
Tracks = _singleTrack,
|
||||||
|
TrackFile = _singleTrackFile
|
||||||
|
};
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
public SampleResult GetMultiEpisodeSample(NamingConfig nameSpec)
|
public SampleResult GetMultiEpisodeSample(NamingConfig nameSpec)
|
||||||
{
|
{
|
||||||
var result = new SampleResult
|
var result = new SampleResult
|
||||||
|
@ -227,6 +279,16 @@ namespace NzbDrone.Core.Organizer
|
||||||
return _buildFileNames.GetSeasonFolder(_standardSeries, _episode1.SeasonNumber, nameSpec);
|
return _buildFileNames.GetSeasonFolder(_standardSeries, _episode1.SeasonNumber, nameSpec);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string GetArtistFolderSample(NamingConfig nameSpec)
|
||||||
|
{
|
||||||
|
return _buildFileNames.GetArtistFolder(_standardArtist, nameSpec);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetAlbumFolderSample(NamingConfig nameSpec)
|
||||||
|
{
|
||||||
|
return _buildFileNames.GetAlbumFolder(_standardArtist, _standardAlbum, nameSpec);
|
||||||
|
}
|
||||||
|
|
||||||
private string BuildSample(List<Episode> episodes, Series series, EpisodeFile episodeFile, NamingConfig nameSpec)
|
private string BuildSample(List<Episode> episodes, Series series, EpisodeFile episodeFile, NamingConfig nameSpec)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
@ -238,5 +300,17 @@ namespace NzbDrone.Core.Organizer
|
||||||
return string.Empty;
|
return string.Empty;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string BuildTrackSample(List<Track> tracks, Artist artist, Album album, TrackFile trackFile, NamingConfig nameSpec)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return _buildFileNames.BuildTrackFileName(tracks, artist, album, trackFile, nameSpec);
|
||||||
|
}
|
||||||
|
catch (NamingFormatException)
|
||||||
|
{
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,12 @@ namespace NzbDrone.Core.Organizer
|
||||||
return ruleBuilder.SetValidator(new ValidStandardEpisodeFormatValidator());
|
return ruleBuilder.SetValidator(new ValidStandardEpisodeFormatValidator());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static IRuleBuilderOptions<T, string> ValidTrackFormat<T>(this IRuleBuilder<T, string> ruleBuilder)
|
||||||
|
{
|
||||||
|
ruleBuilder.SetValidator(new NotEmptyValidator(null));
|
||||||
|
return ruleBuilder.SetValidator(new ValidStandardTrackFormatValidator());
|
||||||
|
}
|
||||||
|
|
||||||
public static IRuleBuilderOptions<T, string> ValidDailyEpisodeFormat<T>(this IRuleBuilder<T, string> ruleBuilder)
|
public static IRuleBuilderOptions<T, string> ValidDailyEpisodeFormat<T>(this IRuleBuilder<T, string> ruleBuilder)
|
||||||
{
|
{
|
||||||
ruleBuilder.SetValidator(new NotEmptyValidator(null));
|
ruleBuilder.SetValidator(new NotEmptyValidator(null));
|
||||||
|
@ -41,6 +47,17 @@ namespace NzbDrone.Core.Organizer
|
||||||
ruleBuilder.SetValidator(new NotEmptyValidator(null));
|
ruleBuilder.SetValidator(new NotEmptyValidator(null));
|
||||||
return ruleBuilder.SetValidator(new RegularExpressionValidator(SeasonFolderRegex)).WithMessage("Must contain season number");
|
return ruleBuilder.SetValidator(new RegularExpressionValidator(SeasonFolderRegex)).WithMessage("Must contain season number");
|
||||||
}
|
}
|
||||||
|
public static IRuleBuilderOptions<T, string> ValidArtistFolderFormat<T>(this IRuleBuilder<T, string> ruleBuilder)
|
||||||
|
{
|
||||||
|
ruleBuilder.SetValidator(new NotEmptyValidator(null));
|
||||||
|
return ruleBuilder.SetValidator(new RegularExpressionValidator(FileNameBuilder.ArtistNameRegex)).WithMessage("Must contain Artist name");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IRuleBuilderOptions<T, string> ValidAlbumFolderFormat<T>(this IRuleBuilder<T, string> ruleBuilder)
|
||||||
|
{
|
||||||
|
ruleBuilder.SetValidator(new NotEmptyValidator(null));
|
||||||
|
return ruleBuilder.SetValidator(new RegularExpressionValidator(FileNameBuilder.AlbumTitleRegex)).WithMessage("Must contain Album name");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ValidStandardEpisodeFormatValidator : PropertyValidator
|
public class ValidStandardEpisodeFormatValidator : PropertyValidator
|
||||||
|
@ -65,6 +82,21 @@ namespace NzbDrone.Core.Organizer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class ValidStandardTrackFormatValidator : PropertyValidator
|
||||||
|
{
|
||||||
|
public ValidStandardTrackFormatValidator()
|
||||||
|
: base("Must contain Album Title and Track numbers OR Original Title")
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool IsValid(PropertyValidatorContext context)
|
||||||
|
{
|
||||||
|
|
||||||
|
return true; //TODO Add Logic here
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public class ValidDailyEpisodeFormatValidator : PropertyValidator
|
public class ValidDailyEpisodeFormatValidator : PropertyValidator
|
||||||
{
|
{
|
||||||
public ValidDailyEpisodeFormatValidator()
|
public ValidDailyEpisodeFormatValidator()
|
||||||
|
|
|
@ -9,6 +9,7 @@ namespace NzbDrone.Core.Organizer
|
||||||
public interface IFilenameValidationService
|
public interface IFilenameValidationService
|
||||||
{
|
{
|
||||||
ValidationFailure ValidateStandardFilename(SampleResult sampleResult);
|
ValidationFailure ValidateStandardFilename(SampleResult sampleResult);
|
||||||
|
ValidationFailure ValidateTrackFilename(SampleResult sampleResult);
|
||||||
ValidationFailure ValidateDailyFilename(SampleResult sampleResult);
|
ValidationFailure ValidateDailyFilename(SampleResult sampleResult);
|
||||||
ValidationFailure ValidateAnimeFilename(SampleResult sampleResult);
|
ValidationFailure ValidateAnimeFilename(SampleResult sampleResult);
|
||||||
}
|
}
|
||||||
|
@ -35,6 +36,27 @@ namespace NzbDrone.Core.Organizer
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ValidationFailure ValidateTrackFilename(SampleResult sampleResult)
|
||||||
|
{
|
||||||
|
var validationFailure = new ValidationFailure("StandardTrackFormat", ERROR_MESSAGE);
|
||||||
|
|
||||||
|
//TODO Add Validation for TrackFilename
|
||||||
|
//var parsedEpisodeInfo = Parser.Parser.ParseTitle(sampleResult.FileName);
|
||||||
|
|
||||||
|
|
||||||
|
//if (parsedEpisodeInfo == null)
|
||||||
|
//{
|
||||||
|
// return validationFailure;
|
||||||
|
//}
|
||||||
|
|
||||||
|
//if (!ValidateSeasonAndEpisodeNumbers(sampleResult.Episodes, parsedEpisodeInfo))
|
||||||
|
//{
|
||||||
|
// return validationFailure;
|
||||||
|
//}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
public ValidationFailure ValidateDailyFilename(SampleResult sampleResult)
|
public ValidationFailure ValidateDailyFilename(SampleResult sampleResult)
|
||||||
{
|
{
|
||||||
var validationFailure = new ValidationFailure("DailyEpisodeFormat", ERROR_MESSAGE);
|
var validationFailure = new ValidationFailure("DailyEpisodeFormat", ERROR_MESSAGE);
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
using NzbDrone.Core.Datastore;
|
using NzbDrone.Core.Datastore;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Organizer
|
namespace NzbDrone.Core.Organizer
|
||||||
{
|
{
|
||||||
|
@ -7,21 +7,25 @@ namespace NzbDrone.Core.Organizer
|
||||||
public static NamingConfig Default => new NamingConfig
|
public static NamingConfig Default => new NamingConfig
|
||||||
{
|
{
|
||||||
RenameEpisodes = false,
|
RenameEpisodes = false,
|
||||||
|
RenameTracks = false,
|
||||||
ReplaceIllegalCharacters = true,
|
ReplaceIllegalCharacters = true,
|
||||||
MultiEpisodeStyle = 0,
|
MultiEpisodeStyle = 0,
|
||||||
StandardEpisodeFormat = "{Series Title} - S{season:00}E{episode:00} - {Episode Title} {Quality Full}",
|
StandardEpisodeFormat = "{Series Title} - S{season:00}E{episode:00} - {Episode Title} {Quality Full}",
|
||||||
|
StandardTrackFormat = "{Artist Name} - {track:00} - {Album Title} - {Track Title}",
|
||||||
DailyEpisodeFormat = "{Series Title} - {Air-Date} - {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}",
|
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}",
|
||||||
ArtistFolderFormat = "{Artist Name}",
|
ArtistFolderFormat = "{Artist Name}",
|
||||||
AlbumFolderFormat = "{Album Name} ({Year})"
|
AlbumFolderFormat = "{Album Title} ({Release Year})"
|
||||||
};
|
};
|
||||||
|
|
||||||
public bool RenameEpisodes { get; set; }
|
public bool RenameEpisodes { get; set; }
|
||||||
|
public bool RenameTracks { get; set; }
|
||||||
public bool ReplaceIllegalCharacters { get; set; }
|
public bool ReplaceIllegalCharacters { get; set; }
|
||||||
public int MultiEpisodeStyle { get; set; }
|
public int MultiEpisodeStyle { get; set; }
|
||||||
public string StandardEpisodeFormat { get; set; }
|
public string StandardEpisodeFormat { get; set; }
|
||||||
|
public string StandardTrackFormat { get; set; }
|
||||||
public string DailyEpisodeFormat { get; set; }
|
public string DailyEpisodeFormat { get; set; }
|
||||||
public string AnimeEpisodeFormat { get; set; }
|
public string AnimeEpisodeFormat { get; set; }
|
||||||
public string SeriesFolderFormat { get; set; }
|
public string SeriesFolderFormat { get; set; }
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using NzbDrone.Core.MediaFiles;
|
using NzbDrone.Core.MediaFiles;
|
||||||
using NzbDrone.Core.Tv;
|
using NzbDrone.Core.Tv;
|
||||||
|
using NzbDrone.Core.Music;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Organizer
|
namespace NzbDrone.Core.Organizer
|
||||||
{
|
{
|
||||||
|
@ -8,7 +9,11 @@ namespace NzbDrone.Core.Organizer
|
||||||
{
|
{
|
||||||
public string FileName { get; set; }
|
public string FileName { get; set; }
|
||||||
public Series Series { get; set; }
|
public Series Series { get; set; }
|
||||||
|
public Artist Artist { get; set; }
|
||||||
|
public Album Album { get; set; }
|
||||||
public List<Episode> Episodes { get; set; }
|
public List<Episode> Episodes { get; set; }
|
||||||
public EpisodeFile EpisodeFile { get; set; }
|
public EpisodeFile EpisodeFile { get; set; }
|
||||||
|
public List<Track> Tracks { get; set; }
|
||||||
|
public TrackFile TrackFile { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,26 +10,20 @@ module.exports = (function() {
|
||||||
template : 'Settings/MediaManagement/Naming/NamingViewTemplate',
|
template : 'Settings/MediaManagement/Naming/NamingViewTemplate',
|
||||||
ui : {
|
ui : {
|
||||||
namingOptions : '.x-naming-options',
|
namingOptions : '.x-naming-options',
|
||||||
renameEpisodesCheckbox : '.x-rename-episodes',
|
renameTracksCheckbox : '.x-rename-tracks',
|
||||||
singleEpisodeExample : '.x-single-episode-example',
|
singleTrackExample : '.x-single-track-example',
|
||||||
multiEpisodeExample : '.x-multi-episode-example',
|
|
||||||
dailyEpisodeExample : '.x-daily-episode-example',
|
|
||||||
animeEpisodeExample : '.x-anime-episode-example',
|
|
||||||
animeMultiEpisodeExample : '.x-anime-multi-episode-example',
|
|
||||||
namingTokenHelper : '.x-naming-token-helper',
|
namingTokenHelper : '.x-naming-token-helper',
|
||||||
multiEpisodeStyle : '.x-multi-episode-style',
|
artistFolderExample : '.x-artist-folder-example',
|
||||||
seriesFolderExample : '.x-series-folder-example',
|
albumFolderExample : '.x-album-folder-example'
|
||||||
seasonFolderExample : '.x-season-folder-example'
|
|
||||||
},
|
},
|
||||||
events : {
|
events : {
|
||||||
"change .x-rename-episodes" : '_setFailedDownloadOptionsVisibility',
|
"change .x-rename-tracks" : '_setFailedDownloadOptionsVisibility',
|
||||||
"click .x-show-wizard" : '_showWizard',
|
"click .x-show-wizard" : '_showWizard',
|
||||||
"click .x-naming-token-helper a" : '_addToken',
|
"click .x-naming-token-helper a" : '_addToken'
|
||||||
"change .x-multi-episode-style" : '_multiEpisodeFomatChanged'
|
|
||||||
},
|
},
|
||||||
regions : { basicNamingRegion : '.x-basic-naming' },
|
regions : { basicNamingRegion : '.x-basic-naming' },
|
||||||
onRender : function() {
|
onRender : function() {
|
||||||
if (!this.model.get('renameEpisodes')) {
|
if (!this.model.get('renameTracks')) {
|
||||||
this.ui.namingOptions.hide();
|
this.ui.namingOptions.hide();
|
||||||
}
|
}
|
||||||
var basicNamingView = new BasicNamingView({ model : this.model });
|
var basicNamingView = new BasicNamingView({ model : this.model });
|
||||||
|
@ -40,7 +34,7 @@ module.exports = (function() {
|
||||||
this._updateSamples();
|
this._updateSamples();
|
||||||
},
|
},
|
||||||
_setFailedDownloadOptionsVisibility : function() {
|
_setFailedDownloadOptionsVisibility : function() {
|
||||||
var checked = this.ui.renameEpisodesCheckbox.prop('checked');
|
var checked = this.ui.renameTracksCheckbox.prop('checked');
|
||||||
if (checked) {
|
if (checked) {
|
||||||
this.ui.namingOptions.slideDown();
|
this.ui.namingOptions.slideDown();
|
||||||
} else {
|
} else {
|
||||||
|
@ -51,13 +45,9 @@ module.exports = (function() {
|
||||||
this.namingSampleModel.fetch({ data : this.model.toJSON() });
|
this.namingSampleModel.fetch({ data : this.model.toJSON() });
|
||||||
},
|
},
|
||||||
_showSamples : function() {
|
_showSamples : function() {
|
||||||
this.ui.singleEpisodeExample.html(this.namingSampleModel.get('singleEpisodeExample'));
|
this.ui.singleTrackExample.html(this.namingSampleModel.get('singleTrackExample'));
|
||||||
this.ui.multiEpisodeExample.html(this.namingSampleModel.get('multiEpisodeExample'));
|
this.ui.artistFolderExample.html(this.namingSampleModel.get('artistFolderExample'));
|
||||||
this.ui.dailyEpisodeExample.html(this.namingSampleModel.get('dailyEpisodeExample'));
|
this.ui.albumFolderExample.html(this.namingSampleModel.get('albumFolderExample'));
|
||||||
this.ui.animeEpisodeExample.html(this.namingSampleModel.get('animeEpisodeExample'));
|
|
||||||
this.ui.animeMultiEpisodeExample.html(this.namingSampleModel.get('animeMultiEpisodeExample'));
|
|
||||||
this.ui.seriesFolderExample.html(this.namingSampleModel.get('seriesFolderExample'));
|
|
||||||
this.ui.seasonFolderExample.html(this.namingSampleModel.get('seasonFolderExample'));
|
|
||||||
},
|
},
|
||||||
_addToken : function(e) {
|
_addToken : function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
@ -75,9 +65,6 @@ module.exports = (function() {
|
||||||
this.ui.namingTokenHelper.removeClass('open');
|
this.ui.namingTokenHelper.removeClass('open');
|
||||||
input.focus();
|
input.focus();
|
||||||
},
|
},
|
||||||
multiEpisodeFormatChanged : function() {
|
|
||||||
this.model.set('multiEpisodeStyle', this.ui.multiEpisodeStyle.val());
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
AsModelBoundView.call(view);
|
AsModelBoundView.call(view);
|
||||||
AsValidatedView.call(view);
|
AsValidatedView.call(view);
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend>Episode Naming</legend>
|
<legend>Track Naming</legend>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="col-sm-3 control-label">Rename Episodes</label>
|
<label class="col-sm-3 control-label">Rename Tracks</label>
|
||||||
|
|
||||||
<div class="col-sm-8">
|
<div class="col-sm-8">
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<label class="checkbox toggle well">
|
<label class="checkbox toggle well">
|
||||||
<input type="checkbox" name="renameEpisodes" class="x-rename-episodes"/>
|
<input type="checkbox" name="renameTracks" class="x-rename-tracks"/>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
<span>Yes</span>
|
<span>Yes</span>
|
||||||
|
@ -51,7 +51,7 @@
|
||||||
<div class="basic-setting x-basic-naming"></div>
|
<div class="basic-setting x-basic-naming"></div>
|
||||||
|
|
||||||
<div class="form-group advanced-setting">
|
<div class="form-group advanced-setting">
|
||||||
<label class="col-sm-3 control-label">Standard Episode Format</label>
|
<label class="col-sm-3 control-label">Standard Track Format</label>
|
||||||
|
|
||||||
<div class="col-sm-1 col-sm-push-8 help-inline">
|
<div class="col-sm-1 col-sm-push-8 help-inline">
|
||||||
<i class="icon-lidarr-form-info" title="" data-original-title="All caps or all lower-case can also be used"></i>
|
<i class="icon-lidarr-form-info" title="" data-original-title="All caps or all lower-case can also be used"></i>
|
||||||
|
@ -60,16 +60,17 @@
|
||||||
|
|
||||||
<div class="col-sm-8 col-sm-pull-1">
|
<div class="col-sm-8 col-sm-pull-1">
|
||||||
<div class="input-group x-helper-input">
|
<div class="input-group x-helper-input">
|
||||||
<input type="text" class="form-control naming-format" name="standardEpisodeFormat" data-onkeyup="true" />
|
<input type="text" class="form-control naming-format" name="standardTrackFormat" data-onkeyup="true" />
|
||||||
<div class="input-group-btn btn-group x-naming-token-helper">
|
<div class="input-group-btn btn-group x-naming-token-helper">
|
||||||
<button class="btn btn-icon-only dropdown-toggle" data-toggle="dropdown">
|
<button class="btn btn-icon-only dropdown-toggle" data-toggle="dropdown">
|
||||||
<i class="icon-lidarr-add"></i>
|
<i class="icon-lidarr-add"></i>
|
||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu">
|
<ul class="dropdown-menu">
|
||||||
{{> SeriesTitleNamingPartial}}
|
{{> ArtistNameNamingPartial}}
|
||||||
{{> SeasonNamingPartial}}
|
{{> AlbumTitleNamingPartial}}
|
||||||
{{> EpisodeNamingPartial}}
|
{{> ReleaseYearNamingPartial}}
|
||||||
{{> EpisodeTitleNamingPartial}}
|
{{> TrackNumNamingPartial}}
|
||||||
|
{{> TrackTitleNamingPartial}}
|
||||||
{{> QualityNamingPartial}}
|
{{> QualityNamingPartial}}
|
||||||
{{> MediaInfoNamingPartial}}
|
{{> MediaInfoNamingPartial}}
|
||||||
{{> ReleaseGroupNamingPartial}}
|
{{> ReleaseGroupNamingPartial}}
|
||||||
|
@ -81,87 +82,24 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group advanced-setting">
|
|
||||||
<label class="col-sm-3 control-label">Daily Episode Format</label>
|
|
||||||
|
|
||||||
<div class="col-sm-1 col-sm-push-8 help-inline">
|
|
||||||
<i class="icon-lidarr-form-info" title="" data-original-title="All caps or all lower-case can also be used"></i>
|
|
||||||
<a href="https://github.com/NzbDrone/NzbDrone/wiki/Sorting-and-Renaming" class="help-link" title="More information"><i class="icon-lidarr-form-info-link"/></a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-sm-8 col-sm-pull-1">
|
|
||||||
<div class="input-group x-helper-input">
|
|
||||||
<input type="text" class="form-control naming-format" name="dailyEpisodeFormat" data-onkeyup="true" />
|
|
||||||
<div class="input-group-btn btn-group x-naming-token-helper">
|
|
||||||
<button class="btn btn-icon-only dropdown-toggle" data-toggle="dropdown">
|
|
||||||
<i class="icon-lidarr-add"></i>
|
|
||||||
</button>
|
|
||||||
<ul class="dropdown-menu">
|
|
||||||
{{> SeriesTitleNamingPartial}}
|
|
||||||
{{> AirDateNamingPartial}}
|
|
||||||
{{> SeasonNamingPartial}}
|
|
||||||
{{> EpisodeNamingPartial}}
|
|
||||||
{{> EpisodeTitleNamingPartial}}
|
|
||||||
{{> QualityNamingPartial}}
|
|
||||||
{{> MediaInfoNamingPartial}}
|
|
||||||
{{> ReleaseGroupNamingPartial}}
|
|
||||||
{{> OriginalTitleNamingPartial}}
|
|
||||||
{{> SeparatorNamingPartial}}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group advanced-setting">
|
|
||||||
<label class="col-sm-3 control-label">Anime Episode Format</label>
|
|
||||||
|
|
||||||
<div class="col-sm-1 col-sm-push-8 help-inline">
|
|
||||||
<i class="icon-lidarr-form-info" title="" data-original-title="All caps or all lower-case can also be used"></i>
|
|
||||||
<a href="https://github.com/NzbDrone/NzbDrone/wiki/Sorting-and-Renaming" class="help-link" title="More information"><i class="icon-lidarr-form-info-link"/></a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-sm-8 col-sm-pull-1">
|
|
||||||
<div class="input-group x-helper-input">
|
|
||||||
<input type="text" class="form-control naming-format" name="animeEpisodeFormat" data-onkeyup="true" />
|
|
||||||
<div class="input-group-btn btn-group x-naming-token-helper">
|
|
||||||
<button class="btn btn-icon-only dropdown-toggle" data-toggle="dropdown">
|
|
||||||
<i class="icon-lidarr-add"></i>
|
|
||||||
</button>
|
|
||||||
<ul class="dropdown-menu">
|
|
||||||
{{> SeriesTitleNamingPartial}}
|
|
||||||
{{> AbsoluteEpisodeNamingPartial}}
|
|
||||||
{{> SeasonNamingPartial}}
|
|
||||||
{{> EpisodeNamingPartial}}
|
|
||||||
{{> EpisodeTitleNamingPartial}}
|
|
||||||
{{> QualityNamingPartial}}
|
|
||||||
{{> MediaInfoNamingPartial}}
|
|
||||||
{{> ReleaseGroupNamingPartial}}
|
|
||||||
{{> OriginalTitleNamingPartial}}
|
|
||||||
{{> SeparatorNamingPartial}}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group advanced-setting">
|
<div class="form-group advanced-setting">
|
||||||
<label class="col-sm-3 control-label">Series Folder Format</label>
|
<label class="col-sm-3 control-label">Artist Folder Format</label>
|
||||||
|
|
||||||
<div class="col-sm-1 col-sm-push-8 help-inline">
|
<div class="col-sm-1 col-sm-push-8 help-inline">
|
||||||
<i class="icon-lidarr-form-info" title="" data-original-title="All caps or all lower-case can also be used. Only used when adding a new series."></i>
|
<i class="icon-lidarr-form-info" title="" data-original-title="All caps or all lower-case can also be used. Only used when adding a new artist."></i>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-sm-8 col-sm-pull-1">
|
<div class="col-sm-8 col-sm-pull-1">
|
||||||
<div class="input-group x-helper-input">
|
<div class="input-group x-helper-input">
|
||||||
<input type="text" class="form-control naming-format" name="seriesFolderFormat" data-onkeyup="true"/>
|
<input type="text" class="form-control naming-format" name="artistFolderFormat" data-onkeyup="true"/>
|
||||||
<div class="input-group-btn btn-group x-naming-token-helper">
|
<div class="input-group-btn btn-group x-naming-token-helper">
|
||||||
<button class="btn btn-icon-only dropdown-toggle" data-toggle="dropdown">
|
<button class="btn btn-icon-only dropdown-toggle" data-toggle="dropdown">
|
||||||
<i class="icon-lidarr-add"></i>
|
<i class="icon-lidarr-add"></i>
|
||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu">
|
<ul class="dropdown-menu">
|
||||||
{{> SeriesTitleNamingPartial}}
|
{{> ArtistNameNamingPartial}}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -169,18 +107,19 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="col-sm-3 control-label">Season Folder Format</label>
|
<label class="col-sm-3 control-label">Album Folder Format</label>
|
||||||
|
|
||||||
<div class="col-sm-8">
|
<div class="col-sm-8">
|
||||||
<div class="input-group x-helper-input">
|
<div class="input-group x-helper-input">
|
||||||
<input type="text" class="form-control naming-format" name="seasonFolderFormat" data-onkeyup="true"/>
|
<input type="text" class="form-control naming-format" name="albumFolderFormat" data-onkeyup="true"/>
|
||||||
<div class="input-group-btn btn-group x-naming-token-helper">
|
<div class="input-group-btn btn-group x-naming-token-helper">
|
||||||
<button class="btn btn-icon-only dropdown-toggle" data-toggle="dropdown">
|
<button class="btn btn-icon-only dropdown-toggle" data-toggle="dropdown">
|
||||||
<i class="icon-lidarr-add"></i>
|
<i class="icon-lidarr-add"></i>
|
||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu">
|
<ul class="dropdown-menu">
|
||||||
{{> SeriesTitleNamingPartial}}
|
{{> ArtistNameNamingPartial}}
|
||||||
{{> SeasonNamingPartial}}
|
{{> AlbumTitleNamingPartial}}
|
||||||
|
{{> ReleaseYearNamingPartial}}
|
||||||
{{> SeparatorNamingPartial}}
|
{{> SeparatorNamingPartial}}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
@ -188,75 +127,27 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="x-naming-options">
|
<div class="form-group">
|
||||||
<div class="form-group">
|
<label class="col-sm-3 control-label">Single Track Example</label>
|
||||||
<label class="col-sm-3 control-label">Multi-Episode Style</label>
|
|
||||||
|
|
||||||
<div class="col-sm-2">
|
<div class="col-sm-8">
|
||||||
<select class="form-control x-multi-episode-style" name="multiEpisodeStyle">
|
<p class="form-control-static x-single-track-example naming-example"></p>
|
||||||
<option value="0">Extend</option>
|
|
||||||
<option value="1">Duplicate</option>
|
|
||||||
<option value="2">Repeat</option>
|
|
||||||
<option value="3">Scene</option>
|
|
||||||
<option value="4">Range</option>
|
|
||||||
<option value="5">Prefixed Range</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="col-sm-3 control-label">Single Episode Example</label>
|
<label class="col-sm-3 control-label">Artist Folder Example</label>
|
||||||
|
|
||||||
<div class="col-sm-8">
|
<div class="col-sm-8">
|
||||||
<p class="form-control-static x-single-episode-example naming-example"></p>
|
<p class="form-control-static x-artist-folder-example naming-example"></p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="col-sm-3 control-label">Multi-Episode Example</label>
|
<label class="col-sm-3 control-label">Album Folder Example</label>
|
||||||
|
|
||||||
<div class="col-sm-8">
|
<div class="col-sm-8">
|
||||||
<p class="form-control-static x-multi-episode-example naming-example"></p>
|
<p class="form-control-static x-album-folder-example naming-example"></p>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label class="col-sm-3 control-label">Daily-Episode Example</label>
|
|
||||||
|
|
||||||
<div class="col-sm-8">
|
|
||||||
<p class="form-control-static x-daily-episode-example naming-example"></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label class="col-sm-3 control-label">Anime Episode Example</label>
|
|
||||||
<div class="col-sm-8">
|
|
||||||
<p class="form-control-static x-anime-episode-example naming-example"></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label class="col-sm-3 control-label">Anime Multi-Episode Example</label>
|
|
||||||
|
|
||||||
<div class="col-sm-8">
|
|
||||||
<p class="form-control-static x-anime-multi-episode-example naming-example"></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label class="col-sm-3 control-label">Series Folder Example</label>
|
|
||||||
|
|
||||||
<div class="col-sm-8">
|
|
||||||
<p class="form-control-static x-series-folder-example naming-example"></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label class="col-sm-3 control-label">Season Folder Example</label>
|
|
||||||
|
|
||||||
<div class="col-sm-8">
|
|
||||||
<p class="form-control-static x-season-folder-example naming-example"></p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
<li class="dropdown-submenu">
|
||||||
|
<a href="#" tabindex="-1" data-token="Album Title">Album Title</a>
|
||||||
|
<ul class="dropdown-menu">
|
||||||
|
<li><a href="#" data-token="Album Title">Album Title</a></li>
|
||||||
|
<li><a href="#" data-token="Album.Title">Album.Title</a></li>
|
||||||
|
<li><a href="#" data-token="Album_Title">Album_Title</a></li>
|
||||||
|
<li><a href="#" data-token="Album CleanTitle">Album CleanTitle</a></li>
|
||||||
|
<li><a href="#" data-token="Album.CleanTitle">Album.CleanTitle</a></li>
|
||||||
|
<li><a href="#" data-token="Album_CleanTitle">Album_CleanTitle</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
|
@ -0,0 +1,11 @@
|
||||||
|
<li class="dropdown-submenu">
|
||||||
|
<a href="#" tabindex="-1" data-token="Artist Name">Artist Name</a>
|
||||||
|
<ul class="dropdown-menu">
|
||||||
|
<li><a href="#" data-token="Artist Name">Artist Name</a></li>
|
||||||
|
<li><a href="#" data-token="Artist.Name">Artist.Name</a></li>
|
||||||
|
<li><a href="#" data-token="Artist_Name">Artist_Name</a></li>
|
||||||
|
<li><a href="#" data-token="Artist CleanName">Artist CleanName</a></li>
|
||||||
|
<li><a href="#" data-token="Artist.CleanName">Artist.CleanName</a></li>
|
||||||
|
<li><a href="#" data-token="Artist_CleanName">Artist_CleanName</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
|
@ -0,0 +1 @@
|
||||||
|
<li><a href="#" data-token="Release Year">Release Year</a></li>
|
|
@ -0,0 +1,7 @@
|
||||||
|
<li class="dropdown-submenu">
|
||||||
|
<a href="#" tabindex="-1" data-token="track">Track</a>
|
||||||
|
<ul class="dropdown-menu">
|
||||||
|
<li><a href="#" data-token="track">1</a></li>
|
||||||
|
<li><a href="#" data-token="track:00">01</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
|
@ -0,0 +1,11 @@
|
||||||
|
<li class="dropdown-submenu">
|
||||||
|
<a href="#" tabindex="-1" data-token="Track Title">Track Title</a>
|
||||||
|
<ul class="dropdown-menu">
|
||||||
|
<li><a href="#" data-token="Track Title">Track Title</a></li>
|
||||||
|
<li><a href="#" data-token="Track.Title">Track.Title</a></li>
|
||||||
|
<li><a href="#" data-token="Track_Title">Track_Title</a></li>
|
||||||
|
<li><a href="#" data-token="Track CleanTitle">Track CleanTitle</a></li>
|
||||||
|
<li><a href="#" data-token="Track.CleanTitle">Track.CleanTitle</a></li>
|
||||||
|
<li><a href="#" data-token="Track_CleanTitle">Track_CleanTitle</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
Loading…
Add table
Add a link
Reference in a new issue