mirror of
https://github.com/lidarr/lidarr.git
synced 2025-07-06 13:02:23 -07:00
Improve the fuzzy matching (#522)
* Fixed: improve track matching * Deal with tracks sequentially numbered across discs
This commit is contained in:
parent
8320508688
commit
e260a29b57
19 changed files with 767 additions and 164 deletions
|
@ -0,0 +1,61 @@
|
||||||
|
using FluentAssertions;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using NzbDrone.Common.Extensions;
|
||||||
|
using NzbDrone.Test.Common;
|
||||||
|
|
||||||
|
namespace NzbDrone.Common.Test
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class FuzzyContainsFixture : TestBase
|
||||||
|
{
|
||||||
|
[TestCase("abcdef", "abcdef", 0.5, 0)]
|
||||||
|
[TestCase("", "abcdef", 0.5, -1)]
|
||||||
|
[TestCase("abcdef", "", 0.5, -1)]
|
||||||
|
[TestCase("", "", 0.5, -1)]
|
||||||
|
[TestCase("abcdef", "de", 0.5, 3)]
|
||||||
|
[TestCase("abcdef", "defy", 0.5, 3)]
|
||||||
|
[TestCase("abcdef", "abcdefy", 0.5, 0)]
|
||||||
|
[TestCase("I am the very model of a modern major general.", " that berry ", 0.3, 4)]
|
||||||
|
[TestCase("abcdefghijk", "fgh", 0.5, 5)]
|
||||||
|
[TestCase("abcdefghijk", "fgh", 0.5, 5)]
|
||||||
|
[TestCase("abcdefghijk", "efxhi", 0.5, 4)]
|
||||||
|
[TestCase("abcdefghijk", "cdefxyhijk", 0.5, 2)]
|
||||||
|
[TestCase("abcdefghijk", "bxy", 0.5, -1)]
|
||||||
|
[TestCase("123456789xx0", "3456789x0", 0.5, 2)]
|
||||||
|
[TestCase("abcdef", "xxabc", 0.5, 0)]
|
||||||
|
[TestCase("abcdef", "defyy", 0.5, 3)]
|
||||||
|
[TestCase("abcdef", "xabcdefy", 0.5, 0)]
|
||||||
|
[TestCase("abcdefghijk", "efxyhi", 0.6, 4)]
|
||||||
|
[TestCase("abcdefghijk", "efxyhi", 0.7, -1)]
|
||||||
|
[TestCase("abcdefghijk", "bcdef", 0.0, 1)]
|
||||||
|
[TestCase("abcdexyzabcde", "abccde", 0.5, 0)]
|
||||||
|
[TestCase("abcdefghijklmnopqrstuvwxyz", "abcdxxefg", 0.5, 0)]
|
||||||
|
[TestCase("abcdefghijklmnopqrstuvwxyz", "abcdefg", 0.5, 0)]
|
||||||
|
[TestCase("The quick brown fox jumps over the lazy dog", "The quick brown fox jumps over the lazy d", 0.5, 0)]
|
||||||
|
[TestCase("The quick brown fox jumps over the lazy dog", "The quick brown fox jumps over the lazy g", 0.5, 0)]
|
||||||
|
[TestCase("The quick brown fox jumps over the lazy dog", "quikc brown fox jumps over the lazy dog", 0.5, 4)]
|
||||||
|
[TestCase("The quick brown fox jumps over the lazy dog", "qui jumps over the lazy dog", 0.5, 16)]
|
||||||
|
[TestCase("The quick brown fox jumps over the lazy dog", "quikc brown fox jumps over the lazy dog", 0.5, 4)]
|
||||||
|
[TestCase("u6IEytQiYpzAccsbjQ5ISuE4smDQ1ZiU42cFBrTeKB2XrVLEqAvgIiKlDP75iApy07jzmK", "xEytQiYpzAccsbjQ5ISuE4smDQ1ZiU42cFBrTeKB2XrVLEqAvgIiKlDP75iApy07jzmK", 0.5, 2)]
|
||||||
|
[TestCase("plusifeelneedforredundantinformationintitlefield", "anthology", 0.5, -1)]
|
||||||
|
public void FuzzyFind(string text, string pattern, double threshold, int expected)
|
||||||
|
{
|
||||||
|
text.FuzzyFind(pattern, threshold).Should().Be(expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestCase("abcdef", "abcdef", 1)]
|
||||||
|
[TestCase("", "abcdef", 0)]
|
||||||
|
[TestCase("abcdef", "", 0)]
|
||||||
|
[TestCase("", "", 0)]
|
||||||
|
[TestCase("abcdef", "de", 1)]
|
||||||
|
[TestCase("abcdef", "defy", 0.75)]
|
||||||
|
[TestCase("abcdef", "abcdefghk", 6.0/9)]
|
||||||
|
[TestCase("abcdef", "zabcdefz", 6.0/8)]
|
||||||
|
[TestCase("plusifeelneedforredundantinformationintitlefield", "anthology", 4.0/9)]
|
||||||
|
[TestCase("+ (Plus) - I feel the need for redundant information in the title field", "+", 1)]
|
||||||
|
public void FuzzyContains(string text, string pattern, double expectedScore)
|
||||||
|
{
|
||||||
|
text.FuzzyContains(pattern).Should().BeApproximately(expectedScore, 1e-9);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -42,5 +42,21 @@ namespace NzbDrone.Common.Test
|
||||||
{
|
{
|
||||||
text.ToLower().LevenshteinDistanceClean(other.ToLower()).Should().Be(expected);
|
text.ToLower().LevenshteinDistanceClean(other.ToLower()).Should().Be(expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestCase("hello", "hello")]
|
||||||
|
[TestCase("hello", "bye")]
|
||||||
|
[TestCase("a longer string", "a different long string")]
|
||||||
|
public void FuzzyMatchSymmetric(string a, string b)
|
||||||
|
{
|
||||||
|
a.FuzzyMatch(b).Should().Be(b.FuzzyMatch(a));
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestCase("", "", 0)]
|
||||||
|
[TestCase("a", "", 0)]
|
||||||
|
[TestCase("", "a", 0)]
|
||||||
|
public void FuzzyMatchEmptyValuesReturnZero(string a, string b, double expected)
|
||||||
|
{
|
||||||
|
a.FuzzyMatch(b).Should().Be(expected);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -84,6 +84,7 @@
|
||||||
<Compile Include="ExtensionTests\IEnumerableExtensionTests\IntersectByFixture.cs" />
|
<Compile Include="ExtensionTests\IEnumerableExtensionTests\IntersectByFixture.cs" />
|
||||||
<Compile Include="ExtensionTests\Int64ExtensionFixture.cs" />
|
<Compile Include="ExtensionTests\Int64ExtensionFixture.cs" />
|
||||||
<Compile Include="ExtensionTests\UrlExtensionsFixture.cs" />
|
<Compile Include="ExtensionTests\UrlExtensionsFixture.cs" />
|
||||||
|
<Compile Include="ExtensionTests\FuzzyContainsFixture.cs" />
|
||||||
<Compile Include="HashUtilFixture.cs" />
|
<Compile Include="HashUtilFixture.cs" />
|
||||||
<Compile Include="Http\HttpClientFixture.cs" />
|
<Compile Include="Http\HttpClientFixture.cs" />
|
||||||
<Compile Include="Http\HttpHeaderFixture.cs" />
|
<Compile Include="Http\HttpHeaderFixture.cs" />
|
||||||
|
|
167
src/NzbDrone.Common/Extensions/FuzzyContains.cs
Normal file
167
src/NzbDrone.Common/Extensions/FuzzyContains.cs
Normal file
|
@ -0,0 +1,167 @@
|
||||||
|
/*
|
||||||
|
* This file incorporates work covered by the following copyright and
|
||||||
|
* permission notice:
|
||||||
|
*
|
||||||
|
* Diff Match and Patch
|
||||||
|
* Copyright 2018 The diff-match-patch Authors.
|
||||||
|
* https://github.com/google/diff-match-patch
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace NzbDrone.Common.Extensions
|
||||||
|
{
|
||||||
|
|
||||||
|
public static class FuzzyContainsExtension {
|
||||||
|
|
||||||
|
public static int FuzzyFind(this string text, string pattern, double matchProb)
|
||||||
|
{
|
||||||
|
return match(text, pattern, matchProb).Item1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// return the accuracy of the best match of pattern within text
|
||||||
|
public static double FuzzyContains(this string text, string pattern)
|
||||||
|
{
|
||||||
|
return match(text, pattern, 0.25).Item2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Locate the best instance of 'pattern' in 'text'.
|
||||||
|
* Returns (-1, 1) if no match found.
|
||||||
|
* @param text The text to search.
|
||||||
|
* @param pattern The pattern to search for.
|
||||||
|
* @return Best match index or -1.
|
||||||
|
*/
|
||||||
|
private static Tuple<int, double> match(string text, string pattern, double matchThreshold = 0.5) {
|
||||||
|
// Check for null inputs not needed since null can't be passed in C#.
|
||||||
|
if (text.Length == 0 || pattern.Length == 0) {
|
||||||
|
// Nothing to match.
|
||||||
|
return new Tuple<int, double> (-1, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pattern.Length <= text.Length)
|
||||||
|
{
|
||||||
|
var loc = text.IndexOf(pattern, StringComparison.Ordinal);
|
||||||
|
if (loc != -1)
|
||||||
|
{
|
||||||
|
// Perfect match!
|
||||||
|
return new Tuple<int, double> (loc, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do a fuzzy compare.
|
||||||
|
return match_bitap(text, pattern, matchThreshold);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Locate the best instance of 'pattern' in 'text' near 'loc' using the
|
||||||
|
* Bitap algorithm. Returns -1 if no match found.
|
||||||
|
* @param text The text to search.
|
||||||
|
* @param pattern The pattern to search for.
|
||||||
|
* @return Best match index or -1.
|
||||||
|
*/
|
||||||
|
private static Tuple<int, double> match_bitap(string text, string pattern, double matchThreshold) {
|
||||||
|
|
||||||
|
// Initialise the alphabet.
|
||||||
|
Dictionary<char, BigInteger> s = alphabet(pattern);
|
||||||
|
// don't keep creating new BigInteger(1)
|
||||||
|
var big1 = new BigInteger(1);
|
||||||
|
|
||||||
|
// Lowest score belowe which we give up.
|
||||||
|
var score_threshold = matchThreshold;
|
||||||
|
|
||||||
|
// Initialise the bit arrays.
|
||||||
|
var matchmask = big1 << (pattern.Length - 1);
|
||||||
|
int best_loc = -1;
|
||||||
|
|
||||||
|
// Empty initialization added to appease C# compiler.
|
||||||
|
var last_rd = new BigInteger[0];
|
||||||
|
for (int d = 0; d < pattern.Length; d++) {
|
||||||
|
// Scan for the best match; each iteration allows for one more error.
|
||||||
|
int start = 1;
|
||||||
|
int finish = text.Length + pattern.Length;
|
||||||
|
|
||||||
|
var rd = new BigInteger[finish + 2];
|
||||||
|
rd[finish + 1] = (big1 << d) - big1;
|
||||||
|
for (int j = finish; j >= start; j--) {
|
||||||
|
BigInteger charMatch;
|
||||||
|
if (text.Length <= j - 1 || !s.ContainsKey(text[j - 1])) {
|
||||||
|
// Out of range.
|
||||||
|
charMatch = 0;
|
||||||
|
} else {
|
||||||
|
charMatch = s[text[j - 1]];
|
||||||
|
}
|
||||||
|
if (d == 0) {
|
||||||
|
// First pass: exact match.
|
||||||
|
rd[j] = ((rd[j + 1] << 1) | big1) & charMatch;
|
||||||
|
} else {
|
||||||
|
// Subsequent passes: fuzzy match.
|
||||||
|
rd[j] = ((rd[j + 1] << 1) | big1) & charMatch
|
||||||
|
| (((last_rd[j + 1] | last_rd[j]) << 1) | big1) | last_rd[j + 1];
|
||||||
|
}
|
||||||
|
if ((rd[j] & matchmask) != 0) {
|
||||||
|
var score = bitapScore(d, pattern);
|
||||||
|
// This match will almost certainly be better than any existing
|
||||||
|
// match. But check anyway.
|
||||||
|
if (score >= score_threshold) {
|
||||||
|
// Told you so.
|
||||||
|
score_threshold = score;
|
||||||
|
best_loc = j - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (bitapScore(d + 1, pattern) < score_threshold) {
|
||||||
|
// No hope for a (better) match at greater error levels.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
last_rd = rd;
|
||||||
|
}
|
||||||
|
return new Tuple<int, double> (best_loc, score_threshold);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compute and return the score for a match with e errors and x location.
|
||||||
|
* @param e Number of errors in match.
|
||||||
|
* @param pattern Pattern being sought.
|
||||||
|
* @return Overall score for match (1.0 = good, 0.0 = bad).
|
||||||
|
*/
|
||||||
|
private static double bitapScore(int e, string pattern) {
|
||||||
|
return 1.0 - (double)e / pattern.Length;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialise the alphabet for the Bitap algorithm.
|
||||||
|
* @param pattern The text to encode.
|
||||||
|
* @return Hash of character locations.
|
||||||
|
*/
|
||||||
|
private static Dictionary<char, BigInteger> alphabet(string pattern) {
|
||||||
|
var s = new Dictionary<char, BigInteger>();
|
||||||
|
char[] char_pattern = pattern.ToCharArray();
|
||||||
|
foreach (char c in char_pattern) {
|
||||||
|
if (!s.ContainsKey(c)) {
|
||||||
|
s.Add(c, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int i = 0;
|
||||||
|
foreach (char c in char_pattern) {
|
||||||
|
s[c] = s[c] | (new BigInteger(1) << (pattern.Length - i - 1));
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -143,29 +143,17 @@ namespace NzbDrone.Common.Extensions
|
||||||
|
|
||||||
public static double FuzzyMatch(this string a, string b)
|
public static double FuzzyMatch(this string a, string b)
|
||||||
{
|
{
|
||||||
if (a.Contains(" ") && b.Contains(" "))
|
if (a.IsNullOrWhiteSpace() || b.IsNullOrWhiteSpace())
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
else if (a.Contains(" ") && b.Contains(" "))
|
||||||
{
|
{
|
||||||
var partsA = a.Split(' ');
|
var partsA = a.Split(' ');
|
||||||
var partsB = b.Split(' ');
|
var partsB = b.Split(' ');
|
||||||
var weightedHighCoefficients = new double[partsA.Length];
|
|
||||||
var distanceRatios = new double[partsA.Length];
|
var coef = (FuzzyMatchComponents(partsA, partsB) + FuzzyMatchComponents(partsB, partsA)) / (partsA.Length + partsB.Length);
|
||||||
for (int i = 0; i < partsA.Length; i++)
|
return Math.Max(coef, LevenshteinCoefficient(a, b));
|
||||||
{
|
|
||||||
double high = 0.0;
|
|
||||||
int indexDistance = 0;
|
|
||||||
for (int x = 0; x < partsB.Length; x++)
|
|
||||||
{
|
|
||||||
var coef = LevenshteinCoefficient(partsA[i], partsB[x]);
|
|
||||||
if (coef > high)
|
|
||||||
{
|
|
||||||
high = coef;
|
|
||||||
indexDistance = Math.Abs(i - x);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
double distanceWeight = 1.0 - (double)indexDistance / (double)partsA.Length;
|
|
||||||
weightedHighCoefficients[i] = high * distanceWeight;
|
|
||||||
}
|
|
||||||
return weightedHighCoefficients.Sum() / (double)partsA.Length;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -173,6 +161,28 @@ namespace NzbDrone.Common.Extensions
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static double FuzzyMatchComponents(string[] a, string[] b)
|
||||||
|
{
|
||||||
|
double weightDenom = Math.Max(a.Length, b.Length);
|
||||||
|
double sum = 0;
|
||||||
|
for (int i = 0; i < a.Length; i++)
|
||||||
|
{
|
||||||
|
double high = 0.0;
|
||||||
|
int indexDistance = 0;
|
||||||
|
for (int x = 0; x < b.Length; x++)
|
||||||
|
{
|
||||||
|
var coef = LevenshteinCoefficient(a[i], b[x]);
|
||||||
|
if (coef > high)
|
||||||
|
{
|
||||||
|
high = coef;
|
||||||
|
indexDistance = Math.Abs(i - x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sum += (1.0 - (double)indexDistance / weightDenom) * high;
|
||||||
|
}
|
||||||
|
return sum;
|
||||||
|
}
|
||||||
|
|
||||||
public static double LevenshteinCoefficient(this string a, string b)
|
public static double LevenshteinCoefficient(this string a, string b)
|
||||||
{
|
{
|
||||||
return 1.0 - (double)a.LevenshteinDistance(b) / Math.Max(a.Length, b.Length);
|
return 1.0 - (double)a.LevenshteinDistance(b) / Math.Max(a.Length, b.Length);
|
||||||
|
|
|
@ -71,6 +71,7 @@
|
||||||
<Reference Include="Microsoft.CSharp" />
|
<Reference Include="Microsoft.CSharp" />
|
||||||
<Reference Include="System.Xml" />
|
<Reference Include="System.Xml" />
|
||||||
<Reference Include="System.Xml.Linq" />
|
<Reference Include="System.Xml.Linq" />
|
||||||
|
<Reference Include="System.Numerics" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="ArchiveService.cs" />
|
<Compile Include="ArchiveService.cs" />
|
||||||
|
@ -198,6 +199,7 @@
|
||||||
<Compile Include="Instrumentation\Sentry\LidarrSentryPacket.cs" />
|
<Compile Include="Instrumentation\Sentry\LidarrSentryPacket.cs" />
|
||||||
<Compile Include="Instrumentation\VersionLayoutRenderer.cs" />
|
<Compile Include="Instrumentation\VersionLayoutRenderer.cs" />
|
||||||
<Compile Include="Extensions\LevenstheinExtensions.cs" />
|
<Compile Include="Extensions\LevenstheinExtensions.cs" />
|
||||||
|
<Compile Include="Extensions\FuzzyContains.cs" />
|
||||||
<Compile Include="Messaging\IEvent.cs" />
|
<Compile Include="Messaging\IEvent.cs" />
|
||||||
<Compile Include="Messaging\IMessage.cs" />
|
<Compile Include="Messaging\IMessage.cs" />
|
||||||
<Compile Include="Model\ProcessInfo.cs" />
|
<Compile Include="Model\ProcessInfo.cs" />
|
||||||
|
|
|
@ -94,7 +94,6 @@ namespace NzbDrone.Core.Test.MusicTests.AlbumRepositoryTests
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void should_find_album_in_db_by_releaseid()
|
public void should_find_album_in_db_by_releaseid()
|
||||||
{
|
{
|
||||||
|
@ -129,6 +128,7 @@ namespace NzbDrone.Core.Test.MusicTests.AlbumRepositoryTests
|
||||||
[TestCase("ANTholog")]
|
[TestCase("ANTholog")]
|
||||||
[TestCase("nthology")]
|
[TestCase("nthology")]
|
||||||
[TestCase("antholoyg")]
|
[TestCase("antholoyg")]
|
||||||
|
[TestCase("÷")]
|
||||||
public void should_not_find_album_in_db_by_incorrect_title(string title)
|
public void should_not_find_album_in_db_by_incorrect_title(string title)
|
||||||
{
|
{
|
||||||
var album = _albumRepo.FindByTitle(_artist.Id, title);
|
var album = _albumRepo.FindByTitle(_artist.Id, title);
|
||||||
|
@ -136,28 +136,6 @@ namespace NzbDrone.Core.Test.MusicTests.AlbumRepositoryTests
|
||||||
album.Should().BeNull();
|
album.Should().BeNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestCase("ANTholog")]
|
|
||||||
[TestCase("antholoyg")]
|
|
||||||
[TestCase("ANThology CD")]
|
|
||||||
public void should_find_album_in_db_by_inexact_title(string title)
|
|
||||||
{
|
|
||||||
var album = _albumRepo.FindByTitleInexact(_artist.Id, title);
|
|
||||||
|
|
||||||
album.Should().NotBeNull();
|
|
||||||
album.Title.Should().Be(_album.Title);
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestCase("ANTholog")]
|
|
||||||
[TestCase("antholoyg")]
|
|
||||||
[TestCase("ANThology CD")]
|
|
||||||
public void should_not_find_album_in_db_by_inexact_title_when_two_similar_matches(string title)
|
|
||||||
{
|
|
||||||
_albumRepo.Insert(_albumSimilar);
|
|
||||||
var album = _albumRepo.FindByTitleInexact(_artist.Id, title);
|
|
||||||
|
|
||||||
album.Should().BeNull();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void should_not_find_album_in_db_by_partial_releaseid()
|
public void should_not_find_album_in_db_by_partial_releaseid()
|
||||||
{
|
{
|
||||||
|
|
77
src/NzbDrone.Core.Test/MusicTests/AlbumServiceFixture.cs
Normal file
77
src/NzbDrone.Core.Test/MusicTests/AlbumServiceFixture.cs
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
using FizzWare.NBuilder;
|
||||||
|
using FluentAssertions;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using NzbDrone.Core.Music;
|
||||||
|
using NzbDrone.Core.Test.Framework;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using NLog;
|
||||||
|
using Moq;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Test.MusicTests.AlbumRepositoryTests
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class AlbumServiceFixture : CoreTest<AlbumService>
|
||||||
|
{
|
||||||
|
private List<Album> _albums;
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void Setup()
|
||||||
|
{
|
||||||
|
_albums = new List<Album>();
|
||||||
|
_albums.Add(new Album
|
||||||
|
{
|
||||||
|
Title = "ANThology",
|
||||||
|
CleanTitle = "anthology",
|
||||||
|
});
|
||||||
|
|
||||||
|
_albums.Add(new Album
|
||||||
|
{
|
||||||
|
Title = "+",
|
||||||
|
CleanTitle = "",
|
||||||
|
});
|
||||||
|
|
||||||
|
Mocker.GetMock<IAlbumRepository>()
|
||||||
|
.Setup(s => s.GetAlbums(It.IsAny<int>()))
|
||||||
|
.Returns(_albums);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GivenSimilarAlbum()
|
||||||
|
{
|
||||||
|
_albums.Add(new Album
|
||||||
|
{
|
||||||
|
Title = "ANThology2",
|
||||||
|
CleanTitle = "anthology2",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestCase("ANTholog", "ANThology")]
|
||||||
|
[TestCase("antholoyg", "ANThology")]
|
||||||
|
[TestCase("ANThology CD", "ANThology")]
|
||||||
|
[TestCase("ANThology CD xxxx (Remastered) - [Oh please why do they do this?]", "ANThology")]
|
||||||
|
[TestCase("+ (Plus) - I feel the need for redundant information in the title field", "+")]
|
||||||
|
public void should_find_album_in_db_by_inexact_title(string title, string expected)
|
||||||
|
{
|
||||||
|
var album = Subject.FindByTitleInexact(0, title);
|
||||||
|
|
||||||
|
album.Should().NotBeNull();
|
||||||
|
album.Title.Should().Be(expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestCase("ANTholog")]
|
||||||
|
[TestCase("antholoyg")]
|
||||||
|
[TestCase("ANThology CD")]
|
||||||
|
[TestCase("÷")]
|
||||||
|
[TestCase("÷ (Divide)")]
|
||||||
|
public void should_not_find_album_in_db_by_inexact_title_when_two_similar_matches(string title)
|
||||||
|
{
|
||||||
|
GivenSimilarAlbum();
|
||||||
|
var album = Subject.FindByTitleInexact(0, title);
|
||||||
|
|
||||||
|
album.Should().BeNull();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,6 +17,24 @@ namespace NzbDrone.Core.Test.MusicTests.ArtistRepositoryTests
|
||||||
|
|
||||||
public class ArtistRepositoryFixture : DbTest<ArtistRepository, Artist>
|
public class ArtistRepositoryFixture : DbTest<ArtistRepository, Artist>
|
||||||
{
|
{
|
||||||
|
private ArtistRepository _artistRepo;
|
||||||
|
|
||||||
|
private Artist CreateArtist(string name)
|
||||||
|
{
|
||||||
|
return Builder<Artist>.CreateNew()
|
||||||
|
.With(a => a.Name = name)
|
||||||
|
.With(a => a.CleanName = Parser.Parser.CleanArtistName(name))
|
||||||
|
.With(a => a.ForeignArtistId = name)
|
||||||
|
.BuildNew();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GivenArtists()
|
||||||
|
{
|
||||||
|
_artistRepo = Mocker.Resolve<ArtistRepository>();
|
||||||
|
_artistRepo.Insert(CreateArtist("The Black Eyed Peas"));
|
||||||
|
_artistRepo.Insert(CreateArtist("The Black Keys"));
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void should_lazyload_profiles()
|
public void should_lazyload_profiles()
|
||||||
{
|
{
|
||||||
|
@ -61,5 +79,16 @@ namespace NzbDrone.Core.Test.MusicTests.ArtistRepositoryTests
|
||||||
StoredModel.MetadataProfile.Should().NotBeNull();
|
StoredModel.MetadataProfile.Should().NotBeNull();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestCase("The Black Eyed Peas")]
|
||||||
|
[TestCase("The Black Keys")]
|
||||||
|
public void should_find_artist_in_db_by_name(string name)
|
||||||
|
{
|
||||||
|
GivenArtists();
|
||||||
|
var artist = _artistRepo.FindByName(Parser.Parser.CleanArtistName(name));
|
||||||
|
|
||||||
|
artist.Should().NotBeNull();
|
||||||
|
artist.Name.Should().Be(name);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,58 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using FizzWare.NBuilder;
|
||||||
|
using FluentAssertions;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using NzbDrone.Core.Test.Framework;
|
||||||
|
using NzbDrone.Core.Music;
|
||||||
|
|
||||||
|
namespace NzbDrone.Core.Test.MusicTests.ArtistServiceTests
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
|
||||||
|
public class FindByNameInexactFixture : CoreTest<ArtistService>
|
||||||
|
{
|
||||||
|
private List<Artist> _artists;
|
||||||
|
|
||||||
|
private Artist CreateArtist(string name)
|
||||||
|
{
|
||||||
|
return Builder<Artist>.CreateNew()
|
||||||
|
.With(a => a.Name = name)
|
||||||
|
.With(a => a.CleanName = Parser.Parser.CleanArtistName(name))
|
||||||
|
.With(a => a.ForeignArtistId = name)
|
||||||
|
.BuildNew();
|
||||||
|
}
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void Setup()
|
||||||
|
{
|
||||||
|
_artists = new List<Artist>();
|
||||||
|
_artists.Add(CreateArtist("The Black Eyed Peas"));
|
||||||
|
_artists.Add(CreateArtist("The Black Keys"));
|
||||||
|
|
||||||
|
Mocker.GetMock<IArtistRepository>()
|
||||||
|
.Setup(s => s.All())
|
||||||
|
.Returns(_artists);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestCase("The Black Eyde Peas", "The Black Eyed Peas")]
|
||||||
|
[TestCase("Black Eyed Peas", "The Black Eyed Peas")]
|
||||||
|
[TestCase("The Black eys", "The Black Keys")]
|
||||||
|
public void should_find_artist_in_db_by_name_inexact(string name, string expected)
|
||||||
|
{
|
||||||
|
var artist = Subject.FindByNameInexact(name);
|
||||||
|
|
||||||
|
artist.Should().NotBeNull();
|
||||||
|
artist.Name.Should().Be(expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestCase("The Black Peas")]
|
||||||
|
public void should_not_find_artist_in_db_by_ambiguous_name(string name)
|
||||||
|
{
|
||||||
|
|
||||||
|
var artist = Subject.FindByNameInexact(name);
|
||||||
|
|
||||||
|
artist.Should().BeNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,27 +1,21 @@
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using NLog;
|
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using NzbDrone.Core.Configuration;
|
|
||||||
using NzbDrone.Core.Music;
|
using NzbDrone.Core.Music;
|
||||||
using NzbDrone.Core.Test.Framework;
|
using NzbDrone.Core.Test.Framework;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using Moq;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Test.MusicTests.TitleMatchingTests
|
namespace NzbDrone.Core.Test.MusicTests.TitleMatchingTests
|
||||||
{
|
{
|
||||||
[TestFixture]
|
[TestFixture]
|
||||||
public class TitleMatchingFixture : DbTest<TrackService, Track>
|
public class TitleMatchingFixture : CoreTest<TrackService>
|
||||||
{
|
{
|
||||||
private TrackRepository _trackRepository;
|
private List<Track> _tracks;
|
||||||
private TrackService _trackService;
|
|
||||||
|
|
||||||
[SetUp]
|
[SetUp]
|
||||||
public void Setup()
|
public void Setup()
|
||||||
{
|
{
|
||||||
_trackRepository = Mocker.Resolve<TrackRepository>();
|
|
||||||
_trackService =
|
|
||||||
new TrackService(_trackRepository, Mocker.Resolve<ConfigService>(), Mocker.Resolve<Logger>());
|
|
||||||
|
|
||||||
var trackNames = new List<string> {
|
var trackNames = new List<string> {
|
||||||
"Courage",
|
"Courage",
|
||||||
"Movies",
|
"Movies",
|
||||||
|
@ -35,65 +29,148 @@ namespace NzbDrone.Core.Test.MusicTests.TitleMatchingTests
|
||||||
"Calico",
|
"Calico",
|
||||||
"(Happy) Death Day",
|
"(Happy) Death Day",
|
||||||
"Smooth Criminal",
|
"Smooth Criminal",
|
||||||
"Universe / Orange Appeal"
|
"Universe / Orange Appeal",
|
||||||
|
"Christian's Inferno"
|
||||||
|
};
|
||||||
|
|
||||||
|
_tracks = new List<Track>();
|
||||||
|
for (int i = 0; i < trackNames.Count; i++) {
|
||||||
|
_tracks.Add(new Track
|
||||||
|
{
|
||||||
|
Title = trackNames[i],
|
||||||
|
ForeignTrackId = (i+1).ToString(),
|
||||||
|
AbsoluteTrackNumber = i+1,
|
||||||
|
MediumNumber = 1
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Mocker.GetMock<ITrackRepository>()
|
||||||
|
.Setup(s => s.GetTracksByMedium(It.IsAny<int>(), It.IsAny<int>()))
|
||||||
|
.Returns(_tracks);
|
||||||
|
|
||||||
|
Mocker.GetMock<ITrackRepository>()
|
||||||
|
.Setup(s => s.Find(1234, 4321, It.IsAny<int>(), It.IsAny<int>()))
|
||||||
|
.Returns((int artistid, int albumid, int medium, int track) => _tracks.Where(t => t.AbsoluteTrackNumber == track && t.MediumNumber == medium).Single());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GivenSecondDisc()
|
||||||
|
{
|
||||||
|
var trackNames = new List<string> {
|
||||||
|
"Courage",
|
||||||
|
"another entry",
|
||||||
|
"random name"
|
||||||
};
|
};
|
||||||
|
|
||||||
for (int i = 0; i < trackNames.Count; i++) {
|
for (int i = 0; i < trackNames.Count; i++) {
|
||||||
_trackRepository.Insert(new Track
|
_tracks.Add(new Track
|
||||||
{
|
{
|
||||||
Title = trackNames[i],
|
Title = trackNames[i],
|
||||||
ForeignTrackId = (i+1).ToString(),
|
ForeignTrackId = (100+i+1).ToString(),
|
||||||
AlbumId = 4321,
|
AbsoluteTrackNumber = i+1,
|
||||||
AbsoluteTrackNumber = i+1,
|
MediumNumber = 2
|
||||||
MediumNumber = 1,
|
});
|
||||||
TrackFileId = i+1
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void should_find_track_in_db_by_tracktitle_longer_then_releasetitle()
|
public void should_find_track_in_db_by_tracktitle_longer_then_releasetitle()
|
||||||
{
|
{
|
||||||
var track = _trackService.FindTrackByTitle(1234, 4321, 1, 1, "Courage with some bla");
|
var track = Subject.FindTrackByTitle(1234, 4321, 1, 1, "Courage with some bla");
|
||||||
|
|
||||||
track.Should().NotBeNull();
|
track.Should().NotBeNull();
|
||||||
track.Title.Should().Be(_trackRepository.GetTracksByFileId(1).First().Title);
|
track.Title.Should().Be(Subject.FindTrack(1234, 4321, 1, 1).Title);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void should_find_track_in_db_by_tracktitle_shorter_then_releasetitle()
|
public void should_find_track_in_db_by_tracktitle_shorter_then_releasetitle()
|
||||||
{
|
{
|
||||||
var track = _trackService.FindTrackByTitle(1234, 4321, 1, 3, "and Bone");
|
var track = Subject.FindTrackByTitle(1234, 4321, 1, 3, "and Bone");
|
||||||
|
|
||||||
track.Should().NotBeNull();
|
track.Should().NotBeNull();
|
||||||
track.Title.Should().Be(_trackRepository.GetTracksByFileId(3).First().Title);
|
track.Title.Should().Be(Subject.FindTrack(1234, 4321, 1, 3).Title);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void should_not_find_track_in_db_by_wrong_title()
|
public void should_not_find_track_in_db_by_wrong_title()
|
||||||
{
|
{
|
||||||
var track = _trackService.FindTrackByTitle(1234, 4321, 1, 1, "Not a track");
|
var track = Subject.FindTrackByTitle(1234, 4321, 1, 1, "Not a track");
|
||||||
|
|
||||||
track.Should().BeNull();
|
track.Should().BeNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestCase("another entry", 2, 2)]
|
||||||
|
[TestCase("random name", 2, 3)]
|
||||||
|
public void should_find_track_on_second_disc_when_disc_tag_missing(string title, int discNumber, int trackNumber)
|
||||||
|
{
|
||||||
|
GivenSecondDisc();
|
||||||
|
var track = Subject.FindTrackByTitle(1234, 4321, 0, trackNumber, title);
|
||||||
|
var expected = Subject.FindTrack(1234, 4321, discNumber, trackNumber);
|
||||||
|
|
||||||
|
track.Should().NotBeNull();
|
||||||
|
expected.Should().NotBeNull();
|
||||||
|
|
||||||
|
track.Title.Should().Be(expected.Title);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void should_return_null_if_tracks_with_same_name_and_number_on_different_discs()
|
||||||
|
{
|
||||||
|
GivenSecondDisc();
|
||||||
|
var track = Subject.FindTrackByTitle(1234, 4321, 0, 1, "Courage");
|
||||||
|
track.Should().BeNull();
|
||||||
|
}
|
||||||
|
|
||||||
[TestCase("Fesh and Bone", 3)]
|
[TestCase("Fesh and Bone", 3)]
|
||||||
[TestCase("Atitude", 7)]
|
[TestCase("Atitude", 7)]
|
||||||
[TestCase("Smoth cRimnal", 12)]
|
[TestCase("Smoth cRimnal", 12)]
|
||||||
[TestCase("Sticks and Stones (live)", 6)]
|
[TestCase("Sticks and Stones (live)", 6)]
|
||||||
|
[TestCase("Sticks and Stones (live) - there's a lot of rubbish here", 6)]
|
||||||
|
[TestCase("Smoth cRimnal feat. someone I don't care about", 12)]
|
||||||
|
[TestCase("Christians Inferno", 14)]
|
||||||
|
[TestCase("xxxyyy some random prefix Christians Infurno", 14)]
|
||||||
public void should_find_track_in_db_by_inexact_title(string title, int trackNumber)
|
public void should_find_track_in_db_by_inexact_title(string title, int trackNumber)
|
||||||
{
|
{
|
||||||
var track = _trackService.FindTrackByTitleInexact(1234, 4321, 1, trackNumber, title);
|
var track = Subject.FindTrackByTitleInexact(1234, 4321, 1, trackNumber, title);
|
||||||
|
var expected = Subject.FindTrack(1234, 4321, 1, trackNumber);
|
||||||
|
|
||||||
track.Should().NotBeNull();
|
track.Should().NotBeNull();
|
||||||
track.Title.Should().Be(_trackRepository.GetTracksByFileId(trackNumber).First().Title);
|
expected.Should().NotBeNull();
|
||||||
|
|
||||||
|
track.Title.Should().Be(expected.Title);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestCase("Fesh and Bone", 1)]
|
||||||
|
[TestCase("Atitude", 1)]
|
||||||
|
[TestCase("Smoth cRimnal", 1)]
|
||||||
|
[TestCase("Sticks and Stones (live)", 1)]
|
||||||
|
[TestCase("Christians Inferno", 1)]
|
||||||
|
public void should_not_find_track_in_db_by_inexact_title_with_wrong_tracknumber(string title, int trackNumber)
|
||||||
|
{
|
||||||
|
var track = Subject.FindTrackByTitleInexact(1234, 4321, 1, trackNumber, title);
|
||||||
|
|
||||||
|
track.Should().BeNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestCase("Movis", 1, 2)]
|
||||||
|
[TestCase("anoth entry", 2, 2)]
|
||||||
|
[TestCase("random.name", 2, 3)]
|
||||||
|
public void should_find_track_in_db_by_inexact_title_when_disc_tag_missing(string title, int discNumber, int trackNumber)
|
||||||
|
{
|
||||||
|
GivenSecondDisc();
|
||||||
|
var track = Subject.FindTrackByTitleInexact(1234, 4321, 0, trackNumber, title);
|
||||||
|
var expected = Subject.FindTrack(1234, 4321, discNumber, trackNumber);
|
||||||
|
|
||||||
|
track.Should().NotBeNull();
|
||||||
|
expected.Should().NotBeNull();
|
||||||
|
|
||||||
|
track.Title.Should().Be(expected.Title);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestCase("A random title", 1)]
|
[TestCase("A random title", 1)]
|
||||||
[TestCase("Stones and Sticks", 6)]
|
[TestCase("Stones and Sticks", 6)]
|
||||||
public void should_not_find_track_in_db_by_different_inexact_title(string title, int trackId)
|
public void should_not_find_track_in_db_by_different_inexact_title(string title, int trackId)
|
||||||
{
|
{
|
||||||
var track = _trackService.FindTrackByTitleInexact(1234, 4321, 1, trackId, title);
|
var track = Subject.FindTrackByTitleInexact(1234, 4321, 1, trackId, title);
|
||||||
|
|
||||||
track.Should().BeNull();
|
track.Should().BeNull();
|
||||||
}
|
}
|
||||||
|
|
|
@ -299,12 +299,14 @@
|
||||||
<Compile Include="MetadataSource\SearchArtistComparerFixture.cs" />
|
<Compile Include="MetadataSource\SearchArtistComparerFixture.cs" />
|
||||||
<Compile Include="MetadataSource\SkyHook\SkyHookProxyFixture.cs" />
|
<Compile Include="MetadataSource\SkyHook\SkyHookProxyFixture.cs" />
|
||||||
<Compile Include="MusicTests\AddAlbumFixture.cs" />
|
<Compile Include="MusicTests\AddAlbumFixture.cs" />
|
||||||
|
<Compile Include="MusicTests\AlbumServiceFixture.cs" />
|
||||||
<Compile Include="MusicTests\AddArtistFixture.cs" />
|
<Compile Include="MusicTests\AddArtistFixture.cs" />
|
||||||
<Compile Include="MusicTests\AlbumMonitoredServiceTests\AlbumMonitoredServiceFixture.cs" />
|
<Compile Include="MusicTests\AlbumMonitoredServiceTests\AlbumMonitoredServiceFixture.cs" />
|
||||||
<Compile Include="MusicTests\AlbumRepositoryTests\AlbumRepositoryFixture.cs" />
|
<Compile Include="MusicTests\AlbumRepositoryTests\AlbumRepositoryFixture.cs" />
|
||||||
<Compile Include="MusicTests\ArtistRepositoryTests\ArtistRepositoryFixture.cs" />
|
<Compile Include="MusicTests\ArtistRepositoryTests\ArtistRepositoryFixture.cs" />
|
||||||
<Compile Include="MusicTests\ArtistServiceTests\AddArtistFixture.cs" />
|
<Compile Include="MusicTests\ArtistServiceTests\AddArtistFixture.cs" />
|
||||||
<Compile Include="MusicTests\ArtistServiceTests\UpdateMultipleArtistFixture.cs" />
|
<Compile Include="MusicTests\ArtistServiceTests\UpdateMultipleArtistFixture.cs" />
|
||||||
|
<Compile Include="MusicTests\ArtistServiceTests\FindByNameInexactFixture.cs" />
|
||||||
<Compile Include="MusicTests\RefreshAlbumServiceFixture.cs" />
|
<Compile Include="MusicTests\RefreshAlbumServiceFixture.cs" />
|
||||||
<Compile Include="MusicTests\ShouldRefreshAlbumFixture.cs" />
|
<Compile Include="MusicTests\ShouldRefreshAlbumFixture.cs" />
|
||||||
<Compile Include="MusicTests\TitleMatchingTests\TitleMatchingFixture.cs" />
|
<Compile Include="MusicTests\TitleMatchingTests\TitleMatchingFixture.cs" />
|
||||||
|
|
|
@ -8,7 +8,6 @@ using System.Collections.Generic;
|
||||||
using NzbDrone.Core.Messaging.Events;
|
using NzbDrone.Core.Messaging.Events;
|
||||||
using NzbDrone.Core.Languages;
|
using NzbDrone.Core.Languages;
|
||||||
using NzbDrone.Core.Qualities;
|
using NzbDrone.Core.Qualities;
|
||||||
using NzbDrone.Common.Extensions;
|
|
||||||
|
|
||||||
namespace NzbDrone.Core.Music
|
namespace NzbDrone.Core.Music
|
||||||
{
|
{
|
||||||
|
@ -17,7 +16,6 @@ namespace NzbDrone.Core.Music
|
||||||
List<Album> GetAlbums(int artistId);
|
List<Album> GetAlbums(int artistId);
|
||||||
Album FindByName(string cleanTitle);
|
Album FindByName(string cleanTitle);
|
||||||
Album FindByTitle(int artistId, string title);
|
Album FindByTitle(int artistId, string title);
|
||||||
Album FindByTitleInexact(int artistId, string title);
|
|
||||||
Album FindByArtistAndName(string artistName, string cleanTitle);
|
Album FindByArtistAndName(string artistName, string cleanTitle);
|
||||||
Album FindById(string spotifyId);
|
Album FindById(string spotifyId);
|
||||||
PagingSpec<Album> AlbumsWithoutFiles(PagingSpec<Album> pagingSpec);
|
PagingSpec<Album> AlbumsWithoutFiles(PagingSpec<Album> pagingSpec);
|
||||||
|
@ -49,7 +47,7 @@ namespace NzbDrone.Core.Music
|
||||||
|
|
||||||
public Album FindById(string foreignAlbumId)
|
public Album FindById(string foreignAlbumId)
|
||||||
{
|
{
|
||||||
return Query.SingleOrDefault(s => s.ForeignAlbumId == foreignAlbumId);
|
return Query.Where(s => s.ForeignAlbumId == foreignAlbumId).SingleOrDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
public PagingSpec<Album> AlbumsWithoutFiles(PagingSpec<Album> pagingSpec)
|
public PagingSpec<Album> AlbumsWithoutFiles(PagingSpec<Album> pagingSpec)
|
||||||
|
@ -285,7 +283,7 @@ namespace NzbDrone.Core.Music
|
||||||
{
|
{
|
||||||
cleanTitle = cleanTitle.ToLowerInvariant();
|
cleanTitle = cleanTitle.ToLowerInvariant();
|
||||||
|
|
||||||
return Query.SingleOrDefault(s => s.CleanTitle == cleanTitle);
|
return Query.Where(s => s.CleanTitle == cleanTitle).SingleOrDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Album FindByTitle(int artistId, string title)
|
public Album FindByTitle(int artistId, string title)
|
||||||
|
@ -300,39 +298,6 @@ namespace NzbDrone.Core.Music
|
||||||
.FirstOrDefault();
|
.FirstOrDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Album FindByTitleInexact(int artistId, string title)
|
|
||||||
{
|
|
||||||
double fuzzThreshold = 0.7;
|
|
||||||
double fuzzGap = 0.4;
|
|
||||||
var cleanTitle = Parser.Parser.CleanArtistName(title);
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(cleanTitle))
|
|
||||||
cleanTitle = title;
|
|
||||||
|
|
||||||
var sortedAlbums = Query.Where(s => s.ArtistId == artistId)
|
|
||||||
.Select(s => new
|
|
||||||
{
|
|
||||||
MatchProb = s.CleanTitle.FuzzyMatch(cleanTitle),
|
|
||||||
Album = s
|
|
||||||
})
|
|
||||||
.ToList()
|
|
||||||
.OrderByDescending(s => s.MatchProb)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
if (!sortedAlbums.Any())
|
|
||||||
return null;
|
|
||||||
|
|
||||||
_logger.Trace("\nFuzzy album match on '{0}':\n{1}",
|
|
||||||
cleanTitle,
|
|
||||||
string.Join("\n", sortedAlbums.Select(x => $"{x.Album.CleanTitle}: {x.MatchProb}")));
|
|
||||||
|
|
||||||
if (sortedAlbums[0].MatchProb > fuzzThreshold
|
|
||||||
&& (sortedAlbums.Count == 1 || sortedAlbums[0].MatchProb - sortedAlbums[1].MatchProb > fuzzGap))
|
|
||||||
return sortedAlbums[0].Album;
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Album FindByArtistAndName(string artistName, string cleanTitle)
|
public Album FindByArtistAndName(string artistName, string cleanTitle)
|
||||||
{
|
{
|
||||||
var cleanArtistName = Parser.Parser.CleanArtistName(artistName);
|
var cleanArtistName = Parser.Parser.CleanArtistName(artistName);
|
||||||
|
@ -340,7 +305,8 @@ namespace NzbDrone.Core.Music
|
||||||
|
|
||||||
return Query.Join<Album, Artist>(JoinType.Inner, album => album.Artist, (album, artist) => album.ArtistId == artist.Id)
|
return Query.Join<Album, Artist>(JoinType.Inner, album => album.Artist, (album, artist) => album.ArtistId == artist.Id)
|
||||||
.Where<Artist>(artist => artist.CleanName == cleanArtistName)
|
.Where<Artist>(artist => artist.CleanName == cleanArtistName)
|
||||||
.SingleOrDefault(album => album.CleanTitle == cleanTitle);
|
.Where<Album>(album => album.CleanTitle == cleanTitle)
|
||||||
|
.SingleOrDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Album FindAlbumByRelease(string releaseId)
|
public Album FindAlbumByRelease(string releaseId)
|
||||||
|
|
|
@ -5,6 +5,8 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NzbDrone.Core.Datastore;
|
using NzbDrone.Core.Datastore;
|
||||||
|
using NzbDrone.Core.Parser;
|
||||||
|
using NzbDrone.Common.Extensions;
|
||||||
|
|
||||||
namespace NzbDrone.Core.Music
|
namespace NzbDrone.Core.Music
|
||||||
{
|
{
|
||||||
|
@ -89,7 +91,63 @@ namespace NzbDrone.Core.Music
|
||||||
|
|
||||||
public Album FindByTitleInexact(int artistId, string title)
|
public Album FindByTitleInexact(int artistId, string title)
|
||||||
{
|
{
|
||||||
return _albumRepository.FindByTitleInexact(artistId, title);
|
var cleanTitle = title.CleanArtistName();
|
||||||
|
|
||||||
|
var albums = GetAlbumsByArtist(artistId);
|
||||||
|
|
||||||
|
Func< Func<Album, string, double>, string, Tuple<Func<Album, string, double>, string>> tc = Tuple.Create;
|
||||||
|
var scoringFunctions = new List<Tuple<Func<Album, string, double>, string>> {
|
||||||
|
tc((a, t) => a.CleanTitle.FuzzyMatch(t), cleanTitle),
|
||||||
|
tc((a, t) => a.Title.FuzzyMatch(t), title),
|
||||||
|
tc((a, t) => a.CleanTitle.FuzzyMatch(t), title.RemoveBracketsAndContents().CleanArtistName()),
|
||||||
|
tc((a, t) => a.CleanTitle.FuzzyMatch(t), title.RemoveAfterDash().CleanArtistName()),
|
||||||
|
tc((a, t) => a.CleanTitle.FuzzyMatch(t), title.RemoveBracketsAndContents().RemoveAfterDash().CleanArtistName()),
|
||||||
|
tc((a, t) => t.FuzzyContains(a.CleanTitle), cleanTitle),
|
||||||
|
tc((a, t) => t.FuzzyContains(a.Title), title)
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (var func in scoringFunctions)
|
||||||
|
{
|
||||||
|
var album = FindByStringInexact(albums, func.Item1, func.Item2);
|
||||||
|
if (album != null)
|
||||||
|
{
|
||||||
|
return album;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Album FindByStringInexact(List<Album> albums, Func<Album, string, double> scoreFunction, string title)
|
||||||
|
{
|
||||||
|
const double fuzzThreshold = 0.7;
|
||||||
|
const double fuzzGap = 0.4;
|
||||||
|
|
||||||
|
var sortedAlbums = albums.Select(s => new
|
||||||
|
{
|
||||||
|
MatchProb = scoreFunction(s, title),
|
||||||
|
Album = s
|
||||||
|
})
|
||||||
|
.ToList()
|
||||||
|
.OrderByDescending(s => s.MatchProb)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
if (!sortedAlbums.Any())
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.Trace("\nFuzzy album match on '{0}':\n{1}",
|
||||||
|
title,
|
||||||
|
string.Join("\n", sortedAlbums.Select(x => $"[{x.Album.Title}] {x.Album.CleanTitle}: {x.MatchProb}")));
|
||||||
|
|
||||||
|
if (sortedAlbums[0].MatchProb > fuzzThreshold
|
||||||
|
&& (sortedAlbums.Count == 1 || sortedAlbums[0].MatchProb - sortedAlbums[1].MatchProb > fuzzGap))
|
||||||
|
{
|
||||||
|
return sortedAlbums[0].Album;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Album> GetAllAlbums()
|
public List<Album> GetAllAlbums()
|
||||||
|
|
|
@ -20,7 +20,7 @@ namespace NzbDrone.Core.Music
|
||||||
List<Artist> AddArtists(List<Artist> newArtists);
|
List<Artist> AddArtists(List<Artist> newArtists);
|
||||||
Artist FindById(string spotifyId);
|
Artist FindById(string spotifyId);
|
||||||
Artist FindByName(string title);
|
Artist FindByName(string title);
|
||||||
Artist FindByTitleInexact(string title);
|
Artist FindByNameInexact(string title);
|
||||||
void DeleteArtist(int artistId, bool deleteFiles);
|
void DeleteArtist(int artistId, bool deleteFiles);
|
||||||
List<Artist> GetAllArtists();
|
List<Artist> GetAllArtists();
|
||||||
List<Artist> AllForTag(int tagId);
|
List<Artist> AllForTag(int tagId);
|
||||||
|
@ -89,9 +89,43 @@ namespace NzbDrone.Core.Music
|
||||||
return _artistRepository.FindByName(title.CleanArtistName());
|
return _artistRepository.FindByName(title.CleanArtistName());
|
||||||
}
|
}
|
||||||
|
|
||||||
public Artist FindByTitleInexact(string title)
|
public Artist FindByNameInexact(string title)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
const double fuzzThreshold = 0.8;
|
||||||
|
const double fuzzGap = 0.2;
|
||||||
|
var cleanTitle = Parser.Parser.CleanArtistName(title);
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(cleanTitle))
|
||||||
|
{
|
||||||
|
cleanTitle = title;
|
||||||
|
}
|
||||||
|
|
||||||
|
var sortedArtists = GetAllArtists()
|
||||||
|
.Select(s => new
|
||||||
|
{
|
||||||
|
MatchProb = s.CleanName.FuzzyMatch(cleanTitle),
|
||||||
|
Artist = s
|
||||||
|
})
|
||||||
|
.ToList()
|
||||||
|
.OrderByDescending(s => s.MatchProb)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
if (!sortedArtists.Any())
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.Trace("\nFuzzy artist match on '{0}':\n{1}",
|
||||||
|
cleanTitle,
|
||||||
|
string.Join("\n", sortedArtists.Select(x => $"{x.Artist.CleanName}: {x.MatchProb}")));
|
||||||
|
|
||||||
|
if (sortedArtists[0].MatchProb > fuzzThreshold
|
||||||
|
&& (sortedArtists.Count == 1 || sortedArtists[0].MatchProb - sortedArtists[1].MatchProb > fuzzGap))
|
||||||
|
{
|
||||||
|
return sortedArtists[0].Artist;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Artist> GetAllArtists()
|
public List<Artist> GetAllArtists()
|
||||||
|
@ -110,7 +144,6 @@ namespace NzbDrone.Core.Music
|
||||||
return _artistRepository.Get(artistDBId);
|
return _artistRepository.Get(artistDBId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public List<Artist> GetArtists(IEnumerable<int> artistIds)
|
public List<Artist> GetArtists(IEnumerable<int> artistIds)
|
||||||
{
|
{
|
||||||
return _artistRepository.Get(artistIds).ToList();
|
return _artistRepository.Get(artistIds).ToList();
|
||||||
|
|
|
@ -61,6 +61,11 @@ namespace NzbDrone.Core.Music
|
||||||
|
|
||||||
public List<Track> GetTracksByMedium(int albumId, int mediumNumber)
|
public List<Track> GetTracksByMedium(int albumId, int mediumNumber)
|
||||||
{
|
{
|
||||||
|
if (mediumNumber < 1)
|
||||||
|
{
|
||||||
|
return GetTracksByAlbum(albumId);
|
||||||
|
}
|
||||||
|
|
||||||
return Query.Where(s => s.AlbumId == albumId)
|
return Query.Where(s => s.AlbumId == albumId)
|
||||||
.AndWhere(s => s.MediumNumber == mediumNumber)
|
.AndWhere(s => s.MediumNumber == mediumNumber)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
|
@ -5,6 +5,7 @@ using NzbDrone.Core.MediaFiles;
|
||||||
using NzbDrone.Core.MediaFiles.Events;
|
using NzbDrone.Core.MediaFiles.Events;
|
||||||
using NzbDrone.Core.Messaging.Events;
|
using NzbDrone.Core.Messaging.Events;
|
||||||
using NzbDrone.Core.Music.Events;
|
using NzbDrone.Core.Music.Events;
|
||||||
|
using NzbDrone.Core.Parser;
|
||||||
using NzbDrone.Common.Extensions;
|
using NzbDrone.Common.Extensions;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
@ -81,60 +82,73 @@ namespace NzbDrone.Core.Music
|
||||||
public Track FindTrackByTitle(int artistId, int albumId, int mediumNumber, int trackNumber, string releaseTitle)
|
public Track FindTrackByTitle(int artistId, int albumId, int mediumNumber, int trackNumber, string releaseTitle)
|
||||||
{
|
{
|
||||||
// TODO: can replace this search mechanism with something smarter/faster/better
|
// TODO: can replace this search mechanism with something smarter/faster/better
|
||||||
var normalizedReleaseTitle = Parser.Parser.NormalizeTrackTitle(releaseTitle).Replace(".", " ");
|
var normalizedReleaseTitle = releaseTitle.NormalizeTrackTitle().Replace(".", " ");
|
||||||
var tracks = _trackRepository.GetTracksByMedium(albumId, mediumNumber);
|
var tracks = _trackRepository.GetTracksByMedium(albumId, mediumNumber);
|
||||||
|
|
||||||
var matches = from track in tracks
|
var matches = tracks.Where(t => (trackNumber == 0 || t.AbsoluteTrackNumber == trackNumber)
|
||||||
//if we have a trackNumber use it
|
&& t.Title.Length > 0
|
||||||
let trackNumCheck = (trackNumber == 0 || track.AbsoluteTrackNumber == trackNumber)
|
&& (normalizedReleaseTitle.Contains(t.Title.NormalizeTrackTitle())
|
||||||
//if release title is longer than track title
|
|| t.Title.NormalizeTrackTitle().Contains(normalizedReleaseTitle)));
|
||||||
let posReleaseTitle = normalizedReleaseTitle.IndexOf(Parser.Parser.NormalizeTrackTitle(track.Title), StringComparison.CurrentCultureIgnoreCase)
|
|
||||||
//if track title is longer than release title
|
|
||||||
let posTrackTitle = Parser.Parser.NormalizeTrackTitle(track.Title).IndexOf(normalizedReleaseTitle, StringComparison.CurrentCultureIgnoreCase)
|
|
||||||
where track.Title.Length > 0 && trackNumCheck && (posReleaseTitle >= 0 || posTrackTitle >= 0)
|
|
||||||
orderby posReleaseTitle, posTrackTitle
|
|
||||||
select new
|
|
||||||
{
|
|
||||||
NormalizedLength = Parser.Parser.NormalizeTrackTitle(track.Title).Length,
|
|
||||||
Track = track
|
|
||||||
};
|
|
||||||
|
|
||||||
return matches.OrderByDescending(e => e.NormalizedLength).FirstOrDefault()?.Track;
|
return matches.Count() > 1 ? null : matches.SingleOrDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Track FindTrackByTitleInexact(int artistId, int albumId, int mediumNumber, int trackNumber, string releaseTitle)
|
public Track FindTrackByTitleInexact(int artistId, int albumId, int mediumNumber, int trackNumber, string title)
|
||||||
{
|
{
|
||||||
double fuzzThreshold = 0.6;
|
var normalizedTitle = title.NormalizeTrackTitle().Replace(".", " ");
|
||||||
double fuzzGap = 0.2;
|
|
||||||
|
|
||||||
var normalizedReleaseTitle = Parser.Parser.NormalizeTrackTitle(releaseTitle).Replace(".", " ");
|
|
||||||
var tracks = _trackRepository.GetTracksByMedium(albumId, mediumNumber);
|
var tracks = _trackRepository.GetTracksByMedium(albumId, mediumNumber);
|
||||||
|
|
||||||
var matches = from track in tracks
|
Func< Func<Track, string, double>, string, Tuple<Func<Track, string, double>, string>> tc = Tuple.Create;
|
||||||
let normalizedTitle = Parser.Parser.NormalizeTrackTitle(track.Title).Replace(".", " ")
|
var scoringFunctions = new List<Tuple<Func<Track, string, double>, string>> {
|
||||||
let matchProb = normalizedTitle.FuzzyMatch(normalizedReleaseTitle)
|
tc((a, t) => a.Title.NormalizeTrackTitle().FuzzyMatch(t), normalizedTitle),
|
||||||
where track.Title.Length > 0
|
tc((a, t) => a.Title.NormalizeTrackTitle().FuzzyContains(t), normalizedTitle),
|
||||||
orderby matchProb descending
|
tc((a, t) => t.FuzzyContains(a.Title.NormalizeTrackTitle()), normalizedTitle)
|
||||||
select new
|
};
|
||||||
|
|
||||||
|
foreach (var func in scoringFunctions)
|
||||||
|
{
|
||||||
|
var track = FindByStringInexact(tracks, func.Item1, func.Item2, trackNumber);
|
||||||
|
if (track != null)
|
||||||
{
|
{
|
||||||
MatchProb = matchProb,
|
return track;
|
||||||
NormalizedTitle = normalizedTitle,
|
}
|
||||||
Track = track
|
}
|
||||||
};
|
|
||||||
|
|
||||||
var matchList = matches.ToList();
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
if (!matchList.Any())
|
private Track FindByStringInexact(List<Track> tracks, Func<Track, string, double> scoreFunction, string title, int trackNumber)
|
||||||
|
{
|
||||||
|
const double fuzzThreshold = 0.7;
|
||||||
|
const double fuzzGap = 0.2;
|
||||||
|
|
||||||
|
var sortedTracks = tracks.Select(s => new
|
||||||
|
{
|
||||||
|
MatchProb = scoreFunction(s, title),
|
||||||
|
Track = s
|
||||||
|
})
|
||||||
|
.ToList()
|
||||||
|
.OrderByDescending(s => s.MatchProb)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
if (!sortedTracks.Any())
|
||||||
|
{
|
||||||
return null;
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
_logger.Trace("\nFuzzy track match on '{0}':\n{1}",
|
_logger.Trace("\nFuzzy track match on '{0:D2} - {1}':\n{2}",
|
||||||
normalizedReleaseTitle,
|
trackNumber,
|
||||||
string.Join("\n", matchList.Select(x => $"{x.NormalizedTitle}: {x.MatchProb}")));
|
title,
|
||||||
|
string.Join("\n", sortedTracks.Select(x => $"{x.Track.AbsoluteTrackNumber:D2} - {x.Track.Title}: {x.MatchProb}")));
|
||||||
|
|
||||||
if (matchList[0].MatchProb > fuzzThreshold
|
if (sortedTracks[0].MatchProb > fuzzThreshold
|
||||||
&& (matchList.Count == 1 || matchList[0].MatchProb - matchList[1].MatchProb > fuzzGap)
|
&& (sortedTracks.Count == 1 || sortedTracks[0].MatchProb - sortedTracks[1].MatchProb > fuzzGap)
|
||||||
&& (trackNumber == 0 || matchList[0].Track.AbsoluteTrackNumber == trackNumber))
|
&& (trackNumber == 0
|
||||||
return matchList[0].Track;
|
|| sortedTracks[0].Track.AbsoluteTrackNumber == trackNumber
|
||||||
|
|| sortedTracks[0].Track.AbsoluteTrackNumber + tracks.Count(t => t.MediumNumber < sortedTracks[0].Track.MediumNumber) == trackNumber))
|
||||||
|
{
|
||||||
|
return sortedTracks[0].Track;
|
||||||
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -208,6 +208,14 @@ namespace NzbDrone.Core.Parser
|
||||||
new Regex(@"(\[|\()*\b((featuring|feat.|feat|ft|ft.)\s{1}){1}\s*.*(\]|\))*", RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
new Regex(@"(\[|\()*\b((featuring|feat.|feat|ft|ft.)\s{1}){1}\s*.*(\]|\))*", RegexOptions.IgnoreCase | RegexOptions.Compiled),
|
||||||
new Regex(@"(?:\(|\[)(?:[^\(\[]*)(?:version|limited|deluxe|single|clean|album|special|bonus|promo|remastered)(?:[^\)\]]*)(?:\)|\])", RegexOptions.IgnoreCase | RegexOptions.Compiled)
|
new Regex(@"(?:\(|\[)(?:[^\(\[]*)(?:version|limited|deluxe|single|clean|album|special|bonus|promo|remastered)(?:[^\)\]]*)(?:\)|\])", RegexOptions.IgnoreCase | RegexOptions.Compiled)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private static readonly Regex[] BracketRegex = new Regex[]
|
||||||
|
{
|
||||||
|
new Regex(@"\(.*\)", RegexOptions.Compiled),
|
||||||
|
new Regex(@"\[.*\]", RegexOptions.Compiled)
|
||||||
|
};
|
||||||
|
|
||||||
|
private static readonly Regex AfterDashRegex = new Regex(@"[-:].*", RegexOptions.Compiled);
|
||||||
|
|
||||||
public static ParsedTrackInfo ParseMusicPath(string path)
|
public static ParsedTrackInfo ParseMusicPath(string path)
|
||||||
{
|
{
|
||||||
|
@ -528,14 +536,13 @@ namespace NzbDrone.Core.Parser
|
||||||
return NormalizeRegex.Replace(name, string.Empty).ToLower().RemoveAccent();
|
return NormalizeRegex.Replace(name, string.Empty).ToLower().RemoveAccent();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string NormalizeTrackTitle(string title)
|
public static string NormalizeTrackTitle(this string title)
|
||||||
{
|
{
|
||||||
title = SpecialEpisodeWordRegex.Replace(title, string.Empty);
|
title = SpecialEpisodeWordRegex.Replace(title, string.Empty);
|
||||||
title = PunctuationRegex.Replace(title, " ");
|
title = PunctuationRegex.Replace(title, " ");
|
||||||
title = DuplicateSpacesRegex.Replace(title, " ");
|
title = DuplicateSpacesRegex.Replace(title, " ");
|
||||||
|
|
||||||
return title.Trim()
|
return title.Trim().ToLower();
|
||||||
.ToLower();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string NormalizeTitle(string title)
|
public static string NormalizeTitle(string title)
|
||||||
|
@ -601,6 +608,22 @@ namespace NzbDrone.Core.Parser
|
||||||
return CommonTagRegex[1].Replace(album, string.Empty).Trim();
|
return CommonTagRegex[1].Replace(album, string.Empty).Trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static string RemoveBracketsAndContents(this string album)
|
||||||
|
{
|
||||||
|
var intermediate = album;
|
||||||
|
foreach (var regex in BracketRegex)
|
||||||
|
{
|
||||||
|
intermediate = regex.Replace(intermediate, string.Empty).Trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
return intermediate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string RemoveAfterDash(this string text)
|
||||||
|
{
|
||||||
|
return AfterDashRegex.Replace(text, string.Empty).Trim();
|
||||||
|
}
|
||||||
|
|
||||||
public static string CleanTrackTitle(string title)
|
public static string CleanTrackTitle(string title)
|
||||||
{
|
{
|
||||||
var intermediateTitle = title;
|
var intermediateTitle = title;
|
||||||
|
@ -619,7 +642,7 @@ namespace NzbDrone.Core.Parser
|
||||||
|
|
||||||
var trackNumber = file.Tag.Track;
|
var trackNumber = file.Tag.Track;
|
||||||
var trackTitle = file.Tag.Title;
|
var trackTitle = file.Tag.Title;
|
||||||
var discNumber = (file.Tag.Disc > 0) ? Convert.ToInt32(file.Tag.Disc) : 1;
|
var discNumber = (int)file.Tag.Disc;
|
||||||
|
|
||||||
var artist = file.Tag.FirstAlbumArtist;
|
var artist = file.Tag.FirstAlbumArtist;
|
||||||
|
|
||||||
|
|
|
@ -50,14 +50,21 @@ namespace NzbDrone.Core.Parser
|
||||||
public Artist GetArtist(string title)
|
public Artist GetArtist(string title)
|
||||||
{
|
{
|
||||||
var parsedAlbumInfo = Parser.ParseAlbumTitle(title);
|
var parsedAlbumInfo = Parser.ParseAlbumTitle(title);
|
||||||
|
|
||||||
if (parsedAlbumInfo == null || parsedAlbumInfo.ArtistName.IsNullOrWhiteSpace())
|
if (parsedAlbumInfo != null && !parsedAlbumInfo.ArtistName.IsNullOrWhiteSpace())
|
||||||
{
|
{
|
||||||
return _artistService.FindByName(title);
|
title = parsedAlbumInfo.ArtistName;
|
||||||
|
}
|
||||||
|
|
||||||
|
var artistInfo = _artistService.FindByName(title);
|
||||||
|
|
||||||
|
if (artistInfo == null)
|
||||||
|
{
|
||||||
|
_logger.Debug("Trying inexact artist match for {0}", title);
|
||||||
|
artistInfo = _artistService.FindByNameInexact(title);
|
||||||
}
|
}
|
||||||
|
|
||||||
return _artistService.FindByName(parsedAlbumInfo.ArtistName);
|
return artistInfo;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Artist GetArtistFromTag(string file)
|
public Artist GetArtistFromTag(string file)
|
||||||
|
@ -81,8 +88,15 @@ namespace NzbDrone.Core.Parser
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return _artistService.FindByName(parsedTrackInfo.ArtistTitle);
|
artist = _artistService.FindByName(parsedTrackInfo.ArtistTitle);
|
||||||
|
|
||||||
|
if (artist == null)
|
||||||
|
{
|
||||||
|
_logger.Debug("Trying inexact artist match for {0}", parsedTrackInfo.ArtistTitle);
|
||||||
|
artist = _artistService.FindByNameInexact(parsedTrackInfo.ArtistTitle);
|
||||||
|
}
|
||||||
|
|
||||||
|
return artist;
|
||||||
}
|
}
|
||||||
|
|
||||||
public RemoteAlbum Map(ParsedAlbumInfo parsedAlbumInfo, SearchCriteriaBase searchCriteria = null)
|
public RemoteAlbum Map(ParsedAlbumInfo parsedAlbumInfo, SearchCriteriaBase searchCriteria = null)
|
||||||
|
@ -147,6 +161,12 @@ namespace NzbDrone.Core.Parser
|
||||||
albumInfo = _albumService.FindByTitle(artist.Id, parsedAlbumInfo.AlbumTitle);
|
albumInfo = _albumService.FindByTitle(artist.Id, parsedAlbumInfo.AlbumTitle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (albumInfo == null)
|
||||||
|
{
|
||||||
|
_logger.Debug("Trying inexact album match for {0}", parsedAlbumInfo.AlbumTitle);
|
||||||
|
albumInfo = _albumService.FindByTitleInexact(artist.Id, parsedAlbumInfo.AlbumTitle);
|
||||||
|
}
|
||||||
|
|
||||||
if (albumInfo != null)
|
if (albumInfo != null)
|
||||||
{
|
{
|
||||||
result.Add(albumInfo);
|
result.Add(albumInfo);
|
||||||
|
@ -186,6 +206,12 @@ namespace NzbDrone.Core.Parser
|
||||||
|
|
||||||
artist = _artistService.FindByName(parsedAlbumInfo.ArtistName);
|
artist = _artistService.FindByName(parsedAlbumInfo.ArtistName);
|
||||||
|
|
||||||
|
if (artist == null)
|
||||||
|
{
|
||||||
|
_logger.Debug("Trying inexact artist match for {0}", parsedAlbumInfo.ArtistName);
|
||||||
|
artist = _artistService.FindByNameInexact(parsedAlbumInfo.ArtistName);
|
||||||
|
}
|
||||||
|
|
||||||
if (artist == null)
|
if (artist == null)
|
||||||
{
|
{
|
||||||
_logger.Debug("No matching artist {0}", parsedAlbumInfo.ArtistName);
|
_logger.Debug("No matching artist {0}", parsedAlbumInfo.ArtistName);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue