New: Better matching of books with subtitles

pull/1064/head
ta264 3 years ago
parent aa45bc3938
commit 7fda41c18b

@ -17,13 +17,14 @@ namespace NzbDrone.Core.Test.IndexerSearchTests
}
[TestCase("…and Justice for All", "and+Justice+for+All")]
[TestCase("American III: Solitary Man", "American+III+Solitary+Man")]
[TestCase("American III: Solitary Man", "American+III")]
[TestCase("Sad Clowns & Hillbillies", "Sad+Clowns+Hillbillies")]
[TestCase("¿Quién sabe?", "Quien+sabe")]
[TestCase("Seal the Deal & Lets Boogie", "Seal+the+Deal+Lets+Boogie")]
[TestCase("Section.80", "Section+80")]
public void should_replace_some_special_characters(string book, string expected)
{
Subject.Author = new Author { Name = "Author" };
Subject.BookTitle = book;
Subject.BookQuery.Should().Be(expected);
}
@ -31,6 +32,7 @@ namespace NzbDrone.Core.Test.IndexerSearchTests
[TestCase("+", "+")]
public void should_not_replace_some_special_characters_if_result_empty_string(string book, string expected)
{
Subject.Author = new Author { Name = "Author" };
Subject.BookTitle = book;
Subject.BookQuery.Should().Be(expected);
}

@ -20,12 +20,20 @@ namespace NzbDrone.Core.Test.MusicTests.BookRepositoryTests
{
Title = "ANThology",
CleanTitle = "anthology",
AuthorMetadata = new AuthorMetadata
{
Name = "Author"
}
});
_books.Add(new Book
{
Title = "+",
CleanTitle = "",
AuthorMetadata = new AuthorMetadata
{
Name = "Author"
}
});
Mocker.GetMock<IBookRepository>()
@ -39,6 +47,10 @@ namespace NzbDrone.Core.Test.MusicTests.BookRepositoryTests
{
Title = "ANThology2",
CleanTitle = "anthology2",
AuthorMetadata = new AuthorMetadata
{
Name = "Author"
}
});
}

@ -272,5 +272,17 @@ namespace NzbDrone.Core.Test.ParserTests
parseResult.AuthorName.Should().Be("Michael Buble");
parseResult.BookTitle.Should().Be("Christmas");
}
[TestCase("Tom Clancy", "Tom Clancy: Ghost Protocol", "Ghost Protocol", "")]
[TestCase("Andrew Steele", "Ageless: The New Science of Getting Older Without Getting Old", "Ageless", "The New Science of Getting Older Without Getting Old")]
[TestCase("Author", "Title (Subtitle with spaces)", "Title", "Subtitle with spaces")]
[TestCase("Author", "Title (Unabridged)", "Title (Unabridged)", "")]
public void should_split_title_correctly(string author, string book, string expectedTitle, string expectedSubtitle)
{
var (title, subtitle) = book.SplitBookTitle(author);
title.Should().Be(expectedTitle);
subtitle.Should().Be(expectedSubtitle);
}
}
}

@ -110,7 +110,8 @@ namespace NzbDrone.Core.Books
tc((a, t) => a.CleanTitle.FuzzyMatch(t), title.RemoveAfterDash().CleanAuthorName()),
tc((a, t) => a.CleanTitle.FuzzyMatch(t), title.RemoveBracketsAndContents().RemoveAfterDash().CleanAuthorName()),
tc((a, t) => t.FuzzyContains(a.CleanTitle), cleanTitle),
tc((a, t) => t.FuzzyContains(a.Title), title)
tc((a, t) => t.FuzzyContains(a.Title), title),
tc((a, t) => a.Title.SplitBookTitle(a.AuthorMetadata.Value.Name).Item1.FuzzyMatch(t), title)
};
return scoringFunctions;

@ -1,4 +1,5 @@
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Parser;
namespace NzbDrone.Core.IndexerSearch.Definitions
{
@ -9,11 +10,11 @@ namespace NzbDrone.Core.IndexerSearch.Definitions
public string BookIsbn { get; set; }
public string Disambiguation { get; set; }
public string BookQuery => GetQueryTitle($"{BookTitle}");
public string BookQuery => GetQueryTitle(BookTitle.SplitBookTitle(Author.Name).Item1);
public override string ToString()
{
return $"[{Author.Name} - {BookTitle} ({BookYear})]";
return $"[{Author.Name} - {BookTitle}]";
}
}
}

@ -49,6 +49,18 @@ namespace NzbDrone.Core.MediaFiles.BookImport.Identification
titleOptions.Add(StripSeriesRegex.Replace(titleOptions[0]));
}
var (maintitle, _) = edition.Book.Value.Title.SplitBookTitle(edition.Book.Value.AuthorMetadata.Value.Name);
if (!titleOptions.Contains(maintitle))
{
titleOptions.Add(maintitle);
}
(maintitle, _) = edition.Title.SplitBookTitle(edition.Book.Value.AuthorMetadata.Value.Name);
if (!titleOptions.Contains(maintitle))
{
titleOptions.Add(maintitle);
}
if (edition.Book.Value.SeriesLinks?.Value?.Any() ?? false)
{
foreach (var l in edition.Book.Value.SeriesLinks.Value)

@ -362,6 +362,11 @@ namespace NzbDrone.Core.Parser
var foundBook = GetTitleFuzzy(remainder, bestBook.Title, out _);
if (foundBook == null)
{
foundBook = GetTitleFuzzy(remainder, bestBook.Title.SplitBookTitle(authorName).Item1, out _);
}
Logger.Trace($"Found {foundAuthor} - {foundBook} with fuzzy parser");
if (foundAuthor == null || foundBook == null)
@ -576,6 +581,58 @@ namespace NzbDrone.Core.Parser
return null;
}
public static (string, string) SplitBookTitle(this string book, string author)
{
// Strip author from title, eg Tom Clancy: Ghost Protocol
if (book.StartsWith($"{author}:"))
{
book = book.Split(':', 2)[1].Trim();
}
var parenthesis = book.IndexOf('(');
var colon = book.IndexOf(':');
string[] parts = null;
if (parenthesis > -1)
{
var endParenthesis = book.IndexOf(')');
if (endParenthesis > -1 && !book.Substring(parenthesis + 1, endParenthesis - parenthesis).Contains(' '))
{
parenthesis = -1;
}
}
if (colon > -1 && parenthesis > -1)
{
if (colon < parenthesis)
{
parts = book.Split(':', 2);
}
else
{
parts = book.Split('(', 2);
parts[1] = parts[1].TrimEnd(')');
}
}
else if (colon > -1)
{
parts = book.Split(':', 2);
}
else if (parenthesis > -1)
{
parts = book.Split('(');
parts[1] = parts[1].TrimEnd(')');
}
if (parts != null)
{
return (parts[0].Trim(), parts[1].TrimEnd(':').Trim());
}
return (book, string.Empty);
}
public static string CleanAuthorName(this string name)
{
// If Title only contains numbers return it as is.

Loading…
Cancel
Save