diff --git a/frontend/src/Settings/MediaManagement/Naming/Naming.js b/frontend/src/Settings/MediaManagement/Naming/Naming.js
index ef22d320a..81367c831 100644
--- a/frontend/src/Settings/MediaManagement/Naming/Naming.js
+++ b/frontend/src/Settings/MediaManagement/Naming/Naming.js
@@ -40,6 +40,18 @@ class Naming extends Component {
});
}
+ onMultiDiscNamingModalOpenClick = () => {
+ this.setState({
+ isNamingModalOpen: true,
+ namingModalOptions: {
+ name: 'multiDiscTrackFormat',
+ album: true,
+ track: true,
+ additional: true
+ }
+ });
+ }
+
onArtistFolderNamingModalOpenClick = () => {
this.setState({
isNamingModalOpen: true,
@@ -87,6 +99,8 @@ class Naming extends Component {
const standardTrackFormatHelpTexts = [];
const standardTrackFormatErrors = [];
+ const multiDiscTrackFormatHelpTexts = [];
+ const multiDiscTrackFormatErrors = [];
const artistFolderFormatHelpTexts = [];
const artistFolderFormatErrors = [];
const albumFolderFormatHelpTexts = [];
@@ -99,6 +113,12 @@ class Naming extends Component {
standardTrackFormatErrors.push({ message: 'Single Track: Invalid Format' });
}
+ if (examples.multiDiscTrackExample) {
+ multiDiscTrackFormatHelpTexts.push(`Multi Disc Track: ${examples.multiDiscTrackExample}`);
+ } else {
+ multiDiscTrackFormatErrors.push({ message: 'Single Track: Invalid Format' });
+ }
+
if (examples.artistFolderExample) {
artistFolderFormatHelpTexts.push(`Example: ${examples.artistFolderExample}`);
} else {
@@ -169,6 +189,21 @@ class Naming extends Component {
/>
+
+ Multi Disc Track Format
+
+ ?}
+ onChange={onInputChange}
+ {...settings.multiDiscTrackFormat}
+ helpTexts={multiDiscTrackFormatHelpTexts}
+ errors={[...multiDiscTrackFormatErrors, ...settings.multiDiscTrackFormat.errors]}
+ />
+
+
}
diff --git a/src/Lidarr.Api.V1/Config/NamingConfigModule.cs b/src/Lidarr.Api.V1/Config/NamingConfigModule.cs
index 4af476d20..c2ba93c5c 100644
--- a/src/Lidarr.Api.V1/Config/NamingConfigModule.cs
+++ b/src/Lidarr.Api.V1/Config/NamingConfigModule.cs
@@ -37,6 +37,7 @@ namespace Lidarr.Api.V1.Config
SharedValidator.RuleFor(c => c.StandardTrackFormat).ValidTrackFormat();
+ SharedValidator.RuleFor(c => c.MultiDiscTrackFormat).ValidTrackFormat();
SharedValidator.RuleFor(c => c.ArtistFolderFormat).ValidArtistFolderFormat();
SharedValidator.RuleFor(c => c.AlbumFolderFormat).ValidAlbumFolderFormat();
}
@@ -60,6 +61,12 @@ namespace Lidarr.Api.V1.Config
basicConfig.AddToResource(resource);
}
+ if (resource.MultiDiscTrackFormat.IsNotNullOrWhiteSpace())
+ {
+ var basicConfig = _filenameBuilder.GetBasicNamingConfig(nameSpec);
+ basicConfig.AddToResource(resource);
+ }
+
return resource;
}
@@ -79,11 +86,16 @@ namespace Lidarr.Api.V1.Config
var sampleResource = new NamingExampleResource();
var singleTrackSampleResult = _filenameSampleService.GetStandardTrackSample(nameSpec);
+ var multiDiscTrackSampleResult = _filenameSampleService.GetMultiDiscTrackSample(nameSpec);
sampleResource.SingleTrackExample = _filenameValidationService.ValidateTrackFilename(singleTrackSampleResult) != null
? null
: singleTrackSampleResult.FileName;
+ sampleResource.MultiDiscTrackExample = _filenameValidationService.ValidateTrackFilename(multiDiscTrackSampleResult) != null
+ ? null
+ : multiDiscTrackSampleResult.FileName;
+
sampleResource.ArtistFolderExample = nameSpec.ArtistFolderFormat.IsNullOrWhiteSpace()
? null
: _filenameSampleService.GetArtistFolderSample(nameSpec);
diff --git a/src/Lidarr.Api.V1/Config/NamingConfigResource.cs b/src/Lidarr.Api.V1/Config/NamingConfigResource.cs
index fce16aa99..ee39b9040 100644
--- a/src/Lidarr.Api.V1/Config/NamingConfigResource.cs
+++ b/src/Lidarr.Api.V1/Config/NamingConfigResource.cs
@@ -7,6 +7,7 @@ namespace Lidarr.Api.V1.Config
public bool RenameTracks { get; set; }
public bool ReplaceIllegalCharacters { get; set; }
public string StandardTrackFormat { get; set; }
+ public string MultiDiscTrackFormat { get; set; }
public string ArtistFolderFormat { get; set; }
public string AlbumFolderFormat { get; set; }
public bool IncludeArtistName { get; set; }
diff --git a/src/Lidarr.Api.V1/Config/NamingExampleResource.cs b/src/Lidarr.Api.V1/Config/NamingExampleResource.cs
index 19c281e8a..7a12db6ea 100644
--- a/src/Lidarr.Api.V1/Config/NamingExampleResource.cs
+++ b/src/Lidarr.Api.V1/Config/NamingExampleResource.cs
@@ -5,6 +5,7 @@ namespace Lidarr.Api.V1.Config
public class NamingExampleResource
{
public string SingleTrackExample { get; set; }
+ public string MultiDiscTrackExample { get; set; }
public string ArtistFolderExample { get; set; }
public string AlbumFolderExample { get; set; }
}
@@ -20,6 +21,7 @@ namespace Lidarr.Api.V1.Config
RenameTracks = model.RenameTracks,
ReplaceIllegalCharacters = model.ReplaceIllegalCharacters,
StandardTrackFormat = model.StandardTrackFormat,
+ MultiDiscTrackFormat = model.MultiDiscTrackFormat,
ArtistFolderFormat = model.ArtistFolderFormat,
AlbumFolderFormat = model.AlbumFolderFormat
};
@@ -44,6 +46,7 @@ namespace Lidarr.Api.V1.Config
RenameTracks = resource.RenameTracks,
ReplaceIllegalCharacters = resource.ReplaceIllegalCharacters,
StandardTrackFormat = resource.StandardTrackFormat,
+ MultiDiscTrackFormat = resource.MultiDiscTrackFormat,
ArtistFolderFormat = resource.ArtistFolderFormat,
AlbumFolderFormat = resource.AlbumFolderFormat
diff --git a/src/NzbDrone.Core/Datastore/Migration/035_multi_disc_naming_format.cs b/src/NzbDrone.Core/Datastore/Migration/035_multi_disc_naming_format.cs
new file mode 100644
index 000000000..e1f9b915b
--- /dev/null
+++ b/src/NzbDrone.Core/Datastore/Migration/035_multi_disc_naming_format.cs
@@ -0,0 +1,17 @@
+using FluentMigrator;
+using NzbDrone.Core.Datastore.Migration.Framework;
+using System.Data;
+using System.IO;
+
+namespace NzbDrone.Core.Datastore.Migration
+{
+ [Migration(35)]
+ public class multi_disc_naming_format : NzbDroneMigrationBase
+ {
+ protected override void MainDbUpgrade()
+ {
+ Alter.Table("NamingConfig").AddColumn("MultiDiscTrackFormat").AsString().Nullable();
+ Execute.Sql("UPDATE NamingConfig SET MultiDiscTrackFormat = '{Medium Format} {medium:00}/{Artist Name} - {Album Title} - {track:00} - {Track Title}'");
+ }
+ }
+}
diff --git a/src/NzbDrone.Core/Organizer/EpisodeSortingType.cs b/src/NzbDrone.Core/Organizer/EpisodeSortingType.cs
deleted file mode 100644
index d68549f07..000000000
--- a/src/NzbDrone.Core/Organizer/EpisodeSortingType.cs
+++ /dev/null
@@ -1,10 +0,0 @@
-namespace NzbDrone.Core.Organizer
-{
- public class EpisodeSortingType
- {
- public int Id { get; set; }
- public string Name { get; set; }
- public string Pattern { get; set; }
- public string EpisodeSeparator { get; set; }
- }
-}
\ No newline at end of file
diff --git a/src/NzbDrone.Core/Organizer/FileNameBuilder.cs b/src/NzbDrone.Core/Organizer/FileNameBuilder.cs
index 15d1966ed..4bdb6b2d6 100644
--- a/src/NzbDrone.Core/Organizer/FileNameBuilder.cs
+++ b/src/NzbDrone.Core/Organizer/FileNameBuilder.cs
@@ -66,7 +66,7 @@ namespace NzbDrone.Core.Organizer
//TODO: Support Written numbers (One, Two, etc) and Roman Numerals (I, II, III etc)
private static readonly Regex MultiPartCleanupRegex = new Regex(@"(?:\(\d+\)|(Part|Pt\.?)\s?\d+)$", RegexOptions.Compiled | RegexOptions.IgnoreCase);
- private static readonly char[] EpisodeTitleTrimCharacters = new[] { ' ', '.', '?' };
+ private static readonly char[] TrackTitleTrimCharacters = new[] { ' ', '.', '?' };
private static readonly Regex TitlePrefixRegex = new Regex(@"^(The|An|A) (.*?)((?: *\([^)]+\))*)$", RegexOptions.Compiled | RegexOptions.IgnoreCase);
@@ -96,18 +96,27 @@ namespace NzbDrone.Core.Organizer
return GetOriginalFileName(trackFile);
}
- if (namingConfig.StandardTrackFormat.IsNullOrWhiteSpace())
+ if (namingConfig.StandardTrackFormat.IsNullOrWhiteSpace() || namingConfig.MultiDiscTrackFormat.IsNullOrWhiteSpace())
{
- throw new NamingFormatException("Standard track format cannot be empty");
+ throw new NamingFormatException("Standard and Multi track formats cannot be empty");
}
var pattern = namingConfig.StandardTrackFormat;
+
+ if (tracks.First().AlbumRelease.Value.Media.Count() > 1)
+ {
+ pattern = namingConfig.MultiDiscTrackFormat;
+ }
+
+ var subFolders = pattern.Split(new[] { '/', '\\' }, StringSplitOptions.RemoveEmptyEntries);
+ var safePattern = subFolders.Aggregate("", (current, folderLevel) => Path.Combine(current, (folderLevel)));
+
var tokenHandlers = new Dictionary>(FileNameBuilderTokenEqualityComparer.Instance);
tracks = tracks.OrderBy(e => e.AlbumReleaseId).ThenBy(e => e.TrackNumber).ToList();
- pattern = FormatTrackNumberTokens(pattern, "", tracks);
- pattern = FormatMediumNumberTokens(pattern, "", tracks);
+ safePattern = FormatTrackNumberTokens(safePattern, "", tracks);
+ safePattern = FormatMediumNumberTokens(safePattern, "", tracks);
AddArtistTokens(tokenHandlers, artist);
AddAlbumTokens(tokenHandlers, album);
@@ -118,7 +127,7 @@ namespace NzbDrone.Core.Organizer
AddMediaInfoTokens(tokenHandlers, trackFile);
AddPreferredWords(tokenHandlers, artist, trackFile, preferredWords);
- var fileName = ReplaceTokens(pattern, tokenHandlers, namingConfig).Trim();
+ var fileName = ReplaceTokens(safePattern, tokenHandlers, namingConfig).Trim();
fileName = FileNameCleanupRegex.Replace(fileName, match => match.Captures[0].Value[0].ToString());
fileName = TrimSeparatorsRegex.Replace(fileName, string.Empty);
@@ -315,12 +324,12 @@ namespace NzbDrone.Core.Organizer
private void AddQualityTokens(Dictionary> tokenHandlers, Artist artist, TrackFile trackFile)
{
var qualityTitle = _qualityDefinitionService.Get(trackFile.Quality.Quality).Title;
- //var qualityProper = GetQualityProper(artist, trackFile.Quality);
+ var qualityProper = GetQualityProper(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 Proper}"] = m => qualityProper;
//tokenHandlers["{Quality Real}"] = m => qualityReal;
}
@@ -459,17 +468,17 @@ namespace NzbDrone.Core.Organizer
if (tracks.Count == 1)
{
- return tracks.First().Title.TrimEnd(EpisodeTitleTrimCharacters);
+ return tracks.First().Title.TrimEnd(TrackTitleTrimCharacters);
}
- var titles = tracks.Select(c => c.Title.TrimEnd(EpisodeTitleTrimCharacters))
+ var titles = tracks.Select(c => c.Title.TrimEnd(TrackTitleTrimCharacters))
.Select(CleanupTrackTitle)
.Distinct()
.ToList();
if (titles.All(t => t.IsNullOrWhiteSpace()))
{
- titles = tracks.Select(c => c.Title.TrimEnd(EpisodeTitleTrimCharacters))
+ titles = tracks.Select(c => c.Title.TrimEnd(TrackTitleTrimCharacters))
.Distinct()
.ToList();
}
@@ -483,21 +492,20 @@ namespace NzbDrone.Core.Organizer
return MultiPartCleanupRegex.Replace(title, string.Empty).Trim();
}
- // TODO: DO WE NEED FOR MUSIC?
- //private string GetQualityProper(Series series, QualityModel quality)
- //{
- // if (quality.Revision.Version > 1)
- // {
- // if (series.SeriesType == SeriesTypes.Anime)
- // {
- // return "v" + quality.Revision.Version;
- // }
+ private string GetQualityProper(QualityModel quality)
+ {
+ if (quality.Revision.Version > 1)
+ {
+ if (quality.Revision.IsRepack)
+ {
+ return "Repack";
+ }
- // return "Proper";
- // }
+ return "Proper";
+ }
- // return String.Empty;
- //}
+ return String.Empty;
+ }
//private string GetQualityReal(Series series, QualityModel quality)
//{
diff --git a/src/NzbDrone.Core/Organizer/FileNameSampleService.cs b/src/NzbDrone.Core/Organizer/FileNameSampleService.cs
index 35a9c42a2..5ded480da 100644
--- a/src/NzbDrone.Core/Organizer/FileNameSampleService.cs
+++ b/src/NzbDrone.Core/Organizer/FileNameSampleService.cs
@@ -9,7 +9,7 @@ namespace NzbDrone.Core.Organizer
public interface IFilenameSampleService
{
SampleResult GetStandardTrackSample(NamingConfig nameSpec);
-
+ SampleResult GetMultiDiscTrackSample(NamingConfig nameSpec);
string GetArtistFolderSample(NamingConfig nameSpec);
string GetAlbumFolderSample(NamingConfig nameSpec);
}
@@ -20,6 +20,8 @@ namespace NzbDrone.Core.Organizer
private static Artist _standardArtist;
private static Album _standardAlbum;
+ private static AlbumRelease _singleRelease;
+ private static AlbumRelease _multiRelease;
private static Track _track1;
private static List