diff --git a/frontend/src/Settings/MediaManagement/Naming/NamingModal.js b/frontend/src/Settings/MediaManagement/Naming/NamingModal.js index 661da3f07..23e800fb9 100644 --- a/frontend/src/Settings/MediaManagement/Naming/NamingModal.js +++ b/frontend/src/Settings/MediaManagement/Naming/NamingModal.js @@ -82,6 +82,12 @@ const trackTitleTokens = [ { token: '{Track CleanTitle}', example: 'Track Title' } ]; +const trackArtistTokens = [ + { token: '{Track ArtistName}', example: 'Artist Name' }, + { token: '{Track ArtistNameThe}', example: 'Artist Name, The' }, + { token: '{Track ArtistCleanName}', example: 'Artist Name' } +]; + const qualityTokens = [ { token: '{Quality Full}', example: 'FLAC Proper' }, { token: '{Quality Title}', example: 'FLAC' } @@ -411,6 +417,28 @@ class NamingModal extends Component { +
+
+ { + trackArtistTokens.map(({ token, example }) => { + return ( + + ); + } + ) + } +
+
+
{ diff --git a/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/FileNameBuilderFixture.cs b/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/FileNameBuilderFixture.cs index a8ee23d2c..1311ecceb 100644 --- a/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/FileNameBuilderFixture.cs +++ b/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/FileNameBuilderFixture.cs @@ -4,6 +4,7 @@ using System.Linq; using FizzWare.NBuilder; using FluentAssertions; using NUnit.Framework; +using NzbDrone.Core.Datastore; using NzbDrone.Core.MediaFiles; using NzbDrone.Core.Music; using NzbDrone.Core.Organizer; @@ -17,10 +18,13 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests public class FileNameBuilderFixture : CoreTest { private Artist _artist; + private Artist _variousArtists; private Album _album; + private Album _mixAlbum; private Medium _medium; private AlbumRelease _release; private Track _track1; + private Track _mixTrack1; private TrackFile _trackFile; private NamingConfig _namingConfig; @@ -37,6 +41,15 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests }) .Build(); + _variousArtists = Builder + .CreateNew() + .With(s => s.Name = "Various Artists") + .With(s => s.Metadata = new ArtistMetadata + { + Name = "Various Artists" + }) + .Build(); + _medium = Builder .CreateNew() .With(m => m.Number = 3) @@ -55,6 +68,12 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests .With(s => s.Disambiguation = "The Best Album") .Build(); + _mixAlbum = Builder + .CreateNew() + .With(s => s.Title = "Cool Music") + .With(s => s.AlbumType = "Album") + .Build(); + _namingConfig = NamingConfig.Default; _namingConfig.RenameTracks = true; @@ -66,8 +85,17 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests .With(e => e.AbsoluteTrackNumber = 6) .With(e => e.AlbumRelease = _release) .With(e => e.MediumNumber = _medium.Number) + .With(e => e.ArtistMetadata = _artist.Metadata) .Build(); + _mixTrack1 = Builder.CreateNew() + .With(e => e.Title = "City Sushi") + .With(e => e.AbsoluteTrackNumber = 1) + .With(e => e.AlbumRelease = _release) + .With(e => e.MediumNumber = _medium.Number) + .With(e => e.ArtistMetadata = _artist.Metadata) + .Build(); + _trackFile = Builder.CreateNew() .With(e => e.Quality = new QualityModel(Quality.MP3_256)) .With(e => e.ReleaseGroup = "LidarrTest") @@ -461,6 +489,7 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests .With(e => e.Title = "Part 1") .With(e => e.AbsoluteTrackNumber = 6) .With(e => e.AlbumRelease = _release) + .With(e => e.ArtistMetadata = new ArtistMetadata { Name = "In The Woods." }) .Build(); Subject.BuildTrackFileName(new List { track }, new Artist { Name = "In The Woods." }, new Album { Title = "30 Rock" }, _trackFile) @@ -476,6 +505,7 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests .With(e => e.Title = "Part 1") .With(e => e.AbsoluteTrackNumber = 6) .With(e => e.AlbumRelease = _release) + .With(e => e.ArtistMetadata = new ArtistMetadata { Name = "In The Woods..." }) .Build(); Subject.BuildTrackFileName(new List { track }, new Artist { Name = "In The Woods..." }, new Album { Title = "30 Rock" }, _trackFile) @@ -648,5 +678,21 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests Subject.BuildTrackFileName(new List { _track1 }, _artist, _album, _trackFile) .Should().Be(releaseGroup); } + + [Test] + public void should_replace_artist_name_for_Various_Artists_album() + { + _namingConfig.StandardTrackFormat = "{Artist Name}"; + Subject.BuildTrackFileName(new List { _mixTrack1 }, _variousArtists, _mixAlbum, _trackFile) + .Should().Be("Various Artists"); + } + + [Test] + public void should_replace_track_artist_name_for_Various_Artists_album() + { + _namingConfig.StandardTrackFormat = "{Track ArtistName}"; + Subject.BuildTrackFileName(new List { _mixTrack1 }, _variousArtists, _mixAlbum, _trackFile) + .Should().Be("Linkin Park"); + } } } diff --git a/src/NzbDrone.Core/Organizer/FileNameBuilder.cs b/src/NzbDrone.Core/Organizer/FileNameBuilder.cs index 3737eae61..b19210070 100644 --- a/src/NzbDrone.Core/Organizer/FileNameBuilder.cs +++ b/src/NzbDrone.Core/Organizer/FileNameBuilder.cs @@ -113,7 +113,7 @@ namespace NzbDrone.Core.Organizer AddArtistTokens(tokenHandlers, artist); AddAlbumTokens(tokenHandlers, album); AddMediumTokens(tokenHandlers, tracks.First().AlbumRelease.Value.Media.SingleOrDefault(m => m.Number == tracks.First().MediumNumber)); - AddTrackTokens(tokenHandlers, tracks); + AddTrackTokens(tokenHandlers, tracks, artist); AddTrackFileTokens(tokenHandlers, trackFile); AddQualityTokens(tokenHandlers, artist, trackFile); AddMediaInfoTokens(tokenHandlers, trackFile); @@ -301,10 +301,21 @@ namespace NzbDrone.Core.Organizer tokenHandlers["{Medium Format}"] = m => medium.Format; } - private void AddTrackTokens(Dictionary> tokenHandlers, List tracks) + private void AddTrackTokens(Dictionary> tokenHandlers, List tracks, Artist artist) { tokenHandlers["{Track Title}"] = m => GetTrackTitle(tracks, "+"); tokenHandlers["{Track CleanTitle}"] = m => CleanTitle(GetTrackTitle(tracks, "and")); + + // Use the track's ArtistMetadata by default, as it will handle the "Various Artists" case + // (where the album artist is "Various Artists" but each track has its own artist). Fall back + // to the album artist if we don't have any track ArtistMetadata for whatever reason. + var firstArtist = tracks.Select(t => t.ArtistMetadata?.Value).FirstOrDefault() ?? artist.Metadata; + if (firstArtist != null) + { + tokenHandlers["{Track ArtistName}"] = m => firstArtist.Name; + tokenHandlers["{Track ArtistCleanName}"] = m => CleanTitle(firstArtist.Name); + tokenHandlers["{Track ArtistNameThe}"] = m => TitleThe(firstArtist.Name); + } } private void AddTrackFileTokens(Dictionary> tokenHandlers, TrackFile trackFile) diff --git a/src/NzbDrone.Core/Organizer/FileNameSampleService.cs b/src/NzbDrone.Core/Organizer/FileNameSampleService.cs index 23b902e80..7c7fb9165 100644 --- a/src/NzbDrone.Core/Organizer/FileNameSampleService.cs +++ b/src/NzbDrone.Core/Organizer/FileNameSampleService.cs @@ -29,14 +29,15 @@ namespace NzbDrone.Core.Organizer public FileNameSampleService(IBuildFileNames buildFileNames) { _buildFileNames = buildFileNames; + var artistMetadata = new ArtistMetadata + { + Name = "The Artist Name", + Disambiguation = "US Rock Band" + }; _standardArtist = new Artist { - Metadata = new ArtistMetadata - { - Name = "The Artist Name", - Disambiguation = "US Rock Band" - } + Metadata = artistMetadata }; _standardAlbum = new Album @@ -86,8 +87,10 @@ namespace NzbDrone.Core.Organizer _track1 = new Track { AlbumRelease = _singleRelease, + Artist = _standardArtist, AbsoluteTrackNumber = 3, MediumNumber = 1, + ArtistMetadata = artistMetadata, Title = "Track Title (1)", };