public List <CandidateAlbumRelease> GetCandidatesFromFingerprint(LocalAlbumRelease localAlbumRelease, Artist artist, Album album, AlbumRelease release, bool includeExisting) { var recordingIds = localAlbumRelease.LocalTracks.Where(x => x.AcoustIdResults != null).SelectMany(x => x.AcoustIdResults).ToList(); var allReleases = _releaseService.GetReleasesByRecordingIds(recordingIds); // make sure releases are consistent with those selected by the user if (release != null) { allReleases = allReleases.Where(x => x.Id == release.Id).ToList(); } else if (album != null) { allReleases = allReleases.Where(x => x.AlbumId == album.Id).ToList(); } else if (artist != null) { allReleases = allReleases.Where(x => x.Album.Value.ArtistMetadataId == artist.ArtistMetadataId).ToList(); } return(GetCandidatesByRelease(allReleases.Select(x => new { Release = x, TrackCount = x.TrackCount, CommonProportion = x.Tracks.Value.Select(y => y.ForeignRecordingId).Intersect(recordingIds).Count() / localAlbumRelease.TrackCount }) .Where(x => x.CommonProportion > 0.6) .ToList() .OrderBy(x => Math.Abs(x.TrackCount - localAlbumRelease.TrackCount)) .ThenByDescending(x => x.CommonProportion) .Select(x => x.Release) .Take(10) .ToList(), includeExisting)); }
private List <CandidateAlbumRelease> GetCandidates(LocalAlbumRelease localAlbumRelease, bool includeExisting) { // most general version, nothing has been specified. // get all plausible artists, then all plausible albums, then get releases for each of these. // check if it looks like VA. if (TrackGroupingService.IsVariousArtists(localAlbumRelease.LocalTracks)) { throw new NotImplementedException("Various artists not supported"); } var candidateReleases = new List <CandidateAlbumRelease>(); var artistTag = MostCommon(localAlbumRelease.LocalTracks.Select(x => x.FileTrackInfo.ArtistTitle)) ?? ""; if (artistTag.IsNotNullOrWhiteSpace()) { var possibleArtists = _artistService.GetCandidates(artistTag); foreach (var artist in possibleArtists) { candidateReleases.AddRange(GetCandidatesByArtist(localAlbumRelease, artist, includeExisting)); } } return(candidateReleases); }
private List <CandidateAlbumRelease> GetDbCandidates(LocalAlbumRelease localAlbumRelease, bool includeExisting) { // most general version, nothing has been specified. // get all plausible artists, then all plausible albums, then get releases for each of these. var candidateReleases = new List <CandidateAlbumRelease>(); // check if it looks like VA. if (TrackGroupingService.IsVariousArtists(localAlbumRelease.LocalTracks)) { var va = _artistService.FindById(DistanceCalculator.VariousArtistIds[0]); if (va != null) { candidateReleases.AddRange(GetDbCandidatesByArtist(localAlbumRelease, va, includeExisting)); } } var artistTag = localAlbumRelease.LocalTracks.MostCommon(x => x.FileTrackInfo.ArtistTitle) ?? ""; if (artistTag.IsNotNullOrWhiteSpace()) { var possibleArtists = _artistService.GetCandidates(artistTag); foreach (var artist in possibleArtists) { candidateReleases.AddRange(GetDbCandidatesByArtist(localAlbumRelease, artist, includeExisting)); } } return(candidateReleases); }
private ImportDecision <LocalAlbumRelease> GetDecision(LocalAlbumRelease localAlbumRelease, DownloadClientItem downloadClientItem) { ImportDecision <LocalAlbumRelease> decision = null; if (localAlbumRelease.AlbumRelease == null) { decision = new ImportDecision <LocalAlbumRelease>(localAlbumRelease, new Rejection($"Couldn't find similar album for {localAlbumRelease}")); } else { var reasons = _albumSpecifications.Select(c => EvaluateSpec(c, localAlbumRelease, downloadClientItem)) .Where(c => c != null); decision = new ImportDecision <LocalAlbumRelease>(localAlbumRelease, reasons.ToArray()); } if (decision == null) { _logger.Error("Unable to make a decision on {0}", localAlbumRelease); } else if (decision.Rejections.Any()) { _logger.Debug("Album rejected for the following reasons: {0}", string.Join(", ", decision.Rejections)); } else { _logger.Debug("Album accepted"); } return(decision); }
private List <CandidateAlbumRelease> GetCandidatesByAlbum(LocalAlbumRelease localAlbumRelease, Album album, bool includeExisting) { // sort candidate releases by closest track count so that we stand a chance of // getting a perfect match early on return(GetCandidatesByRelease(_releaseService.GetReleasesByAlbum(album.Id) .OrderBy(x => Math.Abs(localAlbumRelease.TrackCount - x.TrackCount)) .ToList(), includeExisting)); }
private void EnsureData(LocalAlbumRelease release) { if (release.AlbumRelease != null && release.AlbumRelease.Album.Value.Artist.Value.QualityProfileId == 0) { var rootFolder = _rootFolderService.GetBestRootFolder(release.LocalTracks.First().Path); var qualityProfile = _qualityProfileService.Get(rootFolder.DefaultQualityProfileId); var artist = release.AlbumRelease.Album.Value.Artist.Value; artist.QualityProfileId = qualityProfile.Id; artist.QualityProfile = qualityProfile; } }
public void get_candidates_should_only_return_specified_release_if_set() { var tracks = GivenTracks(3); var release = GivenAlbumRelease("album", tracks); var localTracks = GivenLocalTracks(tracks, release); var localAlbumRelease = new LocalAlbumRelease(localTracks); Subject.GetCandidatesFromTags(localAlbumRelease, null, null, release, false).ShouldBeEquivalentTo( new List <CandidateAlbumRelease> { new CandidateAlbumRelease(release) } ); }
public void get_candidates_should_only_return_specified_release_if_set() { var tracks = GivenTracks(3); var release = GivenAlbumRelease("album", tracks); var localTracks = GivenLocalTracks(tracks, release); var localAlbumRelease = new LocalAlbumRelease(localTracks); var idOverrides = new IdentificationOverrides { AlbumRelease = release }; Subject.GetDbCandidatesFromTags(localAlbumRelease, idOverrides, false).Should().BeEquivalentTo( new List <CandidateAlbumRelease> { new CandidateAlbumRelease(release) }); }
public LocalAlbumRelease Augment(LocalAlbumRelease localAlbum) { foreach (var augmenter in _albumAugmenters) { try { augmenter.Aggregate(localAlbum, false); } catch (Exception ex) { _logger.Warn(ex, ex.Message); } } return(localAlbum); }
private void GetBestRelease(LocalAlbumRelease localAlbumRelease, List <CandidateAlbumRelease> candidateReleases, List <LocalTrack> extraTracksOnDisk) { var watch = System.Diagnostics.Stopwatch.StartNew(); _logger.Debug("Matching {0} track files against {1} candidates", localAlbumRelease.TrackCount, candidateReleases.Count); _logger.Trace("Processing files:\n{0}", string.Join("\n", localAlbumRelease.LocalTracks.Select(x => x.Path))); double bestDistance = 1.0; foreach (var candidateRelease in candidateReleases) { var release = candidateRelease.AlbumRelease; _logger.Debug("Trying Release {0} [{1}, {2} tracks, {3} existing]", release, release.Title, release.TrackCount, candidateRelease.ExistingTracks.Count); var rwatch = System.Diagnostics.Stopwatch.StartNew(); var extraTrackPaths = candidateRelease.ExistingTracks.Select(x => x.Path).ToList(); var extraTracks = extraTracksOnDisk.Where(x => extraTrackPaths.Contains(x.Path)).ToList(); var allLocalTracks = localAlbumRelease.LocalTracks.Concat(extraTracks).DistinctBy(x => x.Path).ToList(); var mapping = MapReleaseTracks(allLocalTracks, release.Tracks.Value); var distance = DistanceCalculator.AlbumReleaseDistance(allLocalTracks, release, mapping); var currDistance = distance.NormalizedDistance(); rwatch.Stop(); _logger.Debug("Release {0} [{1} tracks] has distance {2} vs best distance {3} [{4}ms]", release, release.TrackCount, currDistance, bestDistance, rwatch.ElapsedMilliseconds); if (currDistance < bestDistance) { bestDistance = currDistance; localAlbumRelease.Distance = distance; localAlbumRelease.AlbumRelease = release; localAlbumRelease.ExistingTracks = extraTracks; localAlbumRelease.TrackMapping = mapping; if (currDistance == 0.0) { break; } } } watch.Stop(); _logger.Debug($"Best release: {localAlbumRelease.AlbumRelease} Distance {localAlbumRelease.Distance.NormalizedDistance()} found in {watch.ElapsedMilliseconds}ms"); }
private bool ShouldFingerprint(LocalAlbumRelease localAlbumRelease) { var worstTrackMatchDist = localAlbumRelease.TrackMapping?.Mapping .OrderByDescending(x => x.Value.Item2.NormalizedDistance()) .First() .Value.Item2.NormalizedDistance() ?? 1.0; if (localAlbumRelease.Distance.NormalizedDistance() > 0.15 || localAlbumRelease.TrackMapping.LocalExtra.Any() || localAlbumRelease.TrackMapping.MBExtra.Any() || worstTrackMatchDist > 0.40) { return(true); } return(false); }
public void get_candidates_should_use_consensus_release_id() { var tracks = GivenTracks(3); var release = GivenAlbumRelease("album", tracks); release.ForeignReleaseId = "xxx"; var localTracks = GivenLocalTracks(tracks, release); var localAlbumRelease = new LocalAlbumRelease(localTracks); Mocker.GetMock <IReleaseService>() .Setup(x => x.GetReleaseByForeignReleaseId("xxx", true)) .Returns(release); Subject.GetDbCandidatesFromTags(localAlbumRelease, null, false).Should().BeEquivalentTo( new List <CandidateAlbumRelease> { new CandidateAlbumRelease(release) }); }
private List <CandidateAlbumRelease> GetCandidatesByArtist(LocalAlbumRelease localAlbumRelease, Artist artist, bool includeExisting) { _logger.Trace("Getting candidates for {0}", artist); var candidateReleases = new List <CandidateAlbumRelease>(); var albumTag = MostCommon(localAlbumRelease.LocalTracks.Select(x => x.FileTrackInfo.AlbumTitle)) ?? ""; if (albumTag.IsNotNullOrWhiteSpace()) { var possibleAlbums = _albumService.GetCandidates(artist.ArtistMetadataId, albumTag); foreach (var album in possibleAlbums) { candidateReleases.AddRange(GetCandidatesByAlbum(localAlbumRelease, album, includeExisting)); } } return(candidateReleases); }
private List <LocalTrack> ToLocalTrack(IEnumerable <TrackFile> trackfiles, LocalAlbumRelease localRelease) { var scanned = trackfiles.Join(localRelease.LocalTracks, t => t.Path, l => l.Path, (track, localTrack) => localTrack); var toScan = trackfiles.ExceptBy(t => t.Path, scanned, s => s.Path, StringComparer.InvariantCulture); var localTracks = scanned.Concat(toScan.Select(x => new LocalTrack { Path = x.Path, Size = x.Size, Modified = x.Modified, FileTrackInfo = _audioTagService.ReadTags(x.Path), ExistingFile = true, AdditionalFile = true, Quality = x.Quality })) .ToList(); localTracks.ForEach(x => _augmentingService.Augment(x, true)); return(localTracks); }
public List <CandidateAlbumRelease> GetRemoteCandidates(LocalAlbumRelease localAlbumRelease) { // Gets candidate album releases from the metadata server. // Will eventually need adding locally if we find a match var watch = System.Diagnostics.Stopwatch.StartNew(); List <Album> remoteAlbums; var candidates = new List <CandidateAlbumRelease>(); var albumIds = localAlbumRelease.LocalTracks.Select(x => x.FileTrackInfo.AlbumMBId).Distinct().ToList(); var recordingIds = localAlbumRelease.LocalTracks.Where(x => x.AcoustIdResults != null).SelectMany(x => x.AcoustIdResults).Distinct().ToList(); try { if (albumIds.Count == 1 && albumIds[0].IsNotNullOrWhiteSpace()) { // Use mbids in tags if set remoteAlbums = _albumSearchService.SearchForNewAlbum($"mbid:{albumIds[0]}", null); } else if (recordingIds.Any()) { // If fingerprints present use those remoteAlbums = _albumSearchService.SearchForNewAlbumByRecordingIds(recordingIds); } else { // fall back to artist / album name search string artistTag; if (TrackGroupingService.IsVariousArtists(localAlbumRelease.LocalTracks)) { artistTag = "Various Artists"; } else { artistTag = localAlbumRelease.LocalTracks.MostCommon(x => x.FileTrackInfo.ArtistTitle) ?? ""; } var albumTag = localAlbumRelease.LocalTracks.MostCommon(x => x.FileTrackInfo.AlbumTitle) ?? ""; if (artistTag.IsNullOrWhiteSpace() || albumTag.IsNullOrWhiteSpace()) { return(candidates); } remoteAlbums = _albumSearchService.SearchForNewAlbum(albumTag, artistTag); } } catch (SkyHookException e) { _logger.Info(e, "Skipping album due to SkyHook error"); remoteAlbums = new List <Album>(); } foreach (var album in remoteAlbums) { // We have to make sure various bits and pieces are populated that are normally handled // by a database lazy load foreach (var release in album.AlbumReleases.Value) { release.Album = album; candidates.Add(new CandidateAlbumRelease { AlbumRelease = release, ExistingTracks = new List <TrackFile>() }); } } watch.Stop(); _logger.Debug($"Getting {candidates.Count} remote candidates from tags for {localAlbumRelease.LocalTracks.Count} tracks took {watch.ElapsedMilliseconds}ms"); return(candidates); }
public void Setup() { _albumpass1 = new Mock <IImportDecisionEngineSpecification <LocalAlbumRelease> >(); _albumpass2 = new Mock <IImportDecisionEngineSpecification <LocalAlbumRelease> >(); _albumpass3 = new Mock <IImportDecisionEngineSpecification <LocalAlbumRelease> >(); _albumfail1 = new Mock <IImportDecisionEngineSpecification <LocalAlbumRelease> >(); _albumfail2 = new Mock <IImportDecisionEngineSpecification <LocalAlbumRelease> >(); _albumfail3 = new Mock <IImportDecisionEngineSpecification <LocalAlbumRelease> >(); _pass1 = new Mock <IImportDecisionEngineSpecification <LocalTrack> >(); _pass2 = new Mock <IImportDecisionEngineSpecification <LocalTrack> >(); _pass3 = new Mock <IImportDecisionEngineSpecification <LocalTrack> >(); _fail1 = new Mock <IImportDecisionEngineSpecification <LocalTrack> >(); _fail2 = new Mock <IImportDecisionEngineSpecification <LocalTrack> >(); _fail3 = new Mock <IImportDecisionEngineSpecification <LocalTrack> >(); _albumpass1.Setup(c => c.IsSatisfiedBy(It.IsAny <LocalAlbumRelease>())).Returns(Decision.Accept()); _albumpass2.Setup(c => c.IsSatisfiedBy(It.IsAny <LocalAlbumRelease>())).Returns(Decision.Accept()); _albumpass3.Setup(c => c.IsSatisfiedBy(It.IsAny <LocalAlbumRelease>())).Returns(Decision.Accept()); _albumfail1.Setup(c => c.IsSatisfiedBy(It.IsAny <LocalAlbumRelease>())).Returns(Decision.Reject("_albumfail1")); _albumfail2.Setup(c => c.IsSatisfiedBy(It.IsAny <LocalAlbumRelease>())).Returns(Decision.Reject("_albumfail2")); _albumfail3.Setup(c => c.IsSatisfiedBy(It.IsAny <LocalAlbumRelease>())).Returns(Decision.Reject("_albumfail3")); _pass1.Setup(c => c.IsSatisfiedBy(It.IsAny <LocalTrack>())).Returns(Decision.Accept()); _pass2.Setup(c => c.IsSatisfiedBy(It.IsAny <LocalTrack>())).Returns(Decision.Accept()); _pass3.Setup(c => c.IsSatisfiedBy(It.IsAny <LocalTrack>())).Returns(Decision.Accept()); _fail1.Setup(c => c.IsSatisfiedBy(It.IsAny <LocalTrack>())).Returns(Decision.Reject("_fail1")); _fail2.Setup(c => c.IsSatisfiedBy(It.IsAny <LocalTrack>())).Returns(Decision.Reject("_fail2")); _fail3.Setup(c => c.IsSatisfiedBy(It.IsAny <LocalTrack>())).Returns(Decision.Reject("_fail3")); _artist = Builder <Artist> .CreateNew() .With(e => e.QualityProfile = new QualityProfile { Items = Qualities.QualityFixture.GetDefaultQualities() }) .Build(); _albumRelease = Builder <AlbumRelease> .CreateNew() .Build(); _quality = new QualityModel(Quality.MP3_256); _localTrack = new LocalTrack { Artist = _artist, Quality = _quality, Tracks = new List <Track> { new Track() }, Path = @"C:\Test\Unsorted\The.Office.S03E115.DVDRip.XviD-OSiTV.avi".AsOsAgnostic() }; GivenAudioFiles(new List <string> { @"C:\Test\Unsorted\The.Office.S03E115.DVDRip.XviD-OSiTV.avi".AsOsAgnostic() }); Mocker.GetMock <IIdentificationService>() .Setup(s => s.Identify(It.IsAny <List <LocalTrack> >(), It.IsAny <Artist>(), It.IsAny <Album>(), It.IsAny <AlbumRelease>(), It.IsAny <bool>(), It.IsAny <bool>(), It.IsAny <bool>())) .Returns((List <LocalTrack> tracks, Artist artist, Album album, AlbumRelease release, bool newDownload, bool singleRelease, bool includeExisting) => { var ret = new LocalAlbumRelease(tracks); ret.AlbumRelease = _albumRelease; return(new List <LocalAlbumRelease> { ret }); }); Mocker.GetMock <IMediaFileService>() .Setup(c => c.FilterUnchangedFiles(It.IsAny <List <IFileInfo> >(), It.IsAny <Artist>(), It.IsAny <FilterFilesType>())) .Returns((List <IFileInfo> files, Artist artist, FilterFilesType filter) => files); GivenSpecifications(_albumpass1); }
private void IdentifyRelease(LocalAlbumRelease localAlbumRelease, IdentificationOverrides idOverrides, ImportDecisionMakerConfig config) { var watch = System.Diagnostics.Stopwatch.StartNew(); bool fingerprinted = false; var candidateReleases = _candidateService.GetDbCandidatesFromTags(localAlbumRelease, idOverrides, config.IncludeExisting); if (candidateReleases.Count == 0 && config.AddNewArtists) { candidateReleases = _candidateService.GetRemoteCandidates(localAlbumRelease); } if (candidateReleases.Count == 0 && FingerprintingAllowed(config.NewDownload)) { _logger.Debug("No candidates found, fingerprinting"); _fingerprintingService.Lookup(localAlbumRelease.LocalTracks, 0.5); fingerprinted = true; candidateReleases = _candidateService.GetDbCandidatesFromFingerprint(localAlbumRelease, idOverrides, config.IncludeExisting); if (candidateReleases.Count == 0 && config.AddNewArtists) { // Now fingerprints are populated this will return a different answer candidateReleases = _candidateService.GetRemoteCandidates(localAlbumRelease); } } if (candidateReleases.Count == 0) { // can't find any candidates even after fingerprinting // populate the overrides and return foreach (var localTrack in localAlbumRelease.LocalTracks) { localTrack.Release = idOverrides.AlbumRelease; localTrack.Album = idOverrides.Album; localTrack.Artist = idOverrides.Artist; } return; } _logger.Debug($"Got {candidateReleases.Count} candidates for {localAlbumRelease.LocalTracks.Count} tracks in {watch.ElapsedMilliseconds}ms"); PopulateTracks(candidateReleases); // convert all the TrackFiles that represent extra files to List<LocalTrack> var allLocalTracks = ToLocalTrack(candidateReleases .SelectMany(x => x.ExistingTracks) .DistinctBy(x => x.Path), localAlbumRelease); _logger.Debug($"Retrieved {allLocalTracks.Count} possible tracks in {watch.ElapsedMilliseconds}ms"); GetBestRelease(localAlbumRelease, candidateReleases, allLocalTracks); // If result isn't great and we haven't fingerprinted, try that // Note that this can improve the match even if we try the same candidates if (!fingerprinted && FingerprintingAllowed(config.NewDownload) && ShouldFingerprint(localAlbumRelease)) { _logger.Debug($"Match not good enough, fingerprinting"); _fingerprintingService.Lookup(localAlbumRelease.LocalTracks, 0.5); // Only include extra possible candidates if neither album nor release are specified // Will generally be specified as part of manual import if (idOverrides?.Album == null && idOverrides?.AlbumRelease == null) { var dbCandidates = _candidateService.GetDbCandidatesFromFingerprint(localAlbumRelease, idOverrides, config.IncludeExisting); var remoteCandidates = config.AddNewArtists ? _candidateService.GetRemoteCandidates(localAlbumRelease) : new List <CandidateAlbumRelease>(); var extraCandidates = dbCandidates.Concat(remoteCandidates); var newCandidates = extraCandidates.ExceptBy(x => x.AlbumRelease.Id, candidateReleases, y => y.AlbumRelease.Id, EqualityComparer <int> .Default); candidateReleases.AddRange(newCandidates); PopulateTracks(candidateReleases); allLocalTracks.AddRange(ToLocalTrack(newCandidates .SelectMany(x => x.ExistingTracks) .DistinctBy(x => x.Path) .ExceptBy(x => x.Path, allLocalTracks, x => x.Path, PathEqualityComparer.Instance), localAlbumRelease)); } // fingerprint all the local files in candidates we might be matching against _fingerprintingService.Lookup(allLocalTracks, 0.5); GetBestRelease(localAlbumRelease, candidateReleases, allLocalTracks); } _logger.Debug($"Best release found in {watch.ElapsedMilliseconds}ms"); localAlbumRelease.PopulateMatch(); _logger.Debug($"IdentifyRelease done in {watch.ElapsedMilliseconds}ms"); }
public List <CandidateAlbumRelease> GetCandidatesFromTags(LocalAlbumRelease localAlbumRelease, Artist artist, Album album, AlbumRelease release, bool includeExisting) { var watch = System.Diagnostics.Stopwatch.StartNew(); // Generally artist, album and release are null. But if they're not then limit candidates appropriately. // We've tried to make sure that tracks are all for a single release. List <CandidateAlbumRelease> candidateReleases; // if we have a release ID, use that AlbumRelease tagMbidRelease = null; List <CandidateAlbumRelease> tagCandidate = null; var releaseIds = localAlbumRelease.LocalTracks.Select(x => x.FileTrackInfo.ReleaseMBId).Distinct().ToList(); if (releaseIds.Count == 1 && releaseIds[0].IsNotNullOrWhiteSpace()) { _logger.Debug("Selecting release from consensus ForeignReleaseId [{0}]", releaseIds[0]); tagMbidRelease = _releaseService.GetReleaseByForeignReleaseId(releaseIds[0], true); if (tagMbidRelease != null) { tagCandidate = GetCandidatesByRelease(new List <AlbumRelease> { tagMbidRelease }, includeExisting); } } if (release != null) { // this case overrides the release picked up from the file tags _logger.Debug("Release {0} [{1} tracks] was forced", release, release.TrackCount); candidateReleases = GetCandidatesByRelease(new List <AlbumRelease> { release }, includeExisting); } else if (album != null) { // use the release from file tags if it exists and agrees with the specified album if (tagMbidRelease?.AlbumId == album.Id) { candidateReleases = tagCandidate; } else { candidateReleases = GetCandidatesByAlbum(localAlbumRelease, album, includeExisting); } } else if (artist != null) { // use the release from file tags if it exists and agrees with the specified album if (tagMbidRelease?.Album.Value.ArtistMetadataId == artist.ArtistMetadataId) { candidateReleases = tagCandidate; } else { candidateReleases = GetCandidatesByArtist(localAlbumRelease, artist, includeExisting); } } else { if (tagMbidRelease != null) { candidateReleases = tagCandidate; } else { candidateReleases = GetCandidates(localAlbumRelease, includeExisting); } } watch.Stop(); _logger.Debug($"Getting candidates from tags for {localAlbumRelease.LocalTracks.Count} tracks took {watch.ElapsedMilliseconds}ms"); // if we haven't got any candidates then try fingerprinting return(candidateReleases); }
private void IdentifyRelease(LocalAlbumRelease localAlbumRelease, Artist artist, Album album, AlbumRelease release, bool newDownload, bool includeExisting) { var watch = System.Diagnostics.Stopwatch.StartNew(); bool fingerprinted = false; var candidateReleases = GetCandidatesFromTags(localAlbumRelease, artist, album, release, includeExisting); if (candidateReleases.Count == 0 && FingerprintingAllowed(newDownload)) { _logger.Debug("No candidates found, fingerprinting"); _fingerprintingService.Lookup(localAlbumRelease.LocalTracks, 0.5); fingerprinted = true; candidateReleases = GetCandidatesFromFingerprint(localAlbumRelease, artist, album, release, includeExisting); } if (candidateReleases.Count == 0) { // can't find any candidates even after fingerprinting return; } _logger.Debug($"Got {candidateReleases.Count} candidates for {localAlbumRelease.LocalTracks.Count} tracks in {watch.ElapsedMilliseconds}ms"); var allTracks = _trackService.GetTracksByReleases(candidateReleases.Select(x => x.AlbumRelease.Id).ToList()); // convert all the TrackFiles that represent extra files to List<LocalTrack> var allLocalTracks = ToLocalTrack(candidateReleases .SelectMany(x => x.ExistingTracks) .DistinctBy(x => x.Path), localAlbumRelease); _logger.Debug($"Retrieved {allTracks.Count} possible tracks in {watch.ElapsedMilliseconds}ms"); GetBestRelease(localAlbumRelease, candidateReleases, allTracks, allLocalTracks); // If result isn't great and we haven't fingerprinted, try that // Note that this can improve the match even if we try the same candidates if (!fingerprinted && FingerprintingAllowed(newDownload) && ShouldFingerprint(localAlbumRelease)) { _logger.Debug($"Match not good enough, fingerprinting"); _fingerprintingService.Lookup(localAlbumRelease.LocalTracks, 0.5); // Only include extra possible candidates if neither album nor release are specified // Will generally be specified as part of manual import if (album == null && release == null) { var extraCandidates = GetCandidatesFromFingerprint(localAlbumRelease, artist, album, release, includeExisting); var newCandidates = extraCandidates.ExceptBy(x => x.AlbumRelease.Id, candidateReleases, y => y.AlbumRelease.Id, EqualityComparer <int> .Default); candidateReleases.AddRange(newCandidates); allTracks.AddRange(_trackService.GetTracksByReleases(newCandidates.Select(x => x.AlbumRelease.Id).ToList())); allLocalTracks.AddRange(ToLocalTrack(newCandidates .SelectMany(x => x.ExistingTracks) .DistinctBy(x => x.Path) .ExceptBy(x => x.Path, allLocalTracks, x => x.Path, PathEqualityComparer.Instance), localAlbumRelease)); } // fingerprint all the local files in candidates we might be matching against _fingerprintingService.Lookup(allLocalTracks, 0.5); GetBestRelease(localAlbumRelease, candidateReleases, allTracks, allLocalTracks); } _logger.Debug($"Best release found in {watch.ElapsedMilliseconds}ms"); localAlbumRelease.PopulateMatch(); _logger.Debug($"IdentifyRelease done in {watch.ElapsedMilliseconds}ms"); }