diff --git a/src/NzbDrone.Core.Test/MediaFiles/TrackImport/Identification/DistanceCalculatorFixture.cs b/src/NzbDrone.Core.Test/MediaFiles/TrackImport/Identification/DistanceCalculatorFixture.cs new file mode 100644 index 000000000..ea214d80b --- /dev/null +++ b/src/NzbDrone.Core.Test/MediaFiles/TrackImport/Identification/DistanceCalculatorFixture.cs @@ -0,0 +1,95 @@ +using System.Collections.Generic; +using FluentAssertions; +using NUnit.Framework; +using NzbDrone.Core.MediaFiles.BookImport.Identification; +using NzbDrone.Test.Common; + +namespace NzbDrone.Core.Test.MediaFiles.BookImport.Identification +{ + [TestFixture] + public class DistanceCalculatorFixture : TestBase + { + [Test] + public void should_reverse_single_reversed_author() + { + var input = new List { "Last, First" }; + var authors = DistanceCalculator.GetAuthorVariants(input); + + authors.Should().Contain("First Last"); + } + + [Test] + public void should_reverse_two_reversed_author() + { + var input = new List + { + "Last, First", + "Last2, First2" + }; + + var authors = DistanceCalculator.GetAuthorVariants(input); + + authors.Should().HaveCount(4); + authors.Should().Contain("First Last"); + authors.Should().Contain("First2 Last2"); + authors.Should().Contain("Last, First"); + authors.Should().Contain("Last2, First2"); + } + + [Test] + public void should_not_reverse_single_author() + { + var input = new List { "First Last" }; + var authors = DistanceCalculator.GetAuthorVariants(input); + + authors.Should().HaveCount(1); + authors.Should().Contain("First Last"); + } + + [TestCase("First1 Last1, First2 Last2", "First1 Last1", "First2 Last2")] + [TestCase("First1 Last1; First2 Last2", "First1 Last1", "First2 Last2")] + [TestCase("First1 Last1 & First2 Last2", "First1 Last1", "First2 Last2")] + [TestCase("First1 Last1 / First2 Last2", "First1 Last1", "First2 Last2")] + [TestCase("First1 Last1 and First2 Last2", "First1 Last1", "First2 Last2")] + public void should_split_concatenated_author(string inputString, string first, string second) + { + var input = new List { inputString }; + var authors = DistanceCalculator.GetAuthorVariants(input); + + authors.Should().Contain(inputString); + authors.Should().Contain(first); + authors.Should().Contain(second); + authors.Should().HaveCount(3); + } + + [Test] + public void should_split_concatenated_with_trailing_and() + { + var inputString = "First Last, First2 Last2 & First3 Last3"; + var input = new List { inputString }; + var authors = DistanceCalculator.GetAuthorVariants(input); + + authors.Should().Contain(inputString); + authors.Should().Contain("First Last"); + authors.Should().Contain("First2 Last2"); + authors.Should().Contain("First3 Last3"); + authors.Should().HaveCount(4); + } + + [Test] + public void should_not_split_if_multiple_input() + { + var input = new List + { + "First Last", + "Second Third, Fourth Fifth" + }; + + var authors = DistanceCalculator.GetAuthorVariants(input); + + authors.Should().HaveCount(2); + authors.Should().Contain("First Last"); + authors.Should().Contain("Second Third, Fourth Fifth"); + } + } +} diff --git a/src/NzbDrone.Core/MediaFiles/AudioTag.cs b/src/NzbDrone.Core/MediaFiles/AudioTag.cs index 3c50bea99..4ab7e9fdf 100644 --- a/src/NzbDrone.Core/MediaFiles/AudioTag.cs +++ b/src/NzbDrone.Core/MediaFiles/AudioTag.cs @@ -85,7 +85,7 @@ namespace NzbDrone.Core.MediaFiles authors.AddRange(tag.PerformersSort); } - BookAuthors = authors.ToArray(); + BookAuthors = authors.Distinct().ToArray(); Track = tag.Track; TrackCount = tag.TrackCount; Book = tag.Album ?? tag.AlbumSort; diff --git a/src/NzbDrone.Core/MediaFiles/BookImport/Identification/CandidateService.cs b/src/NzbDrone.Core/MediaFiles/BookImport/Identification/CandidateService.cs index f7d8acc78..6b0f71c95 100644 --- a/src/NzbDrone.Core/MediaFiles/BookImport/Identification/CandidateService.cs +++ b/src/NzbDrone.Core/MediaFiles/BookImport/Identification/CandidateService.cs @@ -130,7 +130,7 @@ namespace NzbDrone.Core.MediaFiles.BookImport.Identification { // Sort by most voted so less likely to swap to a random release return GetDbCandidatesByEdition(_editionService.GetEditionsByBook(book.Id) - .OrderByDescending(x => x.Ratings.Votes) + .OrderByDescending(x => x.Ratings.Popularity) .ToList(), includeExisting); } @@ -171,7 +171,9 @@ namespace NzbDrone.Core.MediaFiles.BookImport.Identification var authorTags = localEdition.LocalBooks.MostCommon(x => x.FileTrackInfo.Authors) ?? new List(); if (authorTags.Any()) { - foreach (var authorTag in authorTags) + var variants = DistanceCalculator.GetAuthorVariants(authorTags.Where(x => x.IsNotNullOrWhiteSpace()).ToList()); + + foreach (var authorTag in variants) { if (authorTag.IsNotNullOrWhiteSpace()) { diff --git a/src/NzbDrone.Core/MediaFiles/BookImport/Identification/DistanceCalculator.cs b/src/NzbDrone.Core/MediaFiles/BookImport/Identification/DistanceCalculator.cs index f47cfa99a..509028155 100644 --- a/src/NzbDrone.Core/MediaFiles/BookImport/Identification/DistanceCalculator.cs +++ b/src/NzbDrone.Core/MediaFiles/BookImport/Identification/DistanceCalculator.cs @@ -30,15 +30,7 @@ namespace NzbDrone.Core.MediaFiles.BookImport.Identification .First() .First(); - var authors = new List(fileAuthors); - - foreach (var author in fileAuthors) - { - if (author.Contains(',')) - { - authors.Add(authors[0].Split(',', 2).Select(x => x.Trim()).Reverse().ConcatToString(" ")); - } - } + var authors = GetAuthorVariants(fileAuthors); dist.AddString("author", authors, edition.Book.Value.AuthorMetadata.Value.Name); Logger.Trace("author: '{0}' vs '{1}'; {2}", authors.ConcatToString("' or '"), edition.Book.Value.AuthorMetadata.Value.Name, dist.NormalizedDistance()); @@ -115,5 +107,75 @@ namespace NzbDrone.Core.MediaFiles.BookImport.Identification return dist; } + + public static List GetAuthorVariants(List fileAuthors) + { + var authors = new List(fileAuthors); + + if (fileAuthors.Count == 1) + { + authors.AddRange(SplitAuthor(fileAuthors[0])); + } + + foreach (var author in fileAuthors) + { + if (author.Contains(',')) + { + var split = author.Split(',', 2).Select(x => x.Trim()); + if (!split.First().Contains(' ')) + { + authors.Add(split.Reverse().ConcatToString(" ")); + } + } + } + + return authors; + } + + private static List SplitAuthor(string input) + { + var seps = new[] { ';', '/' }; + foreach (var sep in seps) + { + if (input.Contains(sep)) + { + return input.Split(sep).Select(x => x.Trim()).ToList(); + } + } + + var andSeps = new List { " and ", " & " }; + foreach (var sep in andSeps) + { + if (input.Contains(sep)) + { + var result = new List(); + foreach (var s in input.Split(sep).Select(x => x.Trim())) + { + var s2 = SplitAuthor(s); + if (s2.Any()) + { + result.AddRange(s2); + } + else + { + result.Add(s); + } + } + + return result; + } + } + + if (input.Contains(',')) + { + var split = input.Split(',').Select(x => x.Trim()).ToList(); + if (split[0].Contains(' ')) + { + return split; + } + } + + return new List(); + } } }