mirror of https://github.com/lidarr/Lidarr
New: Add additional CleanNameThe/CleanTitleThe naming tokens
(cherry picked from commit 81aaf00a4cd2b3a2f8ddf67226c13bb51ea39dda) Add some translations and fix the validation for track naming Closes #4197
This commit is contained in:
parent
2f80957f11
commit
0121095b3e
|
@ -15,16 +15,51 @@ import NamingOption from './NamingOption';
|
|||
import styles from './NamingModal.css';
|
||||
|
||||
const separatorOptions = [
|
||||
{ key: ' ', value: 'Space ( )' },
|
||||
{ key: '.', value: 'Period (.)' },
|
||||
{ key: '_', value: 'Underscore (_)' },
|
||||
{ key: '-', value: 'Dash (-)' }
|
||||
{
|
||||
key: ' ',
|
||||
get value() {
|
||||
return `${translate('Space')} ( )`;
|
||||
}
|
||||
},
|
||||
{
|
||||
key: '.',
|
||||
get value() {
|
||||
return `${translate('Period')} (.)`;
|
||||
}
|
||||
},
|
||||
{
|
||||
key: '_',
|
||||
get value() {
|
||||
return `${translate('Underscore')} (_)`;
|
||||
}
|
||||
},
|
||||
{
|
||||
key: '-',
|
||||
get value() {
|
||||
return `${translate('Dash')} (-)`;
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
const caseOptions = [
|
||||
{ key: 'title', value: 'Default Case' },
|
||||
{ key: 'lower', value: 'Lowercase' },
|
||||
{ key: 'upper', value: 'Uppercase' }
|
||||
{
|
||||
key: 'title',
|
||||
get value() {
|
||||
return translate('DefaultCase');
|
||||
}
|
||||
},
|
||||
{
|
||||
key: 'lower',
|
||||
get value() {
|
||||
return translate('Lowercase');
|
||||
}
|
||||
},
|
||||
{
|
||||
key: 'upper',
|
||||
get value() {
|
||||
return translate('Uppercase');
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
const fileNameTokens = [
|
||||
|
@ -40,33 +75,23 @@ const fileNameTokens = [
|
|||
|
||||
const artistTokens = [
|
||||
{ token: '{Artist Name}', example: 'Artist Name' },
|
||||
|
||||
{ token: '{Artist NameThe}', example: 'Artist Name, The' },
|
||||
|
||||
{ token: '{Artist NameFirstCharacter}', example: 'A' },
|
||||
|
||||
{ token: '{Artist CleanName}', example: 'Artist Name' },
|
||||
|
||||
{ token: '{Artist NameThe}', example: 'Artist Name, The' },
|
||||
{ token: '{Artist CleanNameThe}', example: 'Artist Name, The' },
|
||||
{ token: '{Artist NameFirstCharacter}', example: 'A' },
|
||||
{ token: '{Artist Disambiguation}', example: 'Disambiguation' },
|
||||
|
||||
{ token: '{Artist Genre}', example: 'Pop' },
|
||||
|
||||
{ token: '{Artist MbId}', example: 'db92a151-1ac2-438b-bc43-b82e149ddd50' }
|
||||
];
|
||||
|
||||
const albumTokens = [
|
||||
{ token: '{Album Title}', example: 'Album Title' },
|
||||
|
||||
{ token: '{Album TitleThe}', example: 'Album Title, The' },
|
||||
|
||||
{ token: '{Album CleanTitle}', example: 'Album Title' },
|
||||
|
||||
{ token: '{Album TitleThe}', example: 'Album Title, The' },
|
||||
{ token: '{Album CleanTitleThe}', example: 'Album Title, The' },
|
||||
{ token: '{Album Type}', example: 'Album Type' },
|
||||
|
||||
{ token: '{Album Disambiguation}', example: 'Disambiguation' },
|
||||
|
||||
{ token: '{Album Genre}', example: 'Rock' },
|
||||
|
||||
{ token: '{Album MbId}', example: '082c6aff-a7cc-36e0-a960-35a578ecd937' }
|
||||
];
|
||||
|
||||
|
@ -96,8 +121,9 @@ const trackTitleTokens = [
|
|||
|
||||
const trackArtistTokens = [
|
||||
{ token: '{Track ArtistName}', example: 'Artist Name' },
|
||||
{ token: '{Track ArtistNameThe}', example: 'Artist Name, The' },
|
||||
{ token: '{Track ArtistCleanName}', example: 'Artist Name' },
|
||||
{ token: '{Track ArtistNameThe}', example: 'Artist Name, The' },
|
||||
{ token: '{Track ArtistCleanNameThe}', example: 'Artist Name, The' },
|
||||
{ token: '{Track ArtistMbId}', example: 'db92a151-1ac2-438b-bc43-b82e149ddd50' }
|
||||
];
|
||||
|
||||
|
@ -213,7 +239,7 @@ class NamingModal extends Component {
|
|||
>
|
||||
<ModalContent onModalClose={onModalClose}>
|
||||
<ModalHeader>
|
||||
File Name Tokens
|
||||
{translate('FileNameTokens')}
|
||||
</ModalHeader>
|
||||
|
||||
<ModalBody>
|
||||
|
@ -552,7 +578,7 @@ class NamingModal extends Component {
|
|||
onSelectionChange={this.onInputSelectionChange}
|
||||
/>
|
||||
<Button onPress={onModalClose}>
|
||||
Close
|
||||
{translate('Close')}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</ModalContent>
|
||||
|
|
|
@ -0,0 +1,98 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using FizzWare.NBuilder;
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Core.CustomFormats;
|
||||
using NzbDrone.Core.MediaFiles;
|
||||
using NzbDrone.Core.Music;
|
||||
using NzbDrone.Core.Organizer;
|
||||
using NzbDrone.Core.Qualities;
|
||||
using NzbDrone.Core.Test.Framework;
|
||||
|
||||
namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
|
||||
{
|
||||
[TestFixture]
|
||||
public class CleanTitleTheFixture : CoreTest<FileNameBuilder>
|
||||
{
|
||||
private Artist _artist;
|
||||
private Album _album;
|
||||
private AlbumRelease _release;
|
||||
private Track _track;
|
||||
private TrackFile _trackFile;
|
||||
private NamingConfig _namingConfig;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
_artist = Builder<Artist>
|
||||
.CreateNew()
|
||||
.With(s => s.Name = "Avenged Sevenfold")
|
||||
.Build();
|
||||
|
||||
_album = Builder<Album>
|
||||
.CreateNew()
|
||||
.With(s => s.Title = "Hail to the King")
|
||||
.Build();
|
||||
|
||||
_release = Builder<AlbumRelease>
|
||||
.CreateNew()
|
||||
.With(s => s.Media = new List<Medium> { new Medium { Number = 1 } })
|
||||
.Build();
|
||||
|
||||
_track = Builder<Track>.CreateNew()
|
||||
.With(e => e.Title = "Doing Time")
|
||||
.With(e => e.AbsoluteTrackNumber = 3)
|
||||
.With(e => e.AlbumRelease = _release)
|
||||
.Build();
|
||||
|
||||
_trackFile = new TrackFile { Quality = new QualityModel(Quality.MP3_256), ReleaseGroup = "LidarrTest" };
|
||||
|
||||
_namingConfig = NamingConfig.Default;
|
||||
_namingConfig.RenameTracks = true;
|
||||
|
||||
Mocker.GetMock<INamingConfigService>()
|
||||
.Setup(c => c.GetConfig()).Returns(_namingConfig);
|
||||
|
||||
Mocker.GetMock<IQualityDefinitionService>()
|
||||
.Setup(v => v.Get(Moq.It.IsAny<Quality>()))
|
||||
.Returns<Quality>(v => Quality.DefaultQualityDefinitions.First(c => c.Quality == v));
|
||||
|
||||
Mocker.GetMock<ICustomFormatService>()
|
||||
.Setup(v => v.All())
|
||||
.Returns(new List<CustomFormat>());
|
||||
}
|
||||
|
||||
[TestCase("The Mist", "Mist, The")]
|
||||
[TestCase("A Place to Call Home", "Place to Call Home, A")]
|
||||
[TestCase("An Adventure in Space and Time", "Adventure in Space and Time, An")]
|
||||
[TestCase("The Flash (2010)", "Flash, The 2010")]
|
||||
[TestCase("A League Of Their Own (AU)", "League Of Their Own, A AU")]
|
||||
[TestCase("The Fixer (ZH) (2015)", "Fixer, The ZH 2015")]
|
||||
[TestCase("The Sixth Sense 2 (Thai)", "Sixth Sense 2, The Thai")]
|
||||
[TestCase("The Amazing Race (Latin America)", "Amazing Race, The Latin America")]
|
||||
[TestCase("The Rat Pack (A&E)", "Rat Pack, The AandE")]
|
||||
[TestCase("The Climax: I (Almost) Got Away With It (2016)", "Climax I Almost Got Away With It, The 2016")]
|
||||
public void should_get_expected_title_back(string title, string expected)
|
||||
{
|
||||
_artist.Name = title;
|
||||
_namingConfig.StandardTrackFormat = "{Artist CleanNameThe}";
|
||||
|
||||
Subject.BuildTrackFileName(new List<Track> { _track }, _artist, _album, _trackFile)
|
||||
.Should().Be(expected);
|
||||
}
|
||||
|
||||
[TestCase("A")]
|
||||
[TestCase("Anne")]
|
||||
[TestCase("Theodore")]
|
||||
[TestCase("3%")]
|
||||
public void should_not_change_title(string title)
|
||||
{
|
||||
_artist.Name = title;
|
||||
_namingConfig.StandardTrackFormat = "{Artist CleanNameThe}";
|
||||
|
||||
Subject.BuildTrackFileName(new List<Track> { _track }, _artist, _album, _trackFile)
|
||||
.Should().Be(title);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -256,6 +256,7 @@
|
|||
"CutoffFormatScoreHelpText": "Once this custom format score is reached {appName} will no longer grab album releases",
|
||||
"CutoffHelpText": "Once this quality is reached {appName} will no longer download albums",
|
||||
"CutoffUnmet": "Cutoff Unmet",
|
||||
"Dash": "Dash",
|
||||
"DashOrSpaceDashDependingOnName": "Dash or Space Dash depending on name",
|
||||
"Database": "Database",
|
||||
"DatabaseMigration": "Database Migration",
|
||||
|
@ -263,6 +264,7 @@
|
|||
"DateAdded": "Date Added",
|
||||
"Dates": "Dates",
|
||||
"Deceased": "Deceased",
|
||||
"DefaultCase": "Default Case",
|
||||
"DefaultDelayProfileHelpText": "This is the default profile. It applies to all artist that don't have an explicit profile.",
|
||||
"DefaultLidarrTags": "Default {appName} Tags",
|
||||
"DefaultMetadataProfileIdHelpText": "Default Metadata Profile for artists detected in this folder",
|
||||
|
@ -449,6 +451,7 @@
|
|||
"FailedToLoadQueue": "Failed to load Queue",
|
||||
"FileDateHelpText": "Change file date on import/rescan",
|
||||
"FileManagement": "File Management",
|
||||
"FileNameTokens": "File Name Tokens",
|
||||
"FileNames": "File Names",
|
||||
"Filename": "Filename",
|
||||
"Files": "Files",
|
||||
|
@ -614,6 +617,7 @@
|
|||
"Logout": "Logout",
|
||||
"Logs": "Logs",
|
||||
"LongDateFormat": "Long Date Format",
|
||||
"Lowercase": "Lowercase",
|
||||
"MIA": "MIA",
|
||||
"MaintenanceRelease": "Maintenance Release: bug fixes and other improvements. See Github Commit History for more details",
|
||||
"ManageClients": "Manage Clients",
|
||||
|
@ -764,6 +768,7 @@
|
|||
"PathHelpText": "Root Folder containing your music library",
|
||||
"PathHelpTextWarning": "This must be different to the directory where your download client puts files",
|
||||
"Peers": "Peers",
|
||||
"Period": "Period",
|
||||
"Permissions": "Permissions",
|
||||
"Playlist": "Playlist",
|
||||
"Port": "Port",
|
||||
|
@ -1028,6 +1033,7 @@
|
|||
"Source": "Source",
|
||||
"SourcePath": "Source Path",
|
||||
"SourceTitle": "Source Title",
|
||||
"Space": "Space",
|
||||
"SpecificAlbum": "Specific Album",
|
||||
"SpecificMonitoringOptionHelpText": "Monitor artists but only monitor albums explicitly included in the list",
|
||||
"SslCertPasswordHelpText": "Password for pfx file",
|
||||
|
@ -1147,6 +1153,7 @@
|
|||
"UnableToLoadTags": "Unable to load Tags",
|
||||
"UnableToLoadTheCalendar": "Unable to load the calendar",
|
||||
"UnableToLoadUISettings": "Unable to load UI settings",
|
||||
"Underscore": "Underscore",
|
||||
"Ungroup": "Ungroup",
|
||||
"Unlimited": "Unlimited",
|
||||
"UnmappedFiles": "Unmapped Files",
|
||||
|
@ -1169,6 +1176,7 @@
|
|||
"UpdatingIsDisabledInsideADockerContainerUpdateTheContainerImageInstead": "Updating is disabled inside a docker container. Update the container image instead.",
|
||||
"UpgradeAllowedHelpText": "If disabled qualities will not be upgraded",
|
||||
"UpgradesAllowed": "Upgrades Allowed",
|
||||
"Uppercase": "Uppercase",
|
||||
"Uptime": "Uptime",
|
||||
"UrlBaseHelpText": "For reverse proxy support, default is empty",
|
||||
"UrlBaseHelpTextWarning": "Requires restart to take effect",
|
||||
|
|
|
@ -54,7 +54,7 @@ namespace NzbDrone.Core.Organizer
|
|||
public static readonly Regex AlbumTitleRegex = new Regex(@"(?<token>\{(?:Album)(?<separator>[- ._])(Clean)?Title(The)?(?::(?<customFormat>[0-9-]+))?\})",
|
||||
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
|
||||
public static readonly Regex TrackTitleRegex = new Regex(@"(?<token>\{(?:Track)(?<separator>[- ._])(Clean)?Title(The)?(?::(?<customFormat>[0-9-]+))?\})",
|
||||
public static readonly Regex TrackTitleRegex = new Regex(@"(?<token>\{(?:Track)(?<separator>[- ._])(Clean)?Title(?::(?<customFormat>[0-9-]+))?\})",
|
||||
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
|
||||
private static readonly Regex FileNameCleanupRegex = new Regex(@"([- ._])(\1)+", RegexOptions.Compiled);
|
||||
|
@ -266,6 +266,17 @@ namespace NzbDrone.Core.Organizer
|
|||
return TitlePrefixRegex.Replace(title, "$2, $1$3");
|
||||
}
|
||||
|
||||
public static string CleanTitleThe(string title)
|
||||
{
|
||||
if (TitlePrefixRegex.IsMatch(title))
|
||||
{
|
||||
var splitResult = TitlePrefixRegex.Split(title);
|
||||
return $"{CleanTitle(splitResult[2]).Trim()}, {splitResult[1]}{CleanTitle(splitResult[3])}";
|
||||
}
|
||||
|
||||
return CleanTitle(title);
|
||||
}
|
||||
|
||||
public static string TitleFirstCharacter(string title)
|
||||
{
|
||||
if (char.IsLetterOrDigit(title[0]))
|
||||
|
@ -300,6 +311,7 @@ namespace NzbDrone.Core.Organizer
|
|||
tokenHandlers["{Artist Name}"] = m => Truncate(artist.Name, m.CustomFormat);
|
||||
tokenHandlers["{Artist CleanName}"] = m => Truncate(CleanTitle(artist.Name), m.CustomFormat);
|
||||
tokenHandlers["{Artist NameThe}"] = m => Truncate(TitleThe(artist.Name), m.CustomFormat);
|
||||
tokenHandlers["{Artist CleanNameThe}"] = m => Truncate(CleanTitleThe(artist.Name), m.CustomFormat);
|
||||
tokenHandlers["{Artist Genre}"] = m => artist.Metadata.Value.Genres?.FirstOrDefault() ?? string.Empty;
|
||||
tokenHandlers["{Artist NameFirstCharacter}"] = m => TitleFirstCharacter(TitleThe(artist.Name));
|
||||
tokenHandlers["{Artist MbId}"] = m => artist.ForeignArtistId ?? string.Empty;
|
||||
|
@ -315,6 +327,7 @@ namespace NzbDrone.Core.Organizer
|
|||
tokenHandlers["{Album Title}"] = m => Truncate(album.Title, m.CustomFormat);
|
||||
tokenHandlers["{Album CleanTitle}"] = m => Truncate(CleanTitle(album.Title), m.CustomFormat);
|
||||
tokenHandlers["{Album TitleThe}"] = m => Truncate(TitleThe(album.Title), m.CustomFormat);
|
||||
tokenHandlers["{Album CleanTitleThe}"] = m => Truncate(CleanTitleThe(album.Title), m.CustomFormat);
|
||||
tokenHandlers["{Album Type}"] = m => album.AlbumType;
|
||||
tokenHandlers["{Album Genre}"] = m => album.Genres.FirstOrDefault() ?? string.Empty;
|
||||
tokenHandlers["{Album MbId}"] = m => album.ForeignAlbumId ?? string.Empty;
|
||||
|
@ -346,6 +359,7 @@ namespace NzbDrone.Core.Organizer
|
|||
tokenHandlers["{Track ArtistName}"] = m => Truncate(firstArtist.Name, m.CustomFormat);
|
||||
tokenHandlers["{Track ArtistCleanName}"] = m => Truncate(CleanTitle(firstArtist.Name), m.CustomFormat);
|
||||
tokenHandlers["{Track ArtistNameThe}"] = m => Truncate(TitleThe(firstArtist.Name), m.CustomFormat);
|
||||
tokenHandlers["{Track ArtistCleanNameThe}"] = m => Truncate(CleanTitleThe(firstArtist.Name), m.CustomFormat);
|
||||
tokenHandlers["{Track ArtistMbId}"] = m => firstArtist.ForeignArtistId ?? string.Empty;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue