Merge pull request #38 from fedoranimus/develop

First pass at Media Management
pull/2/head
Tim Turner 8 years ago committed by GitHub
commit 4c2be68549

@ -39,6 +39,8 @@ namespace NzbDrone.Api.Config
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.StandardMovieFormat).ValidMovieFormat();
SharedValidator.RuleFor(c => c.MovieFolderFormat).ValidMovieFolderFormat();
} }
private void UpdateNamingConfig(NamingConfigResource resource) private void UpdateNamingConfig(NamingConfigResource resource)
@ -54,7 +56,13 @@ namespace NzbDrone.Api.Config
var nameSpec = _namingConfigService.GetConfig(); var nameSpec = _namingConfigService.GetConfig();
var resource = nameSpec.ToResource(); var resource = nameSpec.ToResource();
if (resource.StandardEpisodeFormat.IsNotNullOrWhiteSpace()) //if (resource.StandardEpisodeFormat.IsNotNullOrWhiteSpace())
//{
// var basicConfig = _filenameBuilder.GetBasicNamingConfig(nameSpec);
// basicConfig.AddToResource(resource);
//}
if (resource.StandardMovieFormat.IsNotNullOrWhiteSpace())
{ {
var basicConfig = _filenameBuilder.GetBasicNamingConfig(nameSpec); var basicConfig = _filenameBuilder.GetBasicNamingConfig(nameSpec);
basicConfig.AddToResource(resource); basicConfig.AddToResource(resource);
@ -73,39 +81,50 @@ namespace NzbDrone.Api.Config
var nameSpec = config.ToModel(); var nameSpec = config.ToModel();
var sampleResource = new NamingSampleResource(); var sampleResource = new NamingSampleResource();
var singleEpisodeSampleResult = _filenameSampleService.GetStandardSample(nameSpec); //var singleEpisodeSampleResult = _filenameSampleService.GetStandardSample(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);
sampleResource.SingleEpisodeExample = _filenameValidationService.ValidateStandardFilename(singleEpisodeSampleResult) != null var movieSampleResult = _filenameSampleService.GetMovieSample(nameSpec);
? "Invalid format"
: singleEpisodeSampleResult.FileName;
sampleResource.MultiEpisodeExample = _filenameValidationService.ValidateStandardFilename(multiEpisodeSampleResult) != null
? "Invalid format"
: multiEpisodeSampleResult.FileName;
sampleResource.DailyEpisodeExample = _filenameValidationService.ValidateDailyFilename(dailyEpisodeSampleResult) != null //sampleResource.SingleEpisodeExample = _filenameValidationService.ValidateStandardFilename(singleEpisodeSampleResult) != null
? "Invalid format" // ? "Invalid format"
: dailyEpisodeSampleResult.FileName; // : singleEpisodeSampleResult.FileName;
sampleResource.AnimeEpisodeExample = _filenameValidationService.ValidateAnimeFilename(animeEpisodeSampleResult) != null //sampleResource.MultiEpisodeExample = _filenameValidationService.ValidateStandardFilename(multiEpisodeSampleResult) != null
? "Invalid format" // ? "Invalid format"
: animeEpisodeSampleResult.FileName; // : multiEpisodeSampleResult.FileName;
sampleResource.AnimeMultiEpisodeExample = _filenameValidationService.ValidateAnimeFilename(animeMultiEpisodeSampleResult) != null //sampleResource.DailyEpisodeExample = _filenameValidationService.ValidateDailyFilename(dailyEpisodeSampleResult) != null
? "Invalid format" // ? "Invalid format"
: animeMultiEpisodeSampleResult.FileName; // : dailyEpisodeSampleResult.FileName;
sampleResource.SeriesFolderExample = nameSpec.SeriesFolderFormat.IsNullOrWhiteSpace() //sampleResource.AnimeEpisodeExample = _filenameValidationService.ValidateAnimeFilename(animeEpisodeSampleResult) != null
? "Invalid format" // ? "Invalid format"
: _filenameSampleService.GetSeriesFolderSample(nameSpec); // : animeEpisodeSampleResult.FileName;
//sampleResource.AnimeMultiEpisodeExample = _filenameValidationService.ValidateAnimeFilename(animeMultiEpisodeSampleResult) != null
// ? "Invalid format"
// : animeMultiEpisodeSampleResult.FileName;
sampleResource.MovieExample = nameSpec.StandardMovieFormat.IsNullOrWhiteSpace()
? "Invalid Format"
: movieSampleResult.FileName;
//sampleResource.SeriesFolderExample = nameSpec.SeriesFolderFormat.IsNullOrWhiteSpace()
// ? "Invalid format"
// : _filenameSampleService.GetSeriesFolderSample(nameSpec);
sampleResource.SeasonFolderExample = nameSpec.SeasonFolderFormat.IsNullOrWhiteSpace() //sampleResource.SeasonFolderExample = nameSpec.SeasonFolderFormat.IsNullOrWhiteSpace()
// ? "Invalid format"
// : _filenameSampleService.GetSeasonFolderSample(nameSpec);
sampleResource.MovieFolderExample = nameSpec.MovieFolderFormat.IsNullOrWhiteSpace()
? "Invalid format" ? "Invalid format"
: _filenameSampleService.GetSeasonFolderSample(nameSpec); : _filenameSampleService.GetMovieFolderSample(nameSpec);
return sampleResource.AsResponse(); return sampleResource.AsResponse();
} }
@ -118,19 +137,25 @@ namespace NzbDrone.Api.Config
var animeEpisodeSampleResult = _filenameSampleService.GetAnimeSample(nameSpec); var animeEpisodeSampleResult = _filenameSampleService.GetAnimeSample(nameSpec);
var animeMultiEpisodeSampleResult = _filenameSampleService.GetAnimeMultiEpisodeSample(nameSpec); var animeMultiEpisodeSampleResult = _filenameSampleService.GetAnimeMultiEpisodeSample(nameSpec);
var movieSampleResult = _filenameSampleService.GetMovieSample(nameSpec);
var singleEpisodeValidationResult = _filenameValidationService.ValidateStandardFilename(singleEpisodeSampleResult); var singleEpisodeValidationResult = _filenameValidationService.ValidateStandardFilename(singleEpisodeSampleResult);
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);
var animeMultiEpisodeValidationResult = _filenameValidationService.ValidateAnimeFilename(animeMultiEpisodeSampleResult); var animeMultiEpisodeValidationResult = _filenameValidationService.ValidateAnimeFilename(animeMultiEpisodeSampleResult);
//var standardMovieValidationResult = _filenameValidationService.ValidateMovieFilename(movieSampleResult); For now, let's hope the user is not stupid enough :/
var validationFailures = new List<ValidationFailure>(); var validationFailures = new List<ValidationFailure>();
validationFailures.AddIfNotNull(singleEpisodeValidationResult); //validationFailures.AddIfNotNull(singleEpisodeValidationResult);
validationFailures.AddIfNotNull(multiEpisodeValidationResult); //validationFailures.AddIfNotNull(multiEpisodeValidationResult);
validationFailures.AddIfNotNull(dailyEpisodeValidationResult); //validationFailures.AddIfNotNull(dailyEpisodeValidationResult);
validationFailures.AddIfNotNull(animeEpisodeValidationResult); //validationFailures.AddIfNotNull(animeEpisodeValidationResult);
validationFailures.AddIfNotNull(animeMultiEpisodeValidationResult); //validationFailures.AddIfNotNull(animeMultiEpisodeValidationResult);
//validationFailures.AddIfNotNull(standardMovieValidationResult);
if (validationFailures.Any()) if (validationFailures.Any())
{ {

@ -7,6 +7,8 @@ namespace NzbDrone.Api.Config
{ {
public bool RenameEpisodes { get; set; } public bool RenameEpisodes { get; set; }
public bool ReplaceIllegalCharacters { get; set; } public bool ReplaceIllegalCharacters { get; set; }
public string StandardMovieFormat { get; set; }
public string MovieFolderFormat { get; set; }
public int MultiEpisodeStyle { get; set; } public int MultiEpisodeStyle { get; set; }
public string StandardEpisodeFormat { get; set; } public string StandardEpisodeFormat { get; set; }
public string DailyEpisodeFormat { get; set; } public string DailyEpisodeFormat { get; set; }
@ -36,7 +38,9 @@ namespace NzbDrone.Api.Config
DailyEpisodeFormat = model.DailyEpisodeFormat, DailyEpisodeFormat = model.DailyEpisodeFormat,
AnimeEpisodeFormat = model.AnimeEpisodeFormat, AnimeEpisodeFormat = model.AnimeEpisodeFormat,
SeriesFolderFormat = model.SeriesFolderFormat, SeriesFolderFormat = model.SeriesFolderFormat,
SeasonFolderFormat = model.SeasonFolderFormat SeasonFolderFormat = model.SeasonFolderFormat,
StandardMovieFormat = model.StandardMovieFormat,
MovieFolderFormat = model.MovieFolderFormat
//IncludeSeriesTitle //IncludeSeriesTitle
//IncludeEpisodeTitle //IncludeEpisodeTitle
//IncludeQuality //IncludeQuality
@ -64,12 +68,14 @@ namespace NzbDrone.Api.Config
RenameEpisodes = resource.RenameEpisodes, RenameEpisodes = resource.RenameEpisodes,
ReplaceIllegalCharacters = resource.ReplaceIllegalCharacters, ReplaceIllegalCharacters = resource.ReplaceIllegalCharacters,
MultiEpisodeStyle = resource.MultiEpisodeStyle, //MultiEpisodeStyle = resource.MultiEpisodeStyle,
StandardEpisodeFormat = resource.StandardEpisodeFormat, //StandardEpisodeFormat = resource.StandardEpisodeFormat,
DailyEpisodeFormat = resource.DailyEpisodeFormat, //DailyEpisodeFormat = resource.DailyEpisodeFormat,
AnimeEpisodeFormat = resource.AnimeEpisodeFormat, //AnimeEpisodeFormat = resource.AnimeEpisodeFormat,
SeriesFolderFormat = resource.SeriesFolderFormat, //SeriesFolderFormat = resource.SeriesFolderFormat,
SeasonFolderFormat = resource.SeasonFolderFormat //SeasonFolderFormat = resource.SeasonFolderFormat,
StandardMovieFormat = resource.StandardMovieFormat,
MovieFolderFormat = resource.MovieFolderFormat
}; };
} }
} }

@ -9,5 +9,8 @@
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 MovieExample { get; set; }
public string MovieFolderExample { get; set; }
} }
} }

@ -0,0 +1,65 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using FluentMigrator;
using NzbDrone.Core.Datastore.Migration.Framework;
using System.Data;
namespace NzbDrone.Core.Datastore.Migration
{
[Migration(109)]
public class add_movie_formats_to_naming_config : NzbDroneMigrationBase
{
protected override void MainDbUpgrade()
{
Alter.Table("NamingConfig").AddColumn("StandardMovieFormat").AsString().Nullable();
Alter.Table("NamingConfig").AddColumn("MovieFolderFormat").AsString().Nullable();
Execute.WithConnection(ConvertConfig);
}
private void ConvertConfig(IDbConnection conn, IDbTransaction tran)
{
using (IDbCommand namingConfigCmd = conn.CreateCommand())
{
namingConfigCmd.Transaction = tran;
namingConfigCmd.CommandText = @"SELECT * FROM NamingConfig LIMIT 1";
using (IDataReader namingConfigReader = namingConfigCmd.ExecuteReader())
{
while (namingConfigReader.Read())
{
// Output Settings
var movieTitlePattern = "";
var movieYearPattern = "({Release Year})";
var qualityFormat = "[{Quality Title}]";
movieTitlePattern = "{Movie Title}";
var standardMovieFormat = string.Format("{0} {1} {2}", movieTitlePattern,
movieYearPattern,
qualityFormat);
var movieFolderFormat = string.Format("{0} {1}", movieTitlePattern, movieYearPattern);
using (IDbCommand updateCmd = conn.CreateCommand())
{
var text = string.Format("UPDATE NamingConfig " +
"SET StandardMovieFormat = '{0}', " +
"MovieFolderFormat = '{1}'",
standardMovieFormat,
movieFolderFormat);
updateCmd.Transaction = tran;
updateCmd.CommandText = text;
updateCmd.ExecuteNonQuery();
}
}
}
}
}
}
}

@ -286,6 +286,7 @@
<Compile Include="Datastore\Migration\098_remove_titans_of_tv.cs"> <Compile Include="Datastore\Migration\098_remove_titans_of_tv.cs">
<SubType>Code</SubType> <SubType>Code</SubType>
</Compile> </Compile>
<Compile Include="Datastore\Migration\109_add_movie_formats_to_naming_config.cs" />
<Compile Include="Datastore\Migration\Framework\MigrationContext.cs" /> <Compile Include="Datastore\Migration\Framework\MigrationContext.cs" />
<Compile Include="Datastore\Migration\Framework\MigrationController.cs" /> <Compile Include="Datastore\Migration\Framework\MigrationController.cs" />
<Compile Include="Datastore\Migration\Framework\MigrationDbFactory.cs" /> <Compile Include="Datastore\Migration\Framework\MigrationDbFactory.cs" />

@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
@ -24,7 +24,7 @@ namespace NzbDrone.Core.Organizer
BasicNamingConfig GetBasicNamingConfig(NamingConfig nameSpec); BasicNamingConfig GetBasicNamingConfig(NamingConfig nameSpec);
string GetSeriesFolder(Series series, NamingConfig namingConfig = null); string GetSeriesFolder(Series series, NamingConfig namingConfig = null);
string GetSeasonFolder(Series series, int seasonNumber, NamingConfig namingConfig = null); string GetSeasonFolder(Series series, int seasonNumber, NamingConfig namingConfig = null);
string GetMovieFolder(Movie movie); string GetMovieFolder(Movie movie, NamingConfig namingConfig = null);
} }
public class FileNameBuilder : IBuildFileNames public class FileNameBuilder : IBuildFileNames
@ -58,6 +58,9 @@ 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 MovieTitleRegex = new Regex(@"(?<token>\{(?:Movie)(?<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);
@ -76,6 +79,7 @@ namespace NzbDrone.Core.Organizer
{ {
_namingConfigService = namingConfigService; _namingConfigService = namingConfigService;
_qualityDefinitionService = qualityDefinitionService; _qualityDefinitionService = qualityDefinitionService;
//_movieFormatCache = cacheManager.GetCache<MovieFormat>(GetType(), "movieFormat");
_episodeFormatCache = cacheManager.GetCache<EpisodeFormat[]>(GetType(), "episodeFormat"); _episodeFormatCache = cacheManager.GetCache<EpisodeFormat[]>(GetType(), "episodeFormat");
_absoluteEpisodeFormatCache = cacheManager.GetCache<AbsoluteEpisodeFormat[]>(GetType(), "absoluteEpisodeFormat"); _absoluteEpisodeFormatCache = cacheManager.GetCache<AbsoluteEpisodeFormat[]>(GetType(), "absoluteEpisodeFormat");
_logger = logger; _logger = logger;
@ -151,52 +155,20 @@ namespace NzbDrone.Core.Organizer
return GetOriginalTitle(movieFile); return GetOriginalTitle(movieFile);
} }
/*if (namingConfig.StandardEpisodeFormat.IsNullOrWhiteSpace() && series.SeriesType == SeriesTypes.Standard) //TODO: Update namingConfig for Movies!
{ var pattern = namingConfig.StandardMovieFormat;
throw new NamingFormatException("Standard episode format cannot be empty");
}
if (namingConfig.DailyEpisodeFormat.IsNullOrWhiteSpace() && series.SeriesType == SeriesTypes.Daily)
{
throw new NamingFormatException("Daily episode format cannot be empty");
}
if (namingConfig.AnimeEpisodeFormat.IsNullOrWhiteSpace() && series.SeriesType == SeriesTypes.Anime)
{
throw new NamingFormatException("Anime episode format cannot be empty");
}*/
/*var pattern = namingConfig.StandardEpisodeFormat;
var tokenHandlers = new Dictionary<string, Func<TokenMatch, string>>(FileNameBuilderTokenEqualityComparer.Instance); var tokenHandlers = new Dictionary<string, Func<TokenMatch, string>>(FileNameBuilderTokenEqualityComparer.Instance);
episodes = episodes.OrderBy(e => e.SeasonNumber).ThenBy(e => e.EpisodeNumber).ToList(); AddMovieTokens(tokenHandlers, movie);
AddReleaseDateTokens(tokenHandlers, movie.Year); //In case we want to separate the year
if (series.SeriesType == SeriesTypes.Daily) AddQualityTokens(tokenHandlers, movie, movieFile);
{ AddMediaInfoTokens(tokenHandlers, movieFile);
pattern = namingConfig.DailyEpisodeFormat;
}
if (series.SeriesType == SeriesTypes.Anime && episodes.All(e => e.AbsoluteEpisodeNumber.HasValue))
{
pattern = namingConfig.AnimeEpisodeFormat;
}
pattern = AddSeasonEpisodeNumberingTokens(pattern, tokenHandlers, episodes, namingConfig);
pattern = AddAbsoluteNumberingTokens(pattern, tokenHandlers, series, episodes, namingConfig);
AddSeriesTokens(tokenHandlers, series);
AddEpisodeTokens(tokenHandlers, episodes);
AddEpisodeFileTokens(tokenHandlers, episodeFile);
AddQualityTokens(tokenHandlers, series, episodeFile);
AddMediaInfoTokens(tokenHandlers, episodeFile);
var fileName = ReplaceTokens(pattern, tokenHandlers, namingConfig).Trim(); var fileName = ReplaceTokens(pattern, tokenHandlers, namingConfig).Trim();
fileName = FileNameCleanupRegex.Replace(fileName, match => match.Captures[0].Value[0].ToString()); fileName = FileNameCleanupRegex.Replace(fileName, match => match.Captures[0].Value[0].ToString());
fileName = TrimSeparatorsRegex.Replace(fileName, string.Empty);*/ fileName = TrimSeparatorsRegex.Replace(fileName, string.Empty);
//TODO: Update namingConfig for Movies!
return GetOriginalTitle(movieFile); return fileName;
} }
public string BuildFilePath(Series series, int seasonNumber, string fileName, string extension) public string BuildFilePath(Series series, int seasonNumber, string fileName, string extension)
@ -242,6 +214,8 @@ namespace NzbDrone.Core.Organizer
public BasicNamingConfig GetBasicNamingConfig(NamingConfig nameSpec) public BasicNamingConfig GetBasicNamingConfig(NamingConfig nameSpec)
{ {
return new BasicNamingConfig(); //For now let's be lazy
var episodeFormat = GetEpisodeFormat(nameSpec.StandardEpisodeFormat).LastOrDefault(); var episodeFormat = GetEpisodeFormat(nameSpec.StandardEpisodeFormat).LastOrDefault();
if (episodeFormat == null) if (episodeFormat == null)
@ -315,9 +289,19 @@ namespace NzbDrone.Core.Organizer
return CleanFolderName(ReplaceTokens(namingConfig.SeasonFolderFormat, tokenHandlers, namingConfig)); return CleanFolderName(ReplaceTokens(namingConfig.SeasonFolderFormat, tokenHandlers, namingConfig));
} }
public string GetMovieFolder(Movie movie) public string GetMovieFolder(Movie movie, NamingConfig namingConfig = null)
{ {
return CleanFolderName(movie.Title); if(namingConfig == null)
{
namingConfig = _namingConfigService.GetConfig();
}
var tokenHandlers = new Dictionary<string, Func<TokenMatch, string>>(FileNameBuilderTokenEqualityComparer.Instance);
AddMovieTokens(tokenHandlers, movie);
AddReleaseDateTokens(tokenHandlers, movie.Year);
return CleanFolderName(ReplaceTokens(namingConfig.MovieFolderFormat, tokenHandlers, namingConfig));
} }
public static string CleanTitle(string title) public static string CleanTitle(string title)
@ -481,6 +465,17 @@ namespace NzbDrone.Core.Organizer
return pattern; return pattern;
} }
private void AddMovieTokens(Dictionary<string, Func<TokenMatch, string>> tokenHandlers, Movie movie)
{
tokenHandlers["{Movie Title}"] = m => movie.Title;
tokenHandlers["{Movie CleanTitle}"] = m => CleanTitle(movie.Title);
}
private void AddReleaseDateTokens(Dictionary<string, Func<TokenMatch, string>> tokenHandlers, int releaseYear)
{
tokenHandlers["{Release Year}"] = m => string.Format("{0}", releaseYear.ToString()); //Do I need m.CustomFormat?
}
private void AddSeasonTokens(Dictionary<string, Func<TokenMatch, string>> tokenHandlers, int seasonNumber) private void AddSeasonTokens(Dictionary<string, Func<TokenMatch, string>> tokenHandlers, int seasonNumber)
{ {
tokenHandlers["{Season}"] = m => seasonNumber.ToString(m.CustomFormat); tokenHandlers["{Season}"] = m => seasonNumber.ToString(m.CustomFormat);
@ -520,6 +515,18 @@ namespace NzbDrone.Core.Organizer
tokenHandlers["{Quality Real}"] = m => qualityReal; tokenHandlers["{Quality Real}"] = m => qualityReal;
} }
private void AddQualityTokens(Dictionary<string, Func<TokenMatch, string>> tokenHandlers, Movie movie, MovieFile movieFile)
{
var qualityTitle = _qualityDefinitionService.Get(movieFile.Quality.Quality).Title;
var qualityProper = GetQualityProper(movie, movieFile.Quality);
var qualityReal = GetQualityReal(movie, movieFile.Quality);
tokenHandlers["{Quality Full}"] = m => String.Format("{0} {1} {2}", qualityTitle, qualityProper, qualityReal);
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;
@ -624,6 +631,110 @@ namespace NzbDrone.Core.Organizer
tokenHandlers["{MediaInfo Full}"] = m => string.Format("{0} {1}{2} {3}", videoCodec, audioCodec, mediaInfoAudioLanguages, mediaInfoSubtitleLanguages); tokenHandlers["{MediaInfo Full}"] = m => string.Format("{0} {1}{2} {3}", videoCodec, audioCodec, mediaInfoAudioLanguages, mediaInfoSubtitleLanguages);
} }
private void AddMediaInfoTokens(Dictionary<string, Func<TokenMatch, string>> tokenHandlers, MovieFile movieFile)
{
if (movieFile.MediaInfo == null) return;
string videoCodec;
switch (movieFile.MediaInfo.VideoCodec)
{
case "AVC":
if (movieFile.SceneName.IsNotNullOrWhiteSpace() && Path.GetFileNameWithoutExtension(movieFile.SceneName).Contains("h264"))
{
videoCodec = "h264";
}
else
{
videoCodec = "x264";
}
break;
case "V_MPEGH/ISO/HEVC":
if (movieFile.SceneName.IsNotNullOrWhiteSpace() && Path.GetFileNameWithoutExtension(movieFile.SceneName).Contains("h265"))
{
videoCodec = "h265";
}
else
{
videoCodec = "x265";
}
break;
case "MPEG-2 Video":
videoCodec = "MPEG2";
break;
default:
videoCodec = movieFile.MediaInfo.VideoCodec;
break;
}
string audioCodec;
switch (movieFile.MediaInfo.AudioFormat)
{
case "AC-3":
audioCodec = "AC3";
break;
case "E-AC-3":
audioCodec = "EAC3";
break;
case "MPEG Audio":
if (movieFile.MediaInfo.AudioProfile == "Layer 3")
{
audioCodec = "MP3";
}
else
{
audioCodec = movieFile.MediaInfo.AudioFormat;
}
break;
case "DTS":
audioCodec = movieFile.MediaInfo.AudioFormat;
break;
default:
audioCodec = movieFile.MediaInfo.AudioFormat;
break;
}
var mediaInfoAudioLanguages = GetLanguagesToken(movieFile.MediaInfo.AudioLanguages);
if (!mediaInfoAudioLanguages.IsNullOrWhiteSpace())
{
mediaInfoAudioLanguages = string.Format("[{0}]", mediaInfoAudioLanguages);
}
if (mediaInfoAudioLanguages == "[EN]")
{
mediaInfoAudioLanguages = string.Empty;
}
var mediaInfoSubtitleLanguages = GetLanguagesToken(movieFile.MediaInfo.Subtitles);
if (!mediaInfoSubtitleLanguages.IsNullOrWhiteSpace())
{
mediaInfoSubtitleLanguages = string.Format("[{0}]", mediaInfoSubtitleLanguages);
}
var videoBitDepth = movieFile.MediaInfo.VideoBitDepth > 0 ? movieFile.MediaInfo.VideoBitDepth.ToString() : string.Empty;
var audioChannels = movieFile.MediaInfo.FormattedAudioChannels > 0 ?
movieFile.MediaInfo.FormattedAudioChannels.ToString("F1", CultureInfo.InvariantCulture) :
string.Empty;
tokenHandlers["{MediaInfo Video}"] = m => videoCodec;
tokenHandlers["{MediaInfo VideoCodec}"] = m => videoCodec;
tokenHandlers["{MediaInfo VideoBitDepth}"] = m => videoBitDepth;
tokenHandlers["{MediaInfo Audio}"] = m => audioCodec;
tokenHandlers["{MediaInfo AudioCodec}"] = m => audioCodec;
tokenHandlers["{MediaInfo AudioChannels}"] = m => audioChannels;
tokenHandlers["{MediaInfo Simple}"] = m => string.Format("{0} {1}", videoCodec, audioCodec);
tokenHandlers["{MediaInfo Full}"] = m => string.Format("{0} {1}{2} {3}", videoCodec, audioCodec, mediaInfoAudioLanguages, mediaInfoSubtitleLanguages);
}
private string GetLanguagesToken(string mediaInfoLanguages) private string GetLanguagesToken(string mediaInfoLanguages)
{ {
List<string> tokens = new List<string>(); List<string> tokens = new List<string>();
@ -803,6 +914,16 @@ namespace NzbDrone.Core.Organizer
return MultiPartCleanupRegex.Replace(title, string.Empty).Trim(); return MultiPartCleanupRegex.Replace(title, string.Empty).Trim();
} }
private string GetQualityProper(Movie movie, QualityModel quality)
{
if (quality.Revision.Version > 1)
{
return "Proper";
}
return String.Empty;
}
private string GetQualityProper(Series series, QualityModel quality) private string GetQualityProper(Series series, QualityModel quality)
{ {
if (quality.Revision.Version > 1) if (quality.Revision.Version > 1)
@ -828,6 +949,16 @@ namespace NzbDrone.Core.Organizer
return string.Empty; return string.Empty;
} }
private string GetQualityReal(Movie movie, QualityModel quality)
{
if (quality.Revision.Real > 0)
{
return "REAL";
}
return string.Empty;
}
private string GetOriginalTitle(EpisodeFile episodeFile) private string GetOriginalTitle(EpisodeFile episodeFile)
{ {
if (episodeFile.SceneName.IsNullOrWhiteSpace()) if (episodeFile.SceneName.IsNullOrWhiteSpace())

@ -3,6 +3,7 @@ using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.Qualities; using NzbDrone.Core.Qualities;
using NzbDrone.Core.Tv; using NzbDrone.Core.Tv;
using NzbDrone.Core.MediaFiles.MediaInfo; using NzbDrone.Core.MediaFiles.MediaInfo;
using System;
namespace NzbDrone.Core.Organizer namespace NzbDrone.Core.Organizer
{ {
@ -13,8 +14,10 @@ namespace NzbDrone.Core.Organizer
SampleResult GetDailySample(NamingConfig nameSpec); SampleResult GetDailySample(NamingConfig nameSpec);
SampleResult GetAnimeSample(NamingConfig nameSpec); SampleResult GetAnimeSample(NamingConfig nameSpec);
SampleResult GetAnimeMultiEpisodeSample(NamingConfig nameSpec); SampleResult GetAnimeMultiEpisodeSample(NamingConfig nameSpec);
SampleResult GetMovieSample(NamingConfig nameSpec);
string GetSeriesFolderSample(NamingConfig nameSpec); string GetSeriesFolderSample(NamingConfig nameSpec);
string GetSeasonFolderSample(NamingConfig nameSpec); string GetSeasonFolderSample(NamingConfig nameSpec);
string GetMovieFolderSample(NamingConfig nameSpec);
} }
public class FileNameSampleService : IFilenameSampleService public class FileNameSampleService : IFilenameSampleService
@ -34,10 +37,19 @@ namespace NzbDrone.Core.Organizer
private static EpisodeFile _animeEpisodeFile; private static EpisodeFile _animeEpisodeFile;
private static EpisodeFile _animeMultiEpisodeFile; private static EpisodeFile _animeMultiEpisodeFile;
private static MovieFile _movieFile;
private static Movie _movie;
public FileNameSampleService(IBuildFileNames buildFileNames) public FileNameSampleService(IBuildFileNames buildFileNames)
{ {
_buildFileNames = buildFileNames; _buildFileNames = buildFileNames;
_movie = new Movie
{
Title = "Movie Title",
Year = 2010
};
_standardSeries = new Series _standardSeries = new Series
{ {
SeriesType = SeriesTypes.Standard, SeriesType = SeriesTypes.Standard,
@ -106,6 +118,15 @@ namespace NzbDrone.Core.Organizer
Subtitles = "Japanese/English" Subtitles = "Japanese/English"
}; };
_movieFile = new MovieFile
{
Quality = new QualityModel(Quality.Bluray1080p, new Revision(2)),
RelativePath = "Movie.Title.2010.1080p.BluRay.DTS.x264-EVOLVE.mkv",
SceneName = "Movie.Title.2010.1080p.BluRay.DTS.x264-EVOLVE",
ReleaseGroup = "RlsGrp",
MediaInfo = mediaInfo
};
_singleEpisodeFile = new EpisodeFile _singleEpisodeFile = new EpisodeFile
{ {
Quality = new QualityModel(Quality.HDTV720p, new Revision(2)), Quality = new QualityModel(Quality.HDTV720p, new Revision(2)),
@ -217,6 +238,16 @@ namespace NzbDrone.Core.Organizer
return result; return result;
} }
public SampleResult GetMovieSample(NamingConfig nameSpec)
{
var result = new SampleResult
{
FileName = BuildSample(_movie, _movieFile, nameSpec),
};
return result;
}
public string GetSeriesFolderSample(NamingConfig nameSpec) public string GetSeriesFolderSample(NamingConfig nameSpec)
{ {
return _buildFileNames.GetSeriesFolder(_standardSeries, nameSpec); return _buildFileNames.GetSeriesFolder(_standardSeries, nameSpec);
@ -227,6 +258,11 @@ namespace NzbDrone.Core.Organizer
return _buildFileNames.GetSeasonFolder(_standardSeries, _episode1.SeasonNumber, nameSpec); return _buildFileNames.GetSeasonFolder(_standardSeries, _episode1.SeasonNumber, nameSpec);
} }
public string GetMovieFolderSample(NamingConfig nameSpec)
{
return _buildFileNames.GetMovieFolder(_movie, 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 +274,17 @@ namespace NzbDrone.Core.Organizer
return string.Empty; return string.Empty;
} }
} }
private string BuildSample(Movie movie, MovieFile movieFile, NamingConfig nameSpec)
{
try
{
return _buildFileNames.BuildFileName(movie, movieFile, nameSpec);
}
catch (NamingFormatException)
{
return string.Empty;
}
}
} }
} }

@ -41,6 +41,18 @@ 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> ValidMovieFolderFormat<T>(this IRuleBuilder<T, string> ruleBuilder)
{
ruleBuilder.SetValidator(new NotEmptyValidator(null));
return ruleBuilder.SetValidator(new RegularExpressionValidator(FileNameBuilder.MovieTitleRegex)).WithMessage("Must contain movie title");
}
public static IRuleBuilderOptions<T, string> ValidMovieFormat<T>(this IRuleBuilder<T, string> ruleBuilder)
{
ruleBuilder.SetValidator(new NotEmptyValidator(null));
return ruleBuilder.SetValidator(new RegularExpressionValidator(FileNameBuilder.MovieTitleRegex)).WithMessage("Must contain movie title");
}
} }
public class ValidStandardEpisodeFormatValidator : PropertyValidator public class ValidStandardEpisodeFormatValidator : PropertyValidator

@ -11,12 +11,26 @@ namespace NzbDrone.Core.Organizer
ValidationFailure ValidateStandardFilename(SampleResult sampleResult); ValidationFailure ValidateStandardFilename(SampleResult sampleResult);
ValidationFailure ValidateDailyFilename(SampleResult sampleResult); ValidationFailure ValidateDailyFilename(SampleResult sampleResult);
ValidationFailure ValidateAnimeFilename(SampleResult sampleResult); ValidationFailure ValidateAnimeFilename(SampleResult sampleResult);
ValidationFailure ValidateMovieFilename(SampleResult sampleResult);
} }
public class FileNameValidationService : IFilenameValidationService public class FileNameValidationService : IFilenameValidationService
{ {
private const string ERROR_MESSAGE = "Produces invalid file names"; private const string ERROR_MESSAGE = "Produces invalid file names";
public ValidationFailure ValidateMovieFilename(SampleResult sampleResult)
{
var validationFailure = new ValidationFailure("MovieFormat", ERROR_MESSAGE);
var parsedMovieInfo = Parser.Parser.ParseMovieTitle(sampleResult.FileName);
if(parsedMovieInfo == null)
{
return validationFailure;
}
return null;
}
public ValidationFailure ValidateStandardFilename(SampleResult sampleResult) public ValidationFailure ValidateStandardFilename(SampleResult sampleResult)
{ {
var validationFailure = new ValidationFailure("StandardEpisodeFormat", ERROR_MESSAGE); var validationFailure = new ValidationFailure("StandardEpisodeFormat", ERROR_MESSAGE);

@ -1,4 +1,4 @@
using NzbDrone.Core.Datastore; using NzbDrone.Core.Datastore;
namespace NzbDrone.Core.Organizer namespace NzbDrone.Core.Organizer
{ {
@ -13,7 +13,9 @@ namespace NzbDrone.Core.Organizer
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}",
MovieFolderFormat = "{Movie Title} ({Release Year})",
StandardMovieFormat = "{Movie Title} ({Release Year}) {Quality Full}",
}; };
public bool RenameEpisodes { get; set; } public bool RenameEpisodes { get; set; }
@ -24,5 +26,7 @@ namespace NzbDrone.Core.Organizer
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 StandardMovieFormat { get; set; }
public string MovieFolderFormat { get; set; }
} }
} }

@ -26,7 +26,7 @@ module.exports = Marionette.Layout.extend({
}, },
initialize : function(options) { initialize : function(options) {
console.log(options) console.log(options);
this.isExisting = options.isExisting; this.isExisting = options.isExisting;
this.collection = new AddMoviesCollection(); this.collection = new AddMoviesCollection();
@ -125,7 +125,7 @@ module.exports = Marionette.Layout.extend({
} }
else if (!this.isExisting) { else if (!this.isExisting) {
this.resultCollectionView.setExisting(options.movie.get('tmdbId')) this.resultCollectionView.setExisting(options.movie.get('tmdbId'));
/*this.collection.term = ''; /*this.collection.term = '';
this.collection.reset(); this.collection.reset();
this._clearResults(); this._clearResults();

@ -23,7 +23,7 @@ module.exports = Marionette.CollectionView.extend({
setExisting : function(tmdbid) { setExisting : function(tmdbid) {
var movies = this.collection.where({ tmdbId : tmdbid }); var movies = this.collection.where({ tmdbId : tmdbid });
console.warn(movies) console.warn(movies);
//debugger; //debugger;
if (movies.length > 0) { if (movies.length > 0) {
this.children.findByModel(movies[0])._configureTemplateHelpers(); this.children.findByModel(movies[0])._configureTemplateHelpers();

@ -92,14 +92,14 @@ var view = Marionette.ItemView.extend({
_configureTemplateHelpers : function() { _configureTemplateHelpers : function() {
var existingMovies = MoviesCollection.where({ tmdbId : this.model.get('tmdbId') }); var existingMovies = MoviesCollection.where({ tmdbId : this.model.get('tmdbId') });
console.log(existingMovies) console.log(existingMovies);
if (existingMovies.length > 0) { if (existingMovies.length > 0) {
this.templateHelpers.existing = existingMovies[0].toJSON(); this.templateHelpers.existing = existingMovies[0].toJSON();
} }
this.templateHelpers.profiles = Profiles.toJSON(); this.templateHelpers.profiles = Profiles.toJSON();
console.log(this.model) console.log(this.model);
console.log(this.templateHelpers.existing) console.log(this.templateHelpers.existing);
if (!this.model.get('isExisting')) { if (!this.model.get('isExisting')) {
this.templateHelpers.rootFolders = RootFolders.toJSON(); this.templateHelpers.rootFolders = RootFolders.toJSON();
} }
@ -245,14 +245,14 @@ var view = Marionette.ItemView.extend({
options.ignoreEpisodesWithoutFiles = true; options.ignoreEpisodesWithoutFiles = true;
} }
else if (monitor === 'latest') { // else if (monitor === 'latest') {
this.model.setSeasonPass(lastSeason.seasonNumber); // this.model.setSeasonPass(lastSeason.seasonNumber);
} // }
else if (monitor === 'first') { // else if (monitor === 'first') {
this.model.setSeasonPass(lastSeason.seasonNumber + 1); // this.model.setSeasonPass(lastSeason.seasonNumber + 1);
this.model.setSeasonMonitored(firstSeason.seasonNumber); // this.model.setSeasonMonitored(firstSeason.seasonNumber);
} // }
else if (monitor === 'missing') { else if (monitor === 'missing') {
options.ignoreEpisodesWithFiles = true; options.ignoreEpisodesWithFiles = true;
@ -262,9 +262,9 @@ var view = Marionette.ItemView.extend({
options.ignoreEpisodesWithoutFiles = true; options.ignoreEpisodesWithoutFiles = true;
} }
else if (monitor === 'none') { // else if (monitor === 'none') {
this.model.setSeasonPass(lastSeason.seasonNumber + 1); // this.model.setSeasonPass(lastSeason.seasonNumber + 1);
} // }
return options; return options;
} }

@ -2,7 +2,7 @@
<legend>File Management</legend> <legend>File Management</legend>
<div class="form-group"> <div class="form-group">
<label class="col-sm-3 control-label">Ignore Deleted Episodes</label> <label class="col-sm-3 control-label">Ignore Deleted Movies</label>
<div class="col-sm-9"> <div class="col-sm-9">
<div class="input-group"> <div class="input-group">
@ -17,7 +17,7 @@
</label> </label>
<span class="help-inline-checkbox"> <span class="help-inline-checkbox">
<i class="icon-sonarr-form-info" title="Episodes deleted from disk are automatically unmonitored in Sonarr"/> <i class="icon-sonarr-form-info" title="Movies deleted from disk are automatically unmonitored in Radarr"/>
</span> </span>
</div> </div>
</div> </div>
@ -39,7 +39,7 @@
</label> </label>
<span class="help-inline-checkbox"> <span class="help-inline-checkbox">
<i class="icon-sonarr-form-info" title="Should Sonarr automatically upgrade to propers when available?"/> <i class="icon-sonarr-form-info" title="Should Radarr automatically upgrade to propers when available?"/>
</span> </span>
</div> </div>
</div> </div>

@ -26,10 +26,10 @@ var view = Marionette.ItemView.extend({
}, },
_parseNamingModel : function() { _parseNamingModel : function() {
var standardFormat = this.namingModel.get('standardEpisodeFormat'); var standardFormat = this.namingModel.get('standardMovieFormat');
var includeSeriesTitle = standardFormat.match(/\{Series[-_. ]Title\}/i); var includeSeriesTitle = false;//standardFormat.match(/\{Series[-_. ]Title\}/i);
var includeEpisodeTitle = standardFormat.match(/\{Episode[-_. ]Title\}/i); var includeEpisodeTitle = false;//standardFormat.match(/\{Episode[-_. ]Title\}/i);
var includeQuality = standardFormat.match(/\{Quality[-_. ]Title\}/i); var includeQuality = standardFormat.match(/\{Quality[-_. ]Title\}/i);
var numberStyle = standardFormat.match(/s?\{season(?:\:0+)?\}[ex]\{episode(?:\:0+)?\}/i); var numberStyle = standardFormat.match(/s?\{season(?:\:0+)?\}[ex]\{episode(?:\:0+)?\}/i);
var replaceSpaces = standardFormat.indexOf(' ') === -1; var replaceSpaces = standardFormat.indexOf(' ') === -1;

@ -19,7 +19,9 @@ module.exports = (function() {
namingTokenHelper : '.x-naming-token-helper', namingTokenHelper : '.x-naming-token-helper',
multiEpisodeStyle : '.x-multi-episode-style', multiEpisodeStyle : '.x-multi-episode-style',
seriesFolderExample : '.x-series-folder-example', seriesFolderExample : '.x-series-folder-example',
seasonFolderExample : '.x-season-folder-example' seasonFolderExample : '.x-season-folder-example',
movieExample : '.x-movie-example',
movieFolderExample : '.x-movie-folder-example'
}, },
events : { events : {
"change .x-rename-episodes" : '_setFailedDownloadOptionsVisibility', "change .x-rename-episodes" : '_setFailedDownloadOptionsVisibility',
@ -58,6 +60,8 @@ module.exports = (function() {
this.ui.animeMultiEpisodeExample.html(this.namingSampleModel.get('animeMultiEpisodeExample')); this.ui.animeMultiEpisodeExample.html(this.namingSampleModel.get('animeMultiEpisodeExample'));
this.ui.seriesFolderExample.html(this.namingSampleModel.get('seriesFolderExample')); this.ui.seriesFolderExample.html(this.namingSampleModel.get('seriesFolderExample'));
this.ui.seasonFolderExample.html(this.namingSampleModel.get('seasonFolderExample')); this.ui.seasonFolderExample.html(this.namingSampleModel.get('seasonFolderExample'));
this.ui.movieExample.html(this.namingSampleModel.get('movieExample'));
this.ui.movieFolderExample.html(this.namingSampleModel.get('movieFolderExample'));
}, },
_addToken : function(e) { _addToken : function(e) {
e.preventDefault(); e.preventDefault();

@ -1,8 +1,8 @@
<fieldset> <fieldset>
<legend>Episode Naming</legend> <legend>Movie 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 Movies</label>
<div class="col-sm-8"> <div class="col-sm-8">
<div class="input-group"> <div class="input-group">
@ -18,7 +18,7 @@
</label> </label>
<span class="help-inline-checkbox"> <span class="help-inline-checkbox">
<i class="icon-sonarr-form-warning" title="Sonarr will use the existing file name if set to no"/> <i class="icon-sonarr-form-warning" title="Radarr will use the existing file name if set to no"/>
</span> </span>
</div> </div>
</div> </div>
@ -51,25 +51,23 @@
<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 Movie 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-sonarr-form-info" title="" data-original-title="All caps or all lower-case can also be used"></i> <i class="icon-sonarr-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-sonarr-form-info-link"/></a> <a href="https://github.com/NzbDrone/NzbDrone/wiki/Sorting-and-Renaming" class="help-link" title="More information"><i class="icon-sonarr-form-info-link"/></a> <!-- TODO: Update wiki link -->
</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="standardEpisodeFormat" data-onkeyup="true" /> <input type="text" class="form-control naming-format" name="standardMovieFormat" 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-sonarr-add"></i> <i class="icon-sonarr-add"></i>
</button> </button>
<ul class="dropdown-menu"> <ul class="dropdown-menu">
{{> SeriesTitleNamingPartial}} {{> MovieTitleNamingPartial}}
{{> SeasonNamingPartial}} {{> ReleaseYearNamingPartial}}
{{> EpisodeNamingPartial}}
{{> EpisodeTitleNamingPartial}}
{{> QualityNamingPartial}} {{> QualityNamingPartial}}
{{> MediaInfoNamingPartial}} {{> MediaInfoNamingPartial}}
{{> ReleaseGroupNamingPartial}} {{> ReleaseGroupNamingPartial}}
@ -81,7 +79,7 @@
</div> </div>
</div> </div>
<div class="form-group advanced-setting"> {{!--<div class="form-group advanced-setting">
<label class="col-sm-3 control-label">Daily Episode Format</label> <label class="col-sm-3 control-label">Daily Episode Format</label>
<div class="col-sm-1 col-sm-push-8 help-inline"> <div class="col-sm-1 col-sm-push-8 help-inline">
@ -111,9 +109,9 @@
</div> </div>
</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">Anime Episode Format</label> <label class="col-sm-3 control-label">Anime Episode Format</label>
<div class="col-sm-1 col-sm-push-8 help-inline"> <div class="col-sm-1 col-sm-push-8 help-inline">
@ -144,31 +142,32 @@
</div> </div>
</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">Movie 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-sonarr-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-sonarr-form-info" title="" data-original-title="All caps or all lower-case can also be used. Only used when adding a new movie."></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="movieFolderFormat" 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-sonarr-add"></i> <i class="icon-sonarr-add"></i>
</button> </button>
<ul class="dropdown-menu"> <ul class="dropdown-menu">
{{> SeriesTitleNamingPartial}} {{> MovieTitleNamingPartial}}
{{> ReleaseYearNamingPartial}}
</ul> </ul>
</div> </div>
</div> </div>
</div> </div>
</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">Season Folder Format</label>
<div class="col-sm-8"> <div class="col-sm-8">
@ -186,9 +185,9 @@
</div> </div>
</div> </div>
</div> </div>
</div> </div>--}}
<div class="x-naming-options"> {{!--<div class="x-naming-options">
<div class="form-group"> <div class="form-group">
<label class="col-sm-3 control-label">Multi-Episode Style</label> <label class="col-sm-3 control-label">Multi-Episode Style</label>
@ -203,17 +202,17 @@
</select> </select>
</div> </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">Movie 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-movie-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">Multi-Episode Example</label>
<div class="col-sm-8"> <div class="col-sm-8">
@ -242,21 +241,21 @@
<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-anime-multi-episode-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">Movie 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-movie-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">Season 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-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="Movie Title">Movie Title</a>
<ul class="dropdown-menu">
<li><a href="#" data-token="Movie Title">Movie Title</a></li>
<li><a href="#" data-token="Movie.Title">Movie.Title</a></li>
<li><a href="#" data-token="Movie_Title">Movie_Title</a></li>
<li><a href="#" data-token="Movie CleanTitle">Movie CleanTitle</a></li>
<li><a href="#" data-token="Movie.CleanTitle">Movie.CleanTitle</a></li>
<li><a href="#" data-token="Movie_CleanTitle">Movie_CleanTitle</a></li>
</ul>
</li>

@ -0,0 +1 @@
<li><a href="#" data-token="Release Year">Release Year</a></li>

@ -2,7 +2,7 @@
<legend>Folders</legend> <legend>Folders</legend>
<div class="form-group"> <div class="form-group">
<label class="col-sm-3 control-label">Create empty series folders</label> <label class="col-sm-3 control-label">Create empty movie folders</label>
<div class="col-sm-9"> <div class="col-sm-9">
<div class="input-group"> <div class="input-group">
@ -18,7 +18,7 @@
</label> </label>
<span class="help-inline-checkbox"> <span class="help-inline-checkbox">
<i class="icon-sonarr-form-info" title="Create missing series folders during disk scan"/> <i class="icon-sonarr-form-info" title="Create missing movie folders during disk scan"/>
</span> </span>
</div> </div>
</div> </div>
@ -46,7 +46,7 @@
</label> </label>
<span class="help-inline-checkbox"> <span class="help-inline-checkbox">
<i class="icon-sonarr-form-info" title="Use when drone is unable to detect free space from your series root folder"/> <i class="icon-sonarr-form-info" title="Use when drone is unable to detect free space from your movies root folder"/>
</span> </span>
</div> </div>
</div> </div>
@ -71,7 +71,7 @@
<span class="help-inline-checkbox"> <span class="help-inline-checkbox">
<i class="icon-sonarr-form-info" title="Use Hardlinks when trying to copy files from torrents that are still being seeded"/> <i class="icon-sonarr-form-info" title="Use Hardlinks when trying to copy files from torrents that are still being seeded"/>
<i class="icon-sonarr-form-warning" title="Occasionally, file locks may prevent renaming files that are being seeded. You may temporarily disable seeding and use Sonarr's rename function as a work around."/> <i class="icon-sonarr-form-warning" title="Occasionally, file locks may prevent renaming files that are being seeded. You may temporarily disable seeding and use Radarr's rename function as a work around."/>
</span> </span>
</div> </div>
</div> </div>

Loading…
Cancel
Save