mirror of
https://github.com/lidarr/lidarr.git
synced 2025-07-30 03:38:26 -07:00
New: Colon replacement naming option
(cherry picked from commit b3260ba8661f3b2c6996eee7e04974e8f41365d5)
This commit is contained in:
parent
35248c277d
commit
b6967aed47
8 changed files with 219 additions and 19 deletions
|
@ -87,6 +87,15 @@ class Naming extends Component {
|
||||||
} = this.state;
|
} = this.state;
|
||||||
|
|
||||||
const renameTracks = hasSettings && settings.renameTracks.value;
|
const renameTracks = hasSettings && settings.renameTracks.value;
|
||||||
|
const replaceIllegalCharacters = hasSettings && settings.replaceIllegalCharacters.value;
|
||||||
|
|
||||||
|
const colonReplacementOptions = [
|
||||||
|
{ key: 0, value: translate('Delete') },
|
||||||
|
{ key: 1, value: translate('ReplaceWithDash') },
|
||||||
|
{ key: 2, value: translate('ReplaceWithSpaceDash') },
|
||||||
|
{ key: 3, value: translate('ReplaceWithSpaceDashSpace') },
|
||||||
|
{ key: 4, value: translate('SmartReplace'), hint: translate('DashOrSpaceDashDependingOnName') }
|
||||||
|
];
|
||||||
|
|
||||||
const standardTrackFormatHelpTexts = [];
|
const standardTrackFormatHelpTexts = [];
|
||||||
const standardTrackFormatErrors = [];
|
const standardTrackFormatErrors = [];
|
||||||
|
@ -160,6 +169,24 @@ class Naming extends Component {
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
|
|
||||||
|
{
|
||||||
|
replaceIllegalCharacters ?
|
||||||
|
<FormGroup>
|
||||||
|
<FormLabel>
|
||||||
|
{translate('ColonReplacement')}
|
||||||
|
</FormLabel>
|
||||||
|
|
||||||
|
<FormInputGroup
|
||||||
|
type={inputTypes.SELECT}
|
||||||
|
name="colonReplacementFormat"
|
||||||
|
values={colonReplacementOptions}
|
||||||
|
onChange={onInputChange}
|
||||||
|
{...settings.colonReplacementFormat}
|
||||||
|
/>
|
||||||
|
</FormGroup> :
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
renameTracks &&
|
renameTracks &&
|
||||||
<div>
|
<div>
|
||||||
|
|
|
@ -6,6 +6,7 @@ namespace Lidarr.Api.V1.Config
|
||||||
{
|
{
|
||||||
public bool RenameTracks { get; set; }
|
public bool RenameTracks { get; set; }
|
||||||
public bool ReplaceIllegalCharacters { get; set; }
|
public bool ReplaceIllegalCharacters { get; set; }
|
||||||
|
public int ColonReplacementFormat { get; set; }
|
||||||
public string StandardTrackFormat { get; set; }
|
public string StandardTrackFormat { get; set; }
|
||||||
public string MultiDiscTrackFormat { get; set; }
|
public string MultiDiscTrackFormat { get; set; }
|
||||||
public string ArtistFolderFormat { get; set; }
|
public string ArtistFolderFormat { get; set; }
|
||||||
|
|
|
@ -20,6 +20,7 @@ namespace Lidarr.Api.V1.Config
|
||||||
|
|
||||||
RenameTracks = model.RenameTracks,
|
RenameTracks = model.RenameTracks,
|
||||||
ReplaceIllegalCharacters = model.ReplaceIllegalCharacters,
|
ReplaceIllegalCharacters = model.ReplaceIllegalCharacters,
|
||||||
|
ColonReplacementFormat = (int)model.ColonReplacementFormat,
|
||||||
StandardTrackFormat = model.StandardTrackFormat,
|
StandardTrackFormat = model.StandardTrackFormat,
|
||||||
MultiDiscTrackFormat = model.MultiDiscTrackFormat,
|
MultiDiscTrackFormat = model.MultiDiscTrackFormat,
|
||||||
ArtistFolderFormat = model.ArtistFolderFormat
|
ArtistFolderFormat = model.ArtistFolderFormat
|
||||||
|
@ -44,6 +45,7 @@ namespace Lidarr.Api.V1.Config
|
||||||
|
|
||||||
RenameTracks = resource.RenameTracks,
|
RenameTracks = resource.RenameTracks,
|
||||||
ReplaceIllegalCharacters = resource.ReplaceIllegalCharacters,
|
ReplaceIllegalCharacters = resource.ReplaceIllegalCharacters,
|
||||||
|
ColonReplacementFormat = (ColonReplacementFormat)resource.ColonReplacementFormat,
|
||||||
StandardTrackFormat = resource.StandardTrackFormat,
|
StandardTrackFormat = resource.StandardTrackFormat,
|
||||||
MultiDiscTrackFormat = resource.MultiDiscTrackFormat,
|
MultiDiscTrackFormat = resource.MultiDiscTrackFormat,
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,105 @@
|
||||||
|
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 ColonReplacementFixture : 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 = "Nu:Tone")
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
_album = Builder<Album>
|
||||||
|
.CreateNew()
|
||||||
|
.With(s => s.Title = "Medical History")
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
_release = Builder<AlbumRelease>
|
||||||
|
.CreateNew()
|
||||||
|
.With(s => s.Media = new List<Medium> { new () { Number = 14 } })
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
_track = Builder<Track>.CreateNew()
|
||||||
|
.With(e => e.Title = "System: Accapella")
|
||||||
|
.With(e => e.AbsoluteTrackNumber = 14)
|
||||||
|
.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>());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_replace_colon_followed_by_space_with_space_dash_space_by_default()
|
||||||
|
{
|
||||||
|
_namingConfig.StandardTrackFormat = "{Artist Name} - {Album Title} - {Track Title}";
|
||||||
|
|
||||||
|
Subject.BuildTrackFileName(new List<Track> { _track }, _artist, _album, _trackFile)
|
||||||
|
.Should().Be("Nu-Tone - Medical History - System - Accapella");
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestCase("System: Accapella", ColonReplacementFormat.Smart, "Nu-Tone - Medical History - System - Accapella")]
|
||||||
|
[TestCase("System: Accapella", ColonReplacementFormat.Dash, "Nu-Tone - Medical History - System- Accapella")]
|
||||||
|
[TestCase("System: Accapella", ColonReplacementFormat.Delete, "NuTone - Medical History - System Accapella")]
|
||||||
|
[TestCase("System: Accapella", ColonReplacementFormat.SpaceDash, "Nu -Tone - Medical History - System - Accapella")]
|
||||||
|
[TestCase("System: Accapella", ColonReplacementFormat.SpaceDashSpace, "Nu - Tone - Medical History - System - Accapella")]
|
||||||
|
public void should_replace_colon_followed_by_space_with_expected_result(string trackTitle, ColonReplacementFormat replacementFormat, string expected)
|
||||||
|
{
|
||||||
|
_track.Title = trackTitle;
|
||||||
|
_namingConfig.StandardTrackFormat = "{Artist Name} - {Album Title} - {Track Title}";
|
||||||
|
_namingConfig.ColonReplacementFormat = replacementFormat;
|
||||||
|
|
||||||
|
Subject.BuildTrackFileName(new List<Track> { _track }, _artist, _album, _trackFile)
|
||||||
|
.Should().Be(expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestCase("Artist:Name", ColonReplacementFormat.Smart, "Artist-Name")]
|
||||||
|
[TestCase("Artist:Name", ColonReplacementFormat.Dash, "Artist-Name")]
|
||||||
|
[TestCase("Artist:Name", ColonReplacementFormat.Delete, "ArtistName")]
|
||||||
|
[TestCase("Artist:Name", ColonReplacementFormat.SpaceDash, "Artist -Name")]
|
||||||
|
[TestCase("Artist:Name", ColonReplacementFormat.SpaceDashSpace, "Artist - Name")]
|
||||||
|
public void should_replace_colon_with_expected_result(string artistName, ColonReplacementFormat replacementFormat, string expected)
|
||||||
|
{
|
||||||
|
_artist.Name = artistName;
|
||||||
|
_namingConfig.StandardTrackFormat = "{Artist Name}";
|
||||||
|
_namingConfig.ColonReplacementFormat = replacementFormat;
|
||||||
|
|
||||||
|
Subject.BuildTrackFileName(new List<Track> { _track }, _artist, _album, _trackFile)
|
||||||
|
.Should().Be(expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
using FluentMigrator;
|
||||||
|
using NzbDrone.Core.Datastore.Migration.Framework;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Datastore.Migration
|
||||||
|
{
|
||||||
|
[Migration(068)]
|
||||||
|
public class add_colon_replacement_to_naming_config : NzbDroneMigrationBase
|
||||||
|
{
|
||||||
|
protected override void MainDbUpgrade()
|
||||||
|
{
|
||||||
|
Alter.Table("NamingConfig").AddColumn("ColonReplacementFormat").AsInt32().WithDefaultValue(4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -141,6 +141,7 @@
|
||||||
"Close": "Close",
|
"Close": "Close",
|
||||||
"CollapseMultipleAlbums": "Collapse Multiple Albums",
|
"CollapseMultipleAlbums": "Collapse Multiple Albums",
|
||||||
"CollapseMultipleAlbumsHelpText": "Collapse multiple albums releasing on the same day",
|
"CollapseMultipleAlbumsHelpText": "Collapse multiple albums releasing on the same day",
|
||||||
|
"ColonReplacement": "Colon Replacement",
|
||||||
"Columns": "Columns",
|
"Columns": "Columns",
|
||||||
"CombineWithExistingFiles": "Combine With Existing Files",
|
"CombineWithExistingFiles": "Combine With Existing Files",
|
||||||
"CompletedDownloadHandling": "Completed Download Handling",
|
"CompletedDownloadHandling": "Completed Download Handling",
|
||||||
|
@ -174,6 +175,7 @@
|
||||||
"CutoffHelpText": "Once this quality is reached Lidarr will no longer download albums",
|
"CutoffHelpText": "Once this quality is reached Lidarr will no longer download albums",
|
||||||
"CutoffUnmet": "Cutoff Unmet",
|
"CutoffUnmet": "Cutoff Unmet",
|
||||||
"DBMigration": "DB Migration",
|
"DBMigration": "DB Migration",
|
||||||
|
"DashOrSpaceDashDependingOnName": "Dash or Space Dash depending on name",
|
||||||
"Database": "Database",
|
"Database": "Database",
|
||||||
"Date": "Date",
|
"Date": "Date",
|
||||||
"DateAdded": "Date Added",
|
"DateAdded": "Date Added",
|
||||||
|
@ -711,6 +713,9 @@
|
||||||
"ReplaceExistingFiles": "Replace Existing Files",
|
"ReplaceExistingFiles": "Replace Existing Files",
|
||||||
"ReplaceIllegalCharacters": "Replace Illegal Characters",
|
"ReplaceIllegalCharacters": "Replace Illegal Characters",
|
||||||
"ReplaceIllegalCharactersHelpText": "Replace illegal characters. If unchecked, Lidarr will remove them instead",
|
"ReplaceIllegalCharactersHelpText": "Replace illegal characters. If unchecked, Lidarr will remove them instead",
|
||||||
|
"ReplaceWithDash": "Replace with Dash",
|
||||||
|
"ReplaceWithSpaceDash": "Replace with Space Dash",
|
||||||
|
"ReplaceWithSpaceDashSpace": "Replace with Space Dash Space",
|
||||||
"RequiredHelpText": "The release must contain at least one of these terms (case insensitive)",
|
"RequiredHelpText": "The release must contain at least one of these terms (case insensitive)",
|
||||||
"RequiredPlaceHolder": "Add new restriction",
|
"RequiredPlaceHolder": "Add new restriction",
|
||||||
"RequiresRestartToTakeEffect": "Requires restart to take effect",
|
"RequiresRestartToTakeEffect": "Requires restart to take effect",
|
||||||
|
@ -816,6 +821,7 @@
|
||||||
"SkipFreeSpaceCheckWhenImportingHelpText": "Use when Lidarr is unable to detect free space from your artist root folder",
|
"SkipFreeSpaceCheckWhenImportingHelpText": "Use when Lidarr is unable to detect free space from your artist root folder",
|
||||||
"SkipRedownload": "Skip Redownload",
|
"SkipRedownload": "Skip Redownload",
|
||||||
"SkipredownloadHelpText": "Prevents Lidarr from trying download alternative releases for the removed items",
|
"SkipredownloadHelpText": "Prevents Lidarr from trying download alternative releases for the removed items",
|
||||||
|
"SmartReplace": "Smart Replace",
|
||||||
"SomeResultsFiltered": "Some results are hidden by the applied filter",
|
"SomeResultsFiltered": "Some results are hidden by the applied filter",
|
||||||
"SorryThatAlbumCannotBeFound": "Sorry, that album cannot be found.",
|
"SorryThatAlbumCannotBeFound": "Sorry, that album cannot be found.",
|
||||||
"SorryThatArtistCannotBeFound": "Sorry, that artist cannot be found.",
|
"SorryThatArtistCannotBeFound": "Sorry, that artist cannot be found.",
|
||||||
|
|
|
@ -242,30 +242,17 @@ namespace NzbDrone.Core.Organizer
|
||||||
return TitlePrefixRegex.Replace(title, "$2, $1$3");
|
return TitlePrefixRegex.Replace(title, "$2, $1$3");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string CleanFileName(string name, bool replace = true)
|
public static string CleanFileName(string name)
|
||||||
{
|
{
|
||||||
string result = name;
|
return CleanFileName(name, NamingConfig.Default);
|
||||||
string[] badCharacters = { "\\", "/", "<", ">", "?", "*", ":", "|", "\"" };
|
|
||||||
string[] goodCharacters = { "+", "+", "", "", "!", "-", "-", "", "" };
|
|
||||||
|
|
||||||
// Replace a colon followed by a space with space dash space for a better appearance
|
|
||||||
if (replace)
|
|
||||||
{
|
|
||||||
result = result.Replace(": ", " - ");
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < badCharacters.Length; i++)
|
|
||||||
{
|
|
||||||
result = result.Replace(badCharacters[i], replace ? goodCharacters[i] : string.Empty);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result.Trim();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string CleanFolderName(string name)
|
public static string CleanFolderName(string name)
|
||||||
{
|
{
|
||||||
name = FileNameCleanupRegex.Replace(name, match => match.Captures[0].Value[0].ToString());
|
name = FileNameCleanupRegex.Replace(name, match => match.Captures[0].Value[0].ToString());
|
||||||
return name.Trim(' ', '.');
|
name = name.Trim(' ', '.');
|
||||||
|
|
||||||
|
return CleanFileName(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AddArtistTokens(Dictionary<string, Func<TokenMatch, string>> tokenHandlers, Artist artist)
|
private void AddArtistTokens(Dictionary<string, Func<TokenMatch, string>> tokenHandlers, Artist artist)
|
||||||
|
@ -423,7 +410,7 @@ namespace NzbDrone.Core.Organizer
|
||||||
replacementText = replacementText.Replace(" ", tokenMatch.Separator);
|
replacementText = replacementText.Replace(" ", tokenMatch.Separator);
|
||||||
}
|
}
|
||||||
|
|
||||||
replacementText = CleanFileName(replacementText, namingConfig.ReplaceIllegalCharacters);
|
replacementText = CleanFileName(replacementText, namingConfig);
|
||||||
|
|
||||||
if (!replacementText.IsNullOrWhiteSpace())
|
if (!replacementText.IsNullOrWhiteSpace())
|
||||||
{
|
{
|
||||||
|
@ -551,6 +538,53 @@ namespace NzbDrone.Core.Organizer
|
||||||
{
|
{
|
||||||
return Path.GetFileNameWithoutExtension(trackFile.Path);
|
return Path.GetFileNameWithoutExtension(trackFile.Path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string CleanFileName(string name, NamingConfig namingConfig)
|
||||||
|
{
|
||||||
|
var result = name;
|
||||||
|
string[] badCharacters = { "\\", "/", "<", ">", "?", "*", "|", "\"" };
|
||||||
|
string[] goodCharacters = { "+", "+", "", "", "!", "-", "", "" };
|
||||||
|
|
||||||
|
if (namingConfig.ReplaceIllegalCharacters)
|
||||||
|
{
|
||||||
|
// Smart replaces a colon followed by a space with space dash space for a better appearance
|
||||||
|
if (namingConfig.ColonReplacementFormat == ColonReplacementFormat.Smart)
|
||||||
|
{
|
||||||
|
result = result.Replace(": ", " - ");
|
||||||
|
result = result.Replace(":", "-");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var replacement = string.Empty;
|
||||||
|
|
||||||
|
switch (namingConfig.ColonReplacementFormat)
|
||||||
|
{
|
||||||
|
case ColonReplacementFormat.Dash:
|
||||||
|
replacement = "-";
|
||||||
|
break;
|
||||||
|
case ColonReplacementFormat.SpaceDash:
|
||||||
|
replacement = " -";
|
||||||
|
break;
|
||||||
|
case ColonReplacementFormat.SpaceDashSpace:
|
||||||
|
replacement = " - ";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
result = result.Replace(":", replacement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result = result.Replace(":", string.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < badCharacters.Length; i++)
|
||||||
|
{
|
||||||
|
result = result.Replace(badCharacters[i], namingConfig.ReplaceIllegalCharacters ? goodCharacters[i] : string.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.TrimStart(' ', '.').TrimEnd(' ');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed class TokenMatch
|
internal sealed class TokenMatch
|
||||||
|
@ -584,4 +618,13 @@ namespace NzbDrone.Core.Organizer
|
||||||
Range = 4,
|
Range = 4,
|
||||||
PrefixedRange = 5
|
PrefixedRange = 5
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum ColonReplacementFormat
|
||||||
|
{
|
||||||
|
Delete = 0,
|
||||||
|
Dash = 1,
|
||||||
|
SpaceDash = 2,
|
||||||
|
SpaceDashSpace = 3,
|
||||||
|
Smart = 4
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ namespace NzbDrone.Core.Organizer
|
||||||
{
|
{
|
||||||
RenameTracks = false,
|
RenameTracks = false,
|
||||||
ReplaceIllegalCharacters = true,
|
ReplaceIllegalCharacters = true,
|
||||||
|
ColonReplacementFormat = ColonReplacementFormat.Smart,
|
||||||
StandardTrackFormat = "{Album Title} ({Release Year})/{Artist Name} - {Album Title} - {track:00} - {Track Title}",
|
StandardTrackFormat = "{Album Title} ({Release Year})/{Artist Name} - {Album Title} - {track:00} - {Track Title}",
|
||||||
MultiDiscTrackFormat = "{Album Title} ({Release Year})/{Medium Format} {medium:00}/{Artist Name} - {Album Title} - {track:00} - {Track Title}",
|
MultiDiscTrackFormat = "{Album Title} ({Release Year})/{Medium Format} {medium:00}/{Artist Name} - {Album Title} - {track:00} - {Track Title}",
|
||||||
ArtistFolderFormat = "{Artist Name}",
|
ArtistFolderFormat = "{Artist Name}",
|
||||||
|
@ -15,6 +16,7 @@ namespace NzbDrone.Core.Organizer
|
||||||
|
|
||||||
public bool RenameTracks { get; set; }
|
public bool RenameTracks { get; set; }
|
||||||
public bool ReplaceIllegalCharacters { get; set; }
|
public bool ReplaceIllegalCharacters { get; set; }
|
||||||
|
public ColonReplacementFormat ColonReplacementFormat { get; set; }
|
||||||
public string StandardTrackFormat { get; set; }
|
public string StandardTrackFormat { get; set; }
|
||||||
public string MultiDiscTrackFormat { get; set; }
|
public string MultiDiscTrackFormat { get; set; }
|
||||||
public string ArtistFolderFormat { get; set; }
|
public string ArtistFolderFormat { get; set; }
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue