New: Add additional CleanNameThe/CleanTitleThe naming tokens

(cherry picked from commit 81aaf00a4cd2b3a2f8ddf67226c13bb51ea39dda)

Add some translations and fix the validation for track naming

Closes #4197
pull/4547/head
Stevie Robinson 7 months ago committed by Bogdan
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 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 CleanName}', example: 'Artist Name' },
{ 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…
Cancel
Save