NamingConfig Refactor

Adds track NamingConfig, Gets naming section in settings working. Adds Release Year token and track number token
pull/6/head
Qstick 7 years ago
parent a8ac1f3adc
commit fe58f54ad4

@ -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);
// 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); return Path.GetFileNameWithoutExtension(trackFile.RelativePath);
// //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">
<label class="col-sm-3 control-label">Multi-Episode Style</label>
<div class="col-sm-2">
<select class="form-control x-multi-episode-style" name="multiEpisodeStyle">
<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 class="form-group">
<label class="col-sm-3 control-label">Single Episode Example</label>
<div class="col-sm-8">
<p class="form-control-static x-single-episode-example naming-example"></p>
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label">Multi-Episode Example</label>
<div class="col-sm-8">
<p class="form-control-static x-multi-episode-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"> <div class="form-group">
<label class="col-sm-3 control-label">Anime Multi-Episode Example</label> <label class="col-sm-3 control-label">Single Track Example</label>
<div class="col-sm-8"> <div class="col-sm-8">
<p class="form-control-static x-anime-multi-episode-example naming-example"></p> <p class="form-control-static x-single-track-example naming-example"></p>
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label class="col-sm-3 control-label">Series Folder 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-series-folder-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">Season Folder 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-season-folder-example naming-example"></p> <p class="form-control-static x-album-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…
Cancel
Save